diff --git a/runtime/csharp/Prompty.Core.Tests/Model/AgentConversionTests.cs b/runtime/csharp/Prompty.Core.Tests/Model/AgentConversionTests.cs index eba3d9ea0..26ee0d8a0 100644 --- a/runtime/csharp/Prompty.Core.Tests/Model/AgentConversionTests.cs +++ b/runtime/csharp/Prompty.Core.Tests/Model/AgentConversionTests.cs @@ -3502,4 +3502,10 @@ public void ToYamlProducesValidYaml7() var parsed = deserializer.Deserialize(yaml); Assert.NotNull(parsed); } + + [Fact] + public void RejectsMalformedJson() + { + Assert.ThrowsAny(() => Agent.FromJson("{")); + } } diff --git a/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Connectivity/AnonymousConnectionConversionTests.cs b/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Connectivity/AnonymousConnectionConversionTests.cs index 0f0f4e5dc..a4582015b 100644 --- a/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Connectivity/AnonymousConnectionConversionTests.cs +++ b/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Connectivity/AnonymousConnectionConversionTests.cs @@ -122,4 +122,10 @@ public void ToYamlProducesValidYaml() var parsed = deserializer.Deserialize(yaml); Assert.NotNull(parsed); } + + [Fact] + public void RejectsMalformedJson() + { + Assert.ThrowsAny(() => AnonymousConnection.FromJson("{")); + } } diff --git a/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Connectivity/ApiKeyConnectionConversionTests.cs b/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Connectivity/ApiKeyConnectionConversionTests.cs index acbb003c6..e44c62f28 100644 --- a/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Connectivity/ApiKeyConnectionConversionTests.cs +++ b/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Connectivity/ApiKeyConnectionConversionTests.cs @@ -130,4 +130,10 @@ public void ToYamlProducesValidYaml() var parsed = deserializer.Deserialize(yaml); Assert.NotNull(parsed); } + + [Fact] + public void RejectsMalformedJson() + { + Assert.ThrowsAny(() => ApiKeyConnection.FromJson("{")); + } } diff --git a/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Connectivity/ConnectionConversionTests.cs b/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Connectivity/ConnectionConversionTests.cs index 8e1437097..13386ccdc 100644 --- a/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Connectivity/ConnectionConversionTests.cs +++ b/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Connectivity/ConnectionConversionTests.cs @@ -132,4 +132,10 @@ public void ToYamlProducesValidYaml() var parsed = deserializer.Deserialize(yaml); Assert.NotNull(parsed); } + + [Fact] + public void RejectsMalformedJson() + { + Assert.ThrowsAny(() => Connection.FromJson("{")); + } } diff --git a/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Connectivity/DeviceAuthorizationConversionTests.cs b/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Connectivity/DeviceAuthorizationConversionTests.cs index 966298ad2..e15060dfa 100644 --- a/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Connectivity/DeviceAuthorizationConversionTests.cs +++ b/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Connectivity/DeviceAuthorizationConversionTests.cs @@ -152,4 +152,42 @@ public void ToYamlProducesValidYaml() var parsed = deserializer.Deserialize(yaml); Assert.NotNull(parsed); } + + [Fact] + public void WireConversion() + { + string jsonData = """ + { + "deviceCode": "sample", + "userCode": "sample", + "verificationUri": "sample", + "expiresIn": 1, + "interval": 1 + } + """; + + var instance = DeviceAuthorization.FromJson(jsonData); + Assert.NotNull(instance); + + var foundryWire = instance.ToWire("foundry"); + Assert.Contains("device_code", foundryWire.Keys); + Assert.DoesNotContain("deviceCode", foundryWire.Keys); + Assert.Contains("user_code", foundryWire.Keys); + Assert.DoesNotContain("userCode", foundryWire.Keys); + Assert.Contains("verification_uri", foundryWire.Keys); + Assert.DoesNotContain("verificationUri", foundryWire.Keys); + Assert.Contains("expires_in", foundryWire.Keys); + Assert.DoesNotContain("expiresIn", foundryWire.Keys); + Assert.Contains("interval", foundryWire.Keys); + var foundryRestored = DeviceAuthorization.FromWire("foundry", foundryWire); + Assert.Equal( + new System.Collections.Generic.SortedSet(foundryWire.Keys), + new System.Collections.Generic.SortedSet(foundryRestored.ToWire("foundry").Keys)); + } + + [Fact] + public void RejectsMalformedJson() + { + Assert.ThrowsAny(() => DeviceAuthorization.FromJson("{")); + } } diff --git a/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Connectivity/FoundryConnectionConversionTests.cs b/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Connectivity/FoundryConnectionConversionTests.cs index fcae9f725..380527b0b 100644 --- a/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Connectivity/FoundryConnectionConversionTests.cs +++ b/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Connectivity/FoundryConnectionConversionTests.cs @@ -142,4 +142,10 @@ public void ToYamlProducesValidYaml() var parsed = deserializer.Deserialize(yaml); Assert.NotNull(parsed); } + + [Fact] + public void RejectsMalformedJson() + { + Assert.ThrowsAny(() => FoundryConnection.FromJson("{")); + } } diff --git a/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Connectivity/OAuthConnectionConversionTests.cs b/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Connectivity/OAuthConnectionConversionTests.cs index 1eb0dbe31..e02753fee 100644 --- a/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Connectivity/OAuthConnectionConversionTests.cs +++ b/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Connectivity/OAuthConnectionConversionTests.cs @@ -167,4 +167,10 @@ public void ToYamlProducesValidYaml() var parsed = deserializer.Deserialize(yaml); Assert.NotNull(parsed); } + + [Fact] + public void RejectsMalformedJson() + { + Assert.ThrowsAny(() => OAuthConnection.FromJson("{")); + } } diff --git a/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Connectivity/OAuthTokenConversionTests.cs b/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Connectivity/OAuthTokenConversionTests.cs index 056e7383e..6272d11b0 100644 --- a/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Connectivity/OAuthTokenConversionTests.cs +++ b/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Connectivity/OAuthTokenConversionTests.cs @@ -122,4 +122,33 @@ public void ToYamlProducesValidYaml() var parsed = deserializer.Deserialize(yaml); Assert.NotNull(parsed); } + + [Fact] + public void WireConversion() + { + string jsonData = """ + { + "refreshToken": "0.AXoAoffline-refresh-token-value", + "scope": "https://cognitiveservices.azure.com/.default offline_access" + } + """; + + var instance = OAuthToken.FromJson(jsonData); + Assert.NotNull(instance); + + var foundryWire = instance.ToWire("foundry"); + Assert.Contains("refresh_token", foundryWire.Keys); + Assert.DoesNotContain("refreshToken", foundryWire.Keys); + Assert.Contains("scope", foundryWire.Keys); + var foundryRestored = OAuthToken.FromWire("foundry", foundryWire); + Assert.Equal( + new System.Collections.Generic.SortedSet(foundryWire.Keys), + new System.Collections.Generic.SortedSet(foundryRestored.ToWire("foundry").Keys)); + } + + [Fact] + public void RejectsMalformedJson() + { + Assert.ThrowsAny(() => OAuthToken.FromJson("{")); + } } diff --git a/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Connectivity/ReferenceConnectionConversionTests.cs b/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Connectivity/ReferenceConnectionConversionTests.cs index f176e6944..4d710a511 100644 --- a/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Connectivity/ReferenceConnectionConversionTests.cs +++ b/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Connectivity/ReferenceConnectionConversionTests.cs @@ -132,4 +132,10 @@ public void ToYamlProducesValidYaml() var parsed = deserializer.Deserialize(yaml); Assert.NotNull(parsed); } + + [Fact] + public void RejectsMalformedJson() + { + Assert.ThrowsAny(() => ReferenceConnection.FromJson("{")); + } } diff --git a/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Connectivity/RemoteConnectionConversionTests.cs b/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Connectivity/RemoteConnectionConversionTests.cs index 6ccf105a2..aa28289ea 100644 --- a/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Connectivity/RemoteConnectionConversionTests.cs +++ b/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Connectivity/RemoteConnectionConversionTests.cs @@ -132,4 +132,10 @@ public void ToYamlProducesValidYaml() var parsed = deserializer.Deserialize(yaml); Assert.NotNull(parsed); } + + [Fact] + public void RejectsMalformedJson() + { + Assert.ThrowsAny(() => RemoteConnection.FromJson("{")); + } } diff --git a/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Conversation/AudioPartConversionTests.cs b/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Conversation/AudioPartConversionTests.cs index a850e5a53..b30b6c8c4 100644 --- a/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Conversation/AudioPartConversionTests.cs +++ b/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Conversation/AudioPartConversionTests.cs @@ -122,4 +122,10 @@ public void ToYamlProducesValidYaml() var parsed = deserializer.Deserialize(yaml); Assert.NotNull(parsed); } + + [Fact] + public void RejectsMalformedJson() + { + Assert.ThrowsAny(() => AudioPart.FromJson("{")); + } } diff --git a/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Conversation/ContentPartConversionTests.cs b/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Conversation/ContentPartConversionTests.cs index 736dc5425..6389f214a 100644 --- a/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Conversation/ContentPartConversionTests.cs +++ b/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Conversation/ContentPartConversionTests.cs @@ -118,4 +118,10 @@ public void ToYamlProducesValidYaml() var parsed = deserializer.Deserialize(yaml); Assert.NotNull(parsed); } + + [Fact] + public void RejectsMalformedJson() + { + Assert.ThrowsAny(() => ContentPart.FromJson("{")); + } } diff --git a/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Conversation/FilePartConversionTests.cs b/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Conversation/FilePartConversionTests.cs index 7cb996187..9a600a14f 100644 --- a/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Conversation/FilePartConversionTests.cs +++ b/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Conversation/FilePartConversionTests.cs @@ -122,4 +122,10 @@ public void ToYamlProducesValidYaml() var parsed = deserializer.Deserialize(yaml); Assert.NotNull(parsed); } + + [Fact] + public void RejectsMalformedJson() + { + Assert.ThrowsAny(() => FilePart.FromJson("{")); + } } diff --git a/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Conversation/ImagePartConversionTests.cs b/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Conversation/ImagePartConversionTests.cs index 627f0d049..b60a56f44 100644 --- a/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Conversation/ImagePartConversionTests.cs +++ b/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Conversation/ImagePartConversionTests.cs @@ -132,4 +132,10 @@ public void ToYamlProducesValidYaml() var parsed = deserializer.Deserialize(yaml); Assert.NotNull(parsed); } + + [Fact] + public void RejectsMalformedJson() + { + Assert.ThrowsAny(() => ImagePart.FromJson("{")); + } } diff --git a/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Conversation/MessageConversionTests.cs b/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Conversation/MessageConversionTests.cs index 6204a1a7e..c4fdc49c8 100644 --- a/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Conversation/MessageConversionTests.cs +++ b/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Conversation/MessageConversionTests.cs @@ -155,6 +155,12 @@ public void ToYamlProducesValidYaml() Assert.NotNull(parsed); } + [Fact] + public void RejectsMalformedJson() + { + Assert.ThrowsAny(() => Message.FromJson("{")); + } + [Fact] public void FactoryAssistant() { diff --git a/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Conversation/TextPartConversionTests.cs b/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Conversation/TextPartConversionTests.cs index e67a22322..cf3f3c00b 100644 --- a/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Conversation/TextPartConversionTests.cs +++ b/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Conversation/TextPartConversionTests.cs @@ -112,4 +112,10 @@ public void ToYamlProducesValidYaml() var parsed = deserializer.Deserialize(yaml); Assert.NotNull(parsed); } + + [Fact] + public void RejectsMalformedJson() + { + Assert.ThrowsAny(() => TextPart.FromJson("{")); + } } diff --git a/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Conversation/ToolCallConversionTests.cs b/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Conversation/ToolCallConversionTests.cs index 2bac5a5f4..cc6d2427c 100644 --- a/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Conversation/ToolCallConversionTests.cs +++ b/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Conversation/ToolCallConversionTests.cs @@ -132,4 +132,10 @@ public void ToYamlProducesValidYaml() var parsed = deserializer.Deserialize(yaml); Assert.NotNull(parsed); } + + [Fact] + public void RejectsMalformedJson() + { + Assert.ThrowsAny(() => ToolCall.FromJson("{")); + } } diff --git a/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Conversation/ToolResultConversionTests.cs b/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Conversation/ToolResultConversionTests.cs index fbc077de3..e53566fc6 100644 --- a/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Conversation/ToolResultConversionTests.cs +++ b/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Conversation/ToolResultConversionTests.cs @@ -160,6 +160,12 @@ public void ToYamlProducesValidYaml() Assert.NotNull(parsed); } + [Fact] + public void RejectsMalformedJson() + { + Assert.ThrowsAny(() => ToolResult.FromJson("{")); + } + [Fact] public void FactoryText() { diff --git a/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Core/ArrayPropertyConversionTests.cs b/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Core/ArrayPropertyConversionTests.cs index 3fd23da75..50a957b5a 100644 --- a/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Core/ArrayPropertyConversionTests.cs +++ b/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Core/ArrayPropertyConversionTests.cs @@ -117,4 +117,10 @@ public void ToYamlProducesValidYaml() var parsed = deserializer.Deserialize(yaml); Assert.NotNull(parsed); } + + [Fact] + public void RejectsMalformedJson() + { + Assert.ThrowsAny(() => ArrayProperty.FromJson("{")); + } } diff --git a/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Core/ObjectPropertyConversionTests.cs b/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Core/ObjectPropertyConversionTests.cs index f668f283d..9d4f7f781 100644 --- a/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Core/ObjectPropertyConversionTests.cs +++ b/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Core/ObjectPropertyConversionTests.cs @@ -141,4 +141,10 @@ public void ToYamlProducesValidYaml() var parsed = deserializer.Deserialize(yaml); Assert.NotNull(parsed); } + + [Fact] + public void RejectsMalformedJson() + { + Assert.ThrowsAny(() => ObjectProperty.FromJson("{")); + } } diff --git a/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Core/PropertyConversionTests.cs b/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Core/PropertyConversionTests.cs index 67f7618aa..927063180 100644 --- a/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Core/PropertyConversionTests.cs +++ b/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Core/PropertyConversionTests.cs @@ -200,6 +200,12 @@ public void ToYamlProducesValidYaml() Assert.NotNull(parsed); } + [Fact] + public void RejectsMalformedJson() + { + Assert.ThrowsAny(() => Property.FromJson("{")); + } + [Fact] public void LoadJsonFromBoolean() { diff --git a/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Core/UnionPropertyConversionTests.cs b/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Core/UnionPropertyConversionTests.cs index 3c5e86398..bb6e3258e 100644 --- a/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Core/UnionPropertyConversionTests.cs +++ b/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Core/UnionPropertyConversionTests.cs @@ -135,4 +135,10 @@ public void ToYamlProducesValidYaml() var parsed = deserializer.Deserialize(yaml); Assert.NotNull(parsed); } + + [Fact] + public void RejectsMalformedJson() + { + Assert.ThrowsAny(() => UnionProperty.FromJson("{")); + } } diff --git a/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Events/CheckpointConversionTests.cs b/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Events/CheckpointConversionTests.cs index 4aa293acc..bc5632c7f 100644 --- a/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Events/CheckpointConversionTests.cs +++ b/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Events/CheckpointConversionTests.cs @@ -162,4 +162,10 @@ public void ToYamlProducesValidYaml() var parsed = deserializer.Deserialize(yaml); Assert.NotNull(parsed); } + + [Fact] + public void RejectsMalformedJson() + { + Assert.ThrowsAny(() => Checkpoint.FromJson("{")); + } } diff --git a/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Events/ErrorChunkConversionTests.cs b/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Events/ErrorChunkConversionTests.cs index 74534f657..882217f9d 100644 --- a/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Events/ErrorChunkConversionTests.cs +++ b/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Events/ErrorChunkConversionTests.cs @@ -112,4 +112,10 @@ public void ToYamlProducesValidYaml() var parsed = deserializer.Deserialize(yaml); Assert.NotNull(parsed); } + + [Fact] + public void RejectsMalformedJson() + { + Assert.ThrowsAny(() => ErrorChunk.FromJson("{")); + } } diff --git a/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Events/FailureChunkConversionTests.cs b/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Events/FailureChunkConversionTests.cs index 993d3abf5..f453c5957 100644 --- a/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Events/FailureChunkConversionTests.cs +++ b/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Events/FailureChunkConversionTests.cs @@ -123,4 +123,10 @@ public void ToYamlProducesValidYaml() var parsed = deserializer.Deserialize(yaml); Assert.NotNull(parsed); } + + [Fact] + public void RejectsMalformedJson() + { + Assert.ThrowsAny(() => FailureChunk.FromJson("{")); + } } diff --git a/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Events/HostToolRequestConversionTests.cs b/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Events/HostToolRequestConversionTests.cs index a547e67e4..3686b0c16 100644 --- a/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Events/HostToolRequestConversionTests.cs +++ b/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Events/HostToolRequestConversionTests.cs @@ -142,4 +142,10 @@ public void ToYamlProducesValidYaml() var parsed = deserializer.Deserialize(yaml); Assert.NotNull(parsed); } + + [Fact] + public void RejectsMalformedJson() + { + Assert.ThrowsAny(() => HostToolRequest.FromJson("{")); + } } diff --git a/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Events/HostToolResultConversionTests.cs b/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Events/HostToolResultConversionTests.cs index 2895faabd..39f7da9fe 100644 --- a/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Events/HostToolResultConversionTests.cs +++ b/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Events/HostToolResultConversionTests.cs @@ -172,4 +172,10 @@ public void ToYamlProducesValidYaml() var parsed = deserializer.Deserialize(yaml); Assert.NotNull(parsed); } + + [Fact] + public void RejectsMalformedJson() + { + Assert.ThrowsAny(() => HostToolResult.FromJson("{")); + } } diff --git a/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Events/PermissionDecisionConversionTests.cs b/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Events/PermissionDecisionConversionTests.cs index a244d6033..7fc3f0469 100644 --- a/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Events/PermissionDecisionConversionTests.cs +++ b/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Events/PermissionDecisionConversionTests.cs @@ -152,4 +152,10 @@ public void ToYamlProducesValidYaml() var parsed = deserializer.Deserialize(yaml); Assert.NotNull(parsed); } + + [Fact] + public void RejectsMalformedJson() + { + Assert.ThrowsAny(() => PermissionDecision.FromJson("{")); + } } diff --git a/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Events/PermissionRequestConversionTests.cs b/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Events/PermissionRequestConversionTests.cs index 02e3de209..828e9f894 100644 --- a/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Events/PermissionRequestConversionTests.cs +++ b/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Events/PermissionRequestConversionTests.cs @@ -152,4 +152,10 @@ public void ToYamlProducesValidYaml() var parsed = deserializer.Deserialize(yaml); Assert.NotNull(parsed); } + + [Fact] + public void RejectsMalformedJson() + { + Assert.ThrowsAny(() => PermissionRequest.FromJson("{")); + } } diff --git a/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Events/RedactedFieldConversionTests.cs b/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Events/RedactedFieldConversionTests.cs index 69625e89a..af50c2722 100644 --- a/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Events/RedactedFieldConversionTests.cs +++ b/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Events/RedactedFieldConversionTests.cs @@ -132,4 +132,10 @@ public void ToYamlProducesValidYaml() var parsed = deserializer.Deserialize(yaml); Assert.NotNull(parsed); } + + [Fact] + public void RejectsMalformedJson() + { + Assert.ThrowsAny(() => RedactedField.FromJson("{")); + } } diff --git a/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Events/RedactionMetadataConversionTests.cs b/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Events/RedactionMetadataConversionTests.cs index 2d99f4896..24cc73e75 100644 --- a/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Events/RedactionMetadataConversionTests.cs +++ b/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Events/RedactionMetadataConversionTests.cs @@ -122,4 +122,10 @@ public void ToYamlProducesValidYaml() var parsed = deserializer.Deserialize(yaml); Assert.NotNull(parsed); } + + [Fact] + public void RejectsMalformedJson() + { + Assert.ThrowsAny(() => RedactionMetadata.FromJson("{")); + } } diff --git a/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Events/SessionEventConversionTests.cs b/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Events/SessionEventConversionTests.cs index 50fcf44c3..80bbbe473 100644 --- a/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Events/SessionEventConversionTests.cs +++ b/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Events/SessionEventConversionTests.cs @@ -162,4 +162,10 @@ public void ToYamlProducesValidYaml() var parsed = deserializer.Deserialize(yaml); Assert.NotNull(parsed); } + + [Fact] + public void RejectsMalformedJson() + { + Assert.ThrowsAny(() => SessionEvent.FromJson("{")); + } } diff --git a/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Events/SessionSummaryConversionTests.cs b/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Events/SessionSummaryConversionTests.cs index a2937b65f..97054086c 100644 --- a/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Events/SessionSummaryConversionTests.cs +++ b/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Events/SessionSummaryConversionTests.cs @@ -152,4 +152,10 @@ public void ToYamlProducesValidYaml() var parsed = deserializer.Deserialize(yaml); Assert.NotNull(parsed); } + + [Fact] + public void RejectsMalformedJson() + { + Assert.ThrowsAny(() => SessionSummary.FromJson("{")); + } } diff --git a/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Events/StreamChunkConversionTests.cs b/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Events/StreamChunkConversionTests.cs index 6cd590598..c9cffc205 100644 --- a/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Events/StreamChunkConversionTests.cs +++ b/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Events/StreamChunkConversionTests.cs @@ -118,4 +118,10 @@ public void ToYamlProducesValidYaml() var parsed = deserializer.Deserialize(yaml); Assert.NotNull(parsed); } + + [Fact] + public void RejectsMalformedJson() + { + Assert.ThrowsAny(() => StreamChunk.FromJson("{")); + } } diff --git a/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Events/StreamFailureConversionTests.cs b/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Events/StreamFailureConversionTests.cs index d69c1ae83..b16741e34 100644 --- a/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Events/StreamFailureConversionTests.cs +++ b/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Events/StreamFailureConversionTests.cs @@ -122,4 +122,10 @@ public void ToYamlProducesValidYaml() var parsed = deserializer.Deserialize(yaml); Assert.NotNull(parsed); } + + [Fact] + public void RejectsMalformedJson() + { + Assert.ThrowsAny(() => StreamFailure.FromJson("{")); + } } diff --git a/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Events/TextChunkConversionTests.cs b/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Events/TextChunkConversionTests.cs index a350368d2..9d1816ad1 100644 --- a/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Events/TextChunkConversionTests.cs +++ b/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Events/TextChunkConversionTests.cs @@ -112,4 +112,10 @@ public void ToYamlProducesValidYaml() var parsed = deserializer.Deserialize(yaml); Assert.NotNull(parsed); } + + [Fact] + public void RejectsMalformedJson() + { + Assert.ThrowsAny(() => TextChunk.FromJson("{")); + } } diff --git a/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Events/ThinkingChunkConversionTests.cs b/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Events/ThinkingChunkConversionTests.cs index 926ecac9c..5e0b19ab1 100644 --- a/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Events/ThinkingChunkConversionTests.cs +++ b/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Events/ThinkingChunkConversionTests.cs @@ -112,4 +112,10 @@ public void ToYamlProducesValidYaml() var parsed = deserializer.Deserialize(yaml); Assert.NotNull(parsed); } + + [Fact] + public void RejectsMalformedJson() + { + Assert.ThrowsAny(() => ThinkingChunk.FromJson("{")); + } } diff --git a/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Events/ToolChunkConversionTests.cs b/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Events/ToolChunkConversionTests.cs index 36396613b..7315c0121 100644 --- a/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Events/ToolChunkConversionTests.cs +++ b/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Events/ToolChunkConversionTests.cs @@ -129,4 +129,10 @@ public void ToYamlProducesValidYaml() var parsed = deserializer.Deserialize(yaml); Assert.NotNull(parsed); } + + [Fact] + public void RejectsMalformedJson() + { + Assert.ThrowsAny(() => ToolChunk.FromJson("{")); + } } diff --git a/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Events/TurnEventConversionTests.cs b/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Events/TurnEventConversionTests.cs index 115e28a88..236150951 100644 --- a/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Events/TurnEventConversionTests.cs +++ b/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Events/TurnEventConversionTests.cs @@ -162,4 +162,10 @@ public void ToYamlProducesValidYaml() var parsed = deserializer.Deserialize(yaml); Assert.NotNull(parsed); } + + [Fact] + public void RejectsMalformedJson() + { + Assert.ThrowsAny(() => TurnEvent.FromJson("{")); + } } diff --git a/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Events/UsageChunkConversionTests.cs b/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Events/UsageChunkConversionTests.cs index 360cccc7f..0ca5ba681 100644 --- a/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Events/UsageChunkConversionTests.cs +++ b/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Events/UsageChunkConversionTests.cs @@ -139,4 +139,10 @@ public void ToYamlProducesValidYaml() var parsed = deserializer.Deserialize(yaml); Assert.NotNull(parsed); } + + [Fact] + public void RejectsMalformedJson() + { + Assert.ThrowsAny(() => UsageChunk.FromJson("{")); + } } diff --git a/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Memory/MemoryEntryConversionTests.cs b/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Memory/MemoryEntryConversionTests.cs index 80be8c7a5..de5dea86d 100644 --- a/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Memory/MemoryEntryConversionTests.cs +++ b/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Memory/MemoryEntryConversionTests.cs @@ -153,4 +153,10 @@ public void ToYamlProducesValidYaml() var parsed = deserializer.Deserialize(yaml); Assert.NotNull(parsed); } + + [Fact] + public void RejectsMalformedJson() + { + Assert.ThrowsAny(() => MemoryEntry.FromJson("{")); + } } diff --git a/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Memory/MemoryStoreConversionTests.cs b/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Memory/MemoryStoreConversionTests.cs index 0219797f2..6720f2d2a 100644 --- a/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Memory/MemoryStoreConversionTests.cs +++ b/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Memory/MemoryStoreConversionTests.cs @@ -108,4 +108,10 @@ public void ToYamlProducesValidYaml() var parsed = deserializer.Deserialize(yaml); Assert.NotNull(parsed); } + + [Fact] + public void RejectsMalformedJson() + { + Assert.ThrowsAny(() => MemoryStore.FromJson("{")); + } } diff --git a/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Models/AzureModelConversionTests.cs b/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Models/AzureModelConversionTests.cs index b5e94170b..ad5022600 100644 --- a/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Models/AzureModelConversionTests.cs +++ b/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Models/AzureModelConversionTests.cs @@ -112,4 +112,10 @@ public void ToYamlProducesValidYaml() var parsed = deserializer.Deserialize(yaml); Assert.NotNull(parsed); } + + [Fact] + public void RejectsMalformedJson() + { + Assert.ThrowsAny(() => AzureModel.FromJson("{")); + } } diff --git a/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Models/CustomModelConversionTests.cs b/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Models/CustomModelConversionTests.cs index bdefbdf27..36f21a159 100644 --- a/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Models/CustomModelConversionTests.cs +++ b/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Models/CustomModelConversionTests.cs @@ -112,4 +112,10 @@ public void ToYamlProducesValidYaml() var parsed = deserializer.Deserialize(yaml); Assert.NotNull(parsed); } + + [Fact] + public void RejectsMalformedJson() + { + Assert.ThrowsAny(() => CustomModel.FromJson("{")); + } } diff --git a/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Models/InvocationUsageConversionTests.cs b/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Models/InvocationUsageConversionTests.cs index 9dd122d8f..36031c27f 100644 --- a/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Models/InvocationUsageConversionTests.cs +++ b/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Models/InvocationUsageConversionTests.cs @@ -132,4 +132,47 @@ public void ToYamlProducesValidYaml() var parsed = deserializer.Deserialize(yaml); Assert.NotNull(parsed); } + + [Fact] + public void WireConversion() + { + string jsonData = """ + { + "inputTokens": 150, + "outputTokens": 42, + "totalTokens": 192 + } + """; + + var instance = InvocationUsage.FromJson(jsonData); + Assert.NotNull(instance); + + var openaiWire = instance.ToWire("openai"); + Assert.Contains("prompt_tokens", openaiWire.Keys); + Assert.DoesNotContain("inputTokens", openaiWire.Keys); + Assert.Contains("completion_tokens", openaiWire.Keys); + Assert.DoesNotContain("outputTokens", openaiWire.Keys); + Assert.Contains("total_tokens", openaiWire.Keys); + Assert.DoesNotContain("totalTokens", openaiWire.Keys); + var openaiRestored = InvocationUsage.FromWire("openai", openaiWire); + Assert.Equal( + new System.Collections.Generic.SortedSet(openaiWire.Keys), + new System.Collections.Generic.SortedSet(openaiRestored.ToWire("openai").Keys)); + + var anthropicWire = instance.ToWire("anthropic"); + Assert.Contains("input_tokens", anthropicWire.Keys); + Assert.DoesNotContain("inputTokens", anthropicWire.Keys); + Assert.Contains("output_tokens", anthropicWire.Keys); + Assert.DoesNotContain("outputTokens", anthropicWire.Keys); + var anthropicRestored = InvocationUsage.FromWire("anthropic", anthropicWire); + Assert.Equal( + new System.Collections.Generic.SortedSet(anthropicWire.Keys), + new System.Collections.Generic.SortedSet(anthropicRestored.ToWire("anthropic").Keys)); + } + + [Fact] + public void RejectsMalformedJson() + { + Assert.ThrowsAny(() => InvocationUsage.FromJson("{")); + } } diff --git a/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Models/ModelConversionTests.cs b/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Models/ModelConversionTests.cs index 4c7eb4992..0162bc4d7 100644 --- a/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Models/ModelConversionTests.cs +++ b/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Models/ModelConversionTests.cs @@ -187,6 +187,12 @@ public void ToYamlProducesValidYaml() Assert.NotNull(parsed); } + [Fact] + public void RejectsMalformedJson() + { + Assert.ThrowsAny(() => Model.FromJson("{")); + } + [Fact] public void LoadJsonFromString() { diff --git a/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Models/ModelInfoConversionTests.cs b/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Models/ModelInfoConversionTests.cs index 1bbd05fb7..9e23bb8fd 100644 --- a/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Models/ModelInfoConversionTests.cs +++ b/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Models/ModelInfoConversionTests.cs @@ -193,4 +193,60 @@ public void ToYamlProducesValidYaml() var parsed = deserializer.Deserialize(yaml); Assert.NotNull(parsed); } + + [Fact] + public void WireConversion() + { + string jsonData = """ + { + "id": "gpt-4o", + "displayName": "GPT-4o", + "ownedBy": "openai", + "contextWindow": 128000, + "inputModalities": [ + "text", + "image" + ], + "outputModalities": [ + "text" + ], + "additionalProperties": { + "supportsStreaming": true + } + } + """; + + var instance = ModelInfo.FromJson(jsonData); + Assert.NotNull(instance); + + var openaiWire = instance.ToWire("openai"); + Assert.Contains("id", openaiWire.Keys); + Assert.Contains("owned_by", openaiWire.Keys); + Assert.DoesNotContain("ownedBy", openaiWire.Keys); + var openaiRestored = ModelInfo.FromWire("openai", openaiWire); + Assert.Equal( + new System.Collections.Generic.SortedSet(openaiWire.Keys), + new System.Collections.Generic.SortedSet(openaiRestored.ToWire("openai").Keys)); + + var anthropicWire = instance.ToWire("anthropic"); + Assert.Contains("id", anthropicWire.Keys); + Assert.Contains("display_name", anthropicWire.Keys); + Assert.DoesNotContain("displayName", anthropicWire.Keys); + Assert.Contains("context_length", anthropicWire.Keys); + Assert.DoesNotContain("contextWindow", anthropicWire.Keys); + Assert.Contains("input_modalities", anthropicWire.Keys); + Assert.DoesNotContain("inputModalities", anthropicWire.Keys); + Assert.Contains("output_modalities", anthropicWire.Keys); + Assert.DoesNotContain("outputModalities", anthropicWire.Keys); + var anthropicRestored = ModelInfo.FromWire("anthropic", anthropicWire); + Assert.Equal( + new System.Collections.Generic.SortedSet(anthropicWire.Keys), + new System.Collections.Generic.SortedSet(anthropicRestored.ToWire("anthropic").Keys)); + } + + [Fact] + public void RejectsMalformedJson() + { + Assert.ThrowsAny(() => ModelInfo.FromJson("{")); + } } diff --git a/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Models/ModelOptionsConversionTests.cs b/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Models/ModelOptionsConversionTests.cs index 13038901c..196e8ff8b 100644 --- a/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Models/ModelOptionsConversionTests.cs +++ b/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Models/ModelOptionsConversionTests.cs @@ -17,6 +17,7 @@ public void LoadYamlInput() frequencyPenalty: 0.5 maxOutputTokens: 2048 presencePenalty: 0.3 +reasoningEffort: medium seed: 42 temperature: 0.7 topK: 40 @@ -37,6 +38,7 @@ public void LoadYamlInput() Assert.Equal(0.5f, instance.FrequencyPenalty); Assert.Equal(2048, instance.MaxOutputTokens); Assert.Equal(0.3f, instance.PresencePenalty); + Assert.Equal("medium", instance.ReasoningEffort); Assert.Equal(42, instance.Seed); Assert.Equal(0.7f, instance.Temperature); Assert.Equal(40, instance.TopK); @@ -52,6 +54,7 @@ public void LoadJsonInput() "frequencyPenalty": 0.5, "maxOutputTokens": 2048, "presencePenalty": 0.3, + "reasoningEffort": "medium", "seed": 42, "temperature": 0.7, "topK": 40, @@ -73,6 +76,7 @@ public void LoadJsonInput() Assert.Equal(0.5f, instance.FrequencyPenalty); Assert.Equal(2048, instance.MaxOutputTokens); Assert.Equal(0.3f, instance.PresencePenalty); + Assert.Equal("medium", instance.ReasoningEffort); Assert.Equal(42, instance.Seed); Assert.Equal(0.7f, instance.Temperature); Assert.Equal(40, instance.TopK); @@ -89,6 +93,7 @@ public void RoundtripJson() "frequencyPenalty": 0.5, "maxOutputTokens": 2048, "presencePenalty": 0.3, + "reasoningEffort": "medium", "seed": 42, "temperature": 0.7, "topK": 40, @@ -116,6 +121,7 @@ public void RoundtripJson() Assert.Equal(0.5f, reloaded.FrequencyPenalty); Assert.Equal(2048, reloaded.MaxOutputTokens); Assert.Equal(0.3f, reloaded.PresencePenalty); + Assert.Equal("medium", reloaded.ReasoningEffort); Assert.Equal(42, reloaded.Seed); Assert.Equal(0.7f, reloaded.Temperature); Assert.Equal(40, reloaded.TopK); @@ -131,6 +137,7 @@ public void RoundtripYaml() frequencyPenalty: 0.5 maxOutputTokens: 2048 presencePenalty: 0.3 +reasoningEffort: medium seed: 42 temperature: 0.7 topK: 40 @@ -156,6 +163,7 @@ public void RoundtripYaml() Assert.Equal(0.5f, reloaded.FrequencyPenalty); Assert.Equal(2048, reloaded.MaxOutputTokens); Assert.Equal(0.3f, reloaded.PresencePenalty); + Assert.Equal("medium", reloaded.ReasoningEffort); Assert.Equal(42, reloaded.Seed); Assert.Equal(0.7f, reloaded.Temperature); Assert.Equal(40, reloaded.TopK); @@ -171,6 +179,7 @@ public void ToJsonProducesValidJson() "frequencyPenalty": 0.5, "maxOutputTokens": 2048, "presencePenalty": 0.3, + "reasoningEffort": "medium", "seed": 42, "temperature": 0.7, "topK": 40, @@ -202,6 +211,7 @@ public void ToYamlProducesValidYaml() frequencyPenalty: 0.5 maxOutputTokens: 2048 presencePenalty: 0.3 +reasoningEffort: medium seed: 42 temperature: 0.7 topK: 40 @@ -224,4 +234,91 @@ public void ToYamlProducesValidYaml() var parsed = deserializer.Deserialize(yaml); Assert.NotNull(parsed); } + + [Fact] + public void WireConversion() + { + string jsonData = """ + { + "frequencyPenalty": 0.5, + "maxOutputTokens": 2048, + "presencePenalty": 0.3, + "reasoningEffort": "medium", + "seed": 42, + "temperature": 0.7, + "topK": 40, + "topP": 0.9, + "stopSequences": [ + "\n", + "###" + ], + "allowMultipleToolCalls": true, + "additionalProperties": { + "customProperty": "value", + "anotherProperty": "anotherValue" + } + } + """; + + var instance = ModelOptions.FromJson(jsonData); + Assert.NotNull(instance); + + var openaiWire = instance.ToWire("openai"); + Assert.Contains("frequency_penalty", openaiWire.Keys); + Assert.DoesNotContain("frequencyPenalty", openaiWire.Keys); + Assert.Contains("max_completion_tokens", openaiWire.Keys); + Assert.DoesNotContain("maxOutputTokens", openaiWire.Keys); + Assert.Contains("presence_penalty", openaiWire.Keys); + Assert.DoesNotContain("presencePenalty", openaiWire.Keys); + Assert.Contains("reasoning_effort", openaiWire.Keys); + Assert.DoesNotContain("reasoningEffort", openaiWire.Keys); + Assert.Contains("seed", openaiWire.Keys); + Assert.Contains("temperature", openaiWire.Keys); + Assert.Contains("top_k", openaiWire.Keys); + Assert.DoesNotContain("topK", openaiWire.Keys); + Assert.Contains("top_p", openaiWire.Keys); + Assert.DoesNotContain("topP", openaiWire.Keys); + Assert.Contains("stop", openaiWire.Keys); + Assert.DoesNotContain("stopSequences", openaiWire.Keys); + Assert.Contains("parallel_tool_calls", openaiWire.Keys); + Assert.DoesNotContain("allowMultipleToolCalls", openaiWire.Keys); + var openaiRestored = ModelOptions.FromWire("openai", openaiWire); + Assert.Equal( + new System.Collections.Generic.SortedSet(openaiWire.Keys), + new System.Collections.Generic.SortedSet(openaiRestored.ToWire("openai").Keys)); + + var responsesWire = instance.ToWire("responses"); + Assert.Contains("max_output_tokens", responsesWire.Keys); + Assert.DoesNotContain("maxOutputTokens", responsesWire.Keys); + Assert.Contains("reasoning_effort", responsesWire.Keys); + Assert.DoesNotContain("reasoningEffort", responsesWire.Keys); + Assert.Contains("temperature", responsesWire.Keys); + Assert.Contains("top_p", responsesWire.Keys); + Assert.DoesNotContain("topP", responsesWire.Keys); + var responsesRestored = ModelOptions.FromWire("responses", responsesWire); + Assert.Equal( + new System.Collections.Generic.SortedSet(responsesWire.Keys), + new System.Collections.Generic.SortedSet(responsesRestored.ToWire("responses").Keys)); + + var anthropicWire = instance.ToWire("anthropic"); + Assert.Contains("max_tokens", anthropicWire.Keys); + Assert.DoesNotContain("maxOutputTokens", anthropicWire.Keys); + Assert.Contains("temperature", anthropicWire.Keys); + Assert.Contains("top_k", anthropicWire.Keys); + Assert.DoesNotContain("topK", anthropicWire.Keys); + Assert.Contains("top_p", anthropicWire.Keys); + Assert.DoesNotContain("topP", anthropicWire.Keys); + Assert.Contains("stop_sequences", anthropicWire.Keys); + Assert.DoesNotContain("stopSequences", anthropicWire.Keys); + var anthropicRestored = ModelOptions.FromWire("anthropic", anthropicWire); + Assert.Equal( + new System.Collections.Generic.SortedSet(anthropicWire.Keys), + new System.Collections.Generic.SortedSet(anthropicRestored.ToWire("anthropic").Keys)); + } + + [Fact] + public void RejectsMalformedJson() + { + Assert.ThrowsAny(() => ModelOptions.FromJson("{")); + } } diff --git a/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Models/OpenAIModelConversionTests.cs b/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Models/OpenAIModelConversionTests.cs index 9eb2ae09b..f8c8a6cf8 100644 --- a/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Models/OpenAIModelConversionTests.cs +++ b/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Models/OpenAIModelConversionTests.cs @@ -112,4 +112,10 @@ public void ToYamlProducesValidYaml() var parsed = deserializer.Deserialize(yaml); Assert.NotNull(parsed); } + + [Fact] + public void RejectsMalformedJson() + { + Assert.ThrowsAny(() => OpenAIModel.FromJson("{")); + } } diff --git a/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Models/TokenUsageConversionTests.cs b/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Models/TokenUsageConversionTests.cs index e42a9ac1e..cd76f1a8c 100644 --- a/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Models/TokenUsageConversionTests.cs +++ b/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Models/TokenUsageConversionTests.cs @@ -132,4 +132,47 @@ public void ToYamlProducesValidYaml() var parsed = deserializer.Deserialize(yaml); Assert.NotNull(parsed); } + + [Fact] + public void WireConversion() + { + string jsonData = """ + { + "promptTokens": 150, + "completionTokens": 42, + "totalTokens": 192 + } + """; + + var instance = TokenUsage.FromJson(jsonData); + Assert.NotNull(instance); + + var openaiWire = instance.ToWire("openai"); + Assert.Contains("prompt_tokens", openaiWire.Keys); + Assert.DoesNotContain("promptTokens", openaiWire.Keys); + Assert.Contains("completion_tokens", openaiWire.Keys); + Assert.DoesNotContain("completionTokens", openaiWire.Keys); + Assert.Contains("total_tokens", openaiWire.Keys); + Assert.DoesNotContain("totalTokens", openaiWire.Keys); + var openaiRestored = TokenUsage.FromWire("openai", openaiWire); + Assert.Equal( + new System.Collections.Generic.SortedSet(openaiWire.Keys), + new System.Collections.Generic.SortedSet(openaiRestored.ToWire("openai").Keys)); + + var anthropicWire = instance.ToWire("anthropic"); + Assert.Contains("input_tokens", anthropicWire.Keys); + Assert.DoesNotContain("promptTokens", anthropicWire.Keys); + Assert.Contains("output_tokens", anthropicWire.Keys); + Assert.DoesNotContain("completionTokens", anthropicWire.Keys); + var anthropicRestored = TokenUsage.FromWire("anthropic", anthropicWire); + Assert.Equal( + new System.Collections.Generic.SortedSet(anthropicWire.Keys), + new System.Collections.Generic.SortedSet(anthropicRestored.ToWire("anthropic").Keys)); + } + + [Fact] + public void RejectsMalformedJson() + { + Assert.ThrowsAny(() => TokenUsage.FromJson("{")); + } } diff --git a/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Pipeline/DelegatedStateReferenceConversionTests.cs b/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Pipeline/DelegatedStateReferenceConversionTests.cs index 4b07c7934..040695116 100644 --- a/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Pipeline/DelegatedStateReferenceConversionTests.cs +++ b/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Pipeline/DelegatedStateReferenceConversionTests.cs @@ -132,4 +132,10 @@ public void ToYamlProducesValidYaml() var parsed = deserializer.Deserialize(yaml); Assert.NotNull(parsed); } + + [Fact] + public void RejectsMalformedJson() + { + Assert.ThrowsAny(() => DelegatedStateReference.FromJson("{")); + } } diff --git a/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Pipeline/EngineCheckpointConversionTests.cs b/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Pipeline/EngineCheckpointConversionTests.cs index 3e5ced4f1..2bacbcd7d 100644 --- a/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Pipeline/EngineCheckpointConversionTests.cs +++ b/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Pipeline/EngineCheckpointConversionTests.cs @@ -211,4 +211,10 @@ public void ToYamlProducesValidYaml() var parsed = deserializer.Deserialize(yaml); Assert.NotNull(parsed); } + + [Fact] + public void RejectsMalformedJson() + { + Assert.ThrowsAny(() => EngineCheckpoint.FromJson("{")); + } } diff --git a/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Pipeline/EngineEventConversionTests.cs b/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Pipeline/EngineEventConversionTests.cs index b16bb7254..6855ced71 100644 --- a/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Pipeline/EngineEventConversionTests.cs +++ b/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Pipeline/EngineEventConversionTests.cs @@ -152,4 +152,10 @@ public void ToYamlProducesValidYaml() var parsed = deserializer.Deserialize(yaml); Assert.NotNull(parsed); } + + [Fact] + public void RejectsMalformedJson() + { + Assert.ThrowsAny(() => EngineEvent.FromJson("{")); + } } diff --git a/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Pipeline/EnginePermissionDecisionConversionTests.cs b/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Pipeline/EnginePermissionDecisionConversionTests.cs index 39d8cc072..29ac2bedc 100644 --- a/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Pipeline/EnginePermissionDecisionConversionTests.cs +++ b/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Pipeline/EnginePermissionDecisionConversionTests.cs @@ -112,4 +112,10 @@ public void ToYamlProducesValidYaml() var parsed = deserializer.Deserialize(yaml); Assert.NotNull(parsed); } + + [Fact] + public void RejectsMalformedJson() + { + Assert.ThrowsAny(() => EnginePermissionDecision.FromJson("{")); + } } diff --git a/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Pipeline/InvocationContextDecisionConversionTests.cs b/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Pipeline/InvocationContextDecisionConversionTests.cs index 4b84097f8..4bd18911f 100644 --- a/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Pipeline/InvocationContextDecisionConversionTests.cs +++ b/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Pipeline/InvocationContextDecisionConversionTests.cs @@ -122,4 +122,10 @@ public void ToYamlProducesValidYaml() var parsed = deserializer.Deserialize(yaml); Assert.NotNull(parsed); } + + [Fact] + public void RejectsMalformedJson() + { + Assert.ThrowsAny(() => InvocationContextDecision.FromJson("{")); + } } diff --git a/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Pipeline/InvocationContextStateConversionTests.cs b/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Pipeline/InvocationContextStateConversionTests.cs index 7ac754145..97ea4a90d 100644 --- a/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Pipeline/InvocationContextStateConversionTests.cs +++ b/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Pipeline/InvocationContextStateConversionTests.cs @@ -102,4 +102,10 @@ public void ToYamlProducesValidYaml() var parsed = deserializer.Deserialize(yaml); Assert.NotNull(parsed); } + + [Fact] + public void RejectsMalformedJson() + { + Assert.ThrowsAny(() => InvocationContextState.FromJson("{")); + } } diff --git a/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Pipeline/ModelInvocationContextSnapshotConversionTests.cs b/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Pipeline/ModelInvocationContextSnapshotConversionTests.cs index 24ca6ede4..b4dc0bad5 100644 --- a/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Pipeline/ModelInvocationContextSnapshotConversionTests.cs +++ b/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Pipeline/ModelInvocationContextSnapshotConversionTests.cs @@ -211,4 +211,10 @@ public void ToYamlProducesValidYaml() var parsed = deserializer.Deserialize(yaml); Assert.NotNull(parsed); } + + [Fact] + public void RejectsMalformedJson() + { + Assert.ThrowsAny(() => ModelInvocationContextSnapshot.FromJson("{")); + } } diff --git a/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Pipeline/ModelInvocationRequestConversionTests.cs b/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Pipeline/ModelInvocationRequestConversionTests.cs index 949eb6ad0..54c3f8526 100644 --- a/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Pipeline/ModelInvocationRequestConversionTests.cs +++ b/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Pipeline/ModelInvocationRequestConversionTests.cs @@ -153,4 +153,10 @@ public void ToYamlProducesValidYaml() var parsed = deserializer.Deserialize(yaml); Assert.NotNull(parsed); } + + [Fact] + public void RejectsMalformedJson() + { + Assert.ThrowsAny(() => ModelInvocationRequest.FromJson("{")); + } } diff --git a/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Pipeline/ModelInvocationResponseConversionTests.cs b/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Pipeline/ModelInvocationResponseConversionTests.cs index 7475f2a48..cb1be1c40 100644 --- a/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Pipeline/ModelInvocationResponseConversionTests.cs +++ b/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Pipeline/ModelInvocationResponseConversionTests.cs @@ -102,4 +102,10 @@ public void ToYamlProducesValidYaml() var parsed = deserializer.Deserialize(yaml); Assert.NotNull(parsed); } + + [Fact] + public void RejectsMalformedJson() + { + Assert.ThrowsAny(() => ModelInvocationResponse.FromJson("{")); + } } diff --git a/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Pipeline/ModelReconciliationStateConversionTests.cs b/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Pipeline/ModelReconciliationStateConversionTests.cs index f68c5796c..9def0ec14 100644 --- a/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Pipeline/ModelReconciliationStateConversionTests.cs +++ b/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Pipeline/ModelReconciliationStateConversionTests.cs @@ -182,4 +182,10 @@ public void ToYamlProducesValidYaml() var parsed = deserializer.Deserialize(yaml); Assert.NotNull(parsed); } + + [Fact] + public void RejectsMalformedJson() + { + Assert.ThrowsAny(() => ModelReconciliationState.FromJson("{")); + } } diff --git a/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Pipeline/ModelToolRequestConversionTests.cs b/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Pipeline/ModelToolRequestConversionTests.cs index bdd2a11b9..074e5a3ad 100644 --- a/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Pipeline/ModelToolRequestConversionTests.cs +++ b/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Pipeline/ModelToolRequestConversionTests.cs @@ -122,4 +122,10 @@ public void ToYamlProducesValidYaml() var parsed = deserializer.Deserialize(yaml); Assert.NotNull(parsed); } + + [Fact] + public void RejectsMalformedJson() + { + Assert.ThrowsAny(() => ModelToolRequest.FromJson("{")); + } } diff --git a/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Pipeline/ModelToolResultConversionTests.cs b/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Pipeline/ModelToolResultConversionTests.cs index b52eb511b..d1a4d7fff 100644 --- a/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Pipeline/ModelToolResultConversionTests.cs +++ b/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Pipeline/ModelToolResultConversionTests.cs @@ -122,4 +122,10 @@ public void ToYamlProducesValidYaml() var parsed = deserializer.Deserialize(yaml); Assert.NotNull(parsed); } + + [Fact] + public void RejectsMalformedJson() + { + Assert.ThrowsAny(() => ModelToolResult.FromJson("{")); + } } diff --git a/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Pipeline/ReplayJournalRecordConversionTests.cs b/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Pipeline/ReplayJournalRecordConversionTests.cs index 629fc90c2..a9a4f7714 100644 --- a/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Pipeline/ReplayJournalRecordConversionTests.cs +++ b/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Pipeline/ReplayJournalRecordConversionTests.cs @@ -112,4 +112,10 @@ public void ToYamlProducesValidYaml() var parsed = deserializer.Deserialize(yaml); Assert.NotNull(parsed); } + + [Fact] + public void RejectsMalformedJson() + { + Assert.ThrowsAny(() => ReplayJournalRecord.FromJson("{")); + } } diff --git a/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Pipeline/ResumeContextConversionTests.cs b/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Pipeline/ResumeContextConversionTests.cs index 6d844d8e8..043585159 100644 --- a/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Pipeline/ResumeContextConversionTests.cs +++ b/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Pipeline/ResumeContextConversionTests.cs @@ -169,4 +169,10 @@ public void ToYamlProducesValidYaml() var parsed = deserializer.Deserialize(yaml); Assert.NotNull(parsed); } + + [Fact] + public void RejectsMalformedJson() + { + Assert.ThrowsAny(() => ResumeContext.FromJson("{")); + } } diff --git a/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Pipeline/TurnCommitConversionTests.cs b/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Pipeline/TurnCommitConversionTests.cs index 987101f15..3bef7ade2 100644 --- a/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Pipeline/TurnCommitConversionTests.cs +++ b/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Pipeline/TurnCommitConversionTests.cs @@ -191,4 +191,10 @@ public void ToYamlProducesValidYaml() var parsed = deserializer.Deserialize(yaml); Assert.NotNull(parsed); } + + [Fact] + public void RejectsMalformedJson() + { + Assert.ThrowsAny(() => TurnCommit.FromJson("{")); + } } diff --git a/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Templates/CustomFormatConversionTests.cs b/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Templates/CustomFormatConversionTests.cs index 2b7c4196c..7ade934dc 100644 --- a/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Templates/CustomFormatConversionTests.cs +++ b/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Templates/CustomFormatConversionTests.cs @@ -112,4 +112,10 @@ public void ToYamlProducesValidYaml() var parsed = deserializer.Deserialize(yaml); Assert.NotNull(parsed); } + + [Fact] + public void RejectsMalformedJson() + { + Assert.ThrowsAny(() => CustomFormat.FromJson("{")); + } } diff --git a/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Templates/CustomParserConversionTests.cs b/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Templates/CustomParserConversionTests.cs index 4102c7a1a..2691efc8d 100644 --- a/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Templates/CustomParserConversionTests.cs +++ b/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Templates/CustomParserConversionTests.cs @@ -112,4 +112,10 @@ public void ToYamlProducesValidYaml() var parsed = deserializer.Deserialize(yaml); Assert.NotNull(parsed); } + + [Fact] + public void RejectsMalformedJson() + { + Assert.ThrowsAny(() => CustomParser.FromJson("{")); + } } diff --git a/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Templates/FormatConfigConversionTests.cs b/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Templates/FormatConfigConversionTests.cs index 386387f9c..1ac925c86 100644 --- a/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Templates/FormatConfigConversionTests.cs +++ b/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Templates/FormatConfigConversionTests.cs @@ -138,6 +138,12 @@ public void ToYamlProducesValidYaml() Assert.NotNull(parsed); } + [Fact] + public void RejectsMalformedJson() + { + Assert.ThrowsAny(() => FormatConfig.FromJson("{")); + } + [Fact] public void LoadJsonFromString() { diff --git a/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Templates/Jinja2FormatConversionTests.cs b/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Templates/Jinja2FormatConversionTests.cs index 3a988e245..865481922 100644 --- a/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Templates/Jinja2FormatConversionTests.cs +++ b/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Templates/Jinja2FormatConversionTests.cs @@ -112,4 +112,10 @@ public void ToYamlProducesValidYaml() var parsed = deserializer.Deserialize(yaml); Assert.NotNull(parsed); } + + [Fact] + public void RejectsMalformedJson() + { + Assert.ThrowsAny(() => Jinja2Format.FromJson("{")); + } } diff --git a/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Templates/MustacheFormatConversionTests.cs b/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Templates/MustacheFormatConversionTests.cs index dbc054869..1c0698ac6 100644 --- a/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Templates/MustacheFormatConversionTests.cs +++ b/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Templates/MustacheFormatConversionTests.cs @@ -112,4 +112,10 @@ public void ToYamlProducesValidYaml() var parsed = deserializer.Deserialize(yaml); Assert.NotNull(parsed); } + + [Fact] + public void RejectsMalformedJson() + { + Assert.ThrowsAny(() => MustacheFormat.FromJson("{")); + } } diff --git a/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Templates/ParserConfigConversionTests.cs b/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Templates/ParserConfigConversionTests.cs index 4bff0012c..5f814efed 100644 --- a/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Templates/ParserConfigConversionTests.cs +++ b/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Templates/ParserConfigConversionTests.cs @@ -128,6 +128,12 @@ public void ToYamlProducesValidYaml() Assert.NotNull(parsed); } + [Fact] + public void RejectsMalformedJson() + { + Assert.ThrowsAny(() => ParserConfig.FromJson("{")); + } + [Fact] public void LoadJsonFromString() { diff --git a/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Templates/PromptyParserConversionTests.cs b/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Templates/PromptyParserConversionTests.cs index 3543ac06e..d731588ef 100644 --- a/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Templates/PromptyParserConversionTests.cs +++ b/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Templates/PromptyParserConversionTests.cs @@ -112,4 +112,10 @@ public void ToYamlProducesValidYaml() var parsed = deserializer.Deserialize(yaml); Assert.NotNull(parsed); } + + [Fact] + public void RejectsMalformedJson() + { + Assert.ThrowsAny(() => PromptyParser.FromJson("{")); + } } diff --git a/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Templates/TemplateConversionTests.cs b/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Templates/TemplateConversionTests.cs index df95bb3b9..e38a8e99c 100644 --- a/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Templates/TemplateConversionTests.cs +++ b/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Templates/TemplateConversionTests.cs @@ -132,4 +132,10 @@ public void ToYamlProducesValidYaml() var parsed = deserializer.Deserialize(yaml); Assert.NotNull(parsed); } + + [Fact] + public void RejectsMalformedJson() + { + Assert.ThrowsAny(() => Template.FromJson("{")); + } } diff --git a/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Tooling/BindingConversionTests.cs b/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Tooling/BindingConversionTests.cs index eef2fc06b..8acd083f3 100644 --- a/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Tooling/BindingConversionTests.cs +++ b/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Tooling/BindingConversionTests.cs @@ -123,6 +123,12 @@ public void ToYamlProducesValidYaml() Assert.NotNull(parsed); } + [Fact] + public void RejectsMalformedJson() + { + Assert.ThrowsAny(() => Binding.FromJson("{")); + } + [Fact] public void LoadJsonFromString() { diff --git a/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Tooling/CustomToolConversionTests.cs b/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Tooling/CustomToolConversionTests.cs index 51cda281e..622d5863a 100644 --- a/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Tooling/CustomToolConversionTests.cs +++ b/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Tooling/CustomToolConversionTests.cs @@ -138,4 +138,10 @@ public void ToYamlProducesValidYaml() var parsed = deserializer.Deserialize(yaml); Assert.NotNull(parsed); } + + [Fact] + public void RejectsMalformedJson() + { + Assert.ThrowsAny(() => CustomTool.FromJson("{")); + } } diff --git a/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Tooling/FunctionToolConversionTests.cs b/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Tooling/FunctionToolConversionTests.cs index d4f3d4519..366f42e91 100644 --- a/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Tooling/FunctionToolConversionTests.cs +++ b/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Tooling/FunctionToolConversionTests.cs @@ -387,4 +387,10 @@ public void ToYamlProducesValidYaml1() var parsed = deserializer.Deserialize(yaml); Assert.NotNull(parsed); } + + [Fact] + public void RejectsMalformedJson() + { + Assert.ThrowsAny(() => FunctionTool.FromJson("{")); + } } diff --git a/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Tooling/McpApprovalModeConversionTests.cs b/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Tooling/McpApprovalModeConversionTests.cs index 12bfc560c..9d835a6df 100644 --- a/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Tooling/McpApprovalModeConversionTests.cs +++ b/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Tooling/McpApprovalModeConversionTests.cs @@ -143,6 +143,12 @@ public void ToYamlProducesValidYaml() Assert.NotNull(parsed); } + [Fact] + public void RejectsMalformedJson() + { + Assert.ThrowsAny(() => McpApprovalMode.FromJson("{")); + } + [Fact] public void LoadJsonFromString() { diff --git a/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Tooling/McpToolConversionTests.cs b/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Tooling/McpToolConversionTests.cs index e822b756f..b1925050d 100644 --- a/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Tooling/McpToolConversionTests.cs +++ b/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Tooling/McpToolConversionTests.cs @@ -183,4 +183,10 @@ public void ToYamlProducesValidYaml() var parsed = deserializer.Deserialize(yaml); Assert.NotNull(parsed); } + + [Fact] + public void RejectsMalformedJson() + { + Assert.ThrowsAny(() => McpTool.FromJson("{")); + } } diff --git a/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Tooling/OpenApiToolConversionTests.cs b/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Tooling/OpenApiToolConversionTests.cs index 7c940e4fc..dd446f73f 100644 --- a/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Tooling/OpenApiToolConversionTests.cs +++ b/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Tooling/OpenApiToolConversionTests.cs @@ -137,4 +137,10 @@ public void ToYamlProducesValidYaml() var parsed = deserializer.Deserialize(yaml); Assert.NotNull(parsed); } + + [Fact] + public void RejectsMalformedJson() + { + Assert.ThrowsAny(() => OpenApiTool.FromJson("{")); + } } diff --git a/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Tooling/ToolConversionTests.cs b/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Tooling/ToolConversionTests.cs index fe14fd7cf..5b7670b89 100644 --- a/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Tooling/ToolConversionTests.cs +++ b/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Tooling/ToolConversionTests.cs @@ -147,4 +147,10 @@ public void ToYamlProducesValidYaml() var parsed = deserializer.Deserialize(yaml); Assert.NotNull(parsed); } + + [Fact] + public void RejectsMalformedJson() + { + Assert.ThrowsAny(() => Tool.FromJson("{")); + } } diff --git a/runtime/csharp/Prompty.Core.Tests/Model/Operations/Pipeline/RenderSegmentConversionTests.cs b/runtime/csharp/Prompty.Core.Tests/Model/Operations/Pipeline/RenderSegmentConversionTests.cs index 4494ede91..68121595f 100644 --- a/runtime/csharp/Prompty.Core.Tests/Model/Operations/Pipeline/RenderSegmentConversionTests.cs +++ b/runtime/csharp/Prompty.Core.Tests/Model/Operations/Pipeline/RenderSegmentConversionTests.cs @@ -122,4 +122,10 @@ public void ToYamlProducesValidYaml() var parsed = deserializer.Deserialize(yaml); Assert.NotNull(parsed); } + + [Fact] + public void RejectsMalformedJson() + { + Assert.ThrowsAny(() => RenderSegment.FromJson("{")); + } } diff --git a/runtime/csharp/Prompty.Core/Model/Contracts/Models/ModelOptions.cs b/runtime/csharp/Prompty.Core/Model/Contracts/Models/ModelOptions.cs index 60ad17d8e..83db43c14 100644 --- a/runtime/csharp/Prompty.Core/Model/Contracts/Models/ModelOptions.cs +++ b/runtime/csharp/Prompty.Core/Model/Contracts/Models/ModelOptions.cs @@ -45,6 +45,11 @@ public ModelOptions() /// public float? PresencePenalty { get; set; } + /// + /// The reasoning effort level for reasoning-capable models + /// + public string? ReasoningEffort { get; set; } + /// /// A random seed for deterministic output /// @@ -118,6 +123,11 @@ public static ModelOptions Load(Dictionary data, LoadContext? c instance.PresencePenalty = Convert.ToSingle(presencePenaltyValue); } + if (data.TryGetValue("reasoningEffort", out var reasoningEffortValue) && reasoningEffortValue is not null) + { + instance.ReasoningEffort = reasoningEffortValue.ToString()!; + } + if (data.TryGetValue("seed", out var seedValue) && seedValue is not null) { instance.Seed = Convert.ToInt32(seedValue); @@ -200,6 +210,12 @@ public static ModelOptions Load(Dictionary data, LoadContext? c } + if (obj.ReasoningEffort is not null) + { + result["reasoningEffort"] = obj.ReasoningEffort; + } + + if (obj.Seed is not null) { result["seed"] = obj.Seed; @@ -264,6 +280,7 @@ public static ModelOptions Load(Dictionary data, LoadContext? c ["frequencyPenalty"] = new Dictionary { ["openai"] = "frequency_penalty" }, ["maxOutputTokens"] = new Dictionary { ["openai"] = "max_completion_tokens", ["responses"] = "max_output_tokens", ["anthropic"] = "max_tokens" }, ["presencePenalty"] = new Dictionary { ["openai"] = "presence_penalty" }, + ["reasoningEffort"] = new Dictionary { ["openai"] = "reasoning_effort", ["responses"] = "reasoning_effort" }, ["seed"] = new Dictionary { ["openai"] = "seed" }, ["temperature"] = new Dictionary { ["openai"] = "temperature", ["responses"] = "temperature", ["anthropic"] = "temperature" }, ["topK"] = new Dictionary { ["openai"] = "top_k", ["anthropic"] = "top_k" }, @@ -293,6 +310,7 @@ public static ModelOptions FromWire(string provider, Dictionary ["frequencyPenalty"] = new Dictionary { ["openai"] = "frequency_penalty" }, ["maxOutputTokens"] = new Dictionary { ["openai"] = "max_completion_tokens", ["responses"] = "max_output_tokens", ["anthropic"] = "max_tokens" }, ["presencePenalty"] = new Dictionary { ["openai"] = "presence_penalty" }, + ["reasoningEffort"] = new Dictionary { ["openai"] = "reasoning_effort", ["responses"] = "reasoning_effort" }, ["seed"] = new Dictionary { ["openai"] = "seed" }, ["temperature"] = new Dictionary { ["openai"] = "temperature", ["responses"] = "temperature", ["anthropic"] = "temperature" }, ["topK"] = new Dictionary { ["openai"] = "top_k", ["anthropic"] = "top_k" }, diff --git a/runtime/go/prompty/model/model_options.go b/runtime/go/prompty/model/model_options.go index 52c0c923f..b88830589 100644 --- a/runtime/go/prompty/model/model_options.go +++ b/runtime/go/prompty/model/model_options.go @@ -10,12 +10,26 @@ import ( "gopkg.in/yaml.v3" ) +// reasoningEffort represents the allowed values for reasoningEffort. +type reasoningEffort string + +const ( + reasoningEffortNone reasoningEffort = "none" + reasoningEffortMinimal reasoningEffort = "minimal" + reasoningEffortLow reasoningEffort = "low" + reasoningEffortMedium reasoningEffort = "medium" + reasoningEffortHigh reasoningEffort = "high" + reasoningEffortXhigh reasoningEffort = "xhigh" + reasoningEffortMax reasoningEffort = "max" +) + // ModelOptions represents Options for configuring the behavior of the AI model. type ModelOptions struct { FrequencyPenalty *float32 `json:"frequencyPenalty,omitempty" yaml:"frequencyPenalty,omitempty"` MaxOutputTokens *int32 `json:"maxOutputTokens,omitempty" yaml:"maxOutputTokens,omitempty"` PresencePenalty *float32 `json:"presencePenalty,omitempty" yaml:"presencePenalty,omitempty"` + ReasoningEffort *reasoningEffort `json:"reasoningEffort,omitempty" yaml:"reasoningEffort,omitempty"` Seed *int32 `json:"seed,omitempty" yaml:"seed,omitempty"` Temperature *float32 `json:"temperature,omitempty" yaml:"temperature,omitempty"` TopK *int32 `json:"topK,omitempty" yaml:"topK,omitempty"` @@ -77,6 +91,10 @@ func LoadModelOptions(data interface{}, ctx *LoadContext) (ModelOptions, error) } result.PresencePenalty = &v } + if val, ok := m["reasoningEffort"]; ok && val != nil { + v := reasoningEffort(val.(string)) + result.ReasoningEffort = &v + } if val, ok := m["seed"]; ok && val != nil { // Handle various numeric types from JSON/YAML/roundtrip var v int32 switch n := val.(type) { @@ -174,6 +192,9 @@ func (obj ModelOptions) Save(ctx *SaveContext) map[string]interface{} { if obj.PresencePenalty != nil { result["presencePenalty"] = *obj.PresencePenalty } + if obj.ReasoningEffort != nil { + result["reasoningEffort"] = string(*obj.ReasoningEffort) + } if obj.Seed != nil { result["seed"] = *obj.Seed } @@ -207,6 +228,7 @@ func (obj *ModelOptions) ToWire(provider string) map[string]interface{} { "frequencyPenalty": {"openai": "frequency_penalty"}, "maxOutputTokens": {"openai": "max_completion_tokens", "responses": "max_output_tokens", "anthropic": "max_tokens"}, "presencePenalty": {"openai": "presence_penalty"}, + "reasoningEffort": {"openai": "reasoning_effort", "responses": "reasoning_effort"}, "seed": {"openai": "seed"}, "temperature": {"openai": "temperature", "responses": "temperature", "anthropic": "temperature"}, "topK": {"openai": "top_k", "anthropic": "top_k"}, @@ -230,6 +252,7 @@ func ModelOptionsFromWire(provider string, data map[string]interface{}, ctx *Loa "frequencyPenalty": {"openai": "frequency_penalty"}, "maxOutputTokens": {"openai": "max_completion_tokens", "responses": "max_output_tokens", "anthropic": "max_tokens"}, "presencePenalty": {"openai": "presence_penalty"}, + "reasoningEffort": {"openai": "reasoning_effort", "responses": "reasoning_effort"}, "seed": {"openai": "seed"}, "temperature": {"openai": "temperature", "responses": "temperature", "anthropic": "temperature"}, "topK": {"openai": "top_k", "anthropic": "top_k"}, diff --git a/runtime/go/prompty/model/model_options_test.go b/runtime/go/prompty/model/model_options_test.go index d24e58273..907471e4c 100644 --- a/runtime/go/prompty/model/model_options_test.go +++ b/runtime/go/prompty/model/model_options_test.go @@ -20,6 +20,7 @@ func TestModelOptionsLoadJSON(t *testing.T) { "frequencyPenalty": 0.5, "maxOutputTokens": 2048, "presencePenalty": 0.3, + "reasoningEffort": "medium", "seed": 42, "temperature": 0.7, "topK": 40, @@ -54,6 +55,9 @@ func TestModelOptionsLoadJSON(t *testing.T) { if instance.PresencePenalty == nil || *instance.PresencePenalty != 0.3 { t.Errorf(`Expected PresencePenalty to be 0.3, got %v`, instance.PresencePenalty) } + if instance.ReasoningEffort == nil || *instance.ReasoningEffort != "medium" { + t.Errorf(`Expected ReasoningEffort to be "medium", got %v`, instance.ReasoningEffort) + } if instance.Seed == nil || *instance.Seed != 42 { t.Errorf(`Expected Seed to be 42, got %v`, instance.Seed) } @@ -95,6 +99,7 @@ func TestModelOptionsLoadYAML(t *testing.T) { frequencyPenalty: 0.5 maxOutputTokens: 2048 presencePenalty: 0.3 +reasoningEffort: medium seed: 42 temperature: 0.7 topK: 40 @@ -127,6 +132,9 @@ additionalProperties: if instance.PresencePenalty == nil || *instance.PresencePenalty != 0.3 { t.Errorf(`Expected PresencePenalty to be 0.3, got %v`, instance.PresencePenalty) } + if instance.ReasoningEffort == nil || *instance.ReasoningEffort != "medium" { + t.Errorf(`Expected ReasoningEffort to be "medium", got %v`, instance.ReasoningEffort) + } if instance.Seed == nil || *instance.Seed != 42 { t.Errorf(`Expected Seed to be 42, got %v`, instance.Seed) } @@ -169,6 +177,7 @@ func TestModelOptionsFromJSON(t *testing.T) { "frequencyPenalty": 0.5, "maxOutputTokens": 2048, "presencePenalty": 0.3, + "reasoningEffort": "medium", "seed": 42, "temperature": 0.7, "topK": 40, @@ -198,6 +207,9 @@ func TestModelOptionsFromJSON(t *testing.T) { if instance.PresencePenalty == nil || *instance.PresencePenalty != 0.3 { t.Errorf(`Expected PresencePenalty to be 0.3, got %v`, instance.PresencePenalty) } + if instance.ReasoningEffort == nil || *instance.ReasoningEffort != "medium" { + t.Errorf(`Expected ReasoningEffort to be "medium", got %v`, instance.ReasoningEffort) + } if instance.Seed == nil || *instance.Seed != 42 { t.Errorf(`Expected Seed to be 42, got %v`, instance.Seed) } @@ -239,6 +251,7 @@ func TestModelOptionsFromYAML(t *testing.T) { frequencyPenalty: 0.5 maxOutputTokens: 2048 presencePenalty: 0.3 +reasoningEffort: medium seed: 42 temperature: 0.7 topK: 40 @@ -266,6 +279,9 @@ additionalProperties: if instance.PresencePenalty == nil || *instance.PresencePenalty != 0.3 { t.Errorf(`Expected PresencePenalty to be 0.3, got %v`, instance.PresencePenalty) } + if instance.ReasoningEffort == nil || *instance.ReasoningEffort != "medium" { + t.Errorf(`Expected ReasoningEffort to be "medium", got %v`, instance.ReasoningEffort) + } if instance.Seed == nil || *instance.Seed != 42 { t.Errorf(`Expected Seed to be 42, got %v`, instance.Seed) } @@ -308,6 +324,7 @@ func TestModelOptionsRoundtrip(t *testing.T) { "frequencyPenalty": 0.5, "maxOutputTokens": 2048, "presencePenalty": 0.3, + "reasoningEffort": "medium", "seed": 42, "temperature": 0.7, "topK": 40, @@ -349,6 +366,9 @@ func TestModelOptionsRoundtrip(t *testing.T) { if reloaded.PresencePenalty == nil || *reloaded.PresencePenalty != 0.3 { t.Errorf(`Expected PresencePenalty to be 0.3, got %v`, reloaded.PresencePenalty) } + if reloaded.ReasoningEffort == nil || *reloaded.ReasoningEffort != "medium" { + t.Errorf(`Expected ReasoningEffort to be "medium", got %v`, reloaded.ReasoningEffort) + } if reloaded.Seed == nil || *reloaded.Seed != 42 { t.Errorf(`Expected Seed to be 42, got %v`, reloaded.Seed) } @@ -391,6 +411,7 @@ func TestModelOptionsToJSON(t *testing.T) { "frequencyPenalty": 0.5, "maxOutputTokens": 2048, "presencePenalty": 0.3, + "reasoningEffort": "medium", "seed": 42, "temperature": 0.7, "topK": 40, @@ -439,6 +460,9 @@ func TestModelOptionsToJSON(t *testing.T) { if reloaded.PresencePenalty == nil || *reloaded.PresencePenalty != 0.3 { t.Errorf(`Expected PresencePenalty to be 0.3, got %v`, reloaded.PresencePenalty) } + if reloaded.ReasoningEffort == nil || *reloaded.ReasoningEffort != "medium" { + t.Errorf(`Expected ReasoningEffort to be "medium", got %v`, reloaded.ReasoningEffort) + } if reloaded.Seed == nil || *reloaded.Seed != 42 { t.Errorf(`Expected Seed to be 42, got %v`, reloaded.Seed) } @@ -481,6 +505,7 @@ func TestModelOptionsToYAML(t *testing.T) { "frequencyPenalty": 0.5, "maxOutputTokens": 2048, "presencePenalty": 0.3, + "reasoningEffort": "medium", "seed": 42, "temperature": 0.7, "topK": 40, @@ -529,6 +554,9 @@ func TestModelOptionsToYAML(t *testing.T) { if reloaded.PresencePenalty == nil || *reloaded.PresencePenalty != 0.3 { t.Errorf(`Expected PresencePenalty to be 0.3, got %v`, reloaded.PresencePenalty) } + if reloaded.ReasoningEffort == nil || *reloaded.ReasoningEffort != "medium" { + t.Errorf(`Expected ReasoningEffort to be "medium", got %v`, reloaded.ReasoningEffort) + } if reloaded.Seed == nil || *reloaded.Seed != 42 { t.Errorf(`Expected Seed to be 42, got %v`, reloaded.Seed) } @@ -578,6 +606,7 @@ func TestModelOptionsToWire(t *testing.T) { "frequencyPenalty": 0.5, "maxOutputTokens": 2048, "presencePenalty": 0.3, + "reasoningEffort": "medium", "seed": 42, "temperature": 0.7, "topK": 40, @@ -623,6 +652,12 @@ func TestModelOptionsToWire(t *testing.T) { if _, ok := openaiWire["presencePenalty"]; ok { t.Errorf("Expected openai wire output to omit source field presencePenalty") } + if _, ok := openaiWire["reasoning_effort"]; !ok { + t.Errorf("Expected openai wire output to include reasoning_effort") + } + if _, ok := openaiWire["reasoningEffort"]; ok { + t.Errorf("Expected openai wire output to omit source field reasoningEffort") + } if _, ok := openaiWire["seed"]; !ok { t.Errorf("Expected openai wire output to include seed") } @@ -668,6 +703,12 @@ func TestModelOptionsToWire(t *testing.T) { if _, ok := responsesWire["maxOutputTokens"]; ok { t.Errorf("Expected responses wire output to omit source field maxOutputTokens") } + if _, ok := responsesWire["reasoning_effort"]; !ok { + t.Errorf("Expected responses wire output to include reasoning_effort") + } + if _, ok := responsesWire["reasoningEffort"]; ok { + t.Errorf("Expected responses wire output to omit source field reasoningEffort") + } if _, ok := responsesWire["temperature"]; !ok { t.Errorf("Expected responses wire output to include temperature") } diff --git a/runtime/java/prompty/src/main/java/com/microsoft/prompty/model/ModelOptions.java b/runtime/java/prompty/src/main/java/com/microsoft/prompty/model/ModelOptions.java index bae6c87ca..ea3cd46b8 100644 --- a/runtime/java/prompty/src/main/java/com/microsoft/prompty/model/ModelOptions.java +++ b/runtime/java/prompty/src/main/java/com/microsoft/prompty/model/ModelOptions.java @@ -13,6 +13,7 @@ public class ModelOptions { public Float frequencyPenalty = null; public Integer maxOutputTokens = null; public Float presencePenalty = null; + public String reasoningEffort = null; public Integer seed = null; public Float temperature = null; public Integer topK = null; @@ -45,6 +46,9 @@ static void loadBaseInto(ModelOptions result, Map map, LoadContext ctx) { if (map.containsKey("presencePenalty") && map.get("presencePenalty") != null) { result.presencePenalty = (map.get("presencePenalty") instanceof Number n ? n.floatValue() : Float.parseFloat(String.valueOf(map.get("presencePenalty")))); } + if (map.containsKey("reasoningEffort") && map.get("reasoningEffort") != null) { + result.reasoningEffort = String.valueOf(map.get("reasoningEffort")); + } if (map.containsKey("seed") && map.get("seed") != null) { result.seed = (map.get("seed") instanceof Number n ? n.intValue() : Integer.parseInt(String.valueOf(map.get("seed")))); } @@ -88,6 +92,7 @@ protected void saveFields(Map result, SaveContext ctx) { if (obj.frequencyPenalty != null) result.put("frequencyPenalty", serializeScalar(obj.frequencyPenalty)); if (obj.maxOutputTokens != null) result.put("maxOutputTokens", serializeScalar(obj.maxOutputTokens)); if (obj.presencePenalty != null) result.put("presencePenalty", serializeScalar(obj.presencePenalty)); + if (obj.reasoningEffort != null) result.put("reasoningEffort", serializeScalar(obj.reasoningEffort)); if (obj.seed != null) result.put("seed", serializeScalar(obj.seed)); if (obj.temperature != null) result.put("temperature", serializeScalar(obj.temperature)); if (obj.topK != null) result.put("topK", serializeScalar(obj.topK)); @@ -117,6 +122,12 @@ public Map toWire(String provider) { if (target.equals("openai")) { wireName = "presence_penalty"; } if (wireName != null && this.presencePenalty != null) result.put(wireName, serializeScalar(this.presencePenalty)); } + { + String wireName = null; + if (target.equals("openai")) { wireName = "reasoning_effort"; } + if (target.equals("responses")) { wireName = "reasoning_effort"; } + if (wireName != null && this.reasoningEffort != null) result.put(wireName, serializeScalar(this.reasoningEffort)); + } { String wireName = null; if (target.equals("openai")) { wireName = "seed"; } @@ -179,6 +190,12 @@ public static ModelOptions fromWire(String provider, Map data, L m.put("openai", "presence_penalty"); wireMap.put("presencePenalty", m); } + { + Map m = new LinkedHashMap<>(); + m.put("openai", "reasoning_effort"); + m.put("responses", "reasoning_effort"); + wireMap.put("reasoningEffort", m); + } { Map m = new LinkedHashMap<>(); m.put("openai", "seed"); diff --git a/runtime/java/prompty/src/test/java/com/microsoft/prompty/model/DeviceAuthorizationGeneratedTest.java b/runtime/java/prompty/src/test/java/com/microsoft/prompty/model/DeviceAuthorizationGeneratedTest.java index a8ba9c6fa..a2e720a07 100644 --- a/runtime/java/prompty/src/test/java/com/microsoft/prompty/model/DeviceAuthorizationGeneratedTest.java +++ b/runtime/java/prompty/src/test/java/com/microsoft/prompty/model/DeviceAuthorizationGeneratedTest.java @@ -65,7 +65,6 @@ static void run() { assertTrue(foundryWire.containsKey("expires_in"), "Expected foundry wire output to include expires_in"); assertTrue(!foundryWire.containsKey("expiresIn"), "Expected foundry wire output to omit expiresIn"); assertTrue(foundryWire.containsKey("interval"), "Expected foundry wire output to include interval"); - assertTrue(foundryWire.containsKey("message"), "Expected foundry wire output to include message"); DeviceAuthorization foundryRestored = DeviceAuthorization.fromWire("foundry", foundryWire); assertEquals(foundryWire.keySet(), foundryRestored.toWire("foundry").keySet(), "Expected foundry FromWire round-trip to reproduce the wire keys"); } diff --git a/runtime/java/prompty/src/test/java/com/microsoft/prompty/model/ModelOptionsGeneratedTest.java b/runtime/java/prompty/src/test/java/com/microsoft/prompty/model/ModelOptionsGeneratedTest.java index eab4f7ab8..91aef33f2 100644 --- a/runtime/java/prompty/src/test/java/com/microsoft/prompty/model/ModelOptionsGeneratedTest.java +++ b/runtime/java/prompty/src/test/java/com/microsoft/prompty/model/ModelOptionsGeneratedTest.java @@ -13,6 +13,7 @@ static void run() { "frequencyPenalty": 0.5, "maxOutputTokens": 2048, "presencePenalty": 0.3, + "reasoningEffort": "medium", "seed": 42, "temperature": 0.7, "topK": 40, @@ -32,6 +33,7 @@ static void run() { assertEquals(0.5, instance1.frequencyPenalty, "Expected frequencyPenalty"); assertEquals(2048, instance1.maxOutputTokens, "Expected maxOutputTokens"); assertEquals(0.3, instance1.presencePenalty, "Expected presencePenalty"); + assertEquals("medium", instance1.reasoningEffort, "Expected reasoningEffort"); assertEquals(42, instance1.seed, "Expected seed"); assertEquals(0.7, instance1.temperature, "Expected temperature"); assertEquals(40, instance1.topK, "Expected topK"); @@ -47,6 +49,7 @@ static void run() { assertEquals(0.5, fromYaml1.frequencyPenalty, "Expected frequencyPenalty"); assertEquals(2048, fromYaml1.maxOutputTokens, "Expected maxOutputTokens"); assertEquals(0.3, fromYaml1.presencePenalty, "Expected presencePenalty"); + assertEquals("medium", fromYaml1.reasoningEffort, "Expected reasoningEffort"); assertEquals(42, fromYaml1.seed, "Expected seed"); assertEquals(0.7, fromYaml1.temperature, "Expected temperature"); assertEquals(40, fromYaml1.topK, "Expected topK"); @@ -61,6 +64,7 @@ static void run() { assertEquals(0.5, reloaded1.frequencyPenalty, "Expected frequencyPenalty"); assertEquals(2048, reloaded1.maxOutputTokens, "Expected maxOutputTokens"); assertEquals(0.3, reloaded1.presencePenalty, "Expected presencePenalty"); + assertEquals("medium", reloaded1.reasoningEffort, "Expected reasoningEffort"); assertEquals(42, reloaded1.seed, "Expected seed"); assertEquals(0.7, reloaded1.temperature, "Expected temperature"); assertEquals(40, reloaded1.topK, "Expected topK"); @@ -89,7 +93,7 @@ static void run() { assertEquals(yamlParsedExpected, yamlValue, "YAML should decode named and unicode escapes"); assertThrows(() -> TypraYaml.parse("value: \"\\x\""), "YAML should reject unknown escapes"); - ModelOptions wireInstance = ModelOptions.fromJson("{\n \"frequencyPenalty\": 0.5,\n \"maxOutputTokens\": 2048,\n \"presencePenalty\": 0.3,\n \"seed\": 42,\n \"temperature\": 0.7,\n \"topK\": 40,\n \"topP\": 0.9,\n \"stopSequences\": [\n \"\\n\",\n \"###\"\n ],\n \"allowMultipleToolCalls\": true,\n \"additionalProperties\": {\n \"customProperty\": \"value\",\n \"anotherProperty\": \"anotherValue\"\n }\n}"); + ModelOptions wireInstance = ModelOptions.fromJson("{\n \"frequencyPenalty\": 0.5,\n \"maxOutputTokens\": 2048,\n \"presencePenalty\": 0.3,\n \"reasoningEffort\": \"medium\",\n \"seed\": 42,\n \"temperature\": 0.7,\n \"topK\": 40,\n \"topP\": 0.9,\n \"stopSequences\": [\n \"\\n\",\n \"###\"\n ],\n \"allowMultipleToolCalls\": true,\n \"additionalProperties\": {\n \"customProperty\": \"value\",\n \"anotherProperty\": \"anotherValue\"\n }\n}"); java.util.Map openaiWire = wireInstance.toWire("openai"); assertTrue(openaiWire.containsKey("frequency_penalty"), "Expected openai wire output to include frequency_penalty"); assertTrue(!openaiWire.containsKey("frequencyPenalty"), "Expected openai wire output to omit frequencyPenalty"); @@ -97,6 +101,8 @@ static void run() { assertTrue(!openaiWire.containsKey("maxOutputTokens"), "Expected openai wire output to omit maxOutputTokens"); assertTrue(openaiWire.containsKey("presence_penalty"), "Expected openai wire output to include presence_penalty"); assertTrue(!openaiWire.containsKey("presencePenalty"), "Expected openai wire output to omit presencePenalty"); + assertTrue(openaiWire.containsKey("reasoning_effort"), "Expected openai wire output to include reasoning_effort"); + assertTrue(!openaiWire.containsKey("reasoningEffort"), "Expected openai wire output to omit reasoningEffort"); assertTrue(openaiWire.containsKey("seed"), "Expected openai wire output to include seed"); assertTrue(openaiWire.containsKey("temperature"), "Expected openai wire output to include temperature"); assertTrue(openaiWire.containsKey("top_k"), "Expected openai wire output to include top_k"); @@ -112,6 +118,8 @@ static void run() { java.util.Map responsesWire = wireInstance.toWire("responses"); assertTrue(responsesWire.containsKey("max_output_tokens"), "Expected responses wire output to include max_output_tokens"); assertTrue(!responsesWire.containsKey("maxOutputTokens"), "Expected responses wire output to omit maxOutputTokens"); + assertTrue(responsesWire.containsKey("reasoning_effort"), "Expected responses wire output to include reasoning_effort"); + assertTrue(!responsesWire.containsKey("reasoningEffort"), "Expected responses wire output to omit reasoningEffort"); assertTrue(responsesWire.containsKey("temperature"), "Expected responses wire output to include temperature"); assertTrue(responsesWire.containsKey("top_p"), "Expected responses wire output to include top_p"); assertTrue(!responsesWire.containsKey("topP"), "Expected responses wire output to omit topP"); diff --git a/runtime/java/prompty/src/test/java/com/microsoft/prompty/model/OAuthTokenGeneratedTest.java b/runtime/java/prompty/src/test/java/com/microsoft/prompty/model/OAuthTokenGeneratedTest.java index 0c4c29fa1..82070062e 100644 --- a/runtime/java/prompty/src/test/java/com/microsoft/prompty/model/OAuthTokenGeneratedTest.java +++ b/runtime/java/prompty/src/test/java/com/microsoft/prompty/model/OAuthTokenGeneratedTest.java @@ -44,12 +44,6 @@ static void run() { OAuthToken wireInstance = OAuthToken.fromJson("{\n \"refreshToken\": \"0.AXoAoffline-refresh-token-value\",\n \"scope\": \"https://cognitiveservices.azure.com/.default offline_access\"\n}"); java.util.Map foundryWire = wireInstance.toWire("foundry"); - assertTrue(foundryWire.containsKey("access_token"), "Expected foundry wire output to include access_token"); - assertTrue(!foundryWire.containsKey("accessToken"), "Expected foundry wire output to omit accessToken"); - assertTrue(foundryWire.containsKey("token_type"), "Expected foundry wire output to include token_type"); - assertTrue(!foundryWire.containsKey("tokenType"), "Expected foundry wire output to omit tokenType"); - assertTrue(foundryWire.containsKey("expires_in"), "Expected foundry wire output to include expires_in"); - assertTrue(!foundryWire.containsKey("expiresIn"), "Expected foundry wire output to omit expiresIn"); assertTrue(foundryWire.containsKey("refresh_token"), "Expected foundry wire output to include refresh_token"); assertTrue(!foundryWire.containsKey("refreshToken"), "Expected foundry wire output to omit refreshToken"); assertTrue(foundryWire.containsKey("scope"), "Expected foundry wire output to include scope"); diff --git a/runtime/python/prompty/prompty/model/contracts/models/_ModelOptions.py b/runtime/python/prompty/prompty/model/contracts/models/_ModelOptions.py index 30385c871..3da9f57ad 100644 --- a/runtime/python/prompty/prompty/model/contracts/models/_ModelOptions.py +++ b/runtime/python/prompty/prompty/model/contracts/models/_ModelOptions.py @@ -6,10 +6,12 @@ ########################################## from dataclasses import dataclass -from typing import Any, ClassVar +from typing import Any, ClassVar, Literal from ..._context import LoadContext, SaveContext +reasoningEffort = Literal["none", "minimal", "low", "medium", "high", "xhigh", "max"] | str + @dataclass class ModelOptions: @@ -23,6 +25,8 @@ class ModelOptions: The maximum number of tokens to generate in the output presence_penalty : Optional[float] The presence penalty to apply to the model's output + reasoning_effort : Optional[str] + The reasoning effort level for reasoning-capable models seed : Optional[int] A random seed for deterministic output temperature : Optional[float] @@ -44,6 +48,7 @@ class ModelOptions: frequency_penalty: float | None = None max_output_tokens: int | None = None presence_penalty: float | None = None + reasoning_effort: reasoningEffort | None = None seed: int | None = None temperature: float | None = None top_k: int | None = None @@ -79,6 +84,8 @@ def load(data: Any, context: LoadContext | None = None) -> "ModelOptions": instance.max_output_tokens = data["maxOutputTokens"] if data is not None and "presencePenalty" in data: instance.presence_penalty = data["presencePenalty"] + if data is not None and "reasoningEffort" in data: + instance.reasoning_effort = data["reasoningEffort"] if data is not None and "seed" in data: instance.seed = data["seed"] if data is not None and "temperature" in data: @@ -117,6 +124,8 @@ def save(self, context: SaveContext | None = None) -> dict[str, Any]: result["maxOutputTokens"] = obj.max_output_tokens if obj.presence_penalty is not None: result["presencePenalty"] = obj.presence_penalty + if obj.reasoning_effort is not None: + result["reasoningEffort"] = obj.reasoning_effort if obj.seed is not None: result["seed"] = obj.seed if obj.temperature is not None: @@ -154,6 +163,10 @@ def to_wire(self, provider: str) -> dict[str, Any]: "anthropic": "max_tokens", }, "presencePenalty": {"openai": "presence_penalty"}, + "reasoningEffort": { + "openai": "reasoning_effort", + "responses": "reasoning_effort", + }, "seed": {"openai": "seed"}, "temperature": { "openai": "temperature", @@ -190,6 +203,10 @@ def from_wire(provider: str, data: dict[str, Any], context: LoadContext | None = "anthropic": "max_tokens", }, "presencePenalty": {"openai": "presence_penalty"}, + "reasoningEffort": { + "openai": "reasoning_effort", + "responses": "reasoning_effort", + }, "seed": {"openai": "seed"}, "temperature": { "openai": "temperature", diff --git a/runtime/python/prompty/prompty/providers/anthropic/executor.py b/runtime/python/prompty/prompty/providers/anthropic/executor.py index 835e45b82..4c01d2c81 100644 --- a/runtime/python/prompty/prompty/providers/anthropic/executor.py +++ b/runtime/python/prompty/prompty/providers/anthropic/executor.py @@ -12,6 +12,9 @@ from __future__ import annotations +import functools +import inspect +import warnings from typing import Any from ...core.connections import get_connection @@ -143,6 +146,89 @@ def _build_options(agent: Agent) -> dict[str, Any]: return result +# Anthropic sampling params deprecated/removed for models after Claude Opus 4.6. +_ANTHROPIC_DEPRECATED_SAMPLING = ("temperature", "top_k", "top_p") + +# output_config.effort values accepted by anthropic >= 1.4. +_ANTHROPIC_EFFORT_VALUES = frozenset({"low", "medium", "high", "xhigh", "max"}) + +# reasoningEffort (our enum) → Anthropic output_config.effort. +# ``none`` disables effort; ``minimal`` clamps up to the lowest supported level. +_ANTHROPIC_EFFORT_MAP: dict[str, str | None] = { + "none": None, + "minimal": "low", + "low": "low", + "medium": "medium", + "high": "high", + "xhigh": "xhigh", + "max": "max", +} + + +@functools.lru_cache(maxsize=1) +def _anthropic_create_params() -> frozenset[str] | None: + """Named params accepted by the installed ``messages.create()``. + + Returns ``None`` when the signature can't be introspected or accepts + arbitrary ``**kwargs`` (in which case filtering would be unsafe). + """ + try: + from anthropic.resources.messages import Messages + + sig = inspect.signature(Messages.create) + except Exception: + return None + for p in sig.parameters.values(): + if p.kind is inspect.Parameter.VAR_KEYWORD: + return None + return frozenset(sig.parameters) + + +def _drop_unsupported_anthropic_params(result: dict[str, Any]) -> None: + """Remove wire params the installed SDK's ``create()`` won't accept, in place. + + Applied on the final request args in the live execute path (not in the pure + ``_build_options`` mapper) so wire-conformance vectors keep asserting the + deterministic ``@@knownAs`` rename. ``temperature``/``top_k``/``top_p`` were + removed from ``messages.create()`` for models after Claude Opus 4.6; the + replacement control is ``reasoningEffort`` → ``output_config.effort``. + """ + accepted = _anthropic_create_params() + if accepted is None: + return + for key in list(result): + if key not in accepted: + if key in _ANTHROPIC_DEPRECATED_SAMPLING: + warnings.warn( + f"Anthropic no longer accepts '{key}' (deprecated for models after " + f"Claude Opus 4.6); dropping it. Use reasoningEffort instead.", + DeprecationWarning, + stacklevel=3, + ) + result.pop(key, None) + + +def _reasoning_effort_to_anthropic(agent: Agent) -> str | None: + """Translate our ``reasoningEffort`` to an Anthropic ``output_config.effort`` value.""" + opts = agent.model.options + raw = getattr(opts, "reasoning_effort", None) if opts is not None else None + if not raw: + return None + key = str(raw).lower() + mapped = _ANTHROPIC_EFFORT_MAP.get(key, key) + if mapped is None: + return None + if mapped not in _ANTHROPIC_EFFORT_VALUES: + warnings.warn( + f"reasoningEffort '{raw}' is not supported by Anthropic output_config.effort " + f"({sorted(_ANTHROPIC_EFFORT_VALUES)}); dropping it.", + UserWarning, + stacklevel=3, + ) + return None + return mapped + + # Kind → JSON Schema type mapping _KIND_TO_JSON_TYPE: dict[str, str] = { "string": "string", @@ -327,7 +413,10 @@ def _build_chat_args(agent: Agent, messages: list[Message]) -> dict[str, Any]: if tools: args["tools"] = tools - output_config = _output_schema_to_wire(agent) + output_config = _output_schema_to_wire(agent) or {} + effort = _reasoning_effort_to_anthropic(agent) + if effort is not None: + output_config = {**output_config, "effort": effort} if output_config: args["output_config"] = output_config @@ -376,6 +465,7 @@ async def execute_async(self, agent: Agent, data: Any) -> Any: def _execute_chat(self, client: Any, agent: Agent, data: Any) -> Any: args = _build_chat_args(agent, data) + _drop_unsupported_anthropic_params(args) is_streaming = args.pop("stream", False) or ( agent.model.options and agent.model.options.additional_properties @@ -391,6 +481,7 @@ def _execute_chat(self, client: Any, agent: Agent, data: Any) -> Any: async def _execute_chat_async(self, client: Any, agent: Agent, data: Any) -> Any: args = _build_chat_args(agent, data) + _drop_unsupported_anthropic_params(args) is_streaming = args.pop("stream", False) or ( agent.model.options and agent.model.options.additional_properties diff --git a/runtime/python/prompty/prompty/providers/foundry/executor.py b/runtime/python/prompty/prompty/providers/foundry/executor.py index e3a18d6cc..f3edc039e 100644 --- a/runtime/python/prompty/prompty/providers/foundry/executor.py +++ b/runtime/python/prompty/prompty/providers/foundry/executor.py @@ -220,7 +220,7 @@ def _build_client_from_entra(self, conn: FoundryConnection, agent: Agent) -> Any def _build_async_client_from_entra(self, conn: FoundryConnection, agent: Agent) -> Any: """Build an async AsyncAzureOpenAI client using Entra ID (DefaultAzureCredential).""" - from azure.identity import DefaultAzureCredential, get_bearer_token_provider + from azure.identity.aio import DefaultAzureCredential, get_bearer_token_provider from openai import AsyncOpenAI credential = DefaultAzureCredential() diff --git a/runtime/python/prompty/prompty/providers/openai/executor.py b/runtime/python/prompty/prompty/providers/openai/executor.py index 6dcd90918..def2d29a0 100644 --- a/runtime/python/prompty/prompty/providers/openai/executor.py +++ b/runtime/python/prompty/prompty/providers/openai/executor.py @@ -10,6 +10,8 @@ from __future__ import annotations +import re +import warnings from typing import Any from ..._version import VERSION @@ -275,6 +277,56 @@ def _build_options(agent: Agent) -> dict[str, Any]: return opts +# Sampling params that OpenAI reasoning models (o-series, GPT-5 family) reject. +_OPENAI_REASONING_UNSUPPORTED = ( + "temperature", + "top_p", + "top_k", + "frequency_penalty", + "presence_penalty", +) + + +def _is_reasoning_model(model_id: str) -> bool: + """Best-effort detection of OpenAI reasoning models. + + Reasoning models (o1/o3/o4/o5..., GPT-5 family) reject scalar sampling params + and use ``reasoning_effort`` instead. Detection is name-based, so custom Azure + deployment names that don't follow OpenAI's convention won't be recognized. + """ + name = (model_id or "").lower().lstrip("./") + if re.match(r"^o[1-9]", name): + return True + if name.startswith("gpt-5"): + return True + return False + + +def _apply_openai_reasoning_guard(model_id: str, opts: dict[str, Any]) -> None: + """Drop params incompatible with the target model class, in place. + + - Reasoning models: drop scalar sampling params (with a ``DeprecationWarning``). + - Non-reasoning models: drop ``reasoning_effort`` (only valid on reasoning models). + + This is a live-call capability guard, applied in the executor arg builders + just before the SDK call -- not in the pure ``to_wire`` option mappers. The + generated wire mapping stays deterministic (rename-only) so wire-conformance + vectors reflect the contract, while the guard shapes the actual request. + """ + if _is_reasoning_model(model_id): + for key in _OPENAI_REASONING_UNSUPPORTED: + if key in opts: + warnings.warn( + f"OpenAI reasoning model '{model_id}' does not support '{key}'; " + f"dropping it. Use reasoningEffort instead.", + DeprecationWarning, + stacklevel=3, + ) + opts.pop(key, None) + else: + opts.pop("reasoning_effort", None) + + # --------------------------------------------------------------------------- # Responses API wire format helpers # --------------------------------------------------------------------------- @@ -485,6 +537,7 @@ def _build_chat_args(self, agent: Agent, messages: list[Message]) -> dict[str, A "messages": wire_messages, **_build_options(agent), } + _apply_openai_reasoning_guard(agent.model.id, args) tools = _tools_to_wire(agent) if tools: @@ -581,6 +634,7 @@ def _build_responses_args(self, agent: Agent, messages: list[Message]) -> dict[s # Model options (Responses-specific mapping) args.update(_build_responses_options(agent)) + _apply_openai_reasoning_guard(agent.model.id, args) # Tools (flat format) tools = _responses_tools_to_wire(agent) @@ -765,6 +819,8 @@ def _resolve_client_async(self, agent: Agent) -> Any: def _client_kwargs(self, agent: Agent) -> dict[str, Any]: """Extract client constructor kwargs from an ApiKeyConnection.""" + import os + kwargs: dict[str, Any] = {} conn = agent.model.connection if conn and isinstance(conn, ApiKeyConnection): @@ -778,4 +834,10 @@ def _client_kwargs(self, agent: Agent) -> dict[str, Any]: f"Connection kind '{kind}' is not supported by the OpenAI executor. " f"Use 'key' for API key auth or 'reference' with register_connection() for pre-configured clients." ) + # Some openai SDK builds do not fall back to the public API host when + # no base_url is configured, which surfaces as an APIConnectionError + # ("Request URL is missing an 'http://' or 'https://' protocol"). + # Default to the public endpoint, still honoring OPENAI_BASE_URL. + if "base_url" not in kwargs: + kwargs["base_url"] = os.environ.get("OPENAI_BASE_URL") or "https://api.openai.com/v1" return kwargs diff --git a/runtime/python/prompty/prompty/providers/openai/models.py b/runtime/python/prompty/prompty/providers/openai/models.py index c26c846ad..4747fa56f 100644 --- a/runtime/python/prompty/prompty/providers/openai/models.py +++ b/runtime/python/prompty/prompty/providers/openai/models.py @@ -68,12 +68,16 @@ def _build_client_kwargs(connection: Connection) -> dict[str, Any]: """Extract kwargs for ``OpenAI(...)`` from a connection.""" + import os + kwargs: dict[str, Any] = {} if isinstance(connection, ApiKeyConnection): if connection.api_key: kwargs["api_key"] = connection.api_key if connection.endpoint: kwargs["base_url"] = connection.endpoint + if "base_url" not in kwargs: + kwargs["base_url"] = os.environ.get("OPENAI_BASE_URL") or "https://api.openai.com/v1" return kwargs diff --git a/runtime/python/prompty/pyproject.toml b/runtime/python/prompty/pyproject.toml index ca07f83b3..f62e4e307 100644 --- a/runtime/python/prompty/pyproject.toml +++ b/runtime/python/prompty/pyproject.toml @@ -34,8 +34,8 @@ Changelog = "https://github.com/microsoft/prompty/releases" jinja2 = ["jinja2"] mustache = ["chevron"] openai = ["openai"] -foundry = ["openai", "azure-identity"] -azure = ["openai", "azure-identity"] +foundry = ["openai", "azure-identity", "aiohttp"] +azure = ["openai", "azure-identity", "aiohttp"] anthropic = ["anthropic"] otel = ["opentelemetry-api>=1.20"] all = [ @@ -43,6 +43,7 @@ all = [ "chevron", "openai", "azure-identity", + "aiohttp", "anthropic", "opentelemetry-api>=1.20", ] diff --git a/runtime/python/prompty/tests/integration/conftest.py b/runtime/python/prompty/tests/integration/conftest.py index 5eda22bf2..38ad5e71e 100644 --- a/runtime/python/prompty/tests/integration/conftest.py +++ b/runtime/python/prompty/tests/integration/conftest.py @@ -52,14 +52,19 @@ def pytest_collection_modifyitems(items: list[pytest.Item]) -> None: _AZURE_CHAT_DEPLOYMENT = os.environ.get("AZURE_OPENAI_CHAT_DEPLOYMENT", "") _AZURE_EMBEDDING_DEPLOYMENT = os.environ.get("AZURE_OPENAI_EMBEDDING_DEPLOYMENT", "") _AZURE_IMAGE_DEPLOYMENT = os.environ.get("AZURE_OPENAI_IMAGE_DEPLOYMENT", "") +_FOUNDRY_PROJECT_ENDPOINT = os.environ.get("FOUNDRY_PROJECT_ENDPOINT", "") # for keyless deployment listing _ANTHROPIC_KEY = os.environ.get("ANTHROPIC_API_KEY", "") _DIRECT_OPENAI_KEY = os.environ.get("DIRECT_OPENAI_API_KEY", "") _DIRECT_OPENAI_MODEL = os.environ.get("DIRECT_OPENAI_MODEL", "gpt-4o-mini") has_openai = bool(_OPENAI_KEY) -has_azure = bool(_AZURE_KEY and _AZURE_ENDPOINT and _AZURE_CHAT_DEPLOYMENT) -has_foundry = has_azure # Foundry uses Azure OpenAI credentials +has_foundry_key = bool(_AZURE_KEY and _AZURE_ENDPOINT and _AZURE_CHAT_DEPLOYMENT) +has_azure = has_foundry_key # backward-compat alias (key-auth Azure) has_entra = bool(_AZURE_ENDPOINT and _AZURE_CHAT_DEPLOYMENT) # Entra ID: endpoint + deployment, no API key needed +# Live Azure is reachable via EITHER key auth or Entra (az login). When no key is +# present, the Foundry vectors run keyless through DefaultAzureCredential so `az +# login` alone drives them. +has_foundry = has_foundry_key or has_entra has_anthropic = bool(_ANTHROPIC_KEY) has_direct_openai = bool(_DIRECT_OPENAI_KEY) @@ -273,17 +278,35 @@ def make_foundry_agent( if deployment is None: deployment = _AZURE_CHAT_DEPLOYMENT + # Keyless Entra (az login) when no API key is configured; key auth otherwise. + # With no live Azure creds at all, fall back to a dummy key so the autouse + # mock fixture (which patches AzureOpenAI) can serve deterministic responses. + connection: dict[str, Any] + if _AZURE_KEY: + connection = { + "kind": "key", + "endpoint": _AZURE_ENDPOINT or "https://mock.local/", + "apiKey": _AZURE_KEY, + } + elif has_entra: + connection = { + "kind": "foundry", + "endpoint": _AZURE_ENDPOINT, + } + else: + connection = { + "kind": "key", + "endpoint": _AZURE_ENDPOINT or "https://mock.local/", + "apiKey": "mock-azure-key", + } + data: dict[str, Any] = { "name": "integration-test-foundry", "model": { "id": deployment, "provider": "foundry", "apiType": api_type, - "connection": { - "kind": "key", - "endpoint": _AZURE_ENDPOINT or "https://mock.local/", - "apiKey": _AZURE_KEY or "mock-azure-key", - }, + "connection": connection, }, } if options: diff --git a/runtime/python/prompty/tests/integration/test_model_listing.py b/runtime/python/prompty/tests/integration/test_model_listing.py index 679f972f9..81cad692d 100644 --- a/runtime/python/prompty/tests/integration/test_model_listing.py +++ b/runtime/python/prompty/tests/integration/test_model_listing.py @@ -4,7 +4,7 @@ import pytest -from prompty.model import ApiKeyConnection +from prompty.model import ApiKeyConnection, FoundryConnection from prompty.providers.foundry.models import list_models as foundry_list_models from prompty.providers.foundry.models import list_models_async as foundry_list_models_async from prompty.providers.openai.models import list_models as openai_list_models @@ -14,10 +14,11 @@ _AZURE_ENDPOINT, _AZURE_KEY, _DIRECT_OPENAI_KEY, + _FOUNDRY_PROJECT_ENDPOINT, _OPENAI_BASE_URL, _OPENAI_KEY, has_direct_openai, - has_foundry, + has_foundry_key, has_openai, ) @@ -54,13 +55,22 @@ def test_list_models(self) -> None: _assert_models(openai_list_models(connection)) -@pytest.mark.skipif(not has_foundry, reason="Azure OpenAI env vars not set") +# Foundry model listing needs either a key (models.list on the resource) or, for +# keyless Entra, a project endpoint (deployments REST via DefaultAzureCredential). +_has_foundry_listing = has_foundry_key or bool(_FOUNDRY_PROJECT_ENDPOINT) + + +def _foundry_listing_connection(): + if _AZURE_KEY: + return _api_key_connection(_AZURE_KEY, _AZURE_ENDPOINT) + return FoundryConnection.load({"kind": "foundry", "endpoint": _FOUNDRY_PROJECT_ENDPOINT}) + + +@pytest.mark.skipif(not _has_foundry_listing, reason="No Azure key or FOUNDRY_PROJECT_ENDPOINT set") class TestFoundryModelListing: def test_list_models(self) -> None: - connection = _api_key_connection(_AZURE_KEY, _AZURE_ENDPOINT) - _assert_models(foundry_list_models(connection)) + _assert_models(foundry_list_models(_foundry_listing_connection())) @pytest.mark.asyncio async def test_list_models_async(self) -> None: - connection = _api_key_connection(_AZURE_KEY, _AZURE_ENDPOINT) - _assert_models(await foundry_list_models_async(connection)) + _assert_models(await foundry_list_models_async(_foundry_listing_connection())) diff --git a/runtime/python/prompty/tests/model/contracts/connectivity/test_anonymous_connection.py b/runtime/python/prompty/tests/model/contracts/connectivity/test_anonymous_connection.py index 0c3aedc4c..518843831 100644 --- a/runtime/python/prompty/tests/model/contracts/connectivity/test_anonymous_connection.py +++ b/runtime/python/prompty/tests/model/contracts/connectivity/test_anonymous_connection.py @@ -80,3 +80,13 @@ def test_to_yaml_anonymousconnection(): assert yaml_output is not None parsed = yaml.safe_load(yaml_output) assert isinstance(parsed, dict) + + +def test_load_anonymousconnection_invalid(): + """load must reject invalid input instead of silently defaulting.""" + raised = False + try: + AnonymousConnection.load(object()) + except ValueError: + raised = True + assert raised, "Expected invalid input to be rejected" diff --git a/runtime/python/prompty/tests/model/contracts/connectivity/test_api_key_connection.py b/runtime/python/prompty/tests/model/contracts/connectivity/test_api_key_connection.py index 23fe2b2be..f9c792ab5 100644 --- a/runtime/python/prompty/tests/model/contracts/connectivity/test_api_key_connection.py +++ b/runtime/python/prompty/tests/model/contracts/connectivity/test_api_key_connection.py @@ -87,3 +87,13 @@ def test_to_yaml_apikeyconnection(): assert yaml_output is not None parsed = yaml.safe_load(yaml_output) assert isinstance(parsed, dict) + + +def test_load_apikeyconnection_invalid(): + """load must reject invalid input instead of silently defaulting.""" + raised = False + try: + ApiKeyConnection.load(object()) + except ValueError: + raised = True + assert raised, "Expected invalid input to be rejected" diff --git a/runtime/python/prompty/tests/model/contracts/connectivity/test_connection.py b/runtime/python/prompty/tests/model/contracts/connectivity/test_connection.py index 2277658cf..ed4ae0c7a 100644 --- a/runtime/python/prompty/tests/model/contracts/connectivity/test_connection.py +++ b/runtime/python/prompty/tests/model/contracts/connectivity/test_connection.py @@ -88,3 +88,13 @@ def test_to_yaml_connection(): assert yaml_output is not None parsed = yaml.safe_load(yaml_output) assert isinstance(parsed, dict) + + +def test_load_connection_invalid(): + """load must reject invalid input instead of silently defaulting.""" + raised = False + try: + Connection.load(object()) + except ValueError: + raised = True + assert raised, "Expected invalid input to be rejected" diff --git a/runtime/python/prompty/tests/model/contracts/connectivity/test_device_authorization.py b/runtime/python/prompty/tests/model/contracts/connectivity/test_device_authorization.py index 82ff2efbb..80fd9c17d 100644 --- a/runtime/python/prompty/tests/model/contracts/connectivity/test_device_authorization.py +++ b/runtime/python/prompty/tests/model/contracts/connectivity/test_device_authorization.py @@ -104,3 +104,41 @@ def test_to_yaml_deviceauthorization(): assert yaml_output is not None parsed = yaml.safe_load(yaml_output) assert isinstance(parsed, dict) + + +def test_to_wire_deviceauthorization(): + """Test that to_wire()/from_wire() apply provider wire field names.""" + json_data = r""" + { + "deviceCode": "sample", + "userCode": "sample", + "verificationUri": "sample", + "expiresIn": 1, + "interval": 1 + } + """ + data = json.loads(json_data, strict=False) + instance = DeviceAuthorization.load(data) + foundry_wire = instance.to_wire("foundry") + assert "device_code" in foundry_wire + assert "deviceCode" not in foundry_wire + assert "user_code" in foundry_wire + assert "userCode" not in foundry_wire + assert "verification_uri" in foundry_wire + assert "verificationUri" not in foundry_wire + assert "expires_in" in foundry_wire + assert "expiresIn" not in foundry_wire + assert "interval" in foundry_wire + foundry_restored = DeviceAuthorization.from_wire("foundry", foundry_wire) + foundry_round = foundry_restored.to_wire("foundry") + assert set(foundry_round.keys()) == set(foundry_wire.keys()) + + +def test_load_deviceauthorization_invalid(): + """load must reject invalid input instead of silently defaulting.""" + raised = False + try: + DeviceAuthorization.load(object()) + except ValueError: + raised = True + assert raised, "Expected invalid input to be rejected" diff --git a/runtime/python/prompty/tests/model/contracts/connectivity/test_foundry_connection.py b/runtime/python/prompty/tests/model/contracts/connectivity/test_foundry_connection.py index 52677bc51..8fda47ffd 100644 --- a/runtime/python/prompty/tests/model/contracts/connectivity/test_foundry_connection.py +++ b/runtime/python/prompty/tests/model/contracts/connectivity/test_foundry_connection.py @@ -96,3 +96,13 @@ def test_to_yaml_foundryconnection(): assert yaml_output is not None parsed = yaml.safe_load(yaml_output) assert isinstance(parsed, dict) + + +def test_load_foundryconnection_invalid(): + """load must reject invalid input instead of silently defaulting.""" + raised = False + try: + FoundryConnection.load(object()) + except ValueError: + raised = True + assert raised, "Expected invalid input to be rejected" diff --git a/runtime/python/prompty/tests/model/contracts/connectivity/test_o_auth_connection.py b/runtime/python/prompty/tests/model/contracts/connectivity/test_o_auth_connection.py index c38381cd7..2afdbc915 100644 --- a/runtime/python/prompty/tests/model/contracts/connectivity/test_o_auth_connection.py +++ b/runtime/python/prompty/tests/model/contracts/connectivity/test_o_auth_connection.py @@ -118,3 +118,13 @@ def test_to_yaml_oauthconnection(): assert yaml_output is not None parsed = yaml.safe_load(yaml_output) assert isinstance(parsed, dict) + + +def test_load_oauthconnection_invalid(): + """load must reject invalid input instead of silently defaulting.""" + raised = False + try: + OAuthConnection.load(object()) + except ValueError: + raised = True + assert raised, "Expected invalid input to be rejected" diff --git a/runtime/python/prompty/tests/model/contracts/connectivity/test_o_auth_token.py b/runtime/python/prompty/tests/model/contracts/connectivity/test_o_auth_token.py index 73f7f20da..03ee00969 100644 --- a/runtime/python/prompty/tests/model/contracts/connectivity/test_o_auth_token.py +++ b/runtime/python/prompty/tests/model/contracts/connectivity/test_o_auth_token.py @@ -80,3 +80,32 @@ def test_to_yaml_oauthtoken(): assert yaml_output is not None parsed = yaml.safe_load(yaml_output) assert isinstance(parsed, dict) + + +def test_to_wire_oauthtoken(): + """Test that to_wire()/from_wire() apply provider wire field names.""" + json_data = r""" + { + "refreshToken": "0.AXoAoffline-refresh-token-value", + "scope": "https://cognitiveservices.azure.com/.default offline_access" + } + """ + data = json.loads(json_data, strict=False) + instance = OAuthToken.load(data) + foundry_wire = instance.to_wire("foundry") + assert "refresh_token" in foundry_wire + assert "refreshToken" not in foundry_wire + assert "scope" in foundry_wire + foundry_restored = OAuthToken.from_wire("foundry", foundry_wire) + foundry_round = foundry_restored.to_wire("foundry") + assert set(foundry_round.keys()) == set(foundry_wire.keys()) + + +def test_load_oauthtoken_invalid(): + """load must reject invalid input instead of silently defaulting.""" + raised = False + try: + OAuthToken.load(object()) + except ValueError: + raised = True + assert raised, "Expected invalid input to be rejected" diff --git a/runtime/python/prompty/tests/model/contracts/connectivity/test_reference_connection.py b/runtime/python/prompty/tests/model/contracts/connectivity/test_reference_connection.py index 12abdb16e..b9e8817c0 100644 --- a/runtime/python/prompty/tests/model/contracts/connectivity/test_reference_connection.py +++ b/runtime/python/prompty/tests/model/contracts/connectivity/test_reference_connection.py @@ -88,3 +88,13 @@ def test_to_yaml_referenceconnection(): assert yaml_output is not None parsed = yaml.safe_load(yaml_output) assert isinstance(parsed, dict) + + +def test_load_referenceconnection_invalid(): + """load must reject invalid input instead of silently defaulting.""" + raised = False + try: + ReferenceConnection.load(object()) + except ValueError: + raised = True + assert raised, "Expected invalid input to be rejected" diff --git a/runtime/python/prompty/tests/model/contracts/connectivity/test_remote_connection.py b/runtime/python/prompty/tests/model/contracts/connectivity/test_remote_connection.py index bc1ea1538..75e24a135 100644 --- a/runtime/python/prompty/tests/model/contracts/connectivity/test_remote_connection.py +++ b/runtime/python/prompty/tests/model/contracts/connectivity/test_remote_connection.py @@ -88,3 +88,13 @@ def test_to_yaml_remoteconnection(): assert yaml_output is not None parsed = yaml.safe_load(yaml_output) assert isinstance(parsed, dict) + + +def test_load_remoteconnection_invalid(): + """load must reject invalid input instead of silently defaulting.""" + raised = False + try: + RemoteConnection.load(object()) + except ValueError: + raised = True + assert raised, "Expected invalid input to be rejected" diff --git a/runtime/python/prompty/tests/model/contracts/conversation/test_audio_part.py b/runtime/python/prompty/tests/model/contracts/conversation/test_audio_part.py index 1bf286106..cdcc01230 100644 --- a/runtime/python/prompty/tests/model/contracts/conversation/test_audio_part.py +++ b/runtime/python/prompty/tests/model/contracts/conversation/test_audio_part.py @@ -80,3 +80,13 @@ def test_to_yaml_audiopart(): assert yaml_output is not None parsed = yaml.safe_load(yaml_output) assert isinstance(parsed, dict) + + +def test_load_audiopart_invalid(): + """load must reject invalid input instead of silently defaulting.""" + raised = False + try: + AudioPart.load(object()) + except ValueError: + raised = True + assert raised, "Expected invalid input to be rejected" diff --git a/runtime/python/prompty/tests/model/contracts/conversation/test_content_part.py b/runtime/python/prompty/tests/model/contracts/conversation/test_content_part.py index ac1c5e0b3..a1501e588 100644 --- a/runtime/python/prompty/tests/model/contracts/conversation/test_content_part.py +++ b/runtime/python/prompty/tests/model/contracts/conversation/test_content_part.py @@ -77,3 +77,13 @@ def test_to_yaml_contentpart(): assert yaml_output is not None parsed = yaml.safe_load(yaml_output) assert isinstance(parsed, dict) + + +def test_load_contentpart_invalid(): + """load must reject invalid input instead of silently defaulting.""" + raised = False + try: + ContentPart.load(object()) + except ValueError: + raised = True + assert raised, "Expected invalid input to be rejected" diff --git a/runtime/python/prompty/tests/model/contracts/conversation/test_file_part.py b/runtime/python/prompty/tests/model/contracts/conversation/test_file_part.py index c2ef2bea7..e05eb66ff 100644 --- a/runtime/python/prompty/tests/model/contracts/conversation/test_file_part.py +++ b/runtime/python/prompty/tests/model/contracts/conversation/test_file_part.py @@ -80,3 +80,13 @@ def test_to_yaml_filepart(): assert yaml_output is not None parsed = yaml.safe_load(yaml_output) assert isinstance(parsed, dict) + + +def test_load_filepart_invalid(): + """load must reject invalid input instead of silently defaulting.""" + raised = False + try: + FilePart.load(object()) + except ValueError: + raised = True + assert raised, "Expected invalid input to be rejected" diff --git a/runtime/python/prompty/tests/model/contracts/conversation/test_image_part.py b/runtime/python/prompty/tests/model/contracts/conversation/test_image_part.py index 0c326bf7f..cbdbd4f12 100644 --- a/runtime/python/prompty/tests/model/contracts/conversation/test_image_part.py +++ b/runtime/python/prompty/tests/model/contracts/conversation/test_image_part.py @@ -88,3 +88,13 @@ def test_to_yaml_imagepart(): assert yaml_output is not None parsed = yaml.safe_load(yaml_output) assert isinstance(parsed, dict) + + +def test_load_imagepart_invalid(): + """load must reject invalid input instead of silently defaulting.""" + raised = False + try: + ImagePart.load(object()) + except ValueError: + raised = True + assert raised, "Expected invalid input to be rejected" diff --git a/runtime/python/prompty/tests/model/contracts/conversation/test_message.py b/runtime/python/prompty/tests/model/contracts/conversation/test_message.py index e1fc92768..d161e75c9 100644 --- a/runtime/python/prompty/tests/model/contracts/conversation/test_message.py +++ b/runtime/python/prompty/tests/model/contracts/conversation/test_message.py @@ -115,6 +115,16 @@ def test_to_yaml_message(): assert isinstance(parsed, dict) +def test_load_message_invalid(): + """load must reject invalid input instead of silently defaulting.""" + raised = False + try: + Message.load(object()) + except ValueError: + raised = True + assert raised, "Expected invalid input to be rejected" + + def test_factory_assistant_message(): """Test that assistant() factory creates a valid instance.""" instance = Message.assistant("test") diff --git a/runtime/python/prompty/tests/model/contracts/conversation/test_text_part.py b/runtime/python/prompty/tests/model/contracts/conversation/test_text_part.py index 01cf14694..32f61a178 100644 --- a/runtime/python/prompty/tests/model/contracts/conversation/test_text_part.py +++ b/runtime/python/prompty/tests/model/contracts/conversation/test_text_part.py @@ -72,3 +72,13 @@ def test_to_yaml_textpart(): assert yaml_output is not None parsed = yaml.safe_load(yaml_output) assert isinstance(parsed, dict) + + +def test_load_textpart_invalid(): + """load must reject invalid input instead of silently defaulting.""" + raised = False + try: + TextPart.load(object()) + except ValueError: + raised = True + assert raised, "Expected invalid input to be rejected" diff --git a/runtime/python/prompty/tests/model/contracts/conversation/test_tool_call.py b/runtime/python/prompty/tests/model/contracts/conversation/test_tool_call.py index 9f0c636bf..bd7cbbc69 100644 --- a/runtime/python/prompty/tests/model/contracts/conversation/test_tool_call.py +++ b/runtime/python/prompty/tests/model/contracts/conversation/test_tool_call.py @@ -88,3 +88,13 @@ def test_to_yaml_toolcall(): assert yaml_output is not None parsed = yaml.safe_load(yaml_output) assert isinstance(parsed, dict) + + +def test_load_toolcall_invalid(): + """load must reject invalid input instead of silently defaulting.""" + raised = False + try: + ToolCall.load(object()) + except ValueError: + raised = True + assert raised, "Expected invalid input to be rejected" diff --git a/runtime/python/prompty/tests/model/contracts/conversation/test_tool_result.py b/runtime/python/prompty/tests/model/contracts/conversation/test_tool_result.py index 6e26fbbc5..c59912e84 100644 --- a/runtime/python/prompty/tests/model/contracts/conversation/test_tool_result.py +++ b/runtime/python/prompty/tests/model/contracts/conversation/test_tool_result.py @@ -117,6 +117,16 @@ def test_to_yaml_toolresult(): assert isinstance(parsed, dict) +def test_load_toolresult_invalid(): + """load must reject invalid input instead of silently defaulting.""" + raised = False + try: + ToolResult.load(object()) + except ValueError: + raised = True + assert raised, "Expected invalid input to be rejected" + + def test_factory_text_toolresult(): """Test that text() factory creates a valid instance.""" instance = ToolResult.text("test") diff --git a/runtime/python/prompty/tests/model/contracts/core/test_array_property.py b/runtime/python/prompty/tests/model/contracts/core/test_array_property.py index 70f29e0b8..3e14e8c29 100644 --- a/runtime/python/prompty/tests/model/contracts/core/test_array_property.py +++ b/runtime/python/prompty/tests/model/contracts/core/test_array_property.py @@ -78,3 +78,13 @@ def test_to_yaml_arrayproperty(): assert yaml_output is not None parsed = yaml.safe_load(yaml_output) assert isinstance(parsed, dict) + + +def test_load_arrayproperty_invalid(): + """load must reject invalid input instead of silently defaulting.""" + raised = False + try: + ArrayProperty.load(object()) + except ValueError: + raised = True + assert raised, "Expected invalid input to be rejected" diff --git a/runtime/python/prompty/tests/model/contracts/core/test_object_property.py b/runtime/python/prompty/tests/model/contracts/core/test_object_property.py index d1e518d93..40758a74a 100644 --- a/runtime/python/prompty/tests/model/contracts/core/test_object_property.py +++ b/runtime/python/prompty/tests/model/contracts/core/test_object_property.py @@ -101,3 +101,13 @@ def test_to_yaml_objectproperty(): assert yaml_output is not None parsed = yaml.safe_load(yaml_output) assert isinstance(parsed, dict) + + +def test_load_objectproperty_invalid(): + """load must reject invalid input instead of silently defaulting.""" + raised = False + try: + ObjectProperty.load(object()) + except ValueError: + raised = True + assert raised, "Expected invalid input to be rejected" diff --git a/runtime/python/prompty/tests/model/contracts/core/test_property.py b/runtime/python/prompty/tests/model/contracts/core/test_property.py index d5afd59f4..6a8931586 100644 --- a/runtime/python/prompty/tests/model/contracts/core/test_property.py +++ b/runtime/python/prompty/tests/model/contracts/core/test_property.py @@ -146,6 +146,16 @@ def test_to_yaml_property(): assert isinstance(parsed, dict) +def test_load_property_invalid(): + """load must reject invalid input instead of silently defaulting.""" + raised = False + try: + Property.load(object()) + except ValueError: + raised = True + assert raised, "Expected invalid input to be rejected" + + def test_load_property_from_bool(): instance = Property.load(False) assert instance is not None diff --git a/runtime/python/prompty/tests/model/contracts/core/test_union_property.py b/runtime/python/prompty/tests/model/contracts/core/test_union_property.py index 4601821fd..d55785389 100644 --- a/runtime/python/prompty/tests/model/contracts/core/test_union_property.py +++ b/runtime/python/prompty/tests/model/contracts/core/test_union_property.py @@ -99,3 +99,13 @@ def test_to_yaml_unionproperty(): assert yaml_output is not None parsed = yaml.safe_load(yaml_output) assert isinstance(parsed, dict) + + +def test_load_unionproperty_invalid(): + """load must reject invalid input instead of silently defaulting.""" + raised = False + try: + UnionProperty.load(object()) + except ValueError: + raised = True + assert raised, "Expected invalid input to be rejected" diff --git a/runtime/python/prompty/tests/model/contracts/events/test_checkpoint.py b/runtime/python/prompty/tests/model/contracts/events/test_checkpoint.py index 4c8621b10..b7730980d 100644 --- a/runtime/python/prompty/tests/model/contracts/events/test_checkpoint.py +++ b/runtime/python/prompty/tests/model/contracts/events/test_checkpoint.py @@ -112,3 +112,13 @@ def test_to_yaml_checkpoint(): assert yaml_output is not None parsed = yaml.safe_load(yaml_output) assert isinstance(parsed, dict) + + +def test_load_checkpoint_invalid(): + """load must reject invalid input instead of silently defaulting.""" + raised = False + try: + Checkpoint.load(object()) + except ValueError: + raised = True + assert raised, "Expected invalid input to be rejected" diff --git a/runtime/python/prompty/tests/model/contracts/events/test_error_chunk.py b/runtime/python/prompty/tests/model/contracts/events/test_error_chunk.py index b4c1eb28e..3038feda8 100644 --- a/runtime/python/prompty/tests/model/contracts/events/test_error_chunk.py +++ b/runtime/python/prompty/tests/model/contracts/events/test_error_chunk.py @@ -72,3 +72,13 @@ def test_to_yaml_errorchunk(): assert yaml_output is not None parsed = yaml.safe_load(yaml_output) assert isinstance(parsed, dict) + + +def test_load_errorchunk_invalid(): + """load must reject invalid input instead of silently defaulting.""" + raised = False + try: + ErrorChunk.load(object()) + except ValueError: + raised = True + assert raised, "Expected invalid input to be rejected" diff --git a/runtime/python/prompty/tests/model/contracts/events/test_failure_chunk.py b/runtime/python/prompty/tests/model/contracts/events/test_failure_chunk.py index a13d0f481..718b394e9 100644 --- a/runtime/python/prompty/tests/model/contracts/events/test_failure_chunk.py +++ b/runtime/python/prompty/tests/model/contracts/events/test_failure_chunk.py @@ -83,3 +83,13 @@ def test_to_yaml_failurechunk(): assert yaml_output is not None parsed = yaml.safe_load(yaml_output) assert isinstance(parsed, dict) + + +def test_load_failurechunk_invalid(): + """load must reject invalid input instead of silently defaulting.""" + raised = False + try: + FailureChunk.load(object()) + except ValueError: + raised = True + assert raised, "Expected invalid input to be rejected" diff --git a/runtime/python/prompty/tests/model/contracts/events/test_host_tool_request.py b/runtime/python/prompty/tests/model/contracts/events/test_host_tool_request.py index 670be8aff..dcfb1c020 100644 --- a/runtime/python/prompty/tests/model/contracts/events/test_host_tool_request.py +++ b/runtime/python/prompty/tests/model/contracts/events/test_host_tool_request.py @@ -96,3 +96,13 @@ def test_to_yaml_hosttoolrequest(): assert yaml_output is not None parsed = yaml.safe_load(yaml_output) assert isinstance(parsed, dict) + + +def test_load_hosttoolrequest_invalid(): + """load must reject invalid input instead of silently defaulting.""" + raised = False + try: + HostToolRequest.load(object()) + except ValueError: + raised = True + assert raised, "Expected invalid input to be rejected" diff --git a/runtime/python/prompty/tests/model/contracts/events/test_host_tool_result.py b/runtime/python/prompty/tests/model/contracts/events/test_host_tool_result.py index 77a04cd45..1fb906640 100644 --- a/runtime/python/prompty/tests/model/contracts/events/test_host_tool_result.py +++ b/runtime/python/prompty/tests/model/contracts/events/test_host_tool_result.py @@ -120,3 +120,13 @@ def test_to_yaml_hosttoolresult(): assert yaml_output is not None parsed = yaml.safe_load(yaml_output) assert isinstance(parsed, dict) + + +def test_load_hosttoolresult_invalid(): + """load must reject invalid input instead of silently defaulting.""" + raised = False + try: + HostToolResult.load(object()) + except ValueError: + raised = True + assert raised, "Expected invalid input to be rejected" diff --git a/runtime/python/prompty/tests/model/contracts/events/test_permission_decision.py b/runtime/python/prompty/tests/model/contracts/events/test_permission_decision.py index bf50b5fe5..2f3e444b6 100644 --- a/runtime/python/prompty/tests/model/contracts/events/test_permission_decision.py +++ b/runtime/python/prompty/tests/model/contracts/events/test_permission_decision.py @@ -104,3 +104,13 @@ def test_to_yaml_permissiondecision(): assert yaml_output is not None parsed = yaml.safe_load(yaml_output) assert isinstance(parsed, dict) + + +def test_load_permissiondecision_invalid(): + """load must reject invalid input instead of silently defaulting.""" + raised = False + try: + PermissionDecision.load(object()) + except ValueError: + raised = True + assert raised, "Expected invalid input to be rejected" diff --git a/runtime/python/prompty/tests/model/contracts/events/test_permission_request.py b/runtime/python/prompty/tests/model/contracts/events/test_permission_request.py index 11344158c..29c79357c 100644 --- a/runtime/python/prompty/tests/model/contracts/events/test_permission_request.py +++ b/runtime/python/prompty/tests/model/contracts/events/test_permission_request.py @@ -104,3 +104,13 @@ def test_to_yaml_permissionrequest(): assert yaml_output is not None parsed = yaml.safe_load(yaml_output) assert isinstance(parsed, dict) + + +def test_load_permissionrequest_invalid(): + """load must reject invalid input instead of silently defaulting.""" + raised = False + try: + PermissionRequest.load(object()) + except ValueError: + raised = True + assert raised, "Expected invalid input to be rejected" diff --git a/runtime/python/prompty/tests/model/contracts/events/test_redacted_field.py b/runtime/python/prompty/tests/model/contracts/events/test_redacted_field.py index 100ea0f11..fde58c2b9 100644 --- a/runtime/python/prompty/tests/model/contracts/events/test_redacted_field.py +++ b/runtime/python/prompty/tests/model/contracts/events/test_redacted_field.py @@ -88,3 +88,13 @@ def test_to_yaml_redactedfield(): assert yaml_output is not None parsed = yaml.safe_load(yaml_output) assert isinstance(parsed, dict) + + +def test_load_redactedfield_invalid(): + """load must reject invalid input instead of silently defaulting.""" + raised = False + try: + RedactedField.load(object()) + except ValueError: + raised = True + assert raised, "Expected invalid input to be rejected" diff --git a/runtime/python/prompty/tests/model/contracts/events/test_redaction_metadata.py b/runtime/python/prompty/tests/model/contracts/events/test_redaction_metadata.py index 644776f53..7e5f7badc 100644 --- a/runtime/python/prompty/tests/model/contracts/events/test_redaction_metadata.py +++ b/runtime/python/prompty/tests/model/contracts/events/test_redaction_metadata.py @@ -80,3 +80,13 @@ def test_to_yaml_redactionmetadata(): assert yaml_output is not None parsed = yaml.safe_load(yaml_output) assert isinstance(parsed, dict) + + +def test_load_redactionmetadata_invalid(): + """load must reject invalid input instead of silently defaulting.""" + raised = False + try: + RedactionMetadata.load(object()) + except ValueError: + raised = True + assert raised, "Expected invalid input to be rejected" diff --git a/runtime/python/prompty/tests/model/contracts/events/test_session_event.py b/runtime/python/prompty/tests/model/contracts/events/test_session_event.py index 7888076f0..41dde06f2 100644 --- a/runtime/python/prompty/tests/model/contracts/events/test_session_event.py +++ b/runtime/python/prompty/tests/model/contracts/events/test_session_event.py @@ -112,3 +112,13 @@ def test_to_yaml_sessionevent(): assert yaml_output is not None parsed = yaml.safe_load(yaml_output) assert isinstance(parsed, dict) + + +def test_load_sessionevent_invalid(): + """load must reject invalid input instead of silently defaulting.""" + raised = False + try: + SessionEvent.load(object()) + except ValueError: + raised = True + assert raised, "Expected invalid input to be rejected" diff --git a/runtime/python/prompty/tests/model/contracts/events/test_session_summary.py b/runtime/python/prompty/tests/model/contracts/events/test_session_summary.py index b629ee74c..ab75db403 100644 --- a/runtime/python/prompty/tests/model/contracts/events/test_session_summary.py +++ b/runtime/python/prompty/tests/model/contracts/events/test_session_summary.py @@ -104,3 +104,13 @@ def test_to_yaml_sessionsummary(): assert yaml_output is not None parsed = yaml.safe_load(yaml_output) assert isinstance(parsed, dict) + + +def test_load_sessionsummary_invalid(): + """load must reject invalid input instead of silently defaulting.""" + raised = False + try: + SessionSummary.load(object()) + except ValueError: + raised = True + assert raised, "Expected invalid input to be rejected" diff --git a/runtime/python/prompty/tests/model/contracts/events/test_stream_chunk.py b/runtime/python/prompty/tests/model/contracts/events/test_stream_chunk.py index 2c78db3ab..3ade6f6b3 100644 --- a/runtime/python/prompty/tests/model/contracts/events/test_stream_chunk.py +++ b/runtime/python/prompty/tests/model/contracts/events/test_stream_chunk.py @@ -77,3 +77,13 @@ def test_to_yaml_streamchunk(): assert yaml_output is not None parsed = yaml.safe_load(yaml_output) assert isinstance(parsed, dict) + + +def test_load_streamchunk_invalid(): + """load must reject invalid input instead of silently defaulting.""" + raised = False + try: + StreamChunk.load(object()) + except ValueError: + raised = True + assert raised, "Expected invalid input to be rejected" diff --git a/runtime/python/prompty/tests/model/contracts/events/test_stream_failure.py b/runtime/python/prompty/tests/model/contracts/events/test_stream_failure.py index a611f9951..acba5f837 100644 --- a/runtime/python/prompty/tests/model/contracts/events/test_stream_failure.py +++ b/runtime/python/prompty/tests/model/contracts/events/test_stream_failure.py @@ -80,3 +80,13 @@ def test_to_yaml_streamfailure(): assert yaml_output is not None parsed = yaml.safe_load(yaml_output) assert isinstance(parsed, dict) + + +def test_load_streamfailure_invalid(): + """load must reject invalid input instead of silently defaulting.""" + raised = False + try: + StreamFailure.load(object()) + except ValueError: + raised = True + assert raised, "Expected invalid input to be rejected" diff --git a/runtime/python/prompty/tests/model/contracts/events/test_text_chunk.py b/runtime/python/prompty/tests/model/contracts/events/test_text_chunk.py index 46e425e4a..c10d34f83 100644 --- a/runtime/python/prompty/tests/model/contracts/events/test_text_chunk.py +++ b/runtime/python/prompty/tests/model/contracts/events/test_text_chunk.py @@ -72,3 +72,13 @@ def test_to_yaml_textchunk(): assert yaml_output is not None parsed = yaml.safe_load(yaml_output) assert isinstance(parsed, dict) + + +def test_load_textchunk_invalid(): + """load must reject invalid input instead of silently defaulting.""" + raised = False + try: + TextChunk.load(object()) + except ValueError: + raised = True + assert raised, "Expected invalid input to be rejected" diff --git a/runtime/python/prompty/tests/model/contracts/events/test_thinking_chunk.py b/runtime/python/prompty/tests/model/contracts/events/test_thinking_chunk.py index cccdb2240..35a95d227 100644 --- a/runtime/python/prompty/tests/model/contracts/events/test_thinking_chunk.py +++ b/runtime/python/prompty/tests/model/contracts/events/test_thinking_chunk.py @@ -72,3 +72,13 @@ def test_to_yaml_thinkingchunk(): assert yaml_output is not None parsed = yaml.safe_load(yaml_output) assert isinstance(parsed, dict) + + +def test_load_thinkingchunk_invalid(): + """load must reject invalid input instead of silently defaulting.""" + raised = False + try: + ThinkingChunk.load(object()) + except ValueError: + raised = True + assert raised, "Expected invalid input to be rejected" diff --git a/runtime/python/prompty/tests/model/contracts/events/test_tool_chunk.py b/runtime/python/prompty/tests/model/contracts/events/test_tool_chunk.py index be59a227f..c059d862c 100644 --- a/runtime/python/prompty/tests/model/contracts/events/test_tool_chunk.py +++ b/runtime/python/prompty/tests/model/contracts/events/test_tool_chunk.py @@ -88,3 +88,13 @@ def test_to_yaml_toolchunk(): assert yaml_output is not None parsed = yaml.safe_load(yaml_output) assert isinstance(parsed, dict) + + +def test_load_toolchunk_invalid(): + """load must reject invalid input instead of silently defaulting.""" + raised = False + try: + ToolChunk.load(object()) + except ValueError: + raised = True + assert raised, "Expected invalid input to be rejected" diff --git a/runtime/python/prompty/tests/model/contracts/events/test_turn_event.py b/runtime/python/prompty/tests/model/contracts/events/test_turn_event.py index 3e6cb774e..cf8368ae2 100644 --- a/runtime/python/prompty/tests/model/contracts/events/test_turn_event.py +++ b/runtime/python/prompty/tests/model/contracts/events/test_turn_event.py @@ -112,3 +112,13 @@ def test_to_yaml_turnevent(): assert yaml_output is not None parsed = yaml.safe_load(yaml_output) assert isinstance(parsed, dict) + + +def test_load_turnevent_invalid(): + """load must reject invalid input instead of silently defaulting.""" + raised = False + try: + TurnEvent.load(object()) + except ValueError: + raised = True + assert raised, "Expected invalid input to be rejected" diff --git a/runtime/python/prompty/tests/model/contracts/events/test_usage_chunk.py b/runtime/python/prompty/tests/model/contracts/events/test_usage_chunk.py index 2a14b6a1c..6631199b5 100644 --- a/runtime/python/prompty/tests/model/contracts/events/test_usage_chunk.py +++ b/runtime/python/prompty/tests/model/contracts/events/test_usage_chunk.py @@ -96,3 +96,13 @@ def test_to_yaml_usagechunk(): assert yaml_output is not None parsed = yaml.safe_load(yaml_output) assert isinstance(parsed, dict) + + +def test_load_usagechunk_invalid(): + """load must reject invalid input instead of silently defaulting.""" + raised = False + try: + UsageChunk.load(object()) + except ValueError: + raised = True + assert raised, "Expected invalid input to be rejected" diff --git a/runtime/python/prompty/tests/model/contracts/memory/test_memory_entry.py b/runtime/python/prompty/tests/model/contracts/memory/test_memory_entry.py index 351e445d6..4593718c0 100644 --- a/runtime/python/prompty/tests/model/contracts/memory/test_memory_entry.py +++ b/runtime/python/prompty/tests/model/contracts/memory/test_memory_entry.py @@ -107,3 +107,13 @@ def test_to_yaml_memoryentry(): assert yaml_output is not None parsed = yaml.safe_load(yaml_output) assert isinstance(parsed, dict) + + +def test_load_memoryentry_invalid(): + """load must reject invalid input instead of silently defaulting.""" + raised = False + try: + MemoryEntry.load(object()) + except ValueError: + raised = True + assert raised, "Expected invalid input to be rejected" diff --git a/runtime/python/prompty/tests/model/contracts/memory/test_memory_store.py b/runtime/python/prompty/tests/model/contracts/memory/test_memory_store.py index e89bfb9bf..417bd4b47 100644 --- a/runtime/python/prompty/tests/model/contracts/memory/test_memory_store.py +++ b/runtime/python/prompty/tests/model/contracts/memory/test_memory_store.py @@ -69,3 +69,13 @@ def test_to_yaml_memorystore(): assert yaml_output is not None parsed = yaml.safe_load(yaml_output) assert isinstance(parsed, dict) + + +def test_load_memorystore_invalid(): + """load must reject invalid input instead of silently defaulting.""" + raised = False + try: + MemoryStore.load(object()) + except ValueError: + raised = True + assert raised, "Expected invalid input to be rejected" diff --git a/runtime/python/prompty/tests/model/contracts/models/test_azure_model.py b/runtime/python/prompty/tests/model/contracts/models/test_azure_model.py index 8e73e7610..4bcfd5a7e 100644 --- a/runtime/python/prompty/tests/model/contracts/models/test_azure_model.py +++ b/runtime/python/prompty/tests/model/contracts/models/test_azure_model.py @@ -72,3 +72,13 @@ def test_to_yaml_azuremodel(): assert yaml_output is not None parsed = yaml.safe_load(yaml_output) assert isinstance(parsed, dict) + + +def test_load_azuremodel_invalid(): + """load must reject invalid input instead of silently defaulting.""" + raised = False + try: + AzureModel.load(object()) + except ValueError: + raised = True + assert raised, "Expected invalid input to be rejected" diff --git a/runtime/python/prompty/tests/model/contracts/models/test_custom_model.py b/runtime/python/prompty/tests/model/contracts/models/test_custom_model.py index cfb9a1c8e..313b38609 100644 --- a/runtime/python/prompty/tests/model/contracts/models/test_custom_model.py +++ b/runtime/python/prompty/tests/model/contracts/models/test_custom_model.py @@ -72,3 +72,13 @@ def test_to_yaml_custommodel(): assert yaml_output is not None parsed = yaml.safe_load(yaml_output) assert isinstance(parsed, dict) + + +def test_load_custommodel_invalid(): + """load must reject invalid input instead of silently defaulting.""" + raised = False + try: + CustomModel.load(object()) + except ValueError: + raised = True + assert raised, "Expected invalid input to be rejected" diff --git a/runtime/python/prompty/tests/model/contracts/models/test_invocation_usage.py b/runtime/python/prompty/tests/model/contracts/models/test_invocation_usage.py index b7deb5e69..31a5335f8 100644 --- a/runtime/python/prompty/tests/model/contracts/models/test_invocation_usage.py +++ b/runtime/python/prompty/tests/model/contracts/models/test_invocation_usage.py @@ -88,3 +88,44 @@ def test_to_yaml_invocationusage(): assert yaml_output is not None parsed = yaml.safe_load(yaml_output) assert isinstance(parsed, dict) + + +def test_to_wire_invocationusage(): + """Test that to_wire()/from_wire() apply provider wire field names.""" + json_data = r""" + { + "inputTokens": 150, + "outputTokens": 42, + "totalTokens": 192 + } + """ + data = json.loads(json_data, strict=False) + instance = InvocationUsage.load(data) + openai_wire = instance.to_wire("openai") + assert "prompt_tokens" in openai_wire + assert "inputTokens" not in openai_wire + assert "completion_tokens" in openai_wire + assert "outputTokens" not in openai_wire + assert "total_tokens" in openai_wire + assert "totalTokens" not in openai_wire + openai_restored = InvocationUsage.from_wire("openai", openai_wire) + openai_round = openai_restored.to_wire("openai") + assert set(openai_round.keys()) == set(openai_wire.keys()) + anthropic_wire = instance.to_wire("anthropic") + assert "input_tokens" in anthropic_wire + assert "inputTokens" not in anthropic_wire + assert "output_tokens" in anthropic_wire + assert "outputTokens" not in anthropic_wire + anthropic_restored = InvocationUsage.from_wire("anthropic", anthropic_wire) + anthropic_round = anthropic_restored.to_wire("anthropic") + assert set(anthropic_round.keys()) == set(anthropic_wire.keys()) + + +def test_load_invocationusage_invalid(): + """load must reject invalid input instead of silently defaulting.""" + raised = False + try: + InvocationUsage.load(object()) + except ValueError: + raised = True + assert raised, "Expected invalid input to be rejected" diff --git a/runtime/python/prompty/tests/model/contracts/models/test_model.py b/runtime/python/prompty/tests/model/contracts/models/test_model.py index f1da05faa..e47e2af9b 100644 --- a/runtime/python/prompty/tests/model/contracts/models/test_model.py +++ b/runtime/python/prompty/tests/model/contracts/models/test_model.py @@ -138,6 +138,16 @@ def test_to_yaml_model(): assert isinstance(parsed, dict) +def test_load_model_invalid(): + """load must reject invalid input instead of silently defaulting.""" + raised = False + try: + Model.load(object()) + except ValueError: + raised = True + assert raised, "Expected invalid input to be rejected" + + def test_load_model_from_str(): instance = Model.load("example") assert instance is not None diff --git a/runtime/python/prompty/tests/model/contracts/models/test_model_info.py b/runtime/python/prompty/tests/model/contracts/models/test_model_info.py index cb68dc3f5..7d1ed0166 100644 --- a/runtime/python/prompty/tests/model/contracts/models/test_model_info.py +++ b/runtime/python/prompty/tests/model/contracts/models/test_model_info.py @@ -143,3 +143,57 @@ def test_to_yaml_modelinfo(): assert yaml_output is not None parsed = yaml.safe_load(yaml_output) assert isinstance(parsed, dict) + + +def test_to_wire_modelinfo(): + """Test that to_wire()/from_wire() apply provider wire field names.""" + json_data = r""" + { + "id": "gpt-4o", + "displayName": "GPT-4o", + "ownedBy": "openai", + "contextWindow": 128000, + "inputModalities": [ + "text", + "image" + ], + "outputModalities": [ + "text" + ], + "additionalProperties": { + "supportsStreaming": true + } + } + """ + data = json.loads(json_data, strict=False) + instance = ModelInfo.load(data) + openai_wire = instance.to_wire("openai") + assert "id" in openai_wire + assert "owned_by" in openai_wire + assert "ownedBy" not in openai_wire + openai_restored = ModelInfo.from_wire("openai", openai_wire) + openai_round = openai_restored.to_wire("openai") + assert set(openai_round.keys()) == set(openai_wire.keys()) + anthropic_wire = instance.to_wire("anthropic") + assert "id" in anthropic_wire + assert "display_name" in anthropic_wire + assert "displayName" not in anthropic_wire + assert "context_length" in anthropic_wire + assert "contextWindow" not in anthropic_wire + assert "input_modalities" in anthropic_wire + assert "inputModalities" not in anthropic_wire + assert "output_modalities" in anthropic_wire + assert "outputModalities" not in anthropic_wire + anthropic_restored = ModelInfo.from_wire("anthropic", anthropic_wire) + anthropic_round = anthropic_restored.to_wire("anthropic") + assert set(anthropic_round.keys()) == set(anthropic_wire.keys()) + + +def test_load_modelinfo_invalid(): + """load must reject invalid input instead of silently defaulting.""" + raised = False + try: + ModelInfo.load(object()) + except ValueError: + raised = True + assert raised, "Expected invalid input to be rejected" diff --git a/runtime/python/prompty/tests/model/contracts/models/test_model_options.py b/runtime/python/prompty/tests/model/contracts/models/test_model_options.py index a11fa313d..71e839f63 100644 --- a/runtime/python/prompty/tests/model/contracts/models/test_model_options.py +++ b/runtime/python/prompty/tests/model/contracts/models/test_model_options.py @@ -12,6 +12,7 @@ def test_load_json_modeloptions(): "frequencyPenalty": 0.5, "maxOutputTokens": 2048, "presencePenalty": 0.3, + "reasoningEffort": "medium", "seed": 42, "temperature": 0.7, "topK": 40, @@ -33,6 +34,7 @@ def test_load_json_modeloptions(): assert instance.frequency_penalty == 0.5 assert instance.max_output_tokens == 2048 assert instance.presence_penalty == 0.3 + assert instance.reasoning_effort == "medium" assert instance.seed == 42 assert instance.temperature == 0.7 assert instance.top_k == 40 @@ -45,6 +47,7 @@ def test_load_yaml_modeloptions(): frequencyPenalty: 0.5 maxOutputTokens: 2048 presencePenalty: 0.3 + reasoningEffort: medium seed: 42 temperature: 0.7 topK: 40 @@ -64,6 +67,7 @@ def test_load_yaml_modeloptions(): assert instance.frequency_penalty == 0.5 assert instance.max_output_tokens == 2048 assert instance.presence_penalty == 0.3 + assert instance.reasoning_effort == "medium" assert instance.seed == 42 assert instance.temperature == 0.7 assert instance.top_k == 40 @@ -78,6 +82,7 @@ def test_roundtrip_json_modeloptions(): "frequencyPenalty": 0.5, "maxOutputTokens": 2048, "presencePenalty": 0.3, + "reasoningEffort": "medium", "seed": 42, "temperature": 0.7, "topK": 40, @@ -101,6 +106,7 @@ def test_roundtrip_json_modeloptions(): assert reloaded.frequency_penalty == 0.5 assert reloaded.max_output_tokens == 2048 assert reloaded.presence_penalty == 0.3 + assert reloaded.reasoning_effort == "medium" assert reloaded.seed == 42 assert reloaded.temperature == 0.7 assert reloaded.top_k == 40 @@ -115,6 +121,7 @@ def test_to_json_modeloptions(): "frequencyPenalty": 0.5, "maxOutputTokens": 2048, "presencePenalty": 0.3, + "reasoningEffort": "medium", "seed": 42, "temperature": 0.7, "topK": 40, @@ -145,6 +152,7 @@ def test_to_yaml_modeloptions(): "frequencyPenalty": 0.5, "maxOutputTokens": 2048, "presencePenalty": 0.3, + "reasoningEffort": "medium", "seed": 42, "temperature": 0.7, "topK": 40, @@ -166,3 +174,86 @@ def test_to_yaml_modeloptions(): assert yaml_output is not None parsed = yaml.safe_load(yaml_output) assert isinstance(parsed, dict) + + +def test_to_wire_modeloptions(): + """Test that to_wire()/from_wire() apply provider wire field names.""" + json_data = r""" + { + "frequencyPenalty": 0.5, + "maxOutputTokens": 2048, + "presencePenalty": 0.3, + "reasoningEffort": "medium", + "seed": 42, + "temperature": 0.7, + "topK": 40, + "topP": 0.9, + "stopSequences": [ + "\n", + "###" + ], + "allowMultipleToolCalls": true, + "additionalProperties": { + "customProperty": "value", + "anotherProperty": "anotherValue" + } + } + """ + data = json.loads(json_data, strict=False) + instance = ModelOptions.load(data) + openai_wire = instance.to_wire("openai") + assert "frequency_penalty" in openai_wire + assert "frequencyPenalty" not in openai_wire + assert "max_completion_tokens" in openai_wire + assert "maxOutputTokens" not in openai_wire + assert "presence_penalty" in openai_wire + assert "presencePenalty" not in openai_wire + assert "reasoning_effort" in openai_wire + assert "reasoningEffort" not in openai_wire + assert "seed" in openai_wire + assert "temperature" in openai_wire + assert "top_k" in openai_wire + assert "topK" not in openai_wire + assert "top_p" in openai_wire + assert "topP" not in openai_wire + assert "stop" in openai_wire + assert "stopSequences" not in openai_wire + assert "parallel_tool_calls" in openai_wire + assert "allowMultipleToolCalls" not in openai_wire + openai_restored = ModelOptions.from_wire("openai", openai_wire) + openai_round = openai_restored.to_wire("openai") + assert set(openai_round.keys()) == set(openai_wire.keys()) + responses_wire = instance.to_wire("responses") + assert "max_output_tokens" in responses_wire + assert "maxOutputTokens" not in responses_wire + assert "reasoning_effort" in responses_wire + assert "reasoningEffort" not in responses_wire + assert "temperature" in responses_wire + assert "top_p" in responses_wire + assert "topP" not in responses_wire + responses_restored = ModelOptions.from_wire("responses", responses_wire) + responses_round = responses_restored.to_wire("responses") + assert set(responses_round.keys()) == set(responses_wire.keys()) + anthropic_wire = instance.to_wire("anthropic") + assert "max_tokens" in anthropic_wire + assert "maxOutputTokens" not in anthropic_wire + assert "temperature" in anthropic_wire + assert "top_k" in anthropic_wire + assert "topK" not in anthropic_wire + assert "top_p" in anthropic_wire + assert "topP" not in anthropic_wire + assert "stop_sequences" in anthropic_wire + assert "stopSequences" not in anthropic_wire + anthropic_restored = ModelOptions.from_wire("anthropic", anthropic_wire) + anthropic_round = anthropic_restored.to_wire("anthropic") + assert set(anthropic_round.keys()) == set(anthropic_wire.keys()) + + +def test_load_modeloptions_invalid(): + """load must reject invalid input instead of silently defaulting.""" + raised = False + try: + ModelOptions.load(object()) + except ValueError: + raised = True + assert raised, "Expected invalid input to be rejected" diff --git a/runtime/python/prompty/tests/model/contracts/models/test_open_ai_model.py b/runtime/python/prompty/tests/model/contracts/models/test_open_ai_model.py index efbfdc18f..a7089b8f8 100644 --- a/runtime/python/prompty/tests/model/contracts/models/test_open_ai_model.py +++ b/runtime/python/prompty/tests/model/contracts/models/test_open_ai_model.py @@ -72,3 +72,13 @@ def test_to_yaml_openaimodel(): assert yaml_output is not None parsed = yaml.safe_load(yaml_output) assert isinstance(parsed, dict) + + +def test_load_openaimodel_invalid(): + """load must reject invalid input instead of silently defaulting.""" + raised = False + try: + OpenAIModel.load(object()) + except ValueError: + raised = True + assert raised, "Expected invalid input to be rejected" diff --git a/runtime/python/prompty/tests/model/contracts/models/test_token_usage.py b/runtime/python/prompty/tests/model/contracts/models/test_token_usage.py index 512b4b1a7..b15a6a30a 100644 --- a/runtime/python/prompty/tests/model/contracts/models/test_token_usage.py +++ b/runtime/python/prompty/tests/model/contracts/models/test_token_usage.py @@ -88,3 +88,44 @@ def test_to_yaml_tokenusage(): assert yaml_output is not None parsed = yaml.safe_load(yaml_output) assert isinstance(parsed, dict) + + +def test_to_wire_tokenusage(): + """Test that to_wire()/from_wire() apply provider wire field names.""" + json_data = r""" + { + "promptTokens": 150, + "completionTokens": 42, + "totalTokens": 192 + } + """ + data = json.loads(json_data, strict=False) + instance = TokenUsage.load(data) + openai_wire = instance.to_wire("openai") + assert "prompt_tokens" in openai_wire + assert "promptTokens" not in openai_wire + assert "completion_tokens" in openai_wire + assert "completionTokens" not in openai_wire + assert "total_tokens" in openai_wire + assert "totalTokens" not in openai_wire + openai_restored = TokenUsage.from_wire("openai", openai_wire) + openai_round = openai_restored.to_wire("openai") + assert set(openai_round.keys()) == set(openai_wire.keys()) + anthropic_wire = instance.to_wire("anthropic") + assert "input_tokens" in anthropic_wire + assert "promptTokens" not in anthropic_wire + assert "output_tokens" in anthropic_wire + assert "completionTokens" not in anthropic_wire + anthropic_restored = TokenUsage.from_wire("anthropic", anthropic_wire) + anthropic_round = anthropic_restored.to_wire("anthropic") + assert set(anthropic_round.keys()) == set(anthropic_wire.keys()) + + +def test_load_tokenusage_invalid(): + """load must reject invalid input instead of silently defaulting.""" + raised = False + try: + TokenUsage.load(object()) + except ValueError: + raised = True + assert raised, "Expected invalid input to be rejected" diff --git a/runtime/python/prompty/tests/model/contracts/pipeline/test_delegated_state_reference.py b/runtime/python/prompty/tests/model/contracts/pipeline/test_delegated_state_reference.py index 445628812..3ddc8103d 100644 --- a/runtime/python/prompty/tests/model/contracts/pipeline/test_delegated_state_reference.py +++ b/runtime/python/prompty/tests/model/contracts/pipeline/test_delegated_state_reference.py @@ -88,3 +88,13 @@ def test_to_yaml_delegatedstatereference(): assert yaml_output is not None parsed = yaml.safe_load(yaml_output) assert isinstance(parsed, dict) + + +def test_load_delegatedstatereference_invalid(): + """load must reject invalid input instead of silently defaulting.""" + raised = False + try: + DelegatedStateReference.load(object()) + except ValueError: + raised = True + assert raised, "Expected invalid input to be rejected" diff --git a/runtime/python/prompty/tests/model/contracts/pipeline/test_engine_checkpoint.py b/runtime/python/prompty/tests/model/contracts/pipeline/test_engine_checkpoint.py index d927ae066..590a5dcb4 100644 --- a/runtime/python/prompty/tests/model/contracts/pipeline/test_engine_checkpoint.py +++ b/runtime/python/prompty/tests/model/contracts/pipeline/test_engine_checkpoint.py @@ -164,3 +164,13 @@ def test_to_yaml_enginecheckpoint(): assert yaml_output is not None parsed = yaml.safe_load(yaml_output) assert isinstance(parsed, dict) + + +def test_load_enginecheckpoint_invalid(): + """load must reject invalid input instead of silently defaulting.""" + raised = False + try: + EngineCheckpoint.load(object()) + except ValueError: + raised = True + assert raised, "Expected invalid input to be rejected" diff --git a/runtime/python/prompty/tests/model/contracts/pipeline/test_engine_event.py b/runtime/python/prompty/tests/model/contracts/pipeline/test_engine_event.py index 0ae754cd5..81c5c6d53 100644 --- a/runtime/python/prompty/tests/model/contracts/pipeline/test_engine_event.py +++ b/runtime/python/prompty/tests/model/contracts/pipeline/test_engine_event.py @@ -104,3 +104,13 @@ def test_to_yaml_engineevent(): assert yaml_output is not None parsed = yaml.safe_load(yaml_output) assert isinstance(parsed, dict) + + +def test_load_engineevent_invalid(): + """load must reject invalid input instead of silently defaulting.""" + raised = False + try: + EngineEvent.load(object()) + except ValueError: + raised = True + assert raised, "Expected invalid input to be rejected" diff --git a/runtime/python/prompty/tests/model/contracts/pipeline/test_engine_permission_decision.py b/runtime/python/prompty/tests/model/contracts/pipeline/test_engine_permission_decision.py index b19b56742..e12785fe1 100644 --- a/runtime/python/prompty/tests/model/contracts/pipeline/test_engine_permission_decision.py +++ b/runtime/python/prompty/tests/model/contracts/pipeline/test_engine_permission_decision.py @@ -72,3 +72,13 @@ def test_to_yaml_enginepermissiondecision(): assert yaml_output is not None parsed = yaml.safe_load(yaml_output) assert isinstance(parsed, dict) + + +def test_load_enginepermissiondecision_invalid(): + """load must reject invalid input instead of silently defaulting.""" + raised = False + try: + EnginePermissionDecision.load(object()) + except ValueError: + raised = True + assert raised, "Expected invalid input to be rejected" diff --git a/runtime/python/prompty/tests/model/contracts/pipeline/test_invocation_context_decision.py b/runtime/python/prompty/tests/model/contracts/pipeline/test_invocation_context_decision.py index dabfc222a..5b780ed79 100644 --- a/runtime/python/prompty/tests/model/contracts/pipeline/test_invocation_context_decision.py +++ b/runtime/python/prompty/tests/model/contracts/pipeline/test_invocation_context_decision.py @@ -80,3 +80,13 @@ def test_to_yaml_invocationcontextdecision(): assert yaml_output is not None parsed = yaml.safe_load(yaml_output) assert isinstance(parsed, dict) + + +def test_load_invocationcontextdecision_invalid(): + """load must reject invalid input instead of silently defaulting.""" + raised = False + try: + InvocationContextDecision.load(object()) + except ValueError: + raised = True + assert raised, "Expected invalid input to be rejected" diff --git a/runtime/python/prompty/tests/model/contracts/pipeline/test_invocation_context_state.py b/runtime/python/prompty/tests/model/contracts/pipeline/test_invocation_context_state.py index b9a4aa540..bc9b2a951 100644 --- a/runtime/python/prompty/tests/model/contracts/pipeline/test_invocation_context_state.py +++ b/runtime/python/prompty/tests/model/contracts/pipeline/test_invocation_context_state.py @@ -61,3 +61,13 @@ def test_to_yaml_invocationcontextstate(): assert yaml_output is not None parsed = yaml.safe_load(yaml_output) assert isinstance(parsed, dict) + + +def test_load_invocationcontextstate_invalid(): + """load must reject invalid input instead of silently defaulting.""" + raised = False + try: + InvocationContextState.load(object()) + except ValueError: + raised = True + assert raised, "Expected invalid input to be rejected" diff --git a/runtime/python/prompty/tests/model/contracts/pipeline/test_model_invocation_context_snapshot.py b/runtime/python/prompty/tests/model/contracts/pipeline/test_model_invocation_context_snapshot.py index bb9355023..e9810df87 100644 --- a/runtime/python/prompty/tests/model/contracts/pipeline/test_model_invocation_context_snapshot.py +++ b/runtime/python/prompty/tests/model/contracts/pipeline/test_model_invocation_context_snapshot.py @@ -164,3 +164,13 @@ def test_to_yaml_modelinvocationcontextsnapshot(): assert yaml_output is not None parsed = yaml.safe_load(yaml_output) assert isinstance(parsed, dict) + + +def test_load_modelinvocationcontextsnapshot_invalid(): + """load must reject invalid input instead of silently defaulting.""" + raised = False + try: + ModelInvocationContextSnapshot.load(object()) + except ValueError: + raised = True + assert raised, "Expected invalid input to be rejected" diff --git a/runtime/python/prompty/tests/model/contracts/pipeline/test_model_invocation_request.py b/runtime/python/prompty/tests/model/contracts/pipeline/test_model_invocation_request.py index d4178e803..e67df7be4 100644 --- a/runtime/python/prompty/tests/model/contracts/pipeline/test_model_invocation_request.py +++ b/runtime/python/prompty/tests/model/contracts/pipeline/test_model_invocation_request.py @@ -108,3 +108,13 @@ def test_to_yaml_modelinvocationrequest(): assert yaml_output is not None parsed = yaml.safe_load(yaml_output) assert isinstance(parsed, dict) + + +def test_load_modelinvocationrequest_invalid(): + """load must reject invalid input instead of silently defaulting.""" + raised = False + try: + ModelInvocationRequest.load(object()) + except ValueError: + raised = True + assert raised, "Expected invalid input to be rejected" diff --git a/runtime/python/prompty/tests/model/contracts/pipeline/test_model_invocation_response.py b/runtime/python/prompty/tests/model/contracts/pipeline/test_model_invocation_response.py index 09abccf0b..d3d378be4 100644 --- a/runtime/python/prompty/tests/model/contracts/pipeline/test_model_invocation_response.py +++ b/runtime/python/prompty/tests/model/contracts/pipeline/test_model_invocation_response.py @@ -61,3 +61,13 @@ def test_to_yaml_modelinvocationresponse(): assert yaml_output is not None parsed = yaml.safe_load(yaml_output) assert isinstance(parsed, dict) + + +def test_load_modelinvocationresponse_invalid(): + """load must reject invalid input instead of silently defaulting.""" + raised = False + try: + ModelInvocationResponse.load(object()) + except ValueError: + raised = True + assert raised, "Expected invalid input to be rejected" diff --git a/runtime/python/prompty/tests/model/contracts/pipeline/test_model_reconciliation_state.py b/runtime/python/prompty/tests/model/contracts/pipeline/test_model_reconciliation_state.py index 1ae4b3d68..585b68e85 100644 --- a/runtime/python/prompty/tests/model/contracts/pipeline/test_model_reconciliation_state.py +++ b/runtime/python/prompty/tests/model/contracts/pipeline/test_model_reconciliation_state.py @@ -133,3 +133,13 @@ def test_to_yaml_modelreconciliationstate(): assert yaml_output is not None parsed = yaml.safe_load(yaml_output) assert isinstance(parsed, dict) + + +def test_load_modelreconciliationstate_invalid(): + """load must reject invalid input instead of silently defaulting.""" + raised = False + try: + ModelReconciliationState.load(object()) + except ValueError: + raised = True + assert raised, "Expected invalid input to be rejected" diff --git a/runtime/python/prompty/tests/model/contracts/pipeline/test_model_tool_request.py b/runtime/python/prompty/tests/model/contracts/pipeline/test_model_tool_request.py index 1a500250b..611246533 100644 --- a/runtime/python/prompty/tests/model/contracts/pipeline/test_model_tool_request.py +++ b/runtime/python/prompty/tests/model/contracts/pipeline/test_model_tool_request.py @@ -80,3 +80,13 @@ def test_to_yaml_modeltoolrequest(): assert yaml_output is not None parsed = yaml.safe_load(yaml_output) assert isinstance(parsed, dict) + + +def test_load_modeltoolrequest_invalid(): + """load must reject invalid input instead of silently defaulting.""" + raised = False + try: + ModelToolRequest.load(object()) + except ValueError: + raised = True + assert raised, "Expected invalid input to be rejected" diff --git a/runtime/python/prompty/tests/model/contracts/pipeline/test_model_tool_result.py b/runtime/python/prompty/tests/model/contracts/pipeline/test_model_tool_result.py index f12d0ad06..46f0014c6 100644 --- a/runtime/python/prompty/tests/model/contracts/pipeline/test_model_tool_result.py +++ b/runtime/python/prompty/tests/model/contracts/pipeline/test_model_tool_result.py @@ -80,3 +80,13 @@ def test_to_yaml_modeltoolresult(): assert yaml_output is not None parsed = yaml.safe_load(yaml_output) assert isinstance(parsed, dict) + + +def test_load_modeltoolresult_invalid(): + """load must reject invalid input instead of silently defaulting.""" + raised = False + try: + ModelToolResult.load(object()) + except ValueError: + raised = True + assert raised, "Expected invalid input to be rejected" diff --git a/runtime/python/prompty/tests/model/contracts/pipeline/test_replay_journal_record.py b/runtime/python/prompty/tests/model/contracts/pipeline/test_replay_journal_record.py index 28310acb2..e28c15917 100644 --- a/runtime/python/prompty/tests/model/contracts/pipeline/test_replay_journal_record.py +++ b/runtime/python/prompty/tests/model/contracts/pipeline/test_replay_journal_record.py @@ -72,3 +72,13 @@ def test_to_yaml_replayjournalrecord(): assert yaml_output is not None parsed = yaml.safe_load(yaml_output) assert isinstance(parsed, dict) + + +def test_load_replayjournalrecord_invalid(): + """load must reject invalid input instead of silently defaulting.""" + raised = False + try: + ReplayJournalRecord.load(object()) + except ValueError: + raised = True + assert raised, "Expected invalid input to be rejected" diff --git a/runtime/python/prompty/tests/model/contracts/pipeline/test_resume_context.py b/runtime/python/prompty/tests/model/contracts/pipeline/test_resume_context.py index 384ce873b..0d72c452b 100644 --- a/runtime/python/prompty/tests/model/contracts/pipeline/test_resume_context.py +++ b/runtime/python/prompty/tests/model/contracts/pipeline/test_resume_context.py @@ -121,3 +121,13 @@ def test_to_yaml_resumecontext(): assert yaml_output is not None parsed = yaml.safe_load(yaml_output) assert isinstance(parsed, dict) + + +def test_load_resumecontext_invalid(): + """load must reject invalid input instead of silently defaulting.""" + raised = False + try: + ResumeContext.load(object()) + except ValueError: + raised = True + assert raised, "Expected invalid input to be rejected" diff --git a/runtime/python/prompty/tests/model/contracts/pipeline/test_turn_commit.py b/runtime/python/prompty/tests/model/contracts/pipeline/test_turn_commit.py index 895a01521..30e2dfaf1 100644 --- a/runtime/python/prompty/tests/model/contracts/pipeline/test_turn_commit.py +++ b/runtime/python/prompty/tests/model/contracts/pipeline/test_turn_commit.py @@ -148,3 +148,13 @@ def test_to_yaml_turncommit(): assert yaml_output is not None parsed = yaml.safe_load(yaml_output) assert isinstance(parsed, dict) + + +def test_load_turncommit_invalid(): + """load must reject invalid input instead of silently defaulting.""" + raised = False + try: + TurnCommit.load(object()) + except ValueError: + raised = True + assert raised, "Expected invalid input to be rejected" diff --git a/runtime/python/prompty/tests/model/contracts/templates/test_custom_format.py b/runtime/python/prompty/tests/model/contracts/templates/test_custom_format.py index 1f8a471b4..52af4375a 100644 --- a/runtime/python/prompty/tests/model/contracts/templates/test_custom_format.py +++ b/runtime/python/prompty/tests/model/contracts/templates/test_custom_format.py @@ -72,3 +72,13 @@ def test_to_yaml_customformat(): assert yaml_output is not None parsed = yaml.safe_load(yaml_output) assert isinstance(parsed, dict) + + +def test_load_customformat_invalid(): + """load must reject invalid input instead of silently defaulting.""" + raised = False + try: + CustomFormat.load(object()) + except ValueError: + raised = True + assert raised, "Expected invalid input to be rejected" diff --git a/runtime/python/prompty/tests/model/contracts/templates/test_custom_parser.py b/runtime/python/prompty/tests/model/contracts/templates/test_custom_parser.py index cdf711835..100f09241 100644 --- a/runtime/python/prompty/tests/model/contracts/templates/test_custom_parser.py +++ b/runtime/python/prompty/tests/model/contracts/templates/test_custom_parser.py @@ -72,3 +72,13 @@ def test_to_yaml_customparser(): assert yaml_output is not None parsed = yaml.safe_load(yaml_output) assert isinstance(parsed, dict) + + +def test_load_customparser_invalid(): + """load must reject invalid input instead of silently defaulting.""" + raised = False + try: + CustomParser.load(object()) + except ValueError: + raised = True + assert raised, "Expected invalid input to be rejected" diff --git a/runtime/python/prompty/tests/model/contracts/templates/test_format_config.py b/runtime/python/prompty/tests/model/contracts/templates/test_format_config.py index 6cccae4f1..81cf5de01 100644 --- a/runtime/python/prompty/tests/model/contracts/templates/test_format_config.py +++ b/runtime/python/prompty/tests/model/contracts/templates/test_format_config.py @@ -96,6 +96,16 @@ def test_to_yaml_formatconfig(): assert isinstance(parsed, dict) +def test_load_formatconfig_invalid(): + """load must reject invalid input instead of silently defaulting.""" + raised = False + try: + FormatConfig.load(object()) + except ValueError: + raised = True + assert raised, "Expected invalid input to be rejected" + + def test_load_formatconfig_from_str(): instance = FormatConfig.load("example") assert instance is not None diff --git a/runtime/python/prompty/tests/model/contracts/templates/test_jinja2format.py b/runtime/python/prompty/tests/model/contracts/templates/test_jinja2format.py index 83c13551b..910c3e4e3 100644 --- a/runtime/python/prompty/tests/model/contracts/templates/test_jinja2format.py +++ b/runtime/python/prompty/tests/model/contracts/templates/test_jinja2format.py @@ -72,3 +72,13 @@ def test_to_yaml_jinja2format(): assert yaml_output is not None parsed = yaml.safe_load(yaml_output) assert isinstance(parsed, dict) + + +def test_load_jinja2format_invalid(): + """load must reject invalid input instead of silently defaulting.""" + raised = False + try: + Jinja2Format.load(object()) + except ValueError: + raised = True + assert raised, "Expected invalid input to be rejected" diff --git a/runtime/python/prompty/tests/model/contracts/templates/test_mustache_format.py b/runtime/python/prompty/tests/model/contracts/templates/test_mustache_format.py index f49d44103..3ec2d59de 100644 --- a/runtime/python/prompty/tests/model/contracts/templates/test_mustache_format.py +++ b/runtime/python/prompty/tests/model/contracts/templates/test_mustache_format.py @@ -72,3 +72,13 @@ def test_to_yaml_mustacheformat(): assert yaml_output is not None parsed = yaml.safe_load(yaml_output) assert isinstance(parsed, dict) + + +def test_load_mustacheformat_invalid(): + """load must reject invalid input instead of silently defaulting.""" + raised = False + try: + MustacheFormat.load(object()) + except ValueError: + raised = True + assert raised, "Expected invalid input to be rejected" diff --git a/runtime/python/prompty/tests/model/contracts/templates/test_parser_config.py b/runtime/python/prompty/tests/model/contracts/templates/test_parser_config.py index 57b1b4a6a..c440f47cb 100644 --- a/runtime/python/prompty/tests/model/contracts/templates/test_parser_config.py +++ b/runtime/python/prompty/tests/model/contracts/templates/test_parser_config.py @@ -88,6 +88,16 @@ def test_to_yaml_parserconfig(): assert isinstance(parsed, dict) +def test_load_parserconfig_invalid(): + """load must reject invalid input instead of silently defaulting.""" + raised = False + try: + ParserConfig.load(object()) + except ValueError: + raised = True + assert raised, "Expected invalid input to be rejected" + + def test_load_parserconfig_from_str(): instance = ParserConfig.load("example") assert instance is not None diff --git a/runtime/python/prompty/tests/model/contracts/templates/test_prompty_parser.py b/runtime/python/prompty/tests/model/contracts/templates/test_prompty_parser.py index 6ba82ebaf..dbc09c62c 100644 --- a/runtime/python/prompty/tests/model/contracts/templates/test_prompty_parser.py +++ b/runtime/python/prompty/tests/model/contracts/templates/test_prompty_parser.py @@ -72,3 +72,13 @@ def test_to_yaml_promptyparser(): assert yaml_output is not None parsed = yaml.safe_load(yaml_output) assert isinstance(parsed, dict) + + +def test_load_promptyparser_invalid(): + """load must reject invalid input instead of silently defaulting.""" + raised = False + try: + PromptyParser.load(object()) + except ValueError: + raised = True + assert raised, "Expected invalid input to be rejected" diff --git a/runtime/python/prompty/tests/model/contracts/templates/test_template.py b/runtime/python/prompty/tests/model/contracts/templates/test_template.py index e2fb2f215..2c70fdb27 100644 --- a/runtime/python/prompty/tests/model/contracts/templates/test_template.py +++ b/runtime/python/prompty/tests/model/contracts/templates/test_template.py @@ -92,3 +92,13 @@ def test_to_yaml_template(): assert yaml_output is not None parsed = yaml.safe_load(yaml_output) assert isinstance(parsed, dict) + + +def test_load_template_invalid(): + """load must reject invalid input instead of silently defaulting.""" + raised = False + try: + Template.load(object()) + except ValueError: + raised = True + assert raised, "Expected invalid input to be rejected" diff --git a/runtime/python/prompty/tests/model/contracts/tooling/test_binding.py b/runtime/python/prompty/tests/model/contracts/tooling/test_binding.py index 7ea45d5bb..c26544618 100644 --- a/runtime/python/prompty/tests/model/contracts/tooling/test_binding.py +++ b/runtime/python/prompty/tests/model/contracts/tooling/test_binding.py @@ -82,6 +82,16 @@ def test_to_yaml_binding(): assert isinstance(parsed, dict) +def test_load_binding_invalid(): + """load must reject invalid input instead of silently defaulting.""" + raised = False + try: + Binding.load(object()) + except ValueError: + raised = True + assert raised, "Expected invalid input to be rejected" + + def test_load_binding_from_str(): instance = Binding.load("example") assert instance is not None diff --git a/runtime/python/prompty/tests/model/contracts/tooling/test_custom_tool.py b/runtime/python/prompty/tests/model/contracts/tooling/test_custom_tool.py index 293a742ad..46203ae88 100644 --- a/runtime/python/prompty/tests/model/contracts/tooling/test_custom_tool.py +++ b/runtime/python/prompty/tests/model/contracts/tooling/test_custom_tool.py @@ -97,3 +97,13 @@ def test_to_yaml_customtool(): assert yaml_output is not None parsed = yaml.safe_load(yaml_output) assert isinstance(parsed, dict) + + +def test_load_customtool_invalid(): + """load must reject invalid input instead of silently defaulting.""" + raised = False + try: + CustomTool.load(object()) + except ValueError: + raised = True + assert raised, "Expected invalid input to be rejected" diff --git a/runtime/python/prompty/tests/model/contracts/tooling/test_function_tool.py b/runtime/python/prompty/tests/model/contracts/tooling/test_function_tool.py index d0c835066..c4c4d2b64 100644 --- a/runtime/python/prompty/tests/model/contracts/tooling/test_function_tool.py +++ b/runtime/python/prompty/tests/model/contracts/tooling/test_function_tool.py @@ -300,3 +300,13 @@ def test_to_yaml_functiontool_1(): assert yaml_output is not None parsed = yaml.safe_load(yaml_output) assert isinstance(parsed, dict) + + +def test_load_functiontool_invalid(): + """load must reject invalid input instead of silently defaulting.""" + raised = False + try: + FunctionTool.load(object()) + except ValueError: + raised = True + assert raised, "Expected invalid input to be rejected" diff --git a/runtime/python/prompty/tests/model/contracts/tooling/test_mcp_approval_mode.py b/runtime/python/prompty/tests/model/contracts/tooling/test_mcp_approval_mode.py index f94be0ecb..051886e51 100644 --- a/runtime/python/prompty/tests/model/contracts/tooling/test_mcp_approval_mode.py +++ b/runtime/python/prompty/tests/model/contracts/tooling/test_mcp_approval_mode.py @@ -102,6 +102,16 @@ def test_to_yaml_mcpapprovalmode(): assert isinstance(parsed, dict) +def test_load_mcpapprovalmode_invalid(): + """load must reject invalid input instead of silently defaulting.""" + raised = False + try: + McpApprovalMode.load(object()) + except ValueError: + raised = True + assert raised, "Expected invalid input to be rejected" + + def test_load_mcpapprovalmode_from_str(): instance = McpApprovalMode.load("never") assert instance is not None diff --git a/runtime/python/prompty/tests/model/contracts/tooling/test_mcp_tool.py b/runtime/python/prompty/tests/model/contracts/tooling/test_mcp_tool.py index 48c7c35ca..4bc34eabe 100644 --- a/runtime/python/prompty/tests/model/contracts/tooling/test_mcp_tool.py +++ b/runtime/python/prompty/tests/model/contracts/tooling/test_mcp_tool.py @@ -135,3 +135,13 @@ def test_to_yaml_mcptool(): assert yaml_output is not None parsed = yaml.safe_load(yaml_output) assert isinstance(parsed, dict) + + +def test_load_mcptool_invalid(): + """load must reject invalid input instead of silently defaulting.""" + raised = False + try: + McpTool.load(object()) + except ValueError: + raised = True + assert raised, "Expected invalid input to be rejected" diff --git a/runtime/python/prompty/tests/model/contracts/tooling/test_open_api_tool.py b/runtime/python/prompty/tests/model/contracts/tooling/test_open_api_tool.py index 9bd22a71f..37224deba 100644 --- a/runtime/python/prompty/tests/model/contracts/tooling/test_open_api_tool.py +++ b/runtime/python/prompty/tests/model/contracts/tooling/test_open_api_tool.py @@ -94,3 +94,13 @@ def test_to_yaml_openapitool(): assert yaml_output is not None parsed = yaml.safe_load(yaml_output) assert isinstance(parsed, dict) + + +def test_load_openapitool_invalid(): + """load must reject invalid input instead of silently defaulting.""" + raised = False + try: + OpenApiTool.load(object()) + except ValueError: + raised = True + assert raised, "Expected invalid input to be rejected" diff --git a/runtime/python/prompty/tests/model/contracts/tooling/test_tool.py b/runtime/python/prompty/tests/model/contracts/tooling/test_tool.py index c85f2cffc..d1b94f4fd 100644 --- a/runtime/python/prompty/tests/model/contracts/tooling/test_tool.py +++ b/runtime/python/prompty/tests/model/contracts/tooling/test_tool.py @@ -102,3 +102,13 @@ def test_to_yaml_tool(): assert yaml_output is not None parsed = yaml.safe_load(yaml_output) assert isinstance(parsed, dict) + + +def test_load_tool_invalid(): + """load must reject invalid input instead of silently defaulting.""" + raised = False + try: + Tool.load(object()) + except ValueError: + raised = True + assert raised, "Expected invalid input to be rejected" diff --git a/runtime/python/prompty/tests/model/operations/pipeline/test_render_segment.py b/runtime/python/prompty/tests/model/operations/pipeline/test_render_segment.py index ebd1b4a87..5b35939d8 100644 --- a/runtime/python/prompty/tests/model/operations/pipeline/test_render_segment.py +++ b/runtime/python/prompty/tests/model/operations/pipeline/test_render_segment.py @@ -80,3 +80,13 @@ def test_to_yaml_rendersegment(): assert yaml_output is not None parsed = yaml.safe_load(yaml_output) assert isinstance(parsed, dict) + + +def test_load_rendersegment_invalid(): + """load must reject invalid input instead of silently defaulting.""" + raised = False + try: + RenderSegment.load(object()) + except ValueError: + raised = True + assert raised, "Expected invalid input to be rejected" diff --git a/runtime/python/prompty/tests/model/test_agent.py b/runtime/python/prompty/tests/model/test_agent.py index 8880a1a5b..fbb6dde45 100644 --- a/runtime/python/prompty/tests/model/test_agent.py +++ b/runtime/python/prompty/tests/model/test_agent.py @@ -3076,3 +3076,13 @@ def test_to_yaml_agent_7(): assert yaml_output is not None parsed = yaml.safe_load(yaml_output) assert isinstance(parsed, dict) + + +def test_load_agent_invalid(): + """load must reject invalid input instead of silently defaulting.""" + raised = False + try: + Agent.load(object()) + except ValueError: + raised = True + assert raised, "Expected invalid input to be rejected" diff --git a/runtime/python/prompty/tests/test_anthropic.py b/runtime/python/prompty/tests/test_anthropic.py index dd918156f..668856478 100644 --- a/runtime/python/prompty/tests/test_anthropic.py +++ b/runtime/python/prompty/tests/test_anthropic.py @@ -25,9 +25,11 @@ AnthropicExecutor, _build_chat_args, _build_options, + _drop_unsupported_anthropic_params, _message_to_wire, _output_schema_to_wire, _part_to_wire, + _reasoning_effort_to_anthropic, _tools_to_wire, ) from prompty.providers.anthropic.processor import AnthropicProcessor @@ -413,6 +415,100 @@ def test_tools_included(self): # --------------------------------------------------------------------------- +class TestReasoningEffortGuard: + """The deprecated-sampling drop is a live-call guard, not a wire mapping. + + ``_build_options`` / ``_build_chat_args`` stay pure renames so the + ``anthropic_options`` wire-conformance vector keeps asserting temperature/ + top_p/top_k. The drop happens only in the executor, filtered against the + installed SDK's ``messages.create()`` signature. + """ + + def test_build_options_keeps_deprecated_sampling(self): + agent = _make_agent(options={"temperature": 0.5, "topP": 0.9, "topK": 40}) + opts = _build_options(agent) + assert opts["temperature"] == 0.5 + assert opts["top_p"] == 0.9 + assert opts["top_k"] == 40 + + def test_drop_removes_deprecated_when_sdk_lacks_them(self): + accepted = frozenset( + {"model", "messages", "max_tokens", "system", "tools", "output_config", "stop_sequences", "stream"} + ) + args = {"model": "m", "temperature": 0.5, "top_p": 0.9, "top_k": 40, "max_tokens": 10} + with patch("prompty.providers.anthropic.executor._anthropic_create_params", return_value=accepted): + with pytest.warns(DeprecationWarning): + _drop_unsupported_anthropic_params(args) + assert "temperature" not in args + assert "top_p" not in args + assert "top_k" not in args + assert args["max_tokens"] == 10 + + def test_drop_keeps_when_sdk_accepts(self): + accepted = frozenset({"model", "messages", "max_tokens", "temperature", "top_p", "top_k"}) + args = {"model": "m", "temperature": 0.5, "top_p": 0.9, "top_k": 40} + with patch("prompty.providers.anthropic.executor._anthropic_create_params", return_value=accepted): + _drop_unsupported_anthropic_params(args) + assert args["temperature"] == 0.5 + assert args["top_p"] == 0.9 + assert args["top_k"] == 40 + + def test_drop_noop_when_signature_unknown(self): + args = {"temperature": 0.5} + with patch("prompty.providers.anthropic.executor._anthropic_create_params", return_value=None): + _drop_unsupported_anthropic_params(args) + assert args["temperature"] == 0.5 + + @pytest.mark.parametrize( + "raw,expected", + [ + ("medium", "medium"), + ("low", "low"), + ("high", "high"), + ("xhigh", "xhigh"), + ("max", "max"), + ("minimal", "low"), + ("none", None), + ], + ) + def test_reasoning_effort_map(self, raw, expected): + agent = _make_agent(options={"reasoningEffort": raw}) + assert _reasoning_effort_to_anthropic(agent) == expected + + def test_build_chat_args_maps_effort_to_output_config(self): + agent = _make_agent(options={"reasoningEffort": "high"}) + args = _build_chat_args(agent, _make_messages()) + assert args["output_config"]["effort"] == "high" + + def test_build_chat_args_no_effort_when_absent(self): + agent = _make_agent(options={"maxOutputTokens": 100}) + args = _build_chat_args(agent, _make_messages()) + assert "effort" not in args.get("output_config", {}) + + def test_execute_drops_deprecated_params(self): + accepted = frozenset( + {"model", "messages", "max_tokens", "system", "tools", "output_config", "stop_sequences", "stream"} + ) + mock_client = MagicMock() + mock_client.messages.create.return_value = _mock_anthropic_response() + register_connection("mock-anthropic", client=mock_client) + try: + agent = _make_agent( + connection={"kind": "reference", "name": "mock-anthropic"}, + options={"temperature": 0.5, "topP": 0.9, "topK": 40, "maxOutputTokens": 100}, + ) + with patch("prompty.providers.anthropic.executor._anthropic_create_params", return_value=accepted): + with pytest.warns(DeprecationWarning): + AnthropicExecutor().execute(agent, _make_messages()) + call_kwargs = mock_client.messages.create.call_args.kwargs + assert "temperature" not in call_kwargs + assert "top_p" not in call_kwargs + assert "top_k" not in call_kwargs + assert call_kwargs["max_tokens"] == 100 + finally: + clear_connections() + + class TestExecutor: def test_chat_execution(self): mock_client = MagicMock() diff --git a/runtime/python/prompty/tests/test_models.py b/runtime/python/prompty/tests/test_models.py index 175449d46..484019b2c 100644 --- a/runtime/python/prompty/tests/test_models.py +++ b/runtime/python/prompty/tests/test_models.py @@ -2,6 +2,7 @@ from __future__ import annotations +import os from types import SimpleNamespace from unittest.mock import AsyncMock, MagicMock, patch @@ -151,7 +152,10 @@ def test_list_models_passes_api_key(self, mock_openai_cls: MagicMock) -> None: conn = _make_connection("sk-mykey") openai_list_models(conn) - mock_openai_cls.assert_called_once_with(api_key="sk-mykey") + call_kwargs = mock_openai_cls.call_args.kwargs + assert call_kwargs["api_key"] == "sk-mykey" + # Client defaults to the public endpoint (honoring OPENAI_BASE_URL). + assert call_kwargs["base_url"] == (os.environ.get("OPENAI_BASE_URL") or "https://api.openai.com/v1") @patch("openai.OpenAI") def test_list_models_empty_response(self, mock_openai_cls: MagicMock) -> None: diff --git a/runtime/python/prompty/uv.lock b/runtime/python/prompty/uv.lock index 06662fba5..845446e1a 100644 --- a/runtime/python/prompty/uv.lock +++ b/runtime/python/prompty/uv.lock @@ -11,6 +11,146 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/bc/8a/340a1555ae33d7354dbca4faa54948d76d89a27ceef032c8c3bc661d003e/aiofiles-25.1.0-py3-none-any.whl", hash = "sha256:abe311e527c862958650f9438e859c1fa7568a141b22abcd015e120e86a85695", size = 14668, upload-time = "2025-10-09T20:51:03.174Z" }, ] +[[package]] +name = "aiohappyeyeballs" +version = "2.7.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ce/f4/eec0465c2f67b2664688d0240b3212d5196fd89e741df67ddb81f8d35658/aiohappyeyeballs-2.7.1.tar.gz", hash = "sha256:065665c041c42a5938ed220bdcd7230f22527fbec085e1853d2402c8a3615d9d", size = 24757, upload-time = "2026-07-01T17:11:55.501Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/71/43/1947f06babed6b3f1d7f38b0c767f52df66bfb2bc10b468c4a7de9eceff2/aiohappyeyeballs-2.7.1-py3-none-any.whl", hash = "sha256:9243213661e29250eb41368e5daa826fc017156c3b8a11440826b2e3ed376472", size = 15038, upload-time = "2026-07-01T17:11:54.055Z" }, +] + +[[package]] +name = "aiohttp" +version = "3.14.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "aiohappyeyeballs" }, + { name = "aiosignal" }, + { name = "attrs" }, + { name = "frozenlist" }, + { name = "multidict" }, + { name = "propcache" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, + { name = "yarl" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/58/d9/22ce5786ac0c1653ae8b6c23bded02c1686d11f0dbb45b31ce128e0df985/aiohttp-3.14.3.tar.gz", hash = "sha256:9491196535a88924a60afd5b5f434b5b203b6cc616250878dbdb223a8f7844bc", size = 7971213, upload-time = "2026-07-23T01:57:27.037Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f8/5c/b3e4ff8ad43a8afef9602c5e90285936da1beaea8b029016b793891f03c3/aiohttp-3.14.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:e568e14940c09955aa51f4e645b6daa18a581c5dcfcd73744dcc86a856e3ced3", size = 764250, upload-time = "2026-07-23T01:52:48.525Z" }, + { url = "https://files.pythonhosted.org/packages/0e/da/f1b384465e51449d844056b75070461da03a9a23e6c1747003695bf4172a/aiohttp-3.14.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:54cfcdee2770dac994417cbb0ee1f3eb0e7cb6b30c79bf44f2c02ff79ec5124a", size = 516281, upload-time = "2026-07-23T01:52:51.047Z" }, + { url = "https://files.pythonhosted.org/packages/b9/3f/01264f820ee2e3712a827892b1cd6ff80f3300c1fcbffbb45714a915d47a/aiohttp-3.14.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:21c016079415ed3fd676963e9793700a566d85dbbd6bfc564b9b2d209147dcc8", size = 514742, upload-time = "2026-07-23T01:52:53.779Z" }, + { url = "https://files.pythonhosted.org/packages/9e/8d/a71c6f2db52ac1ed142b133f7feddaa6b70539c3f4de24d7e226c95b794c/aiohttp-3.14.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d6088ec9894113802bddb3c09e974929aed2c7b3a8c456219b8aab4481f1a239", size = 1780613, upload-time = "2026-07-23T01:52:56.948Z" }, + { url = "https://files.pythonhosted.org/packages/a5/11/3dd9b3fb3a170f6ec9011b5291d876a6fab4086714c9e158600edf01b4fd/aiohttp-3.14.3-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:16ea7e24c309fb7c0bbd505d149abe4fe4dccfb8db911db7dbec0921bc889a6f", size = 1737688, upload-time = "2026-07-23T01:52:59.294Z" }, + { url = "https://files.pythonhosted.org/packages/6d/3e/834c26918be7d88068822b40e0db30fca50b5f4fe79104aa16a93f1d74e6/aiohttp-3.14.3-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:56f355e79f71aef2a85c80305cc915f894b170dba76de5fe84f6351939b83c06", size = 1845742, upload-time = "2026-07-23T01:53:01.641Z" }, + { url = "https://files.pythonhosted.org/packages/cc/c9/49ab8572df7d66bc13d11e31f781292badb04180dd87ba98733066c6aed7/aiohttp-3.14.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:18c441d0a8fca6de8d1f546849b9f0ab20d435993e2c5b59562b2fae6be2f929", size = 1928412, upload-time = "2026-07-23T01:53:04.018Z" }, + { url = "https://files.pythonhosted.org/packages/a5/b9/2b8f0c0ce09c87a1daf80fd483431b56b1435d3f62789bc86f572e1245de/aiohttp-3.14.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:53e7b4ce82b54a8bcc71b3b67a5cbd177ca1d7f592cbc92cd38b7349f73482db", size = 1786220, upload-time = "2026-07-23T01:53:06.481Z" }, + { url = "https://files.pythonhosted.org/packages/85/00/9c45f81de11710460edfa1dc81317b6e882703b160926c879a9d20da9fcc/aiohttp-3.14.3-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f55119f7bf25f49ed210f6096090715da24f2943c62102448915fde3c62877ce", size = 1637231, upload-time = "2026-07-23T01:53:10.258Z" }, + { url = "https://files.pythonhosted.org/packages/19/ce/967d628e910756f3539c6107cb7844a1b69440dcb3029a5ee7871b09ab63/aiohttp-3.14.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:9aa6e61fdf20105c4144e755bd586008ff450791d67b1c8146fdc15959c4d51c", size = 1753161, upload-time = "2026-07-23T01:53:13.817Z" }, + { url = "https://files.pythonhosted.org/packages/11/b2/0c3d4114f0aee4f580f5b3b4eb71b24d7a23b834ea506a4dfebe76513f35/aiohttp-3.14.3-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:ccd4893707b3e2a13e39c90d43cf80edf2e4d0457935bcc103bf2346214c3f15", size = 1756356, upload-time = "2026-07-23T01:53:16.211Z" }, + { url = "https://files.pythonhosted.org/packages/63/5d/99e7d91c82f1399d1ae2a854e080bd1493fbc31e5e959dbc4ec33dac3bec/aiohttp-3.14.3-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:b2466434105a4e03113c36ec775cc2ebe6676b62eae326fa670bb607ef788c1c", size = 1819846, upload-time = "2026-07-23T01:53:18.289Z" }, + { url = "https://files.pythonhosted.org/packages/ad/05/d5e1cb6480eeffd3f901d40a2c5e2d1e7effdc797837da3b490272699f13/aiohttp-3.14.3-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:ba59d59aba08ac02fc03b0c8983ccd5ee39a199d0552ce9e6d2b4845b34d59ae", size = 1628531, upload-time = "2026-07-23T01:53:23.86Z" }, + { url = "https://files.pythonhosted.org/packages/c9/90/b934682bcaefae18a9e04f3dff5b68522ba810906358ae5029b68110ea3b/aiohttp-3.14.3-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:ed099d105449c4f9e84f24af203cd131349d4761d8813fa7e02c32e7128cd910", size = 1832712, upload-time = "2026-07-23T01:53:27.551Z" }, + { url = "https://files.pythonhosted.org/packages/21/df/6061679faaf81fac746e7307c7adb71e858071a5d34c27583afefc64f543/aiohttp-3.14.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:152516815ef926786a0b6ae2b8f1fd2e0c71582dee0b435636865316fd4891b7", size = 1775014, upload-time = "2026-07-23T01:53:30.223Z" }, + { url = "https://files.pythonhosted.org/packages/8a/1d/f854878bbc69b88faefe924b619a34a6f59ec05fd387c77690667eaa75eb/aiohttp-3.14.3-cp311-cp311-win32.whl", hash = "sha256:a4af35c443e0b1a1bd6a8af3f3485d7fda15c142751a00f3ff8090f0b93346fa", size = 456006, upload-time = "2026-07-23T01:53:34.97Z" }, + { url = "https://files.pythonhosted.org/packages/73/0c/2af9d1674baccd1dbd47282a93d660a22e57ef6167c856deb24b4214fbab/aiohttp-3.14.3-cp311-cp311-win_amd64.whl", hash = "sha256:e1e74298bab6ee0d6e749ed4fd1901c7e604bdda32c03d787a2cc71c46d0433d", size = 481069, upload-time = "2026-07-23T01:53:39.673Z" }, + { url = "https://files.pythonhosted.org/packages/8e/76/88401ff3fc95e85c5fc38d588f36f55e61ecb64343b2bc8d69326f453cc0/aiohttp-3.14.3-cp311-cp311-win_arm64.whl", hash = "sha256:03cd2bde3d7f085b64e549c985f4bb928cad7e8ecf5323bfca320db548d81b39", size = 453021, upload-time = "2026-07-23T01:53:43.749Z" }, + { url = "https://files.pythonhosted.org/packages/18/d4/eb96299230e20acf2efae207cb8d69051f1f68e357e5ea5e479bf6fb097a/aiohttp-3.14.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:39aded8c7f3b935b54aab1d8d73c70ec0ee2d3ec3b943e0e86611bc150ba47f5", size = 754690, upload-time = "2026-07-23T01:53:47.332Z" }, + { url = "https://files.pythonhosted.org/packages/88/11/e7a70a209eb9a067c0d3212b518a0134e3484f5178c7533878b6b514d469/aiohttp-3.14.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:5bcb6ff3fdab1258a192679ff1a05d44f59626430aa05cd1a9d2447423599228", size = 509484, upload-time = "2026-07-23T01:53:51.159Z" }, + { url = "https://files.pythonhosted.org/packages/30/07/4bbc222cc8dbe31d4c3e8a5baad2286e4d42026ac0c570027b89afce6344/aiohttp-3.14.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:617105e2c3018ee38d0c8ce5ee3c84f621a6d8b9f723202aacaff28449ca91ee", size = 511949, upload-time = "2026-07-23T01:53:55.083Z" }, + { url = "https://files.pythonhosted.org/packages/54/b9/42e74c46b7b7c794b995bbc1f573fb48950c38b19d8600c62a6804ee2d67/aiohttp-3.14.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f631fe87a6f30df5fbe6d79640b25e4cffb38c31c7fb6f10871517b84b0f8c1a", size = 1765282, upload-time = "2026-07-23T01:53:59.662Z" }, + { url = "https://files.pythonhosted.org/packages/6b/ed/62bc4d74363ad346d518e0720363a949f63e2e23439a79eb5813d4d29bb3/aiohttp-3.14.3-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:a94dbaae5ae27bd849c93570669bff91e0510f33a80805738e3de72a7be0447b", size = 1741511, upload-time = "2026-07-23T01:54:04.063Z" }, + { url = "https://files.pythonhosted.org/packages/d0/9f/181e8a8bc79e47d13c7fc4540bd7a3b729d9505609c61f392a8dd2fbfe55/aiohttp-3.14.3-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8f2f1c4c032c7cedd7d8da6f54c97b70266c6570c3108d3fdffee7188bb70529", size = 1810680, upload-time = "2026-07-23T01:54:09.882Z" }, + { url = "https://files.pythonhosted.org/packages/5c/9a/dec94d6ad694552fe3424e3f1928d7a606a5d9d9433a04e7ecdd9d38ae7f/aiohttp-3.14.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ea05e1f97ceea523942d9b2a7d7c0359d781d683d6b043f5943a602b14da4787", size = 1905646, upload-time = "2026-07-23T01:54:13.475Z" }, + { url = "https://files.pythonhosted.org/packages/52/b7/7cd31f29d6055bd711ae6e669367fba6f5ae9de463910a793e30556a8db7/aiohttp-3.14.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:543906c127fb1d929b95076db19b83fa2d46751006ff1e23b093aa5ac4d8db42", size = 1792122, upload-time = "2026-07-23T01:54:15.752Z" }, + { url = "https://files.pythonhosted.org/packages/66/73/10b1ef93afa61f4963c746257b70ced619cf31a4798671de5fdb2608501d/aiohttp-3.14.3-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0a5ff2dfbb9ce645fa5b8ef3e02c6c0b9cc3f6030ff863d0c51fffc50cb5541b", size = 1591127, upload-time = "2026-07-23T01:54:19.489Z" }, + { url = "https://files.pythonhosted.org/packages/49/ed/3b203fa6de1b338c14acdc06bf6ca9b043b7944f005966958c2ced932cde/aiohttp-3.14.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:041badb8f84396357c4d3ad26de6afd7a32b112f43d3c63045c0c8278cfd2043", size = 1725210, upload-time = "2026-07-23T01:54:24.129Z" }, + { url = "https://files.pythonhosted.org/packages/28/b7/1c2aab8c706436dcc28598452488ac9cd7c409da815237c28c27d58993e6/aiohttp-3.14.3-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:530125ee1163c4219af35dc3aa1206e541e7b31b6efc1a3f93b70a136f65d427", size = 1764848, upload-time = "2026-07-23T01:54:27.973Z" }, + { url = "https://files.pythonhosted.org/packages/54/50/94c28f08b131c4bf10984ea2c7a536c9920608bb2d6e7f95642c30cc87b7/aiohttp-3.14.3-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c8653fd547c93a61aadc612007790f5555cdd18946fa48cf45e26d8ea4ea473d", size = 1777102, upload-time = "2026-07-23T01:54:31.775Z" }, + { url = "https://files.pythonhosted.org/packages/13/d4/e7d09ba7d345fb2d74440fd2fa033c5e079fac05552927705986f41a364f/aiohttp-3.14.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:89176250f686cb9853c0fb7ead90e639e915b84a6f43eedc2a4e7ec21f1037f0", size = 1580205, upload-time = "2026-07-23T01:54:34.518Z" }, + { url = "https://files.pythonhosted.org/packages/a3/84/072a91d68e1e1eb587985b54baab94221277f877e8ef274fc213a0ceae28/aiohttp-3.14.3-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:3a26434dafe408229ff3403458ca58de24fb51936504decac49ce6755f77e59d", size = 1797219, upload-time = "2026-07-23T01:54:36.995Z" }, + { url = "https://files.pythonhosted.org/packages/e0/eb/aad34e897e668424d6e995da5dff8a4a09af93363d3392488772957a63aa/aiohttp-3.14.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:d1558173930a5a8d3069cee5c92fc91c87c4dbcb099debbb3622053717145a19", size = 1768629, upload-time = "2026-07-23T01:54:40.103Z" }, + { url = "https://files.pythonhosted.org/packages/b6/2b/6bb88ddba0fecd9122aa3ebcad25996cf6c083a4a7040dbb3a4f97972af6/aiohttp-3.14.3-cp312-cp312-win32.whl", hash = "sha256:16100ad3ab8d649fdfbee87602d9d2dcdca9df0b9eda8a1b5fdc0d41f96da559", size = 451481, upload-time = "2026-07-23T01:54:42.547Z" }, + { url = "https://files.pythonhosted.org/packages/76/9b/f2f8f108da17ecef2cc3efc424e8b7ad3782b1a8360f7b8eae8ced84f6ea/aiohttp-3.14.3-cp312-cp312-win_amd64.whl", hash = "sha256:33a2d7c28d33797a2e99923dffa63f83d908a19b6bf26cfe80fa790aa5e1a75a", size = 476845, upload-time = "2026-07-23T01:54:44.853Z" }, + { url = "https://files.pythonhosted.org/packages/3e/44/28dac80a8941b604f4da10ce21097614ca1bf905ce93dca28d8d7de9c1e7/aiohttp-3.14.3-cp312-cp312-win_arm64.whl", hash = "sha256:362a3fd481769cac1a824514bcd86fda51c65e8fe6e051099e008fddde6db17c", size = 448050, upload-time = "2026-07-23T01:54:47.087Z" }, + { url = "https://files.pythonhosted.org/packages/57/be/5afd201cc0ab139029aadb75392efe85a293403d9dd3a3226161c21ce00c/aiohttp-3.14.3-cp313-cp313-android_21_arm64_v8a.whl", hash = "sha256:2e9878ae68e4a5f1c0abe4dd497dbc3d51946f5837b56759e2a02e78fa90ef86", size = 506269, upload-time = "2026-07-23T01:54:49.075Z" }, + { url = "https://files.pythonhosted.org/packages/22/09/dec8189d62b45ade009f6792a2264b942a90cb88aeaf181239933cd72c3c/aiohttp-3.14.3-cp313-cp313-android_21_x86_64.whl", hash = "sha256:f3d2669fe7dec7fc359ecdb5984b29b50d85d5d00f8c1cb61de4f4a24ee42627", size = 515166, upload-time = "2026-07-23T01:54:51.894Z" }, + { url = "https://files.pythonhosted.org/packages/28/24/2854869d29ed8a8b19d74f9ec6629515f7e04d02dd329d9d179201e58e47/aiohttp-3.14.3-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:cc7cb243a68167172f48c1fd43cee91ec4b1d40cefd190edd43369d1a6bc9c82", size = 486263, upload-time = "2026-07-23T01:54:54.223Z" }, + { url = "https://files.pythonhosted.org/packages/d4/dd/57187c8be2a35aea65eaee3bd2c3dcbbcf0204f5106c89637e3610380cd1/aiohttp-3.14.3-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:78253b573e6ffab5028924fc98bc281aae05445969982a10864bc360dea2016c", size = 492299, upload-time = "2026-07-23T01:54:56.236Z" }, + { url = "https://files.pythonhosted.org/packages/b9/11/06ae6ed8f0d414edf4068861e233d8fe23ee699bfd4b3ceb8663db948a62/aiohttp-3.14.3-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:7041d52c3a7fa20c9e8c182b534704abb19502c8bdcbde7ab23bfda6f642394f", size = 502235, upload-time = "2026-07-23T01:54:58.377Z" }, + { url = "https://files.pythonhosted.org/packages/7e/a3/559639c34a345d2cf7c52dff6838119f2eaf29eb508227b5b83f573af813/aiohttp-3.14.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ac74facc01463f138b0da5580329cfcc82818dea5656e83ddcd11268fc12ff80", size = 750883, upload-time = "2026-07-23T01:55:00.65Z" }, + { url = "https://files.pythonhosted.org/packages/91/cd/41e131f13afd1e7b0172a9d9eda085ef90eb8439f41f0d279db81ed3ae60/aiohttp-3.14.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:d6218d92e450824e9b4881f44e8c09f1853b490f9a64130801024a4793b1b3b0", size = 508473, upload-time = "2026-07-23T01:55:02.945Z" }, + { url = "https://files.pythonhosted.org/packages/bc/6b/e7f13410d391c6e55b4c007a8de024355389d7d459e3d64c42b2d33617e5/aiohttp-3.14.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:11fb37ef075669eee52ab1928fbf6e1741fada40409fa309ebde9607a962aebf", size = 509190, upload-time = "2026-07-23T01:55:05.173Z" }, + { url = "https://files.pythonhosted.org/packages/97/21/6464573e53d69672cc1eada3e5c5cb2d2efa82701e8305a0f2047a576967/aiohttp-3.14.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:55bdcc472aafe2de4a253045cc128007a64f1e0264fb675791e132ea5edaa3bd", size = 1761478, upload-time = "2026-07-23T01:55:07.383Z" }, + { url = "https://files.pythonhosted.org/packages/1a/81/d217043a4c17fbce360905e3b2bdd20139ebc9a2de836d035d179c4da006/aiohttp-3.14.3-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:c39846c3aad97a8530c89d7a3869a8f8e9e3762c6ac0504481e5c80948f7e807", size = 1735092, upload-time = "2026-07-23T01:55:09.803Z" }, + { url = "https://files.pythonhosted.org/packages/a1/66/e13a02d0eeb1a9a502402a977abb4e4abff9fe4051c26f80558c57a7c975/aiohttp-3.14.3-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5895ef58c4620afe02fa16044f023dc4dafec08158f9d08874a46a7dbc0341b8", size = 1800546, upload-time = "2026-07-23T01:55:12.012Z" }, + { url = "https://files.pythonhosted.org/packages/26/5e/57d42fca1d18cb5acc1cad945d017fabc5d6ae71d8a08ad66be8dc3ee544/aiohttp-3.14.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:fa9467a8113aa69d3d7c55a70ef0b7c636010a40993f3df9d9d0d73b3eb7ef24", size = 1895250, upload-time = "2026-07-23T01:55:14.357Z" }, + { url = "https://files.pythonhosted.org/packages/ca/1c/7da8d08e74d56f00070822f9638ff3f1c563f8ad87d1efa996c87bfc8644/aiohttp-3.14.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d7d2deec16eeedf55f2c7cf75b521ea3856a5177e123844f8fd0f114ce252cb5", size = 1789289, upload-time = "2026-07-23T01:55:16.668Z" }, + { url = "https://files.pythonhosted.org/packages/cd/0f/cf16bcf56896981c1a0319f5d5db9337994b5165730c48a8fa07e9b34be6/aiohttp-3.14.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:dd54d0e8717de95939766febac482ac0474d8ac3b048115f9f2b1d23a16e7db4", size = 1586706, upload-time = "2026-07-23T01:55:18.913Z" }, + { url = "https://files.pythonhosted.org/packages/fe/6f/76eac12a7f2480e1e304f842efdb07db33256b0d9165b866b6ef0806c202/aiohttp-3.14.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:df82f3787c940c94986b34222d59c9e38843fba85139f36e85255a82ad5355a9", size = 1724652, upload-time = "2026-07-23T01:55:21.296Z" }, + { url = "https://files.pythonhosted.org/packages/39/b6/19c8c592baeeb94b75f966547d40c02ac7590902306ec5863d5c027cf506/aiohttp-3.14.3-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:42a67efc36300d052fb4508a53e8b6901b9284b599ae63945c377569c5fcc1e1", size = 1756239, upload-time = "2026-07-23T01:55:23.705Z" }, + { url = "https://files.pythonhosted.org/packages/dc/c9/4e9383150296f97f873b680c4de8fb2cd88608fb9f48c79edcb111611abc/aiohttp-3.14.3-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:7a75aa63cbf9b21cfaf60dc2657e19df2c2867d91707d653fee171ffeedd1371", size = 1769161, upload-time = "2026-07-23T01:55:26.082Z" }, + { url = "https://files.pythonhosted.org/packages/aa/1e/147bdc6cc5de5f3ab011be8bf5d6e786633249f22c20bae06f85e45f5387/aiohttp-3.14.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:e92eb8acc45eb6a9f4935071a77edf5b85cc6f8dfad5cd99e97653c26593cdde", size = 1578759, upload-time = "2026-07-23T01:55:28.846Z" }, + { url = "https://files.pythonhosted.org/packages/fd/31/78388a9d6040ece2e11df62ea229a822cf5e52d238374b220ae9975b2623/aiohttp-3.14.3-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:b014a6ed7cf912e787149fdc529166d3ceabac23f26efeea3158c9aba2354e7e", size = 1792025, upload-time = "2026-07-23T01:55:31.457Z" }, + { url = "https://files.pythonhosted.org/packages/03/51/a3d29fdf2c25d796746af8ad6fe56a45d6256c38b0a8a2ed752e1160b3a2/aiohttp-3.14.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:3d4f72af88ac2474bb5bca640030320e3d38a0163a1d7533500e87be458eef71", size = 1768477, upload-time = "2026-07-23T01:55:33.87Z" }, + { url = "https://files.pythonhosted.org/packages/29/a6/442e18b5afeade534d877a2dc3c3e392aff8d49787890b0cf84790410267/aiohttp-3.14.3-cp313-cp313-win32.whl", hash = "sha256:5f08ec777f35ee70720233b8b9811d3bb5d728137f30ac91b7457709c3261ac0", size = 451069, upload-time = "2026-07-23T01:55:36.121Z" }, + { url = "https://files.pythonhosted.org/packages/9d/69/3d876ac02659f271cf7f6769f14a8e3de5b6e888ed8b5a7e998086a4cec8/aiohttp-3.14.3-cp313-cp313-win_amd64.whl", hash = "sha256:dff9461ec275f22135650d5ba4b4931a11f3958df7dfbb8db630000d4dee0883", size = 476518, upload-time = "2026-07-23T01:55:38.303Z" }, + { url = "https://files.pythonhosted.org/packages/b2/0e/50d6e6471cd31edce8b282bdec59375a3a69124d8a989a0b1313355cae52/aiohttp-3.14.3-cp313-cp313-win_arm64.whl", hash = "sha256:ddcac3c6b382e81f1dd0499199d4136b877beb4cb5ef770bbbfba56c4b8f55d2", size = 447676, upload-time = "2026-07-23T01:55:40.451Z" }, + { url = "https://files.pythonhosted.org/packages/c8/20/887fdcf832326571b370ffc347b3e70abe101096f3720126aac161b1d872/aiohttp-3.14.3-cp314-cp314-android_24_arm64_v8a.whl", hash = "sha256:49f7325beb0f85ef4aef5f48f490269575f83e6e2acad00a1d80b807eb027062", size = 509067, upload-time = "2026-07-23T01:55:42.618Z" }, + { url = "https://files.pythonhosted.org/packages/ad/a3/92cec936f78cc4bf0fa5554ebe593b73459d94e3c62303e1902a4cccb6f7/aiohttp-3.14.3-cp314-cp314-android_24_x86_64.whl", hash = "sha256:e3be98a7c30b8c25d573dafba7171d66dfb05ee6a9070fc46535464ff97700a6", size = 514774, upload-time = "2026-07-23T01:55:44.937Z" }, + { url = "https://files.pythonhosted.org/packages/29/ba/2a0c38df3fc557620b6a5acd98364af050053b6285b4dc7ee74100c63c18/aiohttp-3.14.3-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:614c61d478b83953e261d02bb2df750f17227cd33ef8002945bf5aebbde21919", size = 488134, upload-time = "2026-07-23T01:55:47.135Z" }, + { url = "https://files.pythonhosted.org/packages/48/d6/d51b7d4bf309af3693940d8ffd2b9ed0b682434ef85959b7c9c137f60cf8/aiohttp-3.14.3-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:1caa7b0d05f3e3a36f87788c59e970a7ee1cefcfcbb924a9f138c4a6551c9cb7", size = 494201, upload-time = "2026-07-23T01:55:49.451Z" }, + { url = "https://files.pythonhosted.org/packages/3f/5a/8f624384e5f1efabb5229b94157eb966b021e97bdb188c62860c2ae243c2/aiohttp-3.14.3-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:dfa68deb2a443bdaa3ea5297b0699c1464f08aef3812b486d1348eee61b07dc0", size = 502766, upload-time = "2026-07-23T01:55:51.656Z" }, + { url = "https://files.pythonhosted.org/packages/a6/26/4ff0164370deec18fb19254ee4ab10b7a73304ac0c860b13f5f84663759b/aiohttp-3.14.3-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:e72ee89e28d907a18f46959b4eb0bb06701cc7f8cf4366e00029e2ccfaaf5924", size = 756557, upload-time = "2026-07-23T01:55:53.964Z" }, + { url = "https://files.pythonhosted.org/packages/97/a3/7056b86dc0d9ec709ea9777eae3b0161428f943372f8b98c01c11593b682/aiohttp-3.14.3-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:ad4c8b7488d745d2ca4838ebd8ae5ba9b56341d30b1da43640e4ce87f9f49646", size = 510168, upload-time = "2026-07-23T01:55:56.22Z" }, + { url = "https://files.pythonhosted.org/packages/85/ed/0357a015892fd68058bf2d39d3fd1958e459b997a7db30aaa6aaa434ae96/aiohttp-3.14.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:db332af25642007330fca8be5c4d194caf2bea7a7fc84415aff3497af5dfee6b", size = 512957, upload-time = "2026-07-23T01:55:58.437Z" }, + { url = "https://files.pythonhosted.org/packages/47/d1/8aba53f15ccb2238405f5e9d30e2a8ca44f93878c26e7165ade00d374b1c/aiohttp-3.14.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:25bd2708db6bdf6a6630dd37bdcdfcb47c4434d22ac69c64665b802910140b30", size = 1750149, upload-time = "2026-07-23T01:56:00.856Z" }, + { url = "https://files.pythonhosted.org/packages/49/bd/40c3fee327529284375c6701cbb0fa4600cc2e8432af1378f897e2ef7d3a/aiohttp-3.14.3-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:cef89a58e628c4efcac3275c2d68083f82426dcdc89c1492a6f654f9f7ea6ab9", size = 1707685, upload-time = "2026-07-23T01:56:03.371Z" }, + { url = "https://files.pythonhosted.org/packages/2a/a3/ca0cc6724cca8114b05694abd916060758c79894c3aa5b012cdadc1bc28e/aiohttp-3.14.3-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c23ec8ee9d5ab2f5421f9c7fffce208435607af27fd46d4a44e031954352838f", size = 1803911, upload-time = "2026-07-23T01:56:05.817Z" }, + { url = "https://files.pythonhosted.org/packages/95/b5/85b099c299c3ffd38ad9b3e43694c8a346934e4a30c88c4fd5a841234f77/aiohttp-3.14.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:e2667f0bbe7eb6c74eae5e9691441ad186e5845ca3cff63230fc09c4e7514f5d", size = 1876929, upload-time = "2026-07-23T01:56:08.413Z" }, + { url = "https://files.pythonhosted.org/packages/d5/b7/1da684a04175473fa4cddbf9a2f572e79514c3fd27a74597f43057d4f3da/aiohttp-3.14.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:18cb43369747b2ae007bd2655fb8e63a099c2ff1d207962943636dac989b3147", size = 1761112, upload-time = "2026-07-23T01:56:10.918Z" }, + { url = "https://files.pythonhosted.org/packages/d1/16/bc4b55e3e5cb175fd69c53c90d60d2f47797cb343da5106e23863dc4dba4/aiohttp-3.14.3-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d77640cc618c1d99fc4f8589c0f24a730adfa54eb1e57ef7bf0c8dfb78da898c", size = 1583500, upload-time = "2026-07-23T01:56:13.613Z" }, + { url = "https://files.pythonhosted.org/packages/2a/e8/13a9d957a1ee40837f46aa30f0f4c657e673ad86a2e6362a9f9be20d26d9/aiohttp-3.14.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:53e5179d8abb5710f8e83ba207c41c8d1261fcffd4616500e15ca2b7a33be10a", size = 1713940, upload-time = "2026-07-23T01:56:15.969Z" }, + { url = "https://files.pythonhosted.org/packages/38/05/d33c680c1bcf1c7e130f9cbfc1fc02fe8bb0c4af2a94a53dd5fb56131e5c/aiohttp-3.14.3-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:cd817772b2fcf2b8c0905795318485f9ec16eae60b29feb7f4c77085311637f0", size = 1724413, upload-time = "2026-07-23T01:56:18.591Z" }, + { url = "https://files.pythonhosted.org/packages/85/1d/af798d306f7a74b6a632dbcabcf62a4c91391b7582d2a8c6d7712e2cc54e/aiohttp-3.14.3-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:4e3ac92d90e92773b2362d506068e9a948192bd553e743c5b2429e28527c8661", size = 1770748, upload-time = "2026-07-23T01:56:21.074Z" }, + { url = "https://files.pythonhosted.org/packages/a8/92/ad720d472556a995049206867765e9410969684f86ee09423ff9969044c1/aiohttp-3.14.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:3f42e9b78301f11c8f861746175d8b9c1ccef713fcad9eab396e2f6db8ed4a22", size = 1577564, upload-time = "2026-07-23T01:56:23.475Z" }, + { url = "https://files.pythonhosted.org/packages/60/ad/0ed7586cbef7a884e23a752fa2bb987a122e6a5dd50dab109258d0a95193/aiohttp-3.14.3-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:9d9edccfe496b476db5f398d97b865e9a6752bcf8aec4eef8390ce20fb64bb41", size = 1782080, upload-time = "2026-07-23T01:56:25.994Z" }, + { url = "https://files.pythonhosted.org/packages/97/ea/dbaed0d73e8a69aad653b045dab451c67c2454bb731a37b45a86593e9422/aiohttp-3.14.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:1c5ec8fb1bcc31a8466f74aaf26c345d5c386fa4bd08a3f0eb9c7a4a3fe8b5bf", size = 1745813, upload-time = "2026-07-23T01:56:28.604Z" }, + { url = "https://files.pythonhosted.org/packages/81/1b/6893d4bc57e434fc93a6c9217c637d967a0b651d989f6e3265179375754a/aiohttp-3.14.3-cp314-cp314-win32.whl", hash = "sha256:38901a84da3ce22249f6e860bf8f90d141bcab7da090cc398f8bb58c0e44b7da", size = 455872, upload-time = "2026-07-23T01:56:31.031Z" }, + { url = "https://files.pythonhosted.org/packages/f5/8b/c7baa1ba1eda4db6989baefe5de6d99834921b84ebd7918624febcb9f290/aiohttp-3.14.3-cp314-cp314-win_amd64.whl", hash = "sha256:8b3b60de05f3dcb6f6a00f818bb2ec781cee4de0645f59ccaf99b1d1823b6100", size = 481030, upload-time = "2026-07-23T01:56:33.365Z" }, + { url = "https://files.pythonhosted.org/packages/22/8c/c29d067df825a2df88ca432db848aa2fe8199598359cc06c12b09320cac9/aiohttp-3.14.3-cp314-cp314-win_arm64.whl", hash = "sha256:1576145bdceeb92382d899751e12743a3a5b8e460a841e3e50543859e54864dc", size = 453669, upload-time = "2026-07-23T01:56:35.731Z" }, + { url = "https://files.pythonhosted.org/packages/6a/a4/9c033beb355d39b6147980597ec9645e4729243f686ee4dc73945de72030/aiohttp-3.14.3-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:8800c996b01c2772a783e3e46f3e1abd5823029adca0df54231960de9bfefa5b", size = 791403, upload-time = "2026-07-23T01:56:37.972Z" }, + { url = "https://files.pythonhosted.org/packages/80/ca/87c32a0a7704583cfc49660bd817889bae5b830bf53b5dcb4e92145ac2da/aiohttp-3.14.3-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:ebe8e504f058fe91223351cecd2d9d6946c9d241bb0250d898ffbdf584cc72b0", size = 526413, upload-time = "2026-07-23T01:56:40.523Z" }, + { url = "https://files.pythonhosted.org/packages/9e/d8/8ec0e471248c500acdce2be3f46db8fb62b5eb60efef072529cc85ee1d26/aiohttp-3.14.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:30402d03a7c0ff52bce290b57e564e9079fd9d0cb545c8aba73f86a103162d2e", size = 532135, upload-time = "2026-07-23T01:56:42.876Z" }, + { url = "https://files.pythonhosted.org/packages/fe/45/f8919fd936e8b79fcd9bda7b6d8e62613462a713f4f17987fd7c34399142/aiohttp-3.14.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9fc7b5bfec6573f3ae844f457fdde5adeb713f8b8e4a81ad64fc207b49383716", size = 1922742, upload-time = "2026-07-23T01:56:45.528Z" }, + { url = "https://files.pythonhosted.org/packages/f6/ec/9ca76b28a27525b0cc53e20842e0228b022f301ce1f436b7d814b4aaf2df/aiohttp-3.14.3-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:8a5fd34f7f7410d1730d5c2ba873cacb2eed3fede366feb268a70ba22581ed8f", size = 1787371, upload-time = "2026-07-23T01:56:48.045Z" }, + { url = "https://files.pythonhosted.org/packages/b1/04/6acdbf17315f7b55f1937e3387acb89a3cddeb4995689553d064af8e92ab/aiohttp-3.14.3-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:270d3dace9ca2f10f0da5d8ebe519b7a310fc6112ed916e32df5866df0888553", size = 1912623, upload-time = "2026-07-23T01:56:50.605Z" }, + { url = "https://files.pythonhosted.org/packages/86/e6/438b0c79ca6f45eb9fd9817dd4c01a91919a38c0de5ee9e05e2b4dc0ece7/aiohttp-3.14.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3ae5b3a59436d089b5395d910121a390feed4d00578eb95a0fd1a329fe963100", size = 2005515, upload-time = "2026-07-23T01:56:53.153Z" }, + { url = "https://files.pythonhosted.org/packages/bb/6b/62cbd6577758699525f5c712d1ddef57d9875fbab0ae8d5f5a202fd598f8/aiohttp-3.14.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2498f0fe69ead802f9675beca44a7c21c62fdaa4ec5145ea1c3ad6edbee29f85", size = 1879906, upload-time = "2026-07-23T01:56:55.818Z" }, + { url = "https://files.pythonhosted.org/packages/00/95/18bcbf830a21dc3aae24d8f6b6feaf3db1d2090242d00a7868db2ffb0b67/aiohttp-3.14.3-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a0dc483c00da8b673abbb367eb6f8d8f4bcec30eb58529ea13cb42e7fd2dfa33", size = 1675849, upload-time = "2026-07-23T01:56:58.861Z" }, + { url = "https://files.pythonhosted.org/packages/a9/19/47f4968659c5e23606c3790c80fc624e691c153d036148449ee84d31b287/aiohttp-3.14.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:c7d3a97c678d34fc5b59da671ee9cd630096ddc643e7b5a30d54a2a6f3574d3f", size = 1843496, upload-time = "2026-07-23T01:57:01.591Z" }, + { url = "https://files.pythonhosted.org/packages/64/af/38c33c4dd82fddcb4e56c4653b6f1072a8edbc6b7fa15809f14932c41e2d/aiohttp-3.14.3-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:f8fb78a83c9e5f741ca3a68cfb455c1f5bb83b4e7249a3848b3cd78d0a8563b0", size = 1827746, upload-time = "2026-07-23T01:57:05.131Z" }, + { url = "https://files.pythonhosted.org/packages/a1/9d/0537cda4885ac8f5b7053d164dd06312f4c483a4edcb8ee5b8aaf2a989bf/aiohttp-3.14.3-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:74ab5b6a9fb13e873e5a90946588baecaf488745e1db1a4a5c433f971f035098", size = 1853810, upload-time = "2026-07-23T01:57:08.043Z" }, + { url = "https://files.pythonhosted.org/packages/19/fe/26f9c5e6458385aa86497836b0dea6fb2f027827d63f37c7856cce9286ee/aiohttp-3.14.3-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:bd52f811e65f6fb634b1047159657c98f52b407f8efec907bcfc09da9a4c0a25", size = 1668895, upload-time = "2026-07-23T01:57:10.837Z" }, + { url = "https://files.pythonhosted.org/packages/ec/4c/618b1db9b9ba079b8875d2cdf78e7c4a3bf72903bd5850fee7dd9544600a/aiohttp-3.14.3-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:f0f177d1b195b9e06376cfd7d308d8a1b920909a609d03ac82a8c73bbb16d3b9", size = 1883833, upload-time = "2026-07-23T01:57:13.672Z" }, + { url = "https://files.pythonhosted.org/packages/94/c6/bd959bd1e4771f9fd944e9e436224c48c77b018b73b519b5aad346335bcc/aiohttp-3.14.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:498c6c623134f8e09a3c4e60bcd607a0b4590dd7dbf08dd40851b27cbb520ccb", size = 1844251, upload-time = "2026-07-23T01:57:16.593Z" }, + { url = "https://files.pythonhosted.org/packages/5e/19/08d41839658bdd44a0ed2480f3891705ecb487ce28c0dde62c9040c997e0/aiohttp-3.14.3-cp314-cp314t-win32.whl", hash = "sha256:b304db572b4368edd8dda8a2274f73156fe15558fca4a917cb8a09fc47af5963", size = 474180, upload-time = "2026-07-23T01:57:19.306Z" }, + { url = "https://files.pythonhosted.org/packages/99/5d/3cd6ef0a2b2851f7ab913b5b079334781bd50ff56a323e4454063377a080/aiohttp-3.14.3-cp314-cp314t-win_amd64.whl", hash = "sha256:b20032766aedf6261c7a566585a40867d092ac03a0d81592d5370ef9b054f99b", size = 500528, upload-time = "2026-07-23T01:57:21.762Z" }, + { url = "https://files.pythonhosted.org/packages/a4/37/cfd1ed540a4d318da025590d96b728e63713c09e9377950fc655dadeb856/aiohttp-3.14.3-cp314-cp314t-win_arm64.whl", hash = "sha256:2e1161602f45a54de2ce0905243a95f58cb42dcd378402f3697f5e0b21e9d2e7", size = 469280, upload-time = "2026-07-23T01:57:24.241Z" }, +] + +[[package]] +name = "aiosignal" +version = "1.4.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "frozenlist" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/61/62/06741b579156360248d1ec624842ad0edf697050bbaf7c3e46394e106ad1/aiosignal-1.4.0.tar.gz", hash = "sha256:f47eecd9468083c2029cc99945502cb7708b082c232f9aca65da147157b251c7", size = 25007, upload-time = "2025-07-03T22:54:43.528Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl", hash = "sha256:053243f8b92b990551949e63930a839ff0cf0b0ebbe0597b0f3fb19e1a0fe82e", size = 7490, upload-time = "2025-07-03T22:54:42.156Z" }, +] + [[package]] name = "annotated-types" version = "0.7.0" @@ -52,6 +192,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/38/0e/27be9fdef66e72d64c0cdc3cc2823101b80585f8119b5c112c2e8f5f7dab/anyio-4.12.1-py3-none-any.whl", hash = "sha256:d405828884fc140aa80a3c667b8beed277f1dfedec42ba031bd6ac3db606ab6c", size = 113592, upload-time = "2026-01-06T11:45:19.497Z" }, ] +[[package]] +name = "attrs" +version = "26.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/9a/8e/82a0fe20a541c03148528be8cac2408564a6c9a0cc7e9171802bc1d26985/attrs-26.1.0.tar.gz", hash = "sha256:d03ceb89cb322a8fd706d4fb91940737b6642aa36998fe130a9bc96c985eff32", size = 952055, upload-time = "2026-03-19T14:22:25.026Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/64/b4/17d4b0b2a2dc85a6df63d1157e028ed19f90d4cd97c36717afef2bc2f395/attrs-26.1.0-py3-none-any.whl", hash = "sha256:c647aa4a12dfbad9333ca4e71fe62ddc36f4e63b2d260a37a8b83d2f043ac309", size = 67548, upload-time = "2026-03-19T14:22:23.645Z" }, +] + [[package]] name = "azure-core" version = "1.38.0" @@ -417,6 +566,111 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/55/e2/2537ebcff11c1ee1ff17d8d0b6f4db75873e3b0fb32c2d4a2ee31ecb310a/docstring_parser-0.17.0-py3-none-any.whl", hash = "sha256:cf2569abd23dce8099b300f9b4fa8191e9582dda731fd533daf54c4551658708", size = 36896, upload-time = "2025-07-21T07:35:00.684Z" }, ] +[[package]] +name = "frozenlist" +version = "1.8.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2d/f5/c831fac6cc817d26fd54c7eaccd04ef7e0288806943f7cc5bbf69f3ac1f0/frozenlist-1.8.0.tar.gz", hash = "sha256:3ede829ed8d842f6cd48fc7081d7a41001a56f1f38603f9d49bf3020d59a31ad", size = 45875, upload-time = "2025-10-06T05:38:17.865Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bc/03/077f869d540370db12165c0aa51640a873fb661d8b315d1d4d67b284d7ac/frozenlist-1.8.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:09474e9831bc2b2199fad6da3c14c7b0fbdd377cce9d3d77131be28906cb7d84", size = 86912, upload-time = "2025-10-06T05:35:45.98Z" }, + { url = "https://files.pythonhosted.org/packages/df/b5/7610b6bd13e4ae77b96ba85abea1c8cb249683217ef09ac9e0ae93f25a91/frozenlist-1.8.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:17c883ab0ab67200b5f964d2b9ed6b00971917d5d8a92df149dc2c9779208ee9", size = 50046, upload-time = "2025-10-06T05:35:47.009Z" }, + { url = "https://files.pythonhosted.org/packages/6e/ef/0e8f1fe32f8a53dd26bdd1f9347efe0778b0fddf62789ea683f4cc7d787d/frozenlist-1.8.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:fa47e444b8ba08fffd1c18e8cdb9a75db1b6a27f17507522834ad13ed5922b93", size = 50119, upload-time = "2025-10-06T05:35:48.38Z" }, + { url = "https://files.pythonhosted.org/packages/11/b1/71a477adc7c36e5fb628245dfbdea2166feae310757dea848d02bd0689fd/frozenlist-1.8.0-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:2552f44204b744fba866e573be4c1f9048d6a324dfe14475103fd51613eb1d1f", size = 231067, upload-time = "2025-10-06T05:35:49.97Z" }, + { url = "https://files.pythonhosted.org/packages/45/7e/afe40eca3a2dc19b9904c0f5d7edfe82b5304cb831391edec0ac04af94c2/frozenlist-1.8.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:957e7c38f250991e48a9a73e6423db1bb9dd14e722a10f6b8bb8e16a0f55f695", size = 233160, upload-time = "2025-10-06T05:35:51.729Z" }, + { url = "https://files.pythonhosted.org/packages/a6/aa/7416eac95603ce428679d273255ffc7c998d4132cfae200103f164b108aa/frozenlist-1.8.0-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:8585e3bb2cdea02fc88ffa245069c36555557ad3609e83be0ec71f54fd4abb52", size = 228544, upload-time = "2025-10-06T05:35:53.246Z" }, + { url = "https://files.pythonhosted.org/packages/8b/3d/2a2d1f683d55ac7e3875e4263d28410063e738384d3adc294f5ff3d7105e/frozenlist-1.8.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:edee74874ce20a373d62dc28b0b18b93f645633c2943fd90ee9d898550770581", size = 243797, upload-time = "2025-10-06T05:35:54.497Z" }, + { url = "https://files.pythonhosted.org/packages/78/1e/2d5565b589e580c296d3bb54da08d206e797d941a83a6fdea42af23be79c/frozenlist-1.8.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:c9a63152fe95756b85f31186bddf42e4c02c6321207fd6601a1c89ebac4fe567", size = 247923, upload-time = "2025-10-06T05:35:55.861Z" }, + { url = "https://files.pythonhosted.org/packages/aa/c3/65872fcf1d326a7f101ad4d86285c403c87be7d832b7470b77f6d2ed5ddc/frozenlist-1.8.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:b6db2185db9be0a04fecf2f241c70b63b1a242e2805be291855078f2b404dd6b", size = 230886, upload-time = "2025-10-06T05:35:57.399Z" }, + { url = "https://files.pythonhosted.org/packages/a0/76/ac9ced601d62f6956f03cc794f9e04c81719509f85255abf96e2510f4265/frozenlist-1.8.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:f4be2e3d8bc8aabd566f8d5b8ba7ecc09249d74ba3c9ed52e54dc23a293f0b92", size = 245731, upload-time = "2025-10-06T05:35:58.563Z" }, + { url = "https://files.pythonhosted.org/packages/b9/49/ecccb5f2598daf0b4a1415497eba4c33c1e8ce07495eb07d2860c731b8d5/frozenlist-1.8.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:c8d1634419f39ea6f5c427ea2f90ca85126b54b50837f31497f3bf38266e853d", size = 241544, upload-time = "2025-10-06T05:35:59.719Z" }, + { url = "https://files.pythonhosted.org/packages/53/4b/ddf24113323c0bbcc54cb38c8b8916f1da7165e07b8e24a717b4a12cbf10/frozenlist-1.8.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:1a7fa382a4a223773ed64242dbe1c9c326ec09457e6b8428efb4118c685c3dfd", size = 241806, upload-time = "2025-10-06T05:36:00.959Z" }, + { url = "https://files.pythonhosted.org/packages/a7/fb/9b9a084d73c67175484ba2789a59f8eebebd0827d186a8102005ce41e1ba/frozenlist-1.8.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:11847b53d722050808926e785df837353bd4d75f1d494377e59b23594d834967", size = 229382, upload-time = "2025-10-06T05:36:02.22Z" }, + { url = "https://files.pythonhosted.org/packages/95/a3/c8fb25aac55bf5e12dae5c5aa6a98f85d436c1dc658f21c3ac73f9fa95e5/frozenlist-1.8.0-cp311-cp311-win32.whl", hash = "sha256:27c6e8077956cf73eadd514be8fb04d77fc946a7fe9f7fe167648b0b9085cc25", size = 39647, upload-time = "2025-10-06T05:36:03.409Z" }, + { url = "https://files.pythonhosted.org/packages/0a/f5/603d0d6a02cfd4c8f2a095a54672b3cf967ad688a60fb9faf04fc4887f65/frozenlist-1.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:ac913f8403b36a2c8610bbfd25b8013488533e71e62b4b4adce9c86c8cea905b", size = 44064, upload-time = "2025-10-06T05:36:04.368Z" }, + { url = "https://files.pythonhosted.org/packages/5d/16/c2c9ab44e181f043a86f9a8f84d5124b62dbcb3a02c0977ec72b9ac1d3e0/frozenlist-1.8.0-cp311-cp311-win_arm64.whl", hash = "sha256:d4d3214a0f8394edfa3e303136d0575eece0745ff2b47bd2cb2e66dd92d4351a", size = 39937, upload-time = "2025-10-06T05:36:05.669Z" }, + { url = "https://files.pythonhosted.org/packages/69/29/948b9aa87e75820a38650af445d2ef2b6b8a6fab1a23b6bb9e4ef0be2d59/frozenlist-1.8.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:78f7b9e5d6f2fdb88cdde9440dc147259b62b9d3b019924def9f6478be254ac1", size = 87782, upload-time = "2025-10-06T05:36:06.649Z" }, + { url = "https://files.pythonhosted.org/packages/64/80/4f6e318ee2a7c0750ed724fa33a4bdf1eacdc5a39a7a24e818a773cd91af/frozenlist-1.8.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:229bf37d2e4acdaf808fd3f06e854a4a7a3661e871b10dc1f8f1896a3b05f18b", size = 50594, upload-time = "2025-10-06T05:36:07.69Z" }, + { url = "https://files.pythonhosted.org/packages/2b/94/5c8a2b50a496b11dd519f4a24cb5496cf125681dd99e94c604ccdea9419a/frozenlist-1.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f833670942247a14eafbb675458b4e61c82e002a148f49e68257b79296e865c4", size = 50448, upload-time = "2025-10-06T05:36:08.78Z" }, + { url = "https://files.pythonhosted.org/packages/6a/bd/d91c5e39f490a49df14320f4e8c80161cfcce09f1e2cde1edd16a551abb3/frozenlist-1.8.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:494a5952b1c597ba44e0e78113a7266e656b9794eec897b19ead706bd7074383", size = 242411, upload-time = "2025-10-06T05:36:09.801Z" }, + { url = "https://files.pythonhosted.org/packages/8f/83/f61505a05109ef3293dfb1ff594d13d64a2324ac3482be2cedc2be818256/frozenlist-1.8.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:96f423a119f4777a4a056b66ce11527366a8bb92f54e541ade21f2374433f6d4", size = 243014, upload-time = "2025-10-06T05:36:11.394Z" }, + { url = "https://files.pythonhosted.org/packages/d8/cb/cb6c7b0f7d4023ddda30cf56b8b17494eb3a79e3fda666bf735f63118b35/frozenlist-1.8.0-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3462dd9475af2025c31cc61be6652dfa25cbfb56cbbf52f4ccfe029f38decaf8", size = 234909, upload-time = "2025-10-06T05:36:12.598Z" }, + { url = "https://files.pythonhosted.org/packages/31/c5/cd7a1f3b8b34af009fb17d4123c5a778b44ae2804e3ad6b86204255f9ec5/frozenlist-1.8.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c4c800524c9cd9bac5166cd6f55285957fcfc907db323e193f2afcd4d9abd69b", size = 250049, upload-time = "2025-10-06T05:36:14.065Z" }, + { url = "https://files.pythonhosted.org/packages/c0/01/2f95d3b416c584a1e7f0e1d6d31998c4a795f7544069ee2e0962a4b60740/frozenlist-1.8.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d6a5df73acd3399d893dafc71663ad22534b5aa4f94e8a2fabfe856c3c1b6a52", size = 256485, upload-time = "2025-10-06T05:36:15.39Z" }, + { url = "https://files.pythonhosted.org/packages/ce/03/024bf7720b3abaebcff6d0793d73c154237b85bdf67b7ed55e5e9596dc9a/frozenlist-1.8.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:405e8fe955c2280ce66428b3ca55e12b3c4e9c336fb2103a4937e891c69a4a29", size = 237619, upload-time = "2025-10-06T05:36:16.558Z" }, + { url = "https://files.pythonhosted.org/packages/69/fa/f8abdfe7d76b731f5d8bd217827cf6764d4f1d9763407e42717b4bed50a0/frozenlist-1.8.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:908bd3f6439f2fef9e85031b59fd4f1297af54415fb60e4254a95f75b3cab3f3", size = 250320, upload-time = "2025-10-06T05:36:17.821Z" }, + { url = "https://files.pythonhosted.org/packages/f5/3c/b051329f718b463b22613e269ad72138cc256c540f78a6de89452803a47d/frozenlist-1.8.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:294e487f9ec720bd8ffcebc99d575f7eff3568a08a253d1ee1a0378754b74143", size = 246820, upload-time = "2025-10-06T05:36:19.046Z" }, + { url = "https://files.pythonhosted.org/packages/0f/ae/58282e8f98e444b3f4dd42448ff36fa38bef29e40d40f330b22e7108f565/frozenlist-1.8.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:74c51543498289c0c43656701be6b077f4b265868fa7f8a8859c197006efb608", size = 250518, upload-time = "2025-10-06T05:36:20.763Z" }, + { url = "https://files.pythonhosted.org/packages/8f/96/007e5944694d66123183845a106547a15944fbbb7154788cbf7272789536/frozenlist-1.8.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:776f352e8329135506a1d6bf16ac3f87bc25b28e765949282dcc627af36123aa", size = 239096, upload-time = "2025-10-06T05:36:22.129Z" }, + { url = "https://files.pythonhosted.org/packages/66/bb/852b9d6db2fa40be96f29c0d1205c306288f0684df8fd26ca1951d461a56/frozenlist-1.8.0-cp312-cp312-win32.whl", hash = "sha256:433403ae80709741ce34038da08511d4a77062aa924baf411ef73d1146e74faf", size = 39985, upload-time = "2025-10-06T05:36:23.661Z" }, + { url = "https://files.pythonhosted.org/packages/b8/af/38e51a553dd66eb064cdf193841f16f077585d4d28394c2fa6235cb41765/frozenlist-1.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:34187385b08f866104f0c0617404c8eb08165ab1272e884abc89c112e9c00746", size = 44591, upload-time = "2025-10-06T05:36:24.958Z" }, + { url = "https://files.pythonhosted.org/packages/a7/06/1dc65480ab147339fecc70797e9c2f69d9cea9cf38934ce08df070fdb9cb/frozenlist-1.8.0-cp312-cp312-win_arm64.whl", hash = "sha256:fe3c58d2f5db5fbd18c2987cba06d51b0529f52bc3a6cdc33d3f4eab725104bd", size = 40102, upload-time = "2025-10-06T05:36:26.333Z" }, + { url = "https://files.pythonhosted.org/packages/2d/40/0832c31a37d60f60ed79e9dfb5a92e1e2af4f40a16a29abcc7992af9edff/frozenlist-1.8.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:8d92f1a84bb12d9e56f818b3a746f3efba93c1b63c8387a73dde655e1e42282a", size = 85717, upload-time = "2025-10-06T05:36:27.341Z" }, + { url = "https://files.pythonhosted.org/packages/30/ba/b0b3de23f40bc55a7057bd38434e25c34fa48e17f20ee273bbde5e0650f3/frozenlist-1.8.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:96153e77a591c8adc2ee805756c61f59fef4cf4073a9275ee86fe8cba41241f7", size = 49651, upload-time = "2025-10-06T05:36:28.855Z" }, + { url = "https://files.pythonhosted.org/packages/0c/ab/6e5080ee374f875296c4243c381bbdef97a9ac39c6e3ce1d5f7d42cb78d6/frozenlist-1.8.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f21f00a91358803399890ab167098c131ec2ddd5f8f5fd5fe9c9f2c6fcd91e40", size = 49417, upload-time = "2025-10-06T05:36:29.877Z" }, + { url = "https://files.pythonhosted.org/packages/d5/4e/e4691508f9477ce67da2015d8c00acd751e6287739123113a9fca6f1604e/frozenlist-1.8.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:fb30f9626572a76dfe4293c7194a09fb1fe93ba94c7d4f720dfae3b646b45027", size = 234391, upload-time = "2025-10-06T05:36:31.301Z" }, + { url = "https://files.pythonhosted.org/packages/40/76/c202df58e3acdf12969a7895fd6f3bc016c642e6726aa63bd3025e0fc71c/frozenlist-1.8.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:eaa352d7047a31d87dafcacbabe89df0aa506abb5b1b85a2fb91bc3faa02d822", size = 233048, upload-time = "2025-10-06T05:36:32.531Z" }, + { url = "https://files.pythonhosted.org/packages/f9/c0/8746afb90f17b73ca5979c7a3958116e105ff796e718575175319b5bb4ce/frozenlist-1.8.0-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:03ae967b4e297f58f8c774c7eabcce57fe3c2434817d4385c50661845a058121", size = 226549, upload-time = "2025-10-06T05:36:33.706Z" }, + { url = "https://files.pythonhosted.org/packages/7e/eb/4c7eefc718ff72f9b6c4893291abaae5fbc0c82226a32dcd8ef4f7a5dbef/frozenlist-1.8.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f6292f1de555ffcc675941d65fffffb0a5bcd992905015f85d0592201793e0e5", size = 239833, upload-time = "2025-10-06T05:36:34.947Z" }, + { url = "https://files.pythonhosted.org/packages/c2/4e/e5c02187cf704224f8b21bee886f3d713ca379535f16893233b9d672ea71/frozenlist-1.8.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:29548f9b5b5e3460ce7378144c3010363d8035cea44bc0bf02d57f5a685e084e", size = 245363, upload-time = "2025-10-06T05:36:36.534Z" }, + { url = "https://files.pythonhosted.org/packages/1f/96/cb85ec608464472e82ad37a17f844889c36100eed57bea094518bf270692/frozenlist-1.8.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ec3cc8c5d4084591b4237c0a272cc4f50a5b03396a47d9caaf76f5d7b38a4f11", size = 229314, upload-time = "2025-10-06T05:36:38.582Z" }, + { url = "https://files.pythonhosted.org/packages/5d/6f/4ae69c550e4cee66b57887daeebe006fe985917c01d0fff9caab9883f6d0/frozenlist-1.8.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:517279f58009d0b1f2e7c1b130b377a349405da3f7621ed6bfae50b10adf20c1", size = 243365, upload-time = "2025-10-06T05:36:40.152Z" }, + { url = "https://files.pythonhosted.org/packages/7a/58/afd56de246cf11780a40a2c28dc7cbabbf06337cc8ddb1c780a2d97e88d8/frozenlist-1.8.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:db1e72ede2d0d7ccb213f218df6a078a9c09a7de257c2fe8fcef16d5925230b1", size = 237763, upload-time = "2025-10-06T05:36:41.355Z" }, + { url = "https://files.pythonhosted.org/packages/cb/36/cdfaf6ed42e2644740d4a10452d8e97fa1c062e2a8006e4b09f1b5fd7d63/frozenlist-1.8.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:b4dec9482a65c54a5044486847b8a66bf10c9cb4926d42927ec4e8fd5db7fed8", size = 240110, upload-time = "2025-10-06T05:36:42.716Z" }, + { url = "https://files.pythonhosted.org/packages/03/a8/9ea226fbefad669f11b52e864c55f0bd57d3c8d7eb07e9f2e9a0b39502e1/frozenlist-1.8.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:21900c48ae04d13d416f0e1e0c4d81f7931f73a9dfa0b7a8746fb2fe7dd970ed", size = 233717, upload-time = "2025-10-06T05:36:44.251Z" }, + { url = "https://files.pythonhosted.org/packages/1e/0b/1b5531611e83ba7d13ccc9988967ea1b51186af64c42b7a7af465dcc9568/frozenlist-1.8.0-cp313-cp313-win32.whl", hash = "sha256:8b7b94a067d1c504ee0b16def57ad5738701e4ba10cec90529f13fa03c833496", size = 39628, upload-time = "2025-10-06T05:36:45.423Z" }, + { url = "https://files.pythonhosted.org/packages/d8/cf/174c91dbc9cc49bc7b7aab74d8b734e974d1faa8f191c74af9b7e80848e6/frozenlist-1.8.0-cp313-cp313-win_amd64.whl", hash = "sha256:878be833caa6a3821caf85eb39c5ba92d28e85df26d57afb06b35b2efd937231", size = 43882, upload-time = "2025-10-06T05:36:46.796Z" }, + { url = "https://files.pythonhosted.org/packages/c1/17/502cd212cbfa96eb1388614fe39a3fc9ab87dbbe042b66f97acb57474834/frozenlist-1.8.0-cp313-cp313-win_arm64.whl", hash = "sha256:44389d135b3ff43ba8cc89ff7f51f5a0bb6b63d829c8300f79a2fe4fe61bcc62", size = 39676, upload-time = "2025-10-06T05:36:47.8Z" }, + { url = "https://files.pythonhosted.org/packages/d2/5c/3bbfaa920dfab09e76946a5d2833a7cbdf7b9b4a91c714666ac4855b88b4/frozenlist-1.8.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:e25ac20a2ef37e91c1b39938b591457666a0fa835c7783c3a8f33ea42870db94", size = 89235, upload-time = "2025-10-06T05:36:48.78Z" }, + { url = "https://files.pythonhosted.org/packages/d2/d6/f03961ef72166cec1687e84e8925838442b615bd0b8854b54923ce5b7b8a/frozenlist-1.8.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:07cdca25a91a4386d2e76ad992916a85038a9b97561bf7a3fd12d5d9ce31870c", size = 50742, upload-time = "2025-10-06T05:36:49.837Z" }, + { url = "https://files.pythonhosted.org/packages/1e/bb/a6d12b7ba4c3337667d0e421f7181c82dda448ce4e7ad7ecd249a16fa806/frozenlist-1.8.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:4e0c11f2cc6717e0a741f84a527c52616140741cd812a50422f83dc31749fb52", size = 51725, upload-time = "2025-10-06T05:36:50.851Z" }, + { url = "https://files.pythonhosted.org/packages/bc/71/d1fed0ffe2c2ccd70b43714c6cab0f4188f09f8a67a7914a6b46ee30f274/frozenlist-1.8.0-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b3210649ee28062ea6099cfda39e147fa1bc039583c8ee4481cb7811e2448c51", size = 284533, upload-time = "2025-10-06T05:36:51.898Z" }, + { url = "https://files.pythonhosted.org/packages/c9/1f/fb1685a7b009d89f9bf78a42d94461bc06581f6e718c39344754a5d9bada/frozenlist-1.8.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:581ef5194c48035a7de2aefc72ac6539823bb71508189e5de01d60c9dcd5fa65", size = 292506, upload-time = "2025-10-06T05:36:53.101Z" }, + { url = "https://files.pythonhosted.org/packages/e6/3b/b991fe1612703f7e0d05c0cf734c1b77aaf7c7d321df4572e8d36e7048c8/frozenlist-1.8.0-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3ef2d026f16a2b1866e1d86fc4e1291e1ed8a387b2c333809419a2f8b3a77b82", size = 274161, upload-time = "2025-10-06T05:36:54.309Z" }, + { url = "https://files.pythonhosted.org/packages/ca/ec/c5c618767bcdf66e88945ec0157d7f6c4a1322f1473392319b7a2501ded7/frozenlist-1.8.0-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5500ef82073f599ac84d888e3a8c1f77ac831183244bfd7f11eaa0289fb30714", size = 294676, upload-time = "2025-10-06T05:36:55.566Z" }, + { url = "https://files.pythonhosted.org/packages/7c/ce/3934758637d8f8a88d11f0585d6495ef54b2044ed6ec84492a91fa3b27aa/frozenlist-1.8.0-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:50066c3997d0091c411a66e710f4e11752251e6d2d73d70d8d5d4c76442a199d", size = 300638, upload-time = "2025-10-06T05:36:56.758Z" }, + { url = "https://files.pythonhosted.org/packages/fc/4f/a7e4d0d467298f42de4b41cbc7ddaf19d3cfeabaf9ff97c20c6c7ee409f9/frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:5c1c8e78426e59b3f8005e9b19f6ff46e5845895adbde20ece9218319eca6506", size = 283067, upload-time = "2025-10-06T05:36:57.965Z" }, + { url = "https://files.pythonhosted.org/packages/dc/48/c7b163063d55a83772b268e6d1affb960771b0e203b632cfe09522d67ea5/frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:eefdba20de0d938cec6a89bd4d70f346a03108a19b9df4248d3cf0d88f1b0f51", size = 292101, upload-time = "2025-10-06T05:36:59.237Z" }, + { url = "https://files.pythonhosted.org/packages/9f/d0/2366d3c4ecdc2fd391e0afa6e11500bfba0ea772764d631bbf82f0136c9d/frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:cf253e0e1c3ceb4aaff6df637ce033ff6535fb8c70a764a8f46aafd3d6ab798e", size = 289901, upload-time = "2025-10-06T05:37:00.811Z" }, + { url = "https://files.pythonhosted.org/packages/b8/94/daff920e82c1b70e3618a2ac39fbc01ae3e2ff6124e80739ce5d71c9b920/frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:032efa2674356903cd0261c4317a561a6850f3ac864a63fc1583147fb05a79b0", size = 289395, upload-time = "2025-10-06T05:37:02.115Z" }, + { url = "https://files.pythonhosted.org/packages/e3/20/bba307ab4235a09fdcd3cc5508dbabd17c4634a1af4b96e0f69bfe551ebd/frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:6da155091429aeba16851ecb10a9104a108bcd32f6c1642867eadaee401c1c41", size = 283659, upload-time = "2025-10-06T05:37:03.711Z" }, + { url = "https://files.pythonhosted.org/packages/fd/00/04ca1c3a7a124b6de4f8a9a17cc2fcad138b4608e7a3fc5877804b8715d7/frozenlist-1.8.0-cp313-cp313t-win32.whl", hash = "sha256:0f96534f8bfebc1a394209427d0f8a63d343c9779cda6fc25e8e121b5fd8555b", size = 43492, upload-time = "2025-10-06T05:37:04.915Z" }, + { url = "https://files.pythonhosted.org/packages/59/5e/c69f733a86a94ab10f68e496dc6b7e8bc078ebb415281d5698313e3af3a1/frozenlist-1.8.0-cp313-cp313t-win_amd64.whl", hash = "sha256:5d63a068f978fc69421fb0e6eb91a9603187527c86b7cd3f534a5b77a592b888", size = 48034, upload-time = "2025-10-06T05:37:06.343Z" }, + { url = "https://files.pythonhosted.org/packages/16/6c/be9d79775d8abe79b05fa6d23da99ad6e7763a1d080fbae7290b286093fd/frozenlist-1.8.0-cp313-cp313t-win_arm64.whl", hash = "sha256:bf0a7e10b077bf5fb9380ad3ae8ce20ef919a6ad93b4552896419ac7e1d8e042", size = 41749, upload-time = "2025-10-06T05:37:07.431Z" }, + { url = "https://files.pythonhosted.org/packages/f1/c8/85da824b7e7b9b6e7f7705b2ecaf9591ba6f79c1177f324c2735e41d36a2/frozenlist-1.8.0-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:cee686f1f4cadeb2136007ddedd0aaf928ab95216e7691c63e50a8ec066336d0", size = 86127, upload-time = "2025-10-06T05:37:08.438Z" }, + { url = "https://files.pythonhosted.org/packages/8e/e8/a1185e236ec66c20afd72399522f142c3724c785789255202d27ae992818/frozenlist-1.8.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:119fb2a1bd47307e899c2fac7f28e85b9a543864df47aa7ec9d3c1b4545f096f", size = 49698, upload-time = "2025-10-06T05:37:09.48Z" }, + { url = "https://files.pythonhosted.org/packages/a1/93/72b1736d68f03fda5fdf0f2180fb6caaae3894f1b854d006ac61ecc727ee/frozenlist-1.8.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:4970ece02dbc8c3a92fcc5228e36a3e933a01a999f7094ff7c23fbd2beeaa67c", size = 49749, upload-time = "2025-10-06T05:37:10.569Z" }, + { url = "https://files.pythonhosted.org/packages/a7/b2/fabede9fafd976b991e9f1b9c8c873ed86f202889b864756f240ce6dd855/frozenlist-1.8.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:cba69cb73723c3f329622e34bdbf5ce1f80c21c290ff04256cff1cd3c2036ed2", size = 231298, upload-time = "2025-10-06T05:37:11.993Z" }, + { url = "https://files.pythonhosted.org/packages/3a/3b/d9b1e0b0eed36e70477ffb8360c49c85c8ca8ef9700a4e6711f39a6e8b45/frozenlist-1.8.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:778a11b15673f6f1df23d9586f83c4846c471a8af693a22e066508b77d201ec8", size = 232015, upload-time = "2025-10-06T05:37:13.194Z" }, + { url = "https://files.pythonhosted.org/packages/dc/94/be719d2766c1138148564a3960fc2c06eb688da592bdc25adcf856101be7/frozenlist-1.8.0-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:0325024fe97f94c41c08872db482cf8ac4800d80e79222c6b0b7b162d5b13686", size = 225038, upload-time = "2025-10-06T05:37:14.577Z" }, + { url = "https://files.pythonhosted.org/packages/e4/09/6712b6c5465f083f52f50cf74167b92d4ea2f50e46a9eea0523d658454ae/frozenlist-1.8.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:97260ff46b207a82a7567b581ab4190bd4dfa09f4db8a8b49d1a958f6aa4940e", size = 240130, upload-time = "2025-10-06T05:37:15.781Z" }, + { url = "https://files.pythonhosted.org/packages/f8/d4/cd065cdcf21550b54f3ce6a22e143ac9e4836ca42a0de1022da8498eac89/frozenlist-1.8.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:54b2077180eb7f83dd52c40b2750d0a9f175e06a42e3213ce047219de902717a", size = 242845, upload-time = "2025-10-06T05:37:17.037Z" }, + { url = "https://files.pythonhosted.org/packages/62/c3/f57a5c8c70cd1ead3d5d5f776f89d33110b1addae0ab010ad774d9a44fb9/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:2f05983daecab868a31e1da44462873306d3cbfd76d1f0b5b69c473d21dbb128", size = 229131, upload-time = "2025-10-06T05:37:18.221Z" }, + { url = "https://files.pythonhosted.org/packages/6c/52/232476fe9cb64f0742f3fde2b7d26c1dac18b6d62071c74d4ded55e0ef94/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:33f48f51a446114bc5d251fb2954ab0164d5be02ad3382abcbfe07e2531d650f", size = 240542, upload-time = "2025-10-06T05:37:19.771Z" }, + { url = "https://files.pythonhosted.org/packages/5f/85/07bf3f5d0fb5414aee5f47d33c6f5c77bfe49aac680bfece33d4fdf6a246/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:154e55ec0655291b5dd1b8731c637ecdb50975a2ae70c606d100750a540082f7", size = 237308, upload-time = "2025-10-06T05:37:20.969Z" }, + { url = "https://files.pythonhosted.org/packages/11/99/ae3a33d5befd41ac0ca2cc7fd3aa707c9c324de2e89db0e0f45db9a64c26/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:4314debad13beb564b708b4a496020e5306c7333fa9a3ab90374169a20ffab30", size = 238210, upload-time = "2025-10-06T05:37:22.252Z" }, + { url = "https://files.pythonhosted.org/packages/b2/60/b1d2da22f4970e7a155f0adde9b1435712ece01b3cd45ba63702aea33938/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:073f8bf8becba60aa931eb3bc420b217bb7d5b8f4750e6f8b3be7f3da85d38b7", size = 231972, upload-time = "2025-10-06T05:37:23.5Z" }, + { url = "https://files.pythonhosted.org/packages/3f/ab/945b2f32de889993b9c9133216c068b7fcf257d8595a0ac420ac8677cab0/frozenlist-1.8.0-cp314-cp314-win32.whl", hash = "sha256:bac9c42ba2ac65ddc115d930c78d24ab8d4f465fd3fc473cdedfccadb9429806", size = 40536, upload-time = "2025-10-06T05:37:25.581Z" }, + { url = "https://files.pythonhosted.org/packages/59/ad/9caa9b9c836d9ad6f067157a531ac48b7d36499f5036d4141ce78c230b1b/frozenlist-1.8.0-cp314-cp314-win_amd64.whl", hash = "sha256:3e0761f4d1a44f1d1a47996511752cf3dcec5bbdd9cc2b4fe595caf97754b7a0", size = 44330, upload-time = "2025-10-06T05:37:26.928Z" }, + { url = "https://files.pythonhosted.org/packages/82/13/e6950121764f2676f43534c555249f57030150260aee9dcf7d64efda11dd/frozenlist-1.8.0-cp314-cp314-win_arm64.whl", hash = "sha256:d1eaff1d00c7751b7c6662e9c5ba6eb2c17a2306ba5e2a37f24ddf3cc953402b", size = 40627, upload-time = "2025-10-06T05:37:28.075Z" }, + { url = "https://files.pythonhosted.org/packages/c0/c7/43200656ecc4e02d3f8bc248df68256cd9572b3f0017f0a0c4e93440ae23/frozenlist-1.8.0-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:d3bb933317c52d7ea5004a1c442eef86f426886fba134ef8cf4226ea6ee1821d", size = 89238, upload-time = "2025-10-06T05:37:29.373Z" }, + { url = "https://files.pythonhosted.org/packages/d1/29/55c5f0689b9c0fb765055629f472c0de484dcaf0acee2f7707266ae3583c/frozenlist-1.8.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:8009897cdef112072f93a0efdce29cd819e717fd2f649ee3016efd3cd885a7ed", size = 50738, upload-time = "2025-10-06T05:37:30.792Z" }, + { url = "https://files.pythonhosted.org/packages/ba/7d/b7282a445956506fa11da8c2db7d276adcbf2b17d8bb8407a47685263f90/frozenlist-1.8.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:2c5dcbbc55383e5883246d11fd179782a9d07a986c40f49abe89ddf865913930", size = 51739, upload-time = "2025-10-06T05:37:32.127Z" }, + { url = "https://files.pythonhosted.org/packages/62/1c/3d8622e60d0b767a5510d1d3cf21065b9db874696a51ea6d7a43180a259c/frozenlist-1.8.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:39ecbc32f1390387d2aa4f5a995e465e9e2f79ba3adcac92d68e3e0afae6657c", size = 284186, upload-time = "2025-10-06T05:37:33.21Z" }, + { url = "https://files.pythonhosted.org/packages/2d/14/aa36d5f85a89679a85a1d44cd7a6657e0b1c75f61e7cad987b203d2daca8/frozenlist-1.8.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:92db2bf818d5cc8d9c1f1fc56b897662e24ea5adb36ad1f1d82875bd64e03c24", size = 292196, upload-time = "2025-10-06T05:37:36.107Z" }, + { url = "https://files.pythonhosted.org/packages/05/23/6bde59eb55abd407d34f77d39a5126fb7b4f109a3f611d3929f14b700c66/frozenlist-1.8.0-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:2dc43a022e555de94c3b68a4ef0b11c4f747d12c024a520c7101709a2144fb37", size = 273830, upload-time = "2025-10-06T05:37:37.663Z" }, + { url = "https://files.pythonhosted.org/packages/d2/3f/22cff331bfad7a8afa616289000ba793347fcd7bc275f3b28ecea2a27909/frozenlist-1.8.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:cb89a7f2de3602cfed448095bab3f178399646ab7c61454315089787df07733a", size = 294289, upload-time = "2025-10-06T05:37:39.261Z" }, + { url = "https://files.pythonhosted.org/packages/a4/89/5b057c799de4838b6c69aa82b79705f2027615e01be996d2486a69ca99c4/frozenlist-1.8.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:33139dc858c580ea50e7e60a1b0ea003efa1fd42e6ec7fdbad78fff65fad2fd2", size = 300318, upload-time = "2025-10-06T05:37:43.213Z" }, + { url = "https://files.pythonhosted.org/packages/30/de/2c22ab3eb2a8af6d69dc799e48455813bab3690c760de58e1bf43b36da3e/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:168c0969a329b416119507ba30b9ea13688fafffac1b7822802537569a1cb0ef", size = 282814, upload-time = "2025-10-06T05:37:45.337Z" }, + { url = "https://files.pythonhosted.org/packages/59/f7/970141a6a8dbd7f556d94977858cfb36fa9b66e0892c6dd780d2219d8cd8/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:28bd570e8e189d7f7b001966435f9dac6718324b5be2990ac496cf1ea9ddb7fe", size = 291762, upload-time = "2025-10-06T05:37:46.657Z" }, + { url = "https://files.pythonhosted.org/packages/c1/15/ca1adae83a719f82df9116d66f5bb28bb95557b3951903d39135620ef157/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:b2a095d45c5d46e5e79ba1e5b9cb787f541a8dee0433836cea4b96a2c439dcd8", size = 289470, upload-time = "2025-10-06T05:37:47.946Z" }, + { url = "https://files.pythonhosted.org/packages/ac/83/dca6dc53bf657d371fbc88ddeb21b79891e747189c5de990b9dfff2ccba1/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:eab8145831a0d56ec9c4139b6c3e594c7a83c2c8be25d5bcf2d86136a532287a", size = 289042, upload-time = "2025-10-06T05:37:49.499Z" }, + { url = "https://files.pythonhosted.org/packages/96/52/abddd34ca99be142f354398700536c5bd315880ed0a213812bc491cff5e4/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:974b28cf63cc99dfb2188d8d222bc6843656188164848c4f679e63dae4b0708e", size = 283148, upload-time = "2025-10-06T05:37:50.745Z" }, + { url = "https://files.pythonhosted.org/packages/af/d3/76bd4ed4317e7119c2b7f57c3f6934aba26d277acc6309f873341640e21f/frozenlist-1.8.0-cp314-cp314t-win32.whl", hash = "sha256:342c97bf697ac5480c0a7ec73cd700ecfa5a8a40ac923bd035484616efecc2df", size = 44676, upload-time = "2025-10-06T05:37:52.222Z" }, + { url = "https://files.pythonhosted.org/packages/89/76/c615883b7b521ead2944bb3480398cbb07e12b7b4e4d073d3752eb721558/frozenlist-1.8.0-cp314-cp314t-win_amd64.whl", hash = "sha256:06be8f67f39c8b1dc671f5d83aaefd3358ae5cdcf8314552c57e7ed3e6475bdd", size = 49451, upload-time = "2025-10-06T05:37:53.425Z" }, + { url = "https://files.pythonhosted.org/packages/e0/a3/5982da14e113d07b325230f95060e2169f5311b1017ea8af2a29b374c289/frozenlist-1.8.0-cp314-cp314t-win_arm64.whl", hash = "sha256:102e6314ca4da683dca92e3b1355490fed5f313b768500084fbe6371fddfdb79", size = 42507, upload-time = "2025-10-06T05:37:54.513Z" }, + { url = "https://files.pythonhosted.org/packages/9a/9a/e35b4a917281c0b8419d4207f4334c8e8c5dbf4f3f5f9ada73958d937dcc/frozenlist-1.8.0-py3-none-any.whl", hash = "sha256:0c18a16eab41e82c295618a77502e17b195883241c563b00f0aa5106fc4eaa0d", size = 13409, upload-time = "2025-10-06T05:38:16.721Z" }, +] + [[package]] name = "h11" version = "0.16.0" @@ -681,6 +935,123 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/5e/75/bd9b7bb966668920f06b200e84454c8f3566b102183bc55c5473d96cb2b9/msal_extensions-1.3.1-py3-none-any.whl", hash = "sha256:96d3de4d034504e969ac5e85bae8106c8373b5c6568e4c8fa7af2eca9dbe6bca", size = 20583, upload-time = "2025-03-14T23:51:03.016Z" }, ] +[[package]] +name = "multidict" +version = "6.7.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1a/c2/c2d94cbe6ac1753f3fc980da97b3d930efe1da3af3c9f5125354436c073d/multidict-6.7.1.tar.gz", hash = "sha256:ec6652a1bee61c53a3e5776b6049172c53b6aaba34f18c9ad04f82712bac623d", size = 102010, upload-time = "2026-01-26T02:46:45.979Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ce/f1/a90635c4f88fb913fbf4ce660b83b7445b7a02615bda034b2f8eb38fd597/multidict-6.7.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7ff981b266af91d7b4b3793ca3382e53229088d193a85dfad6f5f4c27fc73e5d", size = 76626, upload-time = "2026-01-26T02:43:26.485Z" }, + { url = "https://files.pythonhosted.org/packages/a6/9b/267e64eaf6fc637a15b35f5de31a566634a2740f97d8d094a69d34f524a4/multidict-6.7.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:844c5bca0b5444adb44a623fb0a1310c2f4cd41f402126bb269cd44c9b3f3e1e", size = 44706, upload-time = "2026-01-26T02:43:27.607Z" }, + { url = "https://files.pythonhosted.org/packages/dd/a4/d45caf2b97b035c57267791ecfaafbd59c68212004b3842830954bb4b02e/multidict-6.7.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f2a0a924d4c2e9afcd7ec64f9de35fcd96915149b2216e1cb2c10a56df483855", size = 44356, upload-time = "2026-01-26T02:43:28.661Z" }, + { url = "https://files.pythonhosted.org/packages/fd/d2/0a36c8473f0cbaeadd5db6c8b72d15bbceeec275807772bfcd059bef487d/multidict-6.7.1-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:8be1802715a8e892c784c0197c2ace276ea52702a0ede98b6310c8f255a5afb3", size = 244355, upload-time = "2026-01-26T02:43:31.165Z" }, + { url = "https://files.pythonhosted.org/packages/5d/16/8c65be997fd7dd311b7d39c7b6e71a0cb449bad093761481eccbbe4b42a2/multidict-6.7.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2e2d2ed645ea29f31c4c7ea1552fcfd7cb7ba656e1eafd4134a6620c9f5fdd9e", size = 246433, upload-time = "2026-01-26T02:43:32.581Z" }, + { url = "https://files.pythonhosted.org/packages/01/fb/4dbd7e848d2799c6a026ec88ad39cf2b8416aa167fcc903baa55ecaa045c/multidict-6.7.1-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:95922cee9a778659e91db6497596435777bd25ed116701a4c034f8e46544955a", size = 225376, upload-time = "2026-01-26T02:43:34.417Z" }, + { url = "https://files.pythonhosted.org/packages/b6/8a/4a3a6341eac3830f6053062f8fbc9a9e54407c80755b3f05bc427295c2d0/multidict-6.7.1-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:6b83cabdc375ffaaa15edd97eb7c0c672ad788e2687004990074d7d6c9b140c8", size = 257365, upload-time = "2026-01-26T02:43:35.741Z" }, + { url = "https://files.pythonhosted.org/packages/f7/a2/dd575a69c1aa206e12d27d0770cdf9b92434b48a9ef0cd0d1afdecaa93c4/multidict-6.7.1-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:38fb49540705369bab8484db0689d86c0a33a0a9f2c1b197f506b71b4b6c19b0", size = 254747, upload-time = "2026-01-26T02:43:36.976Z" }, + { url = "https://files.pythonhosted.org/packages/5a/56/21b27c560c13822ed93133f08aa6372c53a8e067f11fbed37b4adcdac922/multidict-6.7.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:439cbebd499f92e9aa6793016a8acaa161dfa749ae86d20960189f5398a19144", size = 246293, upload-time = "2026-01-26T02:43:38.258Z" }, + { url = "https://files.pythonhosted.org/packages/5a/a4/23466059dc3854763423d0ad6c0f3683a379d97673b1b89ec33826e46728/multidict-6.7.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6d3bc717b6fe763b8be3f2bee2701d3c8eb1b2a8ae9f60910f1b2860c82b6c49", size = 242962, upload-time = "2026-01-26T02:43:40.034Z" }, + { url = "https://files.pythonhosted.org/packages/1f/67/51dd754a3524d685958001e8fa20a0f5f90a6a856e0a9dcabff69be3dbb7/multidict-6.7.1-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:619e5a1ac57986dbfec9f0b301d865dddf763696435e2962f6d9cf2fdff2bb71", size = 237360, upload-time = "2026-01-26T02:43:41.752Z" }, + { url = "https://files.pythonhosted.org/packages/64/3f/036dfc8c174934d4b55d86ff4f978e558b0e585cef70cfc1ad01adc6bf18/multidict-6.7.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:0b38ebffd9be37c1170d33bc0f36f4f262e0a09bc1aac1c34c7aa51a7293f0b3", size = 245940, upload-time = "2026-01-26T02:43:43.042Z" }, + { url = "https://files.pythonhosted.org/packages/3d/20/6214d3c105928ebc353a1c644a6ef1408bc5794fcb4f170bb524a3c16311/multidict-6.7.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:10ae39c9cfe6adedcdb764f5e8411d4a92b055e35573a2eaa88d3323289ef93c", size = 253502, upload-time = "2026-01-26T02:43:44.371Z" }, + { url = "https://files.pythonhosted.org/packages/b1/e2/c653bc4ae1be70a0f836b82172d643fcf1dade042ba2676ab08ec08bff0f/multidict-6.7.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:25167cc263257660290fba06b9318d2026e3c910be240a146e1f66dd114af2b0", size = 247065, upload-time = "2026-01-26T02:43:45.745Z" }, + { url = "https://files.pythonhosted.org/packages/c8/11/a854b4154cd3bd8b1fd375e8a8ca9d73be37610c361543d56f764109509b/multidict-6.7.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:128441d052254f42989ef98b7b6a6ecb1e6f708aa962c7984235316db59f50fa", size = 241870, upload-time = "2026-01-26T02:43:47.054Z" }, + { url = "https://files.pythonhosted.org/packages/13/bf/9676c0392309b5fdae322333d22a829715b570edb9baa8016a517b55b558/multidict-6.7.1-cp311-cp311-win32.whl", hash = "sha256:d62b7f64ffde3b99d06b707a280db04fb3855b55f5a06df387236051d0668f4a", size = 41302, upload-time = "2026-01-26T02:43:48.753Z" }, + { url = "https://files.pythonhosted.org/packages/c9/68/f16a3a8ba6f7b6dc92a1f19669c0810bd2c43fc5a02da13b1cbf8e253845/multidict-6.7.1-cp311-cp311-win_amd64.whl", hash = "sha256:bdbf9f3b332abd0cdb306e7c2113818ab1e922dc84b8f8fd06ec89ed2a19ab8b", size = 45981, upload-time = "2026-01-26T02:43:49.921Z" }, + { url = "https://files.pythonhosted.org/packages/ac/ad/9dd5305253fa00cd3c7555dbef69d5bf4133debc53b87ab8d6a44d411665/multidict-6.7.1-cp311-cp311-win_arm64.whl", hash = "sha256:b8c990b037d2fff2f4e33d3f21b9b531c5745b33a49a7d6dbe7a177266af44f6", size = 43159, upload-time = "2026-01-26T02:43:51.635Z" }, + { url = "https://files.pythonhosted.org/packages/8d/9c/f20e0e2cf80e4b2e4b1c365bf5fe104ee633c751a724246262db8f1a0b13/multidict-6.7.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:a90f75c956e32891a4eda3639ce6dd86e87105271f43d43442a3aedf3cddf172", size = 76893, upload-time = "2026-01-26T02:43:52.754Z" }, + { url = "https://files.pythonhosted.org/packages/fe/cf/18ef143a81610136d3da8193da9d80bfe1cb548a1e2d1c775f26b23d024a/multidict-6.7.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:3fccb473e87eaa1382689053e4a4618e7ba7b9b9b8d6adf2027ee474597128cd", size = 45456, upload-time = "2026-01-26T02:43:53.893Z" }, + { url = "https://files.pythonhosted.org/packages/a9/65/1caac9d4cd32e8433908683446eebc953e82d22b03d10d41a5f0fefe991b/multidict-6.7.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b0fa96985700739c4c7853a43c0b3e169360d6855780021bfc6d0f1ce7c123e7", size = 43872, upload-time = "2026-01-26T02:43:55.041Z" }, + { url = "https://files.pythonhosted.org/packages/cf/3b/d6bd75dc4f3ff7c73766e04e705b00ed6dbbaccf670d9e05a12b006f5a21/multidict-6.7.1-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:cb2a55f408c3043e42b40cc8eecd575afa27b7e0b956dfb190de0f8499a57a53", size = 251018, upload-time = "2026-01-26T02:43:56.198Z" }, + { url = "https://files.pythonhosted.org/packages/fd/80/c959c5933adedb9ac15152e4067c702a808ea183a8b64cf8f31af8ad3155/multidict-6.7.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:eb0ce7b2a32d09892b3dd6cc44877a0d02a33241fafca5f25c8b6b62374f8b75", size = 258883, upload-time = "2026-01-26T02:43:57.499Z" }, + { url = "https://files.pythonhosted.org/packages/86/85/7ed40adafea3d4f1c8b916e3b5cc3a8e07dfcdcb9cd72800f4ed3ca1b387/multidict-6.7.1-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:c3a32d23520ee37bf327d1e1a656fec76a2edd5c038bf43eddfa0572ec49c60b", size = 242413, upload-time = "2026-01-26T02:43:58.755Z" }, + { url = "https://files.pythonhosted.org/packages/d2/57/b8565ff533e48595503c785f8361ff9a4fde4d67de25c207cd0ba3befd03/multidict-6.7.1-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9c90fed18bffc0189ba814749fdcc102b536e83a9f738a9003e569acd540a733", size = 268404, upload-time = "2026-01-26T02:44:00.216Z" }, + { url = "https://files.pythonhosted.org/packages/e0/50/9810c5c29350f7258180dfdcb2e52783a0632862eb334c4896ac717cebcb/multidict-6.7.1-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:da62917e6076f512daccfbbde27f46fed1c98fee202f0559adec8ee0de67f71a", size = 269456, upload-time = "2026-01-26T02:44:02.202Z" }, + { url = "https://files.pythonhosted.org/packages/f3/8d/5e5be3ced1d12966fefb5c4ea3b2a5b480afcea36406559442c6e31d4a48/multidict-6.7.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bfde23ef6ed9db7eaee6c37dcec08524cb43903c60b285b172b6c094711b3961", size = 256322, upload-time = "2026-01-26T02:44:03.56Z" }, + { url = "https://files.pythonhosted.org/packages/31/6e/d8a26d81ac166a5592782d208dd90dfdc0a7a218adaa52b45a672b46c122/multidict-6.7.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3758692429e4e32f1ba0df23219cd0b4fc0a52f476726fff9337d1a57676a582", size = 253955, upload-time = "2026-01-26T02:44:04.845Z" }, + { url = "https://files.pythonhosted.org/packages/59/4c/7c672c8aad41534ba619bcd4ade7a0dc87ed6b8b5c06149b85d3dd03f0cd/multidict-6.7.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:398c1478926eca669f2fd6a5856b6de9c0acf23a2cb59a14c0ba5844fa38077e", size = 251254, upload-time = "2026-01-26T02:44:06.133Z" }, + { url = "https://files.pythonhosted.org/packages/7b/bd/84c24de512cbafbdbc39439f74e967f19570ce7924e3007174a29c348916/multidict-6.7.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:c102791b1c4f3ab36ce4101154549105a53dc828f016356b3e3bcae2e3a039d3", size = 252059, upload-time = "2026-01-26T02:44:07.518Z" }, + { url = "https://files.pythonhosted.org/packages/fa/ba/f5449385510825b73d01c2d4087bf6d2fccc20a2d42ac34df93191d3dd03/multidict-6.7.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:a088b62bd733e2ad12c50dad01b7d0166c30287c166e137433d3b410add807a6", size = 263588, upload-time = "2026-01-26T02:44:09.382Z" }, + { url = "https://files.pythonhosted.org/packages/d7/11/afc7c677f68f75c84a69fe37184f0f82fce13ce4b92f49f3db280b7e92b3/multidict-6.7.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:3d51ff4785d58d3f6c91bdbffcb5e1f7ddfda557727043aa20d20ec4f65e324a", size = 259642, upload-time = "2026-01-26T02:44:10.73Z" }, + { url = "https://files.pythonhosted.org/packages/2b/17/ebb9644da78c4ab36403739e0e6e0e30ebb135b9caf3440825001a0bddcb/multidict-6.7.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fc5907494fccf3e7d3f94f95c91d6336b092b5fc83811720fae5e2765890dfba", size = 251377, upload-time = "2026-01-26T02:44:12.042Z" }, + { url = "https://files.pythonhosted.org/packages/ca/a4/840f5b97339e27846c46307f2530a2805d9d537d8b8bd416af031cad7fa0/multidict-6.7.1-cp312-cp312-win32.whl", hash = "sha256:28ca5ce2fd9716631133d0e9a9b9a745ad7f60bac2bccafb56aa380fc0b6c511", size = 41887, upload-time = "2026-01-26T02:44:14.245Z" }, + { url = "https://files.pythonhosted.org/packages/80/31/0b2517913687895f5904325c2069d6a3b78f66cc641a86a2baf75a05dcbb/multidict-6.7.1-cp312-cp312-win_amd64.whl", hash = "sha256:fcee94dfbd638784645b066074b338bc9cc155d4b4bffa4adce1615c5a426c19", size = 46053, upload-time = "2026-01-26T02:44:15.371Z" }, + { url = "https://files.pythonhosted.org/packages/0c/5b/aba28e4ee4006ae4c7df8d327d31025d760ffa992ea23812a601d226e682/multidict-6.7.1-cp312-cp312-win_arm64.whl", hash = "sha256:ba0a9fb644d0c1a2194cf7ffb043bd852cea63a57f66fbd33959f7dae18517bf", size = 43307, upload-time = "2026-01-26T02:44:16.852Z" }, + { url = "https://files.pythonhosted.org/packages/f2/22/929c141d6c0dba87d3e1d38fbdf1ba8baba86b7776469f2bc2d3227a1e67/multidict-6.7.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:2b41f5fed0ed563624f1c17630cb9941cf2309d4df00e494b551b5f3e3d67a23", size = 76174, upload-time = "2026-01-26T02:44:18.509Z" }, + { url = "https://files.pythonhosted.org/packages/c7/75/bc704ae15fee974f8fccd871305e254754167dce5f9e42d88a2def741a1d/multidict-6.7.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:84e61e3af5463c19b67ced91f6c634effb89ef8bfc5ca0267f954451ed4bb6a2", size = 45116, upload-time = "2026-01-26T02:44:19.745Z" }, + { url = "https://files.pythonhosted.org/packages/79/76/55cd7186f498ed080a18440c9013011eb548f77ae1b297206d030eb1180a/multidict-6.7.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:935434b9853c7c112eee7ac891bc4cb86455aa631269ae35442cb316790c1445", size = 43524, upload-time = "2026-01-26T02:44:21.571Z" }, + { url = "https://files.pythonhosted.org/packages/e9/3c/414842ef8d5a1628d68edee29ba0e5bcf235dbfb3ccd3ea303a7fe8c72ff/multidict-6.7.1-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:432feb25a1cb67fe82a9680b4d65fb542e4635cb3166cd9c01560651ad60f177", size = 249368, upload-time = "2026-01-26T02:44:22.803Z" }, + { url = "https://files.pythonhosted.org/packages/f6/32/befed7f74c458b4a525e60519fe8d87eef72bb1e99924fa2b0f9d97a221e/multidict-6.7.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e82d14e3c948952a1a85503817e038cba5905a3352de76b9a465075d072fba23", size = 256952, upload-time = "2026-01-26T02:44:24.306Z" }, + { url = "https://files.pythonhosted.org/packages/03/d6/c878a44ba877f366630c860fdf74bfb203c33778f12b6ac274936853c451/multidict-6.7.1-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:4cfb48c6ea66c83bcaaf7e4dfa7ec1b6bbcf751b7db85a328902796dfde4c060", size = 240317, upload-time = "2026-01-26T02:44:25.772Z" }, + { url = "https://files.pythonhosted.org/packages/68/49/57421b4d7ad2e9e60e25922b08ceb37e077b90444bde6ead629095327a6f/multidict-6.7.1-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1d540e51b7e8e170174555edecddbd5538105443754539193e3e1061864d444d", size = 267132, upload-time = "2026-01-26T02:44:27.648Z" }, + { url = "https://files.pythonhosted.org/packages/b7/fe/ec0edd52ddbcea2a2e89e174f0206444a61440b40f39704e64dc807a70bd/multidict-6.7.1-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:273d23f4b40f3dce4d6c8a821c741a86dec62cded82e1175ba3d99be128147ed", size = 268140, upload-time = "2026-01-26T02:44:29.588Z" }, + { url = "https://files.pythonhosted.org/packages/b0/73/6e1b01cbeb458807aa0831742232dbdd1fa92bfa33f52a3f176b4ff3dc11/multidict-6.7.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9d624335fd4fa1c08a53f8b4be7676ebde19cd092b3895c421045ca87895b429", size = 254277, upload-time = "2026-01-26T02:44:30.902Z" }, + { url = "https://files.pythonhosted.org/packages/6a/b2/5fb8c124d7561a4974c342bc8c778b471ebbeb3cc17df696f034a7e9afe7/multidict-6.7.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:12fad252f8b267cc75b66e8fc51b3079604e8d43a75428ffe193cd9e2195dfd6", size = 252291, upload-time = "2026-01-26T02:44:32.31Z" }, + { url = "https://files.pythonhosted.org/packages/5a/96/51d4e4e06bcce92577fcd488e22600bd38e4fd59c20cb49434d054903bd2/multidict-6.7.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:03ede2a6ffbe8ef936b92cb4529f27f42be7f56afcdab5ab739cd5f27fb1cbf9", size = 250156, upload-time = "2026-01-26T02:44:33.734Z" }, + { url = "https://files.pythonhosted.org/packages/db/6b/420e173eec5fba721a50e2a9f89eda89d9c98fded1124f8d5c675f7a0c0f/multidict-6.7.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:90efbcf47dbe33dcf643a1e400d67d59abeac5db07dc3f27d6bdeae497a2198c", size = 249742, upload-time = "2026-01-26T02:44:35.222Z" }, + { url = "https://files.pythonhosted.org/packages/44/a3/ec5b5bd98f306bc2aa297b8c6f11a46714a56b1e6ef5ebda50a4f5d7c5fb/multidict-6.7.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:5c4b9bfc148f5a91be9244d6264c53035c8a0dcd2f51f1c3c6e30e30ebaa1c84", size = 262221, upload-time = "2026-01-26T02:44:36.604Z" }, + { url = "https://files.pythonhosted.org/packages/cd/f7/e8c0d0da0cd1e28d10e624604e1a36bcc3353aaebdfdc3a43c72bc683a12/multidict-6.7.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:401c5a650f3add2472d1d288c26deebc540f99e2fb83e9525007a74cd2116f1d", size = 258664, upload-time = "2026-01-26T02:44:38.008Z" }, + { url = "https://files.pythonhosted.org/packages/52/da/151a44e8016dd33feed44f730bd856a66257c1ee7aed4f44b649fb7edeb3/multidict-6.7.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:97891f3b1b3ffbded884e2916cacf3c6fc87b66bb0dde46f7357404750559f33", size = 249490, upload-time = "2026-01-26T02:44:39.386Z" }, + { url = "https://files.pythonhosted.org/packages/87/af/a3b86bf9630b732897f6fc3f4c4714b90aa4361983ccbdcd6c0339b21b0c/multidict-6.7.1-cp313-cp313-win32.whl", hash = "sha256:e1c5988359516095535c4301af38d8a8838534158f649c05dd1050222321bcb3", size = 41695, upload-time = "2026-01-26T02:44:41.318Z" }, + { url = "https://files.pythonhosted.org/packages/b2/35/e994121b0e90e46134673422dd564623f93304614f5d11886b1b3e06f503/multidict-6.7.1-cp313-cp313-win_amd64.whl", hash = "sha256:960c83bf01a95b12b08fd54324a4eb1d5b52c88932b5cba5d6e712bb3ed12eb5", size = 45884, upload-time = "2026-01-26T02:44:42.488Z" }, + { url = "https://files.pythonhosted.org/packages/ca/61/42d3e5dbf661242a69c97ea363f2d7b46c567da8eadef8890022be6e2ab0/multidict-6.7.1-cp313-cp313-win_arm64.whl", hash = "sha256:563fe25c678aaba333d5399408f5ec3c383ca5b663e7f774dd179a520b8144df", size = 43122, upload-time = "2026-01-26T02:44:43.664Z" }, + { url = "https://files.pythonhosted.org/packages/6d/b3/e6b21c6c4f314bb956016b0b3ef2162590a529b84cb831c257519e7fde44/multidict-6.7.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:c76c4bec1538375dad9d452d246ca5368ad6e1c9039dadcf007ae59c70619ea1", size = 83175, upload-time = "2026-01-26T02:44:44.894Z" }, + { url = "https://files.pythonhosted.org/packages/fb/76/23ecd2abfe0957b234f6c960f4ade497f55f2c16aeb684d4ecdbf1c95791/multidict-6.7.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:57b46b24b5d5ebcc978da4ec23a819a9402b4228b8a90d9c656422b4bdd8a963", size = 48460, upload-time = "2026-01-26T02:44:46.106Z" }, + { url = "https://files.pythonhosted.org/packages/c4/57/a0ed92b23f3a042c36bc4227b72b97eca803f5f1801c1ab77c8a212d455e/multidict-6.7.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e954b24433c768ce78ab7929e84ccf3422e46deb45a4dc9f93438f8217fa2d34", size = 46930, upload-time = "2026-01-26T02:44:47.278Z" }, + { url = "https://files.pythonhosted.org/packages/b5/66/02ec7ace29162e447f6382c495dc95826bf931d3818799bbef11e8f7df1a/multidict-6.7.1-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3bd231490fa7217cc832528e1cd8752a96f0125ddd2b5749390f7c3ec8721b65", size = 242582, upload-time = "2026-01-26T02:44:48.604Z" }, + { url = "https://files.pythonhosted.org/packages/58/18/64f5a795e7677670e872673aca234162514696274597b3708b2c0d276cce/multidict-6.7.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:253282d70d67885a15c8a7716f3a73edf2d635793ceda8173b9ecc21f2fb8292", size = 250031, upload-time = "2026-01-26T02:44:50.544Z" }, + { url = "https://files.pythonhosted.org/packages/c8/ed/e192291dbbe51a8290c5686f482084d31bcd9d09af24f63358c3d42fd284/multidict-6.7.1-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:0b4c48648d7649c9335cf1927a8b87fa692de3dcb15faa676c6a6f1f1aabda43", size = 228596, upload-time = "2026-01-26T02:44:51.951Z" }, + { url = "https://files.pythonhosted.org/packages/1e/7e/3562a15a60cf747397e7f2180b0a11dc0c38d9175a650e75fa1b4d325e15/multidict-6.7.1-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:98bc624954ec4d2c7cb074b8eefc2b5d0ce7d482e410df446414355d158fe4ca", size = 257492, upload-time = "2026-01-26T02:44:53.902Z" }, + { url = "https://files.pythonhosted.org/packages/24/02/7d0f9eae92b5249bb50ac1595b295f10e263dd0078ebb55115c31e0eaccd/multidict-6.7.1-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:1b99af4d9eec0b49927b4402bcbb58dea89d3e0db8806a4086117019939ad3dd", size = 255899, upload-time = "2026-01-26T02:44:55.316Z" }, + { url = "https://files.pythonhosted.org/packages/00/e3/9b60ed9e23e64c73a5cde95269ef1330678e9c6e34dd4eb6b431b85b5a10/multidict-6.7.1-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6aac4f16b472d5b7dc6f66a0d49dd57b0e0902090be16594dc9ebfd3d17c47e7", size = 247970, upload-time = "2026-01-26T02:44:56.783Z" }, + { url = "https://files.pythonhosted.org/packages/3e/06/538e58a63ed5cfb0bd4517e346b91da32fde409d839720f664e9a4ae4f9d/multidict-6.7.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:21f830fe223215dffd51f538e78c172ed7c7f60c9b96a2bf05c4848ad49921c3", size = 245060, upload-time = "2026-01-26T02:44:58.195Z" }, + { url = "https://files.pythonhosted.org/packages/b2/2f/d743a3045a97c895d401e9bd29aaa09b94f5cbdf1bd561609e5a6c431c70/multidict-6.7.1-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:f5dd81c45b05518b9aa4da4aa74e1c93d715efa234fd3e8a179df611cc85e5f4", size = 235888, upload-time = "2026-01-26T02:44:59.57Z" }, + { url = "https://files.pythonhosted.org/packages/38/83/5a325cac191ab28b63c52f14f1131f3b0a55ba3b9aa65a6d0bf2a9b921a0/multidict-6.7.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:eb304767bca2bb92fb9c5bd33cedc95baee5bb5f6c88e63706533a1c06ad08c8", size = 243554, upload-time = "2026-01-26T02:45:01.054Z" }, + { url = "https://files.pythonhosted.org/packages/20/1f/9d2327086bd15da2725ef6aae624208e2ef828ed99892b17f60c344e57ed/multidict-6.7.1-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:c9035dde0f916702850ef66460bc4239d89d08df4d02023a5926e7446724212c", size = 252341, upload-time = "2026-01-26T02:45:02.484Z" }, + { url = "https://files.pythonhosted.org/packages/e8/2c/2a1aa0280cf579d0f6eed8ee5211c4f1730bd7e06c636ba2ee6aafda302e/multidict-6.7.1-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:af959b9beeb66c822380f222f0e0a1889331597e81f1ded7f374f3ecb0fd6c52", size = 246391, upload-time = "2026-01-26T02:45:03.862Z" }, + { url = "https://files.pythonhosted.org/packages/e5/03/7ca022ffc36c5a3f6e03b179a5ceb829be9da5783e6fe395f347c0794680/multidict-6.7.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:41f2952231456154ee479651491e94118229844dd7226541788be783be2b5108", size = 243422, upload-time = "2026-01-26T02:45:05.296Z" }, + { url = "https://files.pythonhosted.org/packages/dc/1d/b31650eab6c5778aceed46ba735bd97f7c7d2f54b319fa916c0f96e7805b/multidict-6.7.1-cp313-cp313t-win32.whl", hash = "sha256:df9f19c28adcb40b6aae30bbaa1478c389efd50c28d541d76760199fc1037c32", size = 47770, upload-time = "2026-01-26T02:45:06.754Z" }, + { url = "https://files.pythonhosted.org/packages/ac/5b/2d2d1d522e51285bd61b1e20df8f47ae1a9d80839db0b24ea783b3832832/multidict-6.7.1-cp313-cp313t-win_amd64.whl", hash = "sha256:d54ecf9f301853f2c5e802da559604b3e95bb7a3b01a9c295c6ee591b9882de8", size = 53109, upload-time = "2026-01-26T02:45:08.044Z" }, + { url = "https://files.pythonhosted.org/packages/3d/a3/cc409ba012c83ca024a308516703cf339bdc4b696195644a7215a5164a24/multidict-6.7.1-cp313-cp313t-win_arm64.whl", hash = "sha256:5a37ca18e360377cfda1d62f5f382ff41f2b8c4ccb329ed974cc2e1643440118", size = 45573, upload-time = "2026-01-26T02:45:09.349Z" }, + { url = "https://files.pythonhosted.org/packages/91/cc/db74228a8be41884a567e88a62fd589a913708fcf180d029898c17a9a371/multidict-6.7.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:8f333ec9c5eb1b7105e3b84b53141e66ca05a19a605368c55450b6ba208cb9ee", size = 75190, upload-time = "2026-01-26T02:45:10.651Z" }, + { url = "https://files.pythonhosted.org/packages/d5/22/492f2246bb5b534abd44804292e81eeaf835388901f0c574bac4eeec73c5/multidict-6.7.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:a407f13c188f804c759fc6a9f88286a565c242a76b27626594c133b82883b5c2", size = 44486, upload-time = "2026-01-26T02:45:11.938Z" }, + { url = "https://files.pythonhosted.org/packages/f1/4f/733c48f270565d78b4544f2baddc2fb2a245e5a8640254b12c36ac7ac68e/multidict-6.7.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:0e161ddf326db5577c3a4cc2d8648f81456e8a20d40415541587a71620d7a7d1", size = 43219, upload-time = "2026-01-26T02:45:14.346Z" }, + { url = "https://files.pythonhosted.org/packages/24/bb/2c0c2287963f4259c85e8bcbba9182ced8d7fca65c780c38e99e61629d11/multidict-6.7.1-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:1e3a8bb24342a8201d178c3b4984c26ba81a577c80d4d525727427460a50c22d", size = 245132, upload-time = "2026-01-26T02:45:15.712Z" }, + { url = "https://files.pythonhosted.org/packages/a7/f9/44d4b3064c65079d2467888794dea218d1601898ac50222ab8a9a8094460/multidict-6.7.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:97231140a50f5d447d3164f994b86a0bed7cd016e2682f8650d6a9158e14fd31", size = 252420, upload-time = "2026-01-26T02:45:17.293Z" }, + { url = "https://files.pythonhosted.org/packages/8b/13/78f7275e73fa17b24c9a51b0bd9d73ba64bb32d0ed51b02a746eb876abe7/multidict-6.7.1-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:6b10359683bd8806a200fd2909e7c8ca3a7b24ec1d8132e483d58e791d881048", size = 233510, upload-time = "2026-01-26T02:45:19.356Z" }, + { url = "https://files.pythonhosted.org/packages/4b/25/8167187f62ae3cbd52da7893f58cb036b47ea3fb67138787c76800158982/multidict-6.7.1-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:283ddac99f7ac25a4acadbf004cb5ae34480bbeb063520f70ce397b281859362", size = 264094, upload-time = "2026-01-26T02:45:20.834Z" }, + { url = "https://files.pythonhosted.org/packages/a1/e7/69a3a83b7b030cf283fb06ce074a05a02322359783424d7edf0f15fe5022/multidict-6.7.1-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:538cec1e18c067d0e6103aa9a74f9e832904c957adc260e61cd9d8cf0c3b3d37", size = 260786, upload-time = "2026-01-26T02:45:22.818Z" }, + { url = "https://files.pythonhosted.org/packages/fe/3b/8ec5074bcfc450fe84273713b4b0a0dd47c0249358f5d82eb8104ffe2520/multidict-6.7.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7eee46ccb30ff48a1e35bb818cc90846c6be2b68240e42a78599166722cea709", size = 248483, upload-time = "2026-01-26T02:45:24.368Z" }, + { url = "https://files.pythonhosted.org/packages/48/5a/d5a99e3acbca0e29c5d9cba8f92ceb15dce78bab963b308ae692981e3a5d/multidict-6.7.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:fa263a02f4f2dd2d11a7b1bb4362aa7cb1049f84a9235d31adf63f30143469a0", size = 248403, upload-time = "2026-01-26T02:45:25.982Z" }, + { url = "https://files.pythonhosted.org/packages/35/48/e58cd31f6c7d5102f2a4bf89f96b9cf7e00b6c6f3d04ecc44417c00a5a3c/multidict-6.7.1-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:2e1425e2f99ec5bd36c15a01b690a1a2456209c5deed58f95469ffb46039ccbb", size = 240315, upload-time = "2026-01-26T02:45:27.487Z" }, + { url = "https://files.pythonhosted.org/packages/94/33/1cd210229559cb90b6786c30676bb0c58249ff42f942765f88793b41fdce/multidict-6.7.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:497394b3239fc6f0e13a78a3e1b61296e72bf1c5f94b4c4eb80b265c37a131cd", size = 245528, upload-time = "2026-01-26T02:45:28.991Z" }, + { url = "https://files.pythonhosted.org/packages/64/f2/6e1107d226278c876c783056b7db43d800bb64c6131cec9c8dfb6903698e/multidict-6.7.1-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:233b398c29d3f1b9676b4b6f75c518a06fcb2ea0b925119fb2c1bc35c05e1601", size = 258784, upload-time = "2026-01-26T02:45:30.503Z" }, + { url = "https://files.pythonhosted.org/packages/4d/c1/11f664f14d525e4a1b5327a82d4de61a1db604ab34c6603bb3c2cc63ad34/multidict-6.7.1-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:93b1818e4a6e0930454f0f2af7dfce69307ca03cdcfb3739bf4d91241967b6c1", size = 251980, upload-time = "2026-01-26T02:45:32.603Z" }, + { url = "https://files.pythonhosted.org/packages/e1/9f/75a9ac888121d0c5bbd4ecf4eead45668b1766f6baabfb3b7f66a410e231/multidict-6.7.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:f33dc2a3abe9249ea5d8360f969ec7f4142e7ac45ee7014d8f8d5acddf178b7b", size = 243602, upload-time = "2026-01-26T02:45:34.043Z" }, + { url = "https://files.pythonhosted.org/packages/9a/e7/50bf7b004cc8525d80dbbbedfdc7aed3e4c323810890be4413e589074032/multidict-6.7.1-cp314-cp314-win32.whl", hash = "sha256:3ab8b9d8b75aef9df299595d5388b14530839f6422333357af1339443cff777d", size = 40930, upload-time = "2026-01-26T02:45:36.278Z" }, + { url = "https://files.pythonhosted.org/packages/e0/bf/52f25716bbe93745595800f36fb17b73711f14da59ed0bb2eba141bc9f0f/multidict-6.7.1-cp314-cp314-win_amd64.whl", hash = "sha256:5e01429a929600e7dab7b166062d9bb54a5eed752384c7384c968c2afab8f50f", size = 45074, upload-time = "2026-01-26T02:45:37.546Z" }, + { url = "https://files.pythonhosted.org/packages/97/ab/22803b03285fa3a525f48217963da3a65ae40f6a1b6f6cf2768879e208f9/multidict-6.7.1-cp314-cp314-win_arm64.whl", hash = "sha256:4885cb0e817aef5d00a2e8451d4665c1808378dc27c2705f1bf4ef8505c0d2e5", size = 42471, upload-time = "2026-01-26T02:45:38.889Z" }, + { url = "https://files.pythonhosted.org/packages/e0/6d/f9293baa6146ba9507e360ea0292b6422b016907c393e2f63fc40ab7b7b5/multidict-6.7.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:0458c978acd8e6ea53c81eefaddbbee9c6c5e591f41b3f5e8e194780fe026581", size = 82401, upload-time = "2026-01-26T02:45:40.254Z" }, + { url = "https://files.pythonhosted.org/packages/7a/68/53b5494738d83558d87c3c71a486504d8373421c3e0dbb6d0db48ad42ee0/multidict-6.7.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:c0abd12629b0af3cf590982c0b413b1e7395cd4ec026f30986818ab95bfaa94a", size = 48143, upload-time = "2026-01-26T02:45:41.635Z" }, + { url = "https://files.pythonhosted.org/packages/37/e8/5284c53310dcdc99ce5d66563f6e5773531a9b9fe9ec7a615e9bc306b05f/multidict-6.7.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:14525a5f61d7d0c94b368a42cff4c9a4e7ba2d52e2672a7b23d84dc86fb02b0c", size = 46507, upload-time = "2026-01-26T02:45:42.99Z" }, + { url = "https://files.pythonhosted.org/packages/e4/fc/6800d0e5b3875568b4083ecf5f310dcf91d86d52573160834fb4bfcf5e4f/multidict-6.7.1-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:17307b22c217b4cf05033dabefe68255a534d637c6c9b0cc8382718f87be4262", size = 239358, upload-time = "2026-01-26T02:45:44.376Z" }, + { url = "https://files.pythonhosted.org/packages/41/75/4ad0973179361cdf3a113905e6e088173198349131be2b390f9fa4da5fc6/multidict-6.7.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7a7e590ff876a3eaf1c02a4dfe0724b6e69a9e9de6d8f556816f29c496046e59", size = 246884, upload-time = "2026-01-26T02:45:47.167Z" }, + { url = "https://files.pythonhosted.org/packages/c3/9c/095bb28b5da139bd41fb9a5d5caff412584f377914bd8787c2aa98717130/multidict-6.7.1-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:5fa6a95dfee63893d80a34758cd0e0c118a30b8dcb46372bf75106c591b77889", size = 225878, upload-time = "2026-01-26T02:45:48.698Z" }, + { url = "https://files.pythonhosted.org/packages/07/d0/c0a72000243756e8f5a277b6b514fa005f2c73d481b7d9e47cd4568aa2e4/multidict-6.7.1-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a0543217a6a017692aa6ae5cc39adb75e587af0f3a82288b1492eb73dd6cc2a4", size = 253542, upload-time = "2026-01-26T02:45:50.164Z" }, + { url = "https://files.pythonhosted.org/packages/c0/6b/f69da15289e384ecf2a68837ec8b5ad8c33e973aa18b266f50fe55f24b8c/multidict-6.7.1-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f99fe611c312b3c1c0ace793f92464d8cd263cc3b26b5721950d977b006b6c4d", size = 252403, upload-time = "2026-01-26T02:45:51.779Z" }, + { url = "https://files.pythonhosted.org/packages/a2/76/b9669547afa5a1a25cd93eaca91c0da1c095b06b6d2d8ec25b713588d3a1/multidict-6.7.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9004d8386d133b7e6135679424c91b0b854d2d164af6ea3f289f8f2761064609", size = 244889, upload-time = "2026-01-26T02:45:53.27Z" }, + { url = "https://files.pythonhosted.org/packages/7e/a9/a50d2669e506dad33cfc45b5d574a205587b7b8a5f426f2fbb2e90882588/multidict-6.7.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e628ef0e6859ffd8273c69412a2465c4be4a9517d07261b33334b5ec6f3c7489", size = 241982, upload-time = "2026-01-26T02:45:54.919Z" }, + { url = "https://files.pythonhosted.org/packages/c5/bb/1609558ad8b456b4827d3c5a5b775c93b87878fd3117ed3db3423dfbce1b/multidict-6.7.1-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:841189848ba629c3552035a6a7f5bf3b02eb304e9fea7492ca220a8eda6b0e5c", size = 232415, upload-time = "2026-01-26T02:45:56.981Z" }, + { url = "https://files.pythonhosted.org/packages/d8/59/6f61039d2aa9261871e03ab9dc058a550d240f25859b05b67fd70f80d4b3/multidict-6.7.1-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:ce1bbd7d780bb5a0da032e095c951f7014d6b0a205f8318308140f1a6aba159e", size = 240337, upload-time = "2026-01-26T02:45:58.698Z" }, + { url = "https://files.pythonhosted.org/packages/a1/29/fdc6a43c203890dc2ae9249971ecd0c41deaedfe00d25cb6564b2edd99eb/multidict-6.7.1-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:b26684587228afed0d50cf804cc71062cc9c1cdf55051c4c6345d372947b268c", size = 248788, upload-time = "2026-01-26T02:46:00.862Z" }, + { url = "https://files.pythonhosted.org/packages/a9/14/a153a06101323e4cf086ecee3faadba52ff71633d471f9685c42e3736163/multidict-6.7.1-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:9f9af11306994335398293f9958071019e3ab95e9a707dc1383a35613f6abcb9", size = 242842, upload-time = "2026-01-26T02:46:02.824Z" }, + { url = "https://files.pythonhosted.org/packages/41/5f/604ae839e64a4a6efc80db94465348d3b328ee955e37acb24badbcd24d83/multidict-6.7.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:b4938326284c4f1224178a560987b6cf8b4d38458b113d9b8c1db1a836e640a2", size = 240237, upload-time = "2026-01-26T02:46:05.898Z" }, + { url = "https://files.pythonhosted.org/packages/5f/60/c3a5187bf66f6fb546ff4ab8fb5a077cbdd832d7b1908d4365c7f74a1917/multidict-6.7.1-cp314-cp314t-win32.whl", hash = "sha256:98655c737850c064a65e006a3df7c997cd3b220be4ec8fe26215760b9697d4d7", size = 48008, upload-time = "2026-01-26T02:46:07.468Z" }, + { url = "https://files.pythonhosted.org/packages/0c/f7/addf1087b860ac60e6f382240f64fb99f8bfb532bb06f7c542b83c29ca61/multidict-6.7.1-cp314-cp314t-win_amd64.whl", hash = "sha256:497bde6223c212ba11d462853cfa4f0ae6ef97465033e7dc9940cdb3ab5b48e5", size = 53542, upload-time = "2026-01-26T02:46:08.809Z" }, + { url = "https://files.pythonhosted.org/packages/4c/81/4629d0aa32302ef7b2ec65c75a728cc5ff4fa410c50096174c1632e70b3e/multidict-6.7.1-cp314-cp314t-win_arm64.whl", hash = "sha256:2bbd113e0d4af5db41d5ebfe9ccaff89de2120578164f86a5d17d5a576d1e5b2", size = 44719, upload-time = "2026-01-26T02:46:11.146Z" }, + { url = "https://files.pythonhosted.org/packages/81/08/7036c080d7117f28a4af526d794aab6a84463126db031b007717c1a6676e/multidict-6.7.1-py3-none-any.whl", hash = "sha256:55d97cc6dae627efa6a6e548885712d4864b81110ac76fa4e534c03819fa4a56", size = 12319, upload-time = "2026-01-26T02:46:44.004Z" }, +] + [[package]] name = "openai" version = "2.17.0" @@ -768,6 +1139,7 @@ dependencies = [ [package.optional-dependencies] all = [ + { name = "aiohttp" }, { name = "anthropic" }, { name = "azure-identity" }, { name = "chevron" }, @@ -779,6 +1151,7 @@ anthropic = [ { name = "anthropic" }, ] azure = [ + { name = "aiohttp" }, { name = "azure-identity" }, { name = "openai" }, ] @@ -794,6 +1167,7 @@ dev = [ { name = "types-pyyaml" }, ] foundry = [ + { name = "aiohttp" }, { name = "azure-identity" }, { name = "openai" }, ] @@ -813,6 +1187,9 @@ otel = [ [package.metadata] requires-dist = [ { name = "aiofiles", specifier = ">=24.1.0" }, + { name = "aiohttp", marker = "extra == 'all'" }, + { name = "aiohttp", marker = "extra == 'azure'" }, + { name = "aiohttp", marker = "extra == 'foundry'" }, { name = "anthropic", marker = "extra == 'all'" }, { name = "anthropic", marker = "extra == 'anthropic'" }, { name = "azure-identity", marker = "extra == 'all'" }, @@ -841,6 +1218,117 @@ requires-dist = [ ] provides-extras = ["all", "anthropic", "azure", "dev", "foundry", "jinja2", "mustache", "openai", "otel"] +[[package]] +name = "propcache" +version = "0.5.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ec/44/c87281c333769159c50594f22610f77398a47ccbfbbf23074e744e86f87c/propcache-0.5.2.tar.gz", hash = "sha256:01c4fc7480cd0598bb4b57022df55b9ca296da7fc5a8760bd8451a7e63a7d427", size = 50208, upload-time = "2026-05-08T21:02:12.199Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e7/f1/8a8cc1c2c7e7934ab77e0163414f736fadbc0f5e8dd9673b952355ac175b/propcache-0.5.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:74b70780220e2dd89175ca24b81b68b67c83db499ae611e7f2313cb329801c78", size = 90744, upload-time = "2026-05-08T20:59:45.799Z" }, + { url = "https://files.pythonhosted.org/packages/c2/f4/651b1225e976bd1a2ba5cfba0c29d096581c2636b437e3a9a7ab6276270a/propcache-0.5.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a4840ab0ae0216d952f4b53dc6d0b992bfc2bedbfe360bdd9b548bc184c08959", size = 52033, upload-time = "2026-05-08T20:59:47.408Z" }, + { url = "https://files.pythonhosted.org/packages/15/a8/8ede85d6aa1f79fc7dc2f8fd2c8d65920b8272c3892903c8a1affde48cfb/propcache-0.5.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c6844ba6364fb12f403928a82cfd295ab103a2b315c77c747b2dbe4a41894ea7", size = 52754, upload-time = "2026-05-08T20:59:49.202Z" }, + { url = "https://files.pythonhosted.org/packages/7d/fe/b3551b41bbc2f5b5bb088fc6920567cd43101253e68fbaa261339eb96fe1/propcache-0.5.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2293949b855ce597f2826452d17c2d545fb5622379c4ea6fdf525e9b8e8a2511", size = 57573, upload-time = "2026-05-08T20:59:50.778Z" }, + { url = "https://files.pythonhosted.org/packages/83/27/ab851ebd1b7172e3e161f5f8d39e315d54a91bea246f01f4d872d3376aef/propcache-0.5.2-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:0fd59b5af35f74da48d905dcbad55449ba13be91823cb05a9bd590bbf5b61660", size = 60645, upload-time = "2026-05-08T20:59:52.227Z" }, + { url = "https://files.pythonhosted.org/packages/95/7d/466b3d18022e9897cbda9c735c493c5bd747d7a4c6f5ea1480b4cec434b6/propcache-0.5.2-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:29f9309a2e42b0d273be006fdb4be2d6c39a47f6f57d8fb1cf9f81481df81b66", size = 61563, upload-time = "2026-05-08T20:59:53.866Z" }, + { url = "https://files.pythonhosted.org/packages/27/1b/16ab7f2cf2041da2f60d156ba64c2484eadf9168075b4ff43c3ef60045af/propcache-0.5.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5aaa2b923c1944ac8febd6609cb373540a5563e7cbcb0fd770f75dace2eb817b", size = 58888, upload-time = "2026-05-08T20:59:55.457Z" }, + { url = "https://files.pythonhosted.org/packages/0a/67/bb777ffd907633563bf35fd859c4ce97b0512c32f4633cf5d1eb7c33512b/propcache-0.5.2-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:66ea454f095ddf5b6b14f56c064c0941c4788be11e18d2464cf643bf7203ff67", size = 59253, upload-time = "2026-05-08T20:59:57.075Z" }, + { url = "https://files.pythonhosted.org/packages/b9/42/64f8d90b73fd9cdc1499b48057ff6d9cd2a98a25734c9bb62ecf07e87061/propcache-0.5.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:95f1e3f4760d404b13c9976c0229b2b49a3c8e2c62a9ce92efdd2b11ada75e3f", size = 57558, upload-time = "2026-05-08T20:59:58.602Z" }, + { url = "https://files.pythonhosted.org/packages/eb/02/dba5bc03c9041f2092ea55a449caf5dfe68352c6654511b29ba0654ddb69/propcache-0.5.2-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:85341b12b9d55bad0bded24cac341bb34289469e03a11f3f583ea1cc1db0326c", size = 55007, upload-time = "2026-05-08T20:59:59.837Z" }, + { url = "https://files.pythonhosted.org/packages/14/c0/43f649c7aa2a77a3b100d84e9dea3a483120ecb608bfe36ce49eaff517fe/propcache-0.5.2-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:26a4dca084132874e639895c3135dfad5eb20bae209f62d1aeb31b03e601c3c0", size = 60355, upload-time = "2026-05-08T21:00:01.144Z" }, + { url = "https://files.pythonhosted.org/packages/83/c0/435dafd27f1cb4a495381dae60e25883ccfe4020bb72818e8184c1678092/propcache-0.5.2-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:3b199b9b2b3d6a7edf3183ba8a9a137a22b97f7df525feb5ae1eccf026d2a9c6", size = 59057, upload-time = "2026-05-08T21:00:02.401Z" }, + { url = "https://files.pythonhosted.org/packages/53/ae/6e292df9135d659944e96cb3389258e4a663e5b2b5f6c217ef0ddc8d2f73/propcache-0.5.2-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:e59bc9e66329185b93dab73f210f1a37f81cb40f321501db8017c9aea15dba27", size = 61938, upload-time = "2026-05-08T21:00:03.638Z" }, + { url = "https://files.pythonhosted.org/packages/0b/42/314ebc50d8159055411fd6b0bda322ff510e4b1f7d2e4927940ad0f6af20/propcache-0.5.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:552ffadf6ad409844bc5919c42a0a83d88314cedddaea0e41e80a8b8fffe881f", size = 59731, upload-time = "2026-05-08T21:00:04.881Z" }, + { url = "https://files.pythonhosted.org/packages/b8/9b/2da6dee38871c3c8772fabc2758325a5c9077d6d18c597737dc04dd884cd/propcache-0.5.2-cp311-cp311-win32.whl", hash = "sha256:cd416c1de191973c52ff1a12a57446bfc7642797b282d7caf2162d7d1b8aa9a0", size = 38966, upload-time = "2026-05-08T21:00:06.511Z" }, + { url = "https://files.pythonhosted.org/packages/42/4e/f17363fb58c0afe05b067361cb6d86ed2d29de6506779a27547c4d183075/propcache-0.5.2-cp311-cp311-win_amd64.whl", hash = "sha256:44e488ef40dbb452700b2b1f8188934121f6648f52c295055662d2191959ff82", size = 42135, upload-time = "2026-05-08T21:00:08.088Z" }, + { url = "https://files.pythonhosted.org/packages/c6/eb/6af6685077d22e8b33358d3c548e3282706a0b3cd85044ffba4e5dd08e3b/propcache-0.5.2-cp311-cp311-win_arm64.whl", hash = "sha256:54adaa85a22078d1e306304a40984dc5be99d599bf3dc0a24dc98f7daeab89ab", size = 38381, upload-time = "2026-05-08T21:00:09.692Z" }, + { url = "https://files.pythonhosted.org/packages/4a/cb/e27bc2b2737a0bb49962b275efa051e8f1c35a936df7d5139b6b658b7dc9/propcache-0.5.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:806719138ecd720339a12410fb9614ac9b2b2d3a5fdf8235d56981c36f4039ba", size = 95887, upload-time = "2026-05-08T21:00:11.277Z" }, + { url = "https://files.pythonhosted.org/packages/e6/13/b8ae04c59392f8d11c6cd9fb4011d1dc7c86b81225c770280300e259ffe1/propcache-0.5.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:db2b80ea58eab4f86b2beec3cc8b39e8ff9276ac20e96b7cce43c8ae84cd6b5a", size = 54654, upload-time = "2026-05-08T21:00:12.604Z" }, + { url = "https://files.pythonhosted.org/packages/2c/7d/49777a3e20b55863d4794384a38acd460c04157b0a00f8602b0d508b8431/propcache-0.5.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:e5cbfac9f61484f7e9f3597775500cd3ebe8274e9b050c38f9525c77c97520bf", size = 55190, upload-time = "2026-05-08T21:00:13.935Z" }, + { url = "https://files.pythonhosted.org/packages/44/c7/085d0cd63062e84044e3f05797749c3f8e3938ff3aeb0eb2f69d43fafc91/propcache-0.5.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5dbc581d2814337da56222fab8dc5f161cd798a434e49bac27930aaef798e144", size = 59995, upload-time = "2026-05-08T21:00:15.526Z" }, + { url = "https://files.pythonhosted.org/packages/9c/42/32cf8e3009e92b2645cf1e944f701e8ea4e924dffde1ee26db860bcbf7e4/propcache-0.5.2-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:857187f381f88c8e2fa2fe56ab94879d011b883d5a2ee5a1b60a8cd2a06846d9", size = 63422, upload-time = "2026-05-08T21:00:16.824Z" }, + { url = "https://files.pythonhosted.org/packages/9e/1b/f112433f99fc979431b87a39ef169e3f8df070d99a72792c56d6937ac48b/propcache-0.5.2-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:178b4a2cdaac1818e2bf1c5a99b94383fa73ea5382e032a48dec07dc5668dc42", size = 64342, upload-time = "2026-05-08T21:00:18.362Z" }, + { url = "https://files.pythonhosted.org/packages/14/15/5574111ae50dd6e879456888c0eadd4c5a869959775854e18e18a6b345f3/propcache-0.5.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6f328175a2cde1f0ff2c4ed8ce968b9dcfb55f3a7153f39e2957ed994da13476", size = 61639, upload-time = "2026-05-08T21:00:19.692Z" }, + { url = "https://files.pythonhosted.org/packages/cc/da/4d775080b1490c0ae604acda868bd71aabe3a89ed16f2aa4339eb8a283e7/propcache-0.5.2-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:5671d09a36b06d0fd4a3da0fccbcae360e9b1570924171a15e9e0997f0249fba", size = 61588, upload-time = "2026-05-08T21:00:21.155Z" }, + { url = "https://files.pythonhosted.org/packages/04/ac/f076982cbe2195ee9cf32de5a1e46951d9fb399fc207f390562dd0fd8fb2/propcache-0.5.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:80168e2ebe4d3ec6599d10ad8f520304ae1cad9b6c5a95372aef1b66b7bfb53a", size = 60029, upload-time = "2026-05-08T21:00:22.713Z" }, + { url = "https://files.pythonhosted.org/packages/70/60/189be62e0dd898dce3b331e1b8c7a543cd3a405ac0c81fe8ee8a9d5d77e1/propcache-0.5.2-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:45f11346f884bc47444f6e6647131055844134c3175b629f84952e2b5cd62b64", size = 56774, upload-time = "2026-05-08T21:00:24.001Z" }, + { url = "https://files.pythonhosted.org/packages/ea/9e/93377b9c7939c1ffae98f878dee955efadfd638078bc86dbc21f9d52f651/propcache-0.5.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:8e778ebd44ef4f66ed60a0416b06b489687db264a9c0b3620362f26489492913", size = 63532, upload-time = "2026-05-08T21:00:25.545Z" }, + { url = "https://files.pythonhosted.org/packages/14/f9/590ef6cfb9b8028d516d287812ece32bb0bc5f11fbb9c8bf6b2e6313fec8/propcache-0.5.2-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:c0cb9ed24c8964e172768d455a38254c2dd8a552905729ce006cad3d3dda59b1", size = 61592, upload-time = "2026-05-08T21:00:27.186Z" }, + { url = "https://files.pythonhosted.org/packages/b4/5e/70958b3034c297a630bba2f17ca7abc2d5f39a803ad7e370ab79d1ecd022/propcache-0.5.2-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:1d1ad32d9d4355e2be65574fd0bfd3677e7066b009cd5b9b2dee8aa6a6393b33", size = 64788, upload-time = "2026-05-08T21:00:28.8Z" }, + { url = "https://files.pythonhosted.org/packages/12/fd/77fe5936d8c3086ca9048f7f415f122ed82e53884a9ec193646b42deef06/propcache-0.5.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c80f4ba3e8f00189165999a742ee526ebeccedf6c3f7beb0c7df821e9772435a", size = 62514, upload-time = "2026-05-08T21:00:30.098Z" }, + { url = "https://files.pythonhosted.org/packages/cf/74/66bd798b5b3be70aa1b391f5cc9d6a0a5532d7fd3b19ec0b213e72e6ad9d/propcache-0.5.2-cp312-cp312-win32.whl", hash = "sha256:8c7972d8f193740d9175f0998ab38717e6cd322d5935c5b0fef8c0d323fd9031", size = 39018, upload-time = "2026-05-08T21:00:31.622Z" }, + { url = "https://files.pythonhosted.org/packages/61/7c/5c0d34aa3024694d6dcb9271cdbdd08c4e47c1c0ad95ec7e7bc74cdea145/propcache-0.5.2-cp312-cp312-win_amd64.whl", hash = "sha256:d9ee8826a7d47863a08ac44e1a5f611a462eefc3a194b492da242128bec75b42", size = 42322, upload-time = "2026-05-08T21:00:32.918Z" }, + { url = "https://files.pythonhosted.org/packages/4d/91/875812f1a3feb20ceba818ef39fbe4d92f1081e04ac815c822496d0d038b/propcache-0.5.2-cp312-cp312-win_arm64.whl", hash = "sha256:2800a4a8ead6b28cccd1ec54b59346f0def7922ee1c7598e8499c733cfbb7c84", size = 38172, upload-time = "2026-05-08T21:00:35.124Z" }, + { url = "https://files.pythonhosted.org/packages/c5/09/f049e45385503fe67db75a6b6186a7b9f0c3930366dc960522c312a825b1/propcache-0.5.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:099aaf4b4d1a02265b92a977edf00b5c4f63b3b17ac6de39b0d637c9cac0188a", size = 94457, upload-time = "2026-05-08T21:00:36.355Z" }, + { url = "https://files.pythonhosted.org/packages/6b/65/83d1d05655baf63113731bd5a1008435e14f8d1e5a06cbe4ec5b23ad7a31/propcache-0.5.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:68ce1c44c7a813a7f71ea04315a8c7b330b63db99d059a797a4651bb6f69f117", size = 53835, upload-time = "2026-05-08T21:00:38.072Z" }, + { url = "https://files.pythonhosted.org/packages/a9/12/a6ba6482bb5ea3260c000c9b20881c95fa11c6b30173715668259f844ed7/propcache-0.5.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:fc299c129490f55f254cd90be0deca4764e36e9a7c08b4aa588479a3bbed3098", size = 54545, upload-time = "2026-05-08T21:00:39.319Z" }, + { url = "https://files.pythonhosted.org/packages/a9/19/7fa086f5764c59ec8a8e157cd93aa8497acc00aba9dcdec56bfffb32602d/propcache-0.5.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a6ae2198be502c10f09b2516e7b5d019816924bc3183a43ce792a7bd6625e6f4", size = 59886, upload-time = "2026-05-08T21:00:40.621Z" }, + { url = "https://files.pythonhosted.org/packages/a1/e4/5d7663dc8235956c8f5281698a3af1d351d8820341ddd890f59d9a9127f2/propcache-0.5.2-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:6041d31504dc1779d700e1edcfb08eea334b357620b06681a4eabb57a74e574e", size = 63261, upload-time = "2026-05-08T21:00:41.775Z" }, + { url = "https://files.pythonhosted.org/packages/4a/4a/15a03adee24d6350da4292caeac44c34c033d2afe5e87eb370f38854560f/propcache-0.5.2-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f7eabc04151c78a9f4d5bbb5f1faf571e4defeb4b585e0fe95b60ff2dbe4d3d7", size = 64184, upload-time = "2026-05-08T21:00:43.018Z" }, + { url = "https://files.pythonhosted.org/packages/8b/c6/979176efdaa3d239e36d503d5af63a0a773b36662ed8f52e5b6a6d9fd40e/propcache-0.5.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4db0ba63d693afd40d249bd93f842b5f144f8fcbb83de05660373bcf30517b1d", size = 61534, upload-time = "2026-05-08T21:00:44.507Z" }, + { url = "https://files.pythonhosted.org/packages/c8/22/63e8cd1bae4c2d2be6493b6b7d10566ddafad88137cfbc99964a1119853c/propcache-0.5.2-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:1dbcf7675229b35d31abb6547d8ebc8c27a830ac3f9a794edff6254873ec7c0a", size = 61500, upload-time = "2026-05-08T21:00:45.796Z" }, + { url = "https://files.pythonhosted.org/packages/60/5a/28e5d9acbac1cc9ccb67045e8c1b943aa8d79fdf39c93bd73cacd68008ea/propcache-0.5.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d310c013aad2c72f1c3f2f8dd3279d460a858c551f97aeb8c63e4693cca7b4d2", size = 59994, upload-time = "2026-05-08T21:00:47.093Z" }, + { url = "https://files.pythonhosted.org/packages/f3/40/db650677f554a95b9c01a7c9d93d629e93a15562f5deb4573c9ee136fed2/propcache-0.5.2-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:06187263ddad280d05b4d8a8b3bb7d164cbebd469236544a42e6d9b28ac6a4fa", size = 56884, upload-time = "2026-05-08T21:00:48.376Z" }, + { url = "https://files.pythonhosted.org/packages/80/45/70b39b89516ff8b96bf732fa6fded8cef20f293cb1508690101c3c07ec51/propcache-0.5.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:3115559b8effafd63b142ea5ed53d63a16ea6469cbc63dce4ee194b42db5d853", size = 63464, upload-time = "2026-05-08T21:00:49.954Z" }, + { url = "https://files.pythonhosted.org/packages/f9/e2/fa59d3a89eac5534293124af4f1d0d0ada091ce4a0ab4610ce03fd2bdd8d/propcache-0.5.2-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:c60462af8e6dc30c35407c7237ea908d777b22862bbee27bc4699c0d8bcdc45a", size = 61588, upload-time = "2026-05-08T21:00:51.281Z" }, + { url = "https://files.pythonhosted.org/packages/0b/97/efb547a55c4bc7381cfb202d6a2239ac621045277bc1ea5dfd3a7f0516c0/propcache-0.5.2-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:40314bca9ac559716fe374094fc81c11dcc34b64fd6c585360f5775690505704", size = 64667, upload-time = "2026-05-08T21:00:52.602Z" }, + { url = "https://files.pythonhosted.org/packages/92/56/f5c7d9b4b7595d5127da38974d791b2153f3d1eae6c674af3583ace92ad3/propcache-0.5.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:cfa21e036ce1e1db2be04ba3b85d2df1bb1702fa01932d984c5464c665228ff4", size = 62463, upload-time = "2026-05-08T21:00:54.303Z" }, + { url = "https://files.pythonhosted.org/packages/bd/3b/484a3a65fc9f9f60c41dcd17b428bace5389544e2c680994534a20755066/propcache-0.5.2-cp313-cp313-win32.whl", hash = "sha256:f156a3529f38063b6dbaf356e15602a7f95f8055b1295a438433a6386f10463d", size = 38621, upload-time = "2026-05-08T21:00:55.808Z" }, + { url = "https://files.pythonhosted.org/packages/1c/fd/3f0f10dba4dabad3bf53102be007abf55481067952bde0fdddff439e7c61/propcache-0.5.2-cp313-cp313-win_amd64.whl", hash = "sha256:dfed59d0a5aeb01e242e66ff0300bc4a265a7c05f612d30016f0b60b1017d757", size = 41649, upload-time = "2026-05-08T21:00:57.061Z" }, + { url = "https://files.pythonhosted.org/packages/90/ec/6ce619cc32bb500a482f811f9cd509368b4e58e638d13f2c68f370d6b475/propcache-0.5.2-cp313-cp313-win_arm64.whl", hash = "sha256:ba338430e87ceb9c8f0cf754de38a9860560261e56c00376debd628698a7364f", size = 37636, upload-time = "2026-05-08T21:00:58.646Z" }, + { url = "https://files.pythonhosted.org/packages/1b/82/c1d268bbbf2ef981c5bf0fbbe746db617c66e3bcefe431a1aa8943fbe23a/propcache-0.5.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:a592f5f3da71c8691c788c13cb6734b6d17663d2e1cb8caddf0673d01ef8847d", size = 98872, upload-time = "2026-05-08T21:00:59.889Z" }, + { url = "https://files.pythonhosted.org/packages/f4/d4/52c871e73e864e6b34c0e2d58ac1ec5ccd149497ddc7ad2137ae98323a35/propcache-0.5.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:6a997d0489e9668a384fcfd5061b857aa5361de73191cac204d04b889cfbbafa", size = 56257, upload-time = "2026-05-08T21:01:01.195Z" }, + { url = "https://files.pythonhosted.org/packages/67/f0/9b90ca2a210b3d09bcfcd96ecd0f55545c091535abce2a45de2775cfd357/propcache-0.5.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:10734b5484ea113152ee25a91dccedf81631791805d2c9ccb054958e51842c94", size = 56696, upload-time = "2026-05-08T21:01:02.941Z" }, + { url = "https://files.pythonhosted.org/packages/9d/0e/6e9d4ba07c8e56e21ddec1e75f12148142b21ca83a51871babce095334f4/propcache-0.5.2-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cafca7e56c12bb02ae16d283742bef25a61122e9dab2b5b3f2ccbe589ce32164", size = 62378, upload-time = "2026-05-08T21:01:04.475Z" }, + { url = "https://files.pythonhosted.org/packages/65/19/c10badaa463dde8a27ce884f8ee2ec37e6035b7c9f5ff0c8f74f06f08dac/propcache-0.5.2-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f064f8d2b59177878b7615df1735cd8fe3462ed6be8c7b217d17a276489c2b7f", size = 65283, upload-time = "2026-05-08T21:01:05.959Z" }, + { url = "https://files.pythonhosted.org/packages/b0/b6/93bea99ca80e19cef6512a8580e5b7857bbe09422d9daa7fd4ef5723306c/propcache-0.5.2-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f78abfa8dfc32376fd1aacf597b2f2fbbe0ea751419aee718af5d4f82537ef8c", size = 66616, upload-time = "2026-05-08T21:01:07.228Z" }, + { url = "https://files.pythonhosted.org/packages/83/e4/5c7462e50625f051f37fb38b8224f7639f667184bbd34424ec83819bb1b7/propcache-0.5.2-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f7467da8a9822bf1a55336f877340c5bcbd3c482afc43a99771169f74a26dedc", size = 63773, upload-time = "2026-05-08T21:01:08.514Z" }, + { url = "https://files.pythonhosted.org/packages/ca/b6/99238894047b13c823be25027e736626cd414a52a5e30d2c3347c2733529/propcache-0.5.2-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a6ddc6ac9e25de626c1f129c1b467d7ecd33ce2237d3fd0c4e429feef0a7ee1f", size = 63664, upload-time = "2026-05-08T21:01:09.874Z" }, + { url = "https://files.pythonhosted.org/packages/85/1e/a3a1a63116a2b8edb415a8bb9a6f0c34bd03830b1e18e8ce2904e1dc1cf4/propcache-0.5.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:2f22cbbac9e26a8e864c0985ff1268d5d939d53d9d9411a9824279097e03a2cb", size = 62643, upload-time = "2026-05-08T21:01:11.132Z" }, + { url = "https://files.pythonhosted.org/packages/e4/03/893cf147de2fc6543c5eaa07ad833170e7e2a2385725bbebe8c0503723bb/propcache-0.5.2-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:fc76378c62a0f04d0cd82fbb1a2cd2d7e28fcb40d5873f28a6c44e388aaa2751", size = 59595, upload-time = "2026-05-08T21:01:12.387Z" }, + { url = "https://files.pythonhosted.org/packages/86/3b/04c1a2e12c57766568ba75ba72b3bf2042818d4c1425fab6fc07155c7cff/propcache-0.5.2-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:acd2c8edba48e31e58a363b8cf4e5c7db3b04b3f9e371f601df30d9b0d244836", size = 65711, upload-time = "2026-05-08T21:01:13.676Z" }, + { url = "https://files.pythonhosted.org/packages/1c/34/80f8d0099f8d6bacc4de1624c85672681c8cd1149ca2da0e38fd120b817f/propcache-0.5.2-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:452b5065457eb9991ec5eb38ff41d6cd4c991c9ac7c531c4d5849ae473a9a13f", size = 64247, upload-time = "2026-05-08T21:01:14.936Z" }, + { url = "https://files.pythonhosted.org/packages/f3/1a/8b08f3a5f1037e9e370c55883ceeeee0f6dd0416fb2d2d67b8bfc91f2a79/propcache-0.5.2-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:3430bb2bfe1331885c427745a751e774ee679fd4344f80b97bf879815fe8fa55", size = 67102, upload-time = "2026-05-08T21:01:16.281Z" }, + { url = "https://files.pythonhosted.org/packages/34/68/8bdb7bb7756d76e005490649d10e4a8369e610c74d619f71e1aedf889e9c/propcache-0.5.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:cef6cea3922890dd6c9654971001fa797b526c16ab5e1e46c05fd6f877be7568", size = 64964, upload-time = "2026-05-08T21:01:17.57Z" }, + { url = "https://files.pythonhosted.org/packages/0a/aa/50fb0b5d3968b61a510926ff8b8465f1d6e976b3ab74496d7a4b9fc42515/propcache-0.5.2-cp313-cp313t-win32.whl", hash = "sha256:72d61e16dd78228b58c5d47be830ff3da7e5f139abdf0aef9d86cde1c5cf2191", size = 42546, upload-time = "2026-05-08T21:01:18.946Z" }, + { url = "https://files.pythonhosted.org/packages/ae/4c/0ddbae64321bd4a95bcbfc19307238016b5b1fee645c84626c8d539e5b74/propcache-0.5.2-cp313-cp313t-win_amd64.whl", hash = "sha256:0958834041a0166d343b8d2cedcd8bcbaeb4fdbe0cf08320c5379f143c3be6e7", size = 46330, upload-time = "2026-05-08T21:01:20.162Z" }, + { url = "https://files.pythonhosted.org/packages/00/d9/9cddc8efb78d8af264c5ec9f6d10b62f57c515feda8d321595f56010fb23/propcache-0.5.2-cp313-cp313t-win_arm64.whl", hash = "sha256:6de8bd93ddde9b992cf2b2e0d796d501a19026b5b9fd87356d7d0779531a8d96", size = 40521, upload-time = "2026-05-08T21:01:21.399Z" }, + { url = "https://files.pythonhosted.org/packages/e2/ea/23ee535d90ce8bcc465a3028eb3cc0ce3bd1005f4bb27710b30587de798d/propcache-0.5.2-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:46088abff4cba581dea21ae0467a480526cb25aa5f3c269e909f800328bc3999", size = 94662, upload-time = "2026-05-08T21:01:22.683Z" }, + { url = "https://files.pythonhosted.org/packages/b5/06/c5a52f419b5d8972f8d46a7577476090d8e3263ff589ce40b5ca4968d5be/propcache-0.5.2-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:fc88b26f08d634f7bc819a7852e5214f5802641ab8d9fd5326892292eee1993e", size = 53928, upload-time = "2026-05-08T21:01:23.986Z" }, + { url = "https://files.pythonhosted.org/packages/63/b1/4260d67d6bd85e58a66b72d54ce15d5de789b6f3870cc6bedf8ff9667401/propcache-0.5.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:97797ebb098e670a2f92dd66f32897e30d7615b14e7f59711de23e30a9072539", size = 54650, upload-time = "2026-05-08T21:01:25.305Z" }, + { url = "https://files.pythonhosted.org/packages/70/06/2f46c318e3307cd7a6a7481def374ce838c0fe20084b39dd54b0879d0e99/propcache-0.5.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ba57fffe4ac99c5d30076161b5866336d97600769bad35cc68f7774b15298a4e", size = 59912, upload-time = "2026-05-08T21:01:26.545Z" }, + { url = "https://files.pythonhosted.org/packages/4c/29/fe1aebec2ce57ab985a9c382bded1124431f85078113aa222c5d278430d4/propcache-0.5.2-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:583c19759d9eec1e5b69e2fbef36a7d9c326041be9746cb822d335c8cedc2979", size = 63300, upload-time = "2026-05-08T21:01:27.937Z" }, + { url = "https://files.pythonhosted.org/packages/b4/18/2334b26768b6c82be8c69e83671b767d5ef426aa09b0cba6c2ea47816774/propcache-0.5.2-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d0326e2e5e1f3163fa306c834e48e8d490e5fae607a097a40c0648109b47ba80", size = 64208, upload-time = "2026-05-08T21:01:29.484Z" }, + { url = "https://files.pythonhosted.org/packages/2b/76/7f1bfd6afff4c5e38e36a3c6d68eb5f4b7311ea80baf693db78d95b603c4/propcache-0.5.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e00820e192c8dbebcafb383ebbf99030895f09905e7a0eb2e0340a0bcc2bc825", size = 61633, upload-time = "2026-05-08T21:01:31.068Z" }, + { url = "https://files.pythonhosted.org/packages/c4/46/b3ff8aba2b4953a3e50de2cf72f1b5748b8eca93b15f3dc2c84339084c09/propcache-0.5.2-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c66afea89b1e43725731d2004732a046fe6fe955d51f952c3e95a7314a284a39", size = 61724, upload-time = "2026-05-08T21:01:32.374Z" }, + { url = "https://files.pythonhosted.org/packages/c5/01/814cfcafbcff954f94c01cf30e097ddc88a076b5440fbcf4570753437d40/propcache-0.5.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:d4dc37dec6c6cdad0b57881a5658fd14fbf53e333b1a86cf86559f190e1d9ec4", size = 60069, upload-time = "2026-05-08T21:01:33.67Z" }, + { url = "https://files.pythonhosted.org/packages/da/68/5c6f7622d510cc666a300687e06fd060c1a43361c0c9b20d284f06d8096a/propcache-0.5.2-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:5570dbcc97571c15f68068e529c92715a12f8d54030e272d264b377e22bd17a5", size = 57099, upload-time = "2026-05-08T21:01:34.915Z" }, + { url = "https://files.pythonhosted.org/packages/55/27/9cb0b4c679124085327957d42521c99dba04c88c90c3e55a6f0b633ebccc/propcache-0.5.2-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:f814362777a9f841adddb200ecdf8f5cb1e5a3c4b7a86378edbd6ccb26edd702", size = 63391, upload-time = "2026-05-08T21:01:36.231Z" }, + { url = "https://files.pythonhosted.org/packages/f0/9d/7258aaa5bdf60fc6f27591eef6fe52768cb0beda7140be477c8b12c9794a/propcache-0.5.2-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:196913dea116aeb5a2ba95af4ddcb7ea85559ae07d8eee8751688310d09168c3", size = 61626, upload-time = "2026-05-08T21:01:37.545Z" }, + { url = "https://files.pythonhosted.org/packages/8e/0d/41c602003e8a9b16fe1e7eadf62c7bfba9d5474370b24200bf48b315f45f/propcache-0.5.2-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:6e7b8719005dd1175be4ab1cd25e9b98659a5e0347331506ec6760d2773a7fb5", size = 64781, upload-time = "2026-05-08T21:01:38.83Z" }, + { url = "https://files.pythonhosted.org/packages/8b/f3/38e66b1856e9bd079deea015bc4a55f7767c0e4db2f7dcf69e7e680ba4ce/propcache-0.5.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:51f96d685ab16e88cab128cd37a52c5da540809c8b879fa047731bfcb4ad35a4", size = 62570, upload-time = "2026-05-08T21:01:40.415Z" }, + { url = "https://files.pythonhosted.org/packages/95/ca/bbfe9b910ce57dde8bb4876b4520fc02a4e89497c10de26be936758a3aaa/propcache-0.5.2-cp314-cp314-win32.whl", hash = "sha256:cc6fc3cc62e8501d3ed62894425040d2728ecddb1ed072737a5c70bd537aa9f0", size = 39436, upload-time = "2026-05-08T21:01:41.654Z" }, + { url = "https://files.pythonhosted.org/packages/61/d2/45c9defbaa1ea297035d9d4cce9e8f80daafbf19319c6007f157c6256ea9/propcache-0.5.2-cp314-cp314-win_amd64.whl", hash = "sha256:81e3a30b0bb60caa22033dd0f8a3618d1d67356212514f62c57db75cb0ef410c", size = 42373, upload-time = "2026-05-08T21:01:43.041Z" }, + { url = "https://files.pythonhosted.org/packages/44/68/9ea5103f41d5217d7d6ec24db90018e23aebec070c3f9a6e54d12b841fd8/propcache-0.5.2-cp314-cp314-win_arm64.whl", hash = "sha256:0d2c9bf8528f135dbb805ce027567e09164f7efa51a2be07458a2c0420f292d0", size = 38554, upload-time = "2026-05-08T21:01:44.336Z" }, + { url = "https://files.pythonhosted.org/packages/8a/81/fadf555f42d3b762eea8a53950b0489fdc0aa9da5f8ed9e10ce0a4e01b48/propcache-0.5.2-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:4bc8ff1feffc6a61c7002ffe84634c41b822e104990ae009f44a0834430070bb", size = 99395, upload-time = "2026-05-08T21:01:45.883Z" }, + { url = "https://files.pythonhosted.org/packages/f5/c9/c61e134a686949cf7971af3a390148b1156f7be81c73bc0cd12c873e2d48/propcache-0.5.2-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:79aa3ff0a9b566633b642fa9caf7e21ed1c13d6feca718187873f199e1514078", size = 56653, upload-time = "2026-05-08T21:01:47.307Z" }, + { url = "https://files.pythonhosted.org/packages/cb/73/daf935ea7048ddd7ec8eec5345b4a40b619d2d178b3c0a0900796bc3c794/propcache-0.5.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1b31822f4474c4036bae62de9402710051d431a606d6a0f907fec79935a071aa", size = 56914, upload-time = "2026-05-08T21:01:48.573Z" }, + { url = "https://files.pythonhosted.org/packages/79/9f/aba959b435ea18617edd7cf0a7ad0b9c574b8fc7e3d2cd55fb59cb255d33/propcache-0.5.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:13fef48778b5a2a756523fdb781326b028ca75e32858b04f2cdd19f394564917", size = 62567, upload-time = "2026-05-08T21:01:49.903Z" }, + { url = "https://files.pythonhosted.org/packages/6c/a1/859942de9a791ff42f6141736f5b37749b8f53e65edfa49638c67dd67e6a/propcache-0.5.2-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8b73ab70f1a3351fbc71f663b3e645af6dd0329100c353081cf69c37433fc6fe", size = 65542, upload-time = "2026-05-08T21:01:51.204Z" }, + { url = "https://files.pythonhosted.org/packages/b5/61/315bc0fd6c0fc7f80a528b8afd209e5fc4a875ea79571b91b8f50f442907/propcache-0.5.2-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5538d2c13d93e4698af7e092b57bc7298fd35d1d58e656ae18f23ee0d0378e03", size = 66845, upload-time = "2026-05-08T21:01:52.539Z" }, + { url = "https://files.pythonhosted.org/packages/47/f7/9f8122e3132e8e354ac41975ef8f1099be7d5a16bc7ae562734e993665c0/propcache-0.5.2-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cd645f03898405cabe694fb8bc35241e3a9c332ec85627584fe3de201452b335", size = 63985, upload-time = "2026-05-08T21:01:53.847Z" }, + { url = "https://files.pythonhosted.org/packages/c8/54/c317819ec157cbf6f35df9df9657a6f82daf34d5faf15948b2f639c2192e/propcache-0.5.2-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a473b3440261e0c60706e732b2ed2f517857344fc21bf48fdfe211e2d98eb285", size = 63999, upload-time = "2026-05-08T21:01:55.179Z" }, + { url = "https://files.pythonhosted.org/packages/5a/56/387e3f7dfce0a9233df41fb888aa1c30222cb4bbbf09537c02dd9bd85fe2/propcache-0.5.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:7afa37062e6650640e932e4cc9297d81f9f42d9944029cc386b8247dea4da837", size = 62779, upload-time = "2026-05-08T21:01:57.489Z" }, + { url = "https://files.pythonhosted.org/packages/a1/9c/596784cb5824ed61ee960d3f8655a3f0993e107c6e98ab6c818b7fb92ccb/propcache-0.5.2-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:8a90efd5777e996e42d568db9ac740b944d691e565cbfd31b2f7832f9184b2b8", size = 59796, upload-time = "2026-05-08T21:01:58.736Z" }, + { url = "https://files.pythonhosted.org/packages/c2/3d/1a6cfa1726a48542c1e8784a0761421476a5b68e09b7f36bf95eb954aaba/propcache-0.5.2-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:f19bb891234d72535764d703bfed1153cc34f4214d5bd7150aee1eec9e8f4366", size = 66023, upload-time = "2026-05-08T21:02:00.228Z" }, + { url = "https://files.pythonhosted.org/packages/e4/0e/05fd6990369477076e4e280bcb970de760fddf0161a46e988bc95f7940ec/propcache-0.5.2-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:32775082acd2d807ee3db715c7770d38767b817870acfa08c29e057f3c4d5b56", size = 64448, upload-time = "2026-05-08T21:02:01.888Z" }, + { url = "https://files.pythonhosted.org/packages/cd/86/5f8da315a4309c62c10c0b2516b17492d5d3bbe1bb862b96604db67e2a37/propcache-0.5.2-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:9282fb1a3bccd038da9f768b927b24a0c753e466c086b7c4f3c6982851eefb2d", size = 67329, upload-time = "2026-05-08T21:02:03.484Z" }, + { url = "https://files.pythonhosted.org/packages/da/d3/3368efe79ab21f0cdf86ef49895811c9cc933131d4cde1f28a624e22e712/propcache-0.5.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:cc49723e2f60d6b32a0f0b08a3fd6d13203c07f1cd9566cfce0f12a917c967a2", size = 65172, upload-time = "2026-05-08T21:02:04.745Z" }, + { url = "https://files.pythonhosted.org/packages/d5/07/127e8b0bacfb325396196f9d976a22453049b89b9b2b08477cc3145faa44/propcache-0.5.2-cp314-cp314t-win32.whl", hash = "sha256:2d7aa89ebca5acc98cba9d1472d976e394782f587bad6661003602a619fd1821", size = 43813, upload-time = "2026-05-08T21:02:06.025Z" }, + { url = "https://files.pythonhosted.org/packages/88/fb/46dad6c0ae49ed230ab1b16c890c2b6314e2403e6c412976f4a72d64a527/propcache-0.5.2-cp314-cp314t-win_amd64.whl", hash = "sha256:d447bb0b3054be5818458fbb171208b1d9ff11eba14e18ca18b90cbb45767370", size = 47764, upload-time = "2026-05-08T21:02:07.353Z" }, + { url = "https://files.pythonhosted.org/packages/e7/c4/a47d0a63aa309d10d59ede6e9d4cff03a344a79d1f0f4cd0cd74997b53e0/propcache-0.5.2-cp314-cp314t-win_arm64.whl", hash = "sha256:fe67a3d11cd9b4efabfa45c3d00ffba2b26811442a73a581a94b67c2b5faccf6", size = 41140, upload-time = "2026-05-08T21:02:09.065Z" }, + { url = "https://files.pythonhosted.org/packages/3a/ed/1cdcab6ba3d6ab7feca11fc14f0eeea80755bb53ef4e892079f31b10a25f/propcache-0.5.2-py3-none-any.whl", hash = "sha256:be1ddfcbb376e3de5d2e2db1d58d6d67463e6b4f9f040c000de8e300295465fe", size = 14036, upload-time = "2026-05-08T21:02:10.673Z" }, +] + [[package]] name = "pycparser" version = "3.0" @@ -1255,6 +1743,105 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/7f/3e/5db95bcf282c52709639744ca2a8b149baccf648e39c8cc87553df9eae0c/urllib3-2.7.0-py3-none-any.whl", hash = "sha256:9fb4c81ebbb1ce9531cce37674bbc6f1360472bc18ca9a553ede278ef7276897", size = 131087, upload-time = "2026-05-07T16:13:17.151Z" }, ] +[[package]] +name = "yarl" +version = "1.24.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "idna" }, + { name = "multidict" }, + { name = "propcache" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/31/33/ebe9e3d1f86c7a0b51094c0a146392045ca1631d2664889539dec8088a33/yarl-1.24.5.tar.gz", hash = "sha256:e81b83143bee16329c23db3c1b2d82b29892fcbcb849186d2f6e98a5abe9a57f", size = 228679, upload-time = "2026-07-20T02:07:45.435Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fe/db/3cb5df059756a45761cc3dee8fd25ec82b83a6585ea3542b969fda850f99/yarl-1.24.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:2c1fe720934a16ea8e7146175cba2126f87f54912c8c5435e7f7c7a51ef808d3", size = 135043, upload-time = "2026-07-20T02:04:52.39Z" }, + { url = "https://files.pythonhosted.org/packages/44/f8/767d6bd5a03db63bc467df2fb56d6fafeae9667d74aea92cd6af399f828b/yarl-1.24.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c687ed078e145f5fd53a14854beff320e1d2ab76df03e2009c98f39a0f68f39a", size = 96942, upload-time = "2026-07-20T02:04:54.26Z" }, + { url = "https://files.pythonhosted.org/packages/ce/97/10b939c44d7b28d1dbc389cfc7012306d1ea8dba01eaef44b39fffaee52a/yarl-1.24.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:709f1efed56c4a145793c046cd4939f9959bcd818979a787b77d8e09c57a0840", size = 97046, upload-time = "2026-07-20T02:04:56.638Z" }, + { url = "https://files.pythonhosted.org/packages/5b/7a/b410dbe39b6255c55fb2a2bcee96eb844d0789235ddc381a889a90dc72d6/yarl-1.24.5-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:874019bd513008b009f58657134e5d0c5e030b3559bd0553976837adf52fe966", size = 110512, upload-time = "2026-07-20T02:04:58.955Z" }, + { url = "https://files.pythonhosted.org/packages/83/c7/da591971f78a5617e1f21f5699858ebccd836fe181a6493788ffc91ba69b/yarl-1.24.5-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:a4582acf7ef76482f6f511ebaf1946dae7f2e85ec4728b81a678c01df63bd723", size = 102454, upload-time = "2026-07-20T02:05:00.623Z" }, + { url = "https://files.pythonhosted.org/packages/c4/8e/73b0ed4de47289a78a96045d76d1cfe5e41848bf0da59ce25b2ec87ee05d/yarl-1.24.5-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2cabe6546e41dabe439999a23fcb5246e0c3b595b4315b96ef755252be90caeb", size = 117617, upload-time = "2026-07-20T02:05:02.325Z" }, + { url = "https://files.pythonhosted.org/packages/cf/14/b744747bc4f57a8d55bd744df463457524583e1e9f7538b5ace0346ab92e/yarl-1.24.5-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:17f57620f5475b3c69109376cc87e42a7af5db13c9398e4292772a706ff10780", size = 116135, upload-time = "2026-07-20T02:05:04.05Z" }, + { url = "https://files.pythonhosted.org/packages/66/ca/95aa4d0e5b7ea4f20e4d577c42d001ed9df207569fdb063cc5ed4ebb496b/yarl-1.24.5-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:570fec8fbd22b032733625f03f10b7ff023bc399213db15e72a7acaef28c2f4e", size = 111935, upload-time = "2026-07-20T02:05:05.738Z" }, + { url = "https://files.pythonhosted.org/packages/72/0d/d2ad8d6b147832d177a4e720ba1962fe686eb0913b74503b3eca094b8bba/yarl-1.24.5-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:5fede79c6f73ff2c3ef822864cb1ada23196e62756df53bc6231d351a49516a2", size = 110010, upload-time = "2026-07-20T02:05:07.471Z" }, + { url = "https://files.pythonhosted.org/packages/50/18/eb335e4120903903f4865041355ae46256a2406eb2865bc24827f4f27b61/yarl-1.24.5-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8ccf9aca873b767977c73df497a85dbedee4ee086ae9ae49dc461333b9b79f58", size = 110058, upload-time = "2026-07-20T02:05:09.246Z" }, + { url = "https://files.pythonhosted.org/packages/44/70/97353add32c62ad6f206d948ac5a5ee84398225e534dc6ed6433d1b335b6/yarl-1.24.5-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:ad5d8201d310b031e6cd839d9bac2d4e5a01533ce5d3d5b50b7de1ef3af1de61", size = 103308, upload-time = "2026-07-20T02:05:11.31Z" }, + { url = "https://files.pythonhosted.org/packages/68/39/5e7398d4b6f6b3c9062823ebc60802df5b272e3fe9e788f9734c6ee46c85/yarl-1.24.5-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:841f0852f48fefea3b12c9dfec00704dfa3aef5215d0e3ce564bb3d7cd8d57c6", size = 116898, upload-time = "2026-07-20T02:05:13.099Z" }, + { url = "https://files.pythonhosted.org/packages/e4/c9/09e52f2239e8b96357eccca05915382e4ba5405ebfb623b6036040d99654/yarl-1.24.5-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:9baafc71b04f8f4bb0703b21d6fc9f0c30b346c636a532ff16ec8491a5ea4b1f", size = 109400, upload-time = "2026-07-20T02:05:14.821Z" }, + { url = "https://files.pythonhosted.org/packages/4b/6a/e94133d4c2d1a14d2384310bf3e79d9cf32c9d1eae1c6f034fb80d098fa1/yarl-1.24.5-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:d897129df1a22b12aeed2c2c98df0785a2e8e6e0bde87b389491d0025c187077", size = 115934, upload-time = "2026-07-20T02:05:17.78Z" }, + { url = "https://files.pythonhosted.org/packages/4e/3c/34955ed967b976fc38edcbb6d538dee79dbda4cb7fc7f72a0907a7c78e0f/yarl-1.24.5-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:dd625535328fd9882374356269227670189adfcc6a2d90284f323c05862eecbd", size = 112178, upload-time = "2026-07-20T02:05:19.675Z" }, + { url = "https://files.pythonhosted.org/packages/f5/46/d7bd3a8859d47dcfaffd7127af7076032a7da278a9a02e17b5f37bfb6712/yarl-1.24.5-cp311-cp311-win_amd64.whl", hash = "sha256:f4239bbec5a3577ddb49e4b50aeb32d8e5792098262ae2f63723f916a29b1a25", size = 97544, upload-time = "2026-07-20T02:05:21.523Z" }, + { url = "https://files.pythonhosted.org/packages/01/69/c1bfd21e32c638974ea2c542a0b8c53ef1fa9eff336020f5d014f9503ff2/yarl-1.24.5-cp311-cp311-win_arm64.whl", hash = "sha256:3ac6aff147deb9c09461b2d4bbdf6256831198f5d8a23f5d37138213090b6d8a", size = 93359, upload-time = "2026-07-20T02:05:23.493Z" }, + { url = "https://files.pythonhosted.org/packages/1b/84/71d051c850b5af41d168c679d9eb67eb7c55283ac4ee131673edf134bc4e/yarl-1.24.5-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:d693396e5aea78db03decd60aec9ece16c9b40ba00a587f089615ff4e718a81d", size = 136035, upload-time = "2026-07-20T02:05:25.489Z" }, + { url = "https://files.pythonhosted.org/packages/03/4d/8ad27f9a1b7e69313cca5d695b925b48efe51208d3490e0844bae97cabc0/yarl-1.24.5-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:3363fcc96e665878946ad7a106b9a13eac0541766a690ef287c0232ac768b6ec", size = 97642, upload-time = "2026-07-20T02:05:27.429Z" }, + { url = "https://files.pythonhosted.org/packages/ea/b4/05b4131c407006cd1e410e9c6539f16a0945724677e5364447313c15ea3e/yarl-1.24.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:9d399bdcfb4a0f659b9b3788bbc89babe63d9a6a65aacdf4d4e7065ff2e6316c", size = 97323, upload-time = "2026-07-20T02:05:29.441Z" }, + { url = "https://files.pythonhosted.org/packages/20/16/e618c875c73e0e39611f20a581b3d5e8d59b8857bf001bee3263044c6deb/yarl-1.24.5-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:90333fd89b43c0d08ac85f3f1447593fc2c66de18c3d6378d7125ea118dc7a54", size = 107741, upload-time = "2026-07-20T02:05:31.367Z" }, + { url = "https://files.pythonhosted.org/packages/d9/9a/c4defeaf3ed33fcb346aacf9c6e971a8d4e2bde04a0310e79abb208e7965/yarl-1.24.5-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:665b0a2c463cc9423dd647e0bfd9f4ccc9b50f768c55304d5e9f80b177c1de12", size = 103570, upload-time = "2026-07-20T02:05:33.303Z" }, + { url = "https://files.pythonhosted.org/packages/5f/e7/0e0e0de5865ebd5914537ef486f36c727a59865c3ac0cf5ff1b32aececbf/yarl-1.24.5-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e006d3a974c4ee19512e5f058abedb6eef36a5e553c14812bdeba1758d812e6d", size = 115815, upload-time = "2026-07-20T02:05:35.292Z" }, + { url = "https://files.pythonhosted.org/packages/2b/27/ca56b700cb170aba25a3893b75355b213935657dc5714d2383354a270e62/yarl-1.24.5-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:e7d42c531243450ef0d4d9c172e7ed6ef052640f195629065041b5add4e058d1", size = 116025, upload-time = "2026-07-20T02:05:37.503Z" }, + { url = "https://files.pythonhosted.org/packages/d6/d0/d56c859b8222116f5d68459199f48359e0bf121b6f65a69bf329b3602ba0/yarl-1.24.5-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f08c7513ecef5aad65687bfdf6bc601ae9fccd04a42904501f8f7141abad9eb9", size = 109835, upload-time = "2026-07-20T02:05:39.506Z" }, + { url = "https://files.pythonhosted.org/packages/70/a2/3a35557e4d1a79425040eba202ccaf08bdc8717680fc77e2498a1ad2e0a5/yarl-1.24.5-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:6c95b17fe34ed802f17e205112e6e10db92275c34fee290aa9bdc55a9c724027", size = 108884, upload-time = "2026-07-20T02:05:41.584Z" }, + { url = "https://files.pythonhosted.org/packages/e4/35/ef4c26356b7913c68983bac2d72a4212b3347af551cb8d250b99b5ed7b7f/yarl-1.24.5-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:56b149b22de33b23b0c6077ab9518c6dcb538ad462e1830e68d06591ccf6e38b", size = 107308, upload-time = "2026-07-20T02:05:43.697Z" }, + { url = "https://files.pythonhosted.org/packages/d5/91/ff0dc66c2ccf3e0153ab97ff61eabab4400e6a5264af427ab30cd69f1857/yarl-1.24.5-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:a8fe66b8f300da93798025a785a5b90b42f3810dc2b72283ff84a41aaaebc293", size = 103646, upload-time = "2026-07-20T02:05:45.895Z" }, + { url = "https://files.pythonhosted.org/packages/74/f0/33b9271c7f881766359d58266fa0811d2e5210ed860e28da7dc6d7786344/yarl-1.24.5-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:377fe3732edbaf78ee74efdf2c9f49f6e99f20e7f9d2649fda3eb4badd77d76e", size = 115305, upload-time = "2026-07-20T02:05:47.832Z" }, + { url = "https://files.pythonhosted.org/packages/ef/65/fd79fb1868c4a80db8661091de525bf430f63c3bea1b20e8b6a84fc7d359/yarl-1.24.5-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:e8ffa78582120024f476a611d7befc123cee59e47e8309d470cf667d806e613b", size = 108404, upload-time = "2026-07-20T02:05:49.604Z" }, + { url = "https://files.pythonhosted.org/packages/ff/ba/dbabe6b262f17a816c70cfc09558dbf03ece3ec76684d02f911a3d3a189c/yarl-1.24.5-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:daba5e594f06114e37db186efd2dd916609071e59daca901a0a2e71f02b142ce", size = 115940, upload-time = "2026-07-20T02:05:51.741Z" }, + { url = "https://files.pythonhosted.org/packages/a5/43/fab2d1dad9d340a268cdde63756a123d069723efff6a372d123fa74a9517/yarl-1.24.5-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:65be18ec59496c13908f02a2472751d9ef840b4f3fb5726f129306bf6a2a7bba", size = 110006, upload-time = "2026-07-20T02:05:53.554Z" }, + { url = "https://files.pythonhosted.org/packages/c4/27/41eb51bbd1b8d89546b83897cfb0164f1e109304fd408dbb151b639eec0f/yarl-1.24.5-cp312-cp312-win_amd64.whl", hash = "sha256:a929d878fec099030c292803b31e5d5540a7b6a31e6a3cc76cb4685fc2a2f51b", size = 97618, upload-time = "2026-07-20T02:05:55.57Z" }, + { url = "https://files.pythonhosted.org/packages/3c/25/b2553764b3d65db711d8f45416351ec4f420847558eb669edcbcaadf5780/yarl-1.24.5-cp312-cp312-win_arm64.whl", hash = "sha256:7ce27823052e2013b597e0c738b13e7e36b8ccb9400df8959417b052ab0fd92c", size = 93018, upload-time = "2026-07-20T02:05:57.554Z" }, + { url = "https://files.pythonhosted.org/packages/e1/63/64ef361967cc983573149dc1515d531db5da8a4c92d22bb833d59e01b313/yarl-1.24.5-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:79af890482fc94648e8cde4c68620378f7fef60932710fa17a66abc039244da2", size = 135075, upload-time = "2026-07-20T02:05:59.671Z" }, + { url = "https://files.pythonhosted.org/packages/bb/89/55920fd853ce43e608adbc3962456f0d649d6bb15250dc2988321da0fe1c/yarl-1.24.5-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:46c2f213e23a04b93a392942d782eb9e413e6ef6bf7c8c53884e599a5c174dcb", size = 97225, upload-time = "2026-07-20T02:06:01.769Z" }, + { url = "https://files.pythonhosted.org/packages/15/f0/7688d3f2cfff7590df2af38ec46d969f4281a4dddb08a9ad2eafbcdddf98/yarl-1.24.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:92ab3e11448f2ff7bf53c5a26eff0edc086898ec8b21fb154b85839ce1d88075", size = 96751, upload-time = "2026-07-20T02:06:03.676Z" }, + { url = "https://files.pythonhosted.org/packages/05/1a/a851a0f94aaaf379dd4f901bfc80f634280bec51eb260b47363e2a4cd62e/yarl-1.24.5-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ebb0ec7f17803063d5aeb982f3b1bd2b2f4e4fae6751226cbd6ba1fcfe9e63ff", size = 107960, upload-time = "2026-07-20T02:06:05.699Z" }, + { url = "https://files.pythonhosted.org/packages/6c/a8/faea066c12f9c77ca0de90641f1655f9dd7b412477bf28c76d692f3aecff/yarl-1.24.5-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:82632daed195dcc8ea664e8556dc9bdbd671960fb3776bd92806ce05792c2448", size = 103500, upload-time = "2026-07-20T02:06:07.556Z" }, + { url = "https://files.pythonhosted.org/packages/fb/9c/1e67084c2a6e2f2db0e3be798328cb3be42c0119b621d25461479a224d21/yarl-1.24.5-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:53e549287ef628fecba270045c9701b0c564563a9b0577d24a4ec75b8ab8040f", size = 115780, upload-time = "2026-07-20T02:06:09.599Z" }, + { url = "https://files.pythonhosted.org/packages/58/86/1f94664e147474337e3359f52012cf3d02f825f694317b178bfba1078c62/yarl-1.24.5-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:fcd3b77e2f17bbe4ca56ec7bcb07992647d19d0b9c05d84886dcd6f9eb810afd", size = 115308, upload-time = "2026-07-20T02:06:11.352Z" }, + { url = "https://files.pythonhosted.org/packages/0a/43/8e55ae7538ba5f28ccb3c845c6dd4549cf7016d5992e5326512519107cdd/yarl-1.24.5-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d46b86567dd4e248c6c159fcbcdcce01e0a5c8a7cd2334a0fff759d0fa075b16", size = 110574, upload-time = "2026-07-20T02:06:13.129Z" }, + { url = "https://files.pythonhosted.org/packages/ce/ba/a889ec8765cedcf2ac44dcb02d6a21e4861399b243b263c5f2dde27ee740/yarl-1.24.5-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:7f72c74aa99359e27a2ee8d6613fefa28b5f76a983c083074dfc2aaa4ab46213", size = 109914, upload-time = "2026-07-20T02:06:15.243Z" }, + { url = "https://files.pythonhosted.org/packages/9c/c3/e45f821af67b791c2dbbe4a9f4137a1d33f8d386654a05a0c3f47bdfa25d/yarl-1.24.5-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:3f45789ce415a7ec0820dc4f82925f9b5f7732070be1dec1f5f23ec381435a24", size = 107712, upload-time = "2026-07-20T02:06:17.443Z" }, + { url = "https://files.pythonhosted.org/packages/02/00/2ab0f42c9857fcb490bfaa6647b14540b53d241ab209f23220b958cc5832/yarl-1.24.5-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:6e73e7fe93f17a7b191f52ec9da9dd8c06a8fe735a1ecbd13b97d1c723bff385", size = 104251, upload-time = "2026-07-20T02:06:19.259Z" }, + { url = "https://files.pythonhosted.org/packages/7a/70/709d9a286e98af2c7fd8e4e6cada658b5c0e30d87dd7e2a63c2fb5767217/yarl-1.24.5-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:4a36f9becdd4c5c52a20c3e9484128b070b1dcfc8944c006f3a528295a359a9c", size = 115319, upload-time = "2026-07-20T02:06:21.207Z" }, + { url = "https://files.pythonhosted.org/packages/5c/6c/3eaa515142991fe84cfc483ff986492211f1978f90161ccefdbec919d09b/yarl-1.24.5-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:7bcbe0fcf850eae67b6b01749815a4f7161c560a844c769ad7b48fcd99f791c4", size = 109163, upload-time = "2026-07-20T02:06:23.006Z" }, + { url = "https://files.pythonhosted.org/packages/bb/64/711dafce66c323a3144d470547a71c5384c57623308ac8bb5e4b903ac148/yarl-1.24.5-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:24e861e9630e0daddcb9191fb187f60f034e17a4426f8101279f0c475cd74144", size = 115435, upload-time = "2026-07-20T02:06:24.923Z" }, + { url = "https://files.pythonhosted.org/packages/cf/f3/9b9d0e6d84bea851eb1ba99e4bdc755b86fd813e49ec86dfe42f26befdef/yarl-1.24.5-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9335a099ad87287c37fe5d1a982ff392fa5efe5d14b40a730b1ec1d6a41382b4", size = 110691, upload-time = "2026-07-20T02:06:26.973Z" }, + { url = "https://files.pythonhosted.org/packages/86/e4/62a06b7e87c4246ac76b7c2da136f972eb4a3a1fc94abb07e7022d6fdb0a/yarl-1.24.5-cp313-cp313-win_amd64.whl", hash = "sha256:2dbe06fc16bc91502bca713704022182e5729861ae00277c3a23354b40929740", size = 97454, upload-time = "2026-07-20T02:06:29.163Z" }, + { url = "https://files.pythonhosted.org/packages/9e/c9/5fc8025b318ab10db413b61056bd0d95c557a70e8df4210c7511f866329c/yarl-1.24.5-cp313-cp313-win_arm64.whl", hash = "sha256:6b8536851f9f65e7f00c7a1d49ba7f2be0ffe2c11555367fc9f50d9f842410a1", size = 92813, upload-time = "2026-07-20T02:06:31.113Z" }, + { url = "https://files.pythonhosted.org/packages/a9/08/5f3085fef9564217074db9dd8573de1795bc82cde61a7ad10b6a7234a569/yarl-1.24.5-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:2729fcfc4f6a596fb0c50f32090400aa9367774ac296a00387e65098c0befa76", size = 135680, upload-time = "2026-07-20T02:06:33.273Z" }, + { url = "https://files.pythonhosted.org/packages/98/35/ba9436e579bd48a8801f2021d842d9ab4994c26e4c7dd3a4c1f1bcb57a9e/yarl-1.24.5-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:ff330d3c30db4eb6b01d79e29d2d0b407a7ecad39cfd9ec993ece57396a2ec0d", size = 97395, upload-time = "2026-07-20T02:06:35.259Z" }, + { url = "https://files.pythonhosted.org/packages/18/a9/a07f76f3c44e02b25cc743af5ef93eef27f7013eadca770451b6a6ccb5db/yarl-1.24.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:e42d75862735da90e7fc5a7b23db0c976f737113a54b3c9777a9b665e9cbff75", size = 97223, upload-time = "2026-07-20T02:06:37.216Z" }, + { url = "https://files.pythonhosted.org/packages/77/f7/a9a1d6fa7dd9e388f95b30f6ad3ec4e285f6c8f61f44ce16070c3fcfe414/yarl-1.24.5-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a3732e66413163e72508da9eff9ce9d2846fde51fae45d3605393d3e6cd303e9", size = 108777, upload-time = "2026-07-20T02:06:39.292Z" }, + { url = "https://files.pythonhosted.org/packages/2f/44/e0b86c302471fabd6f02808ecf2ac52b8412b624787849d4bf2cdb466f6f/yarl-1.24.5-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:5b8ee53be440a0cffc991a27be3057e0530122548dbe7c0892df08822fce5ede", size = 103119, upload-time = "2026-07-20T02:06:41.456Z" }, + { url = "https://files.pythonhosted.org/packages/d1/16/9c16d180bf8faaf223225eb50e1245870ff1ae0e302a27153988e65c51fd/yarl-1.24.5-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:af3aefa655adb5869491fa907e652290386800ae99cc50095cba71e2c6aefdca", size = 116471, upload-time = "2026-07-20T02:06:43.696Z" }, + { url = "https://files.pythonhosted.org/packages/d2/8d/b219b9df28a02ce95cfbdd41d2f7caa5669d0ff979c1c9975697145e33c5/yarl-1.24.5-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2120b96872df4a117cde97d270bac96aea7cc52205d305cf4611df694a487027", size = 115974, upload-time = "2026-07-20T02:06:45.874Z" }, + { url = "https://files.pythonhosted.org/packages/9b/e8/f20557aca240d88e69850ad1ee91756821d094bb1310565c04d25c6682a2/yarl-1.24.5-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:66410eb6345d467151934b49bfa70fb32f5b35a6140baa40ad97d6436abea2e9", size = 110830, upload-time = "2026-07-20T02:06:47.852Z" }, + { url = "https://files.pythonhosted.org/packages/db/18/199b85109a53eeca64ee19c9cca228287e8e4ab0cc1a09b28f530e65cce0/yarl-1.24.5-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:4af7b7e1be0a69bee8210735fe6dcfc38879adfac6d62e789d53ba432d1ffa41", size = 110054, upload-time = "2026-07-20T02:06:49.84Z" }, + { url = "https://files.pythonhosted.org/packages/aa/2f/ed28147f8cd7f48c49367c90713b30a555284b6105a6a56f3a05568da795/yarl-1.24.5-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:fa139875ff98ab97da323cfadfaff08900d1ad42f1b5087b0b812a55c5a06373", size = 108312, upload-time = "2026-07-20T02:06:51.835Z" }, + { url = "https://files.pythonhosted.org/packages/c5/c5/55e16ae0a5c227cea8df1c6871ba57d614a34243146c05729caf2a1bd9c5/yarl-1.24.5-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:0055afc45e864b92729ac7600e2d102c17bef060647e74bca75fa84d66b9ff36", size = 103662, upload-time = "2026-07-20T02:06:54.061Z" }, + { url = "https://files.pythonhosted.org/packages/8d/ea/dbd7c2caec459c9a426f18b02688ecbfb58620d0f6a3422d24769fbaf8ab/yarl-1.24.5-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:f0e466ed7511fe9d459a819edbc6c2585c0b6eabde9fa8a8947552468a7a6ef0", size = 116090, upload-time = "2026-07-20T02:06:56.015Z" }, + { url = "https://files.pythonhosted.org/packages/06/84/39ce4ce3059e07fece5fbdbee8c4053406af9aca911ce9fa5f8548aab6af/yarl-1.24.5-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:f141474e85b7e54998ec5180530a7cda99ab29e282fa50e0756d89981a9b43c5", size = 109523, upload-time = "2026-07-20T02:06:57.926Z" }, + { url = "https://files.pythonhosted.org/packages/a9/8b/71ff44137b405c64a7788075669c24010019f57a7464b78c3a6cbee539d9/yarl-1.24.5-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:e2935f8c39e3b03e83519292d78f075189978f3f4adc15a78144c7c8e2a1cba5", size = 116084, upload-time = "2026-07-20T02:06:59.868Z" }, + { url = "https://files.pythonhosted.org/packages/62/c0/423078fdd4042e1862c11f0ffd977a0ffa393783c12bee94685923bc189e/yarl-1.24.5-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:9d1216a7f6f77836617dba35687c5b78a4170afc3c3f18fc788f785ba26565c4", size = 111006, upload-time = "2026-07-20T02:07:01.907Z" }, + { url = "https://files.pythonhosted.org/packages/cf/52/6daa2ee9d95e5c98b8128f8df91eb692eb423ab274b8cf08db52152fad26/yarl-1.24.5-cp314-cp314-win_amd64.whl", hash = "sha256:5ba4f78df2bcc19f764a4b26a8a4f5049c110090ad5825993aacb052bf8003ad", size = 99215, upload-time = "2026-07-20T02:07:03.852Z" }, + { url = "https://files.pythonhosted.org/packages/ec/0e/464a847d7359e0da75dd9fc5c1d1aa35d0159ea31e5f8e66a3c1c29ff3d0/yarl-1.24.5-cp314-cp314-win_arm64.whl", hash = "sha256:9e4e16c73d717c5cf27626c524d0a2e261ad20e46932b2670f64ad5dde23e26f", size = 94566, upload-time = "2026-07-20T02:07:06.074Z" }, + { url = "https://files.pythonhosted.org/packages/e2/55/e03acc4446772660bc335e86e41ef31e4d0d838fd641531a11a5ee33b493/yarl-1.24.5-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:e1ae548a9d901adca07899a4147a7c826bbcc06239d3ce9a59f57886a28a4c88", size = 142533, upload-time = "2026-07-20T02:07:08.284Z" }, + { url = "https://files.pythonhosted.org/packages/ae/71/4acd3a1fc7cf14345cdb302665ecd2097f62c365b4f14ca17d4f37775cf9/yarl-1.24.5-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:ff405d91509d88e8d44129cd87b18d70acd1f0c1aeabd7bc3c46792b1fe2acba", size = 100776, upload-time = "2026-07-20T02:07:10.197Z" }, + { url = "https://files.pythonhosted.org/packages/ff/0b/cfb76b7fe99686db264bff829779a539d923e7564ffd7ef18da6c54c3774/yarl-1.24.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:47e98aab9d8d82ff682e7b0b5dded33bf138a32b817fcf7fa3b27b2d7c412928", size = 100913, upload-time = "2026-07-20T02:07:12.357Z" }, + { url = "https://files.pythonhosted.org/packages/8b/3f/7116e782992abbd4fb6948488aec72078895e929a23078290739e8396fce/yarl-1.24.5-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f0a658a6d3fafee5c6f63c58f3e785c8c43c93fbc02bf9f2b6663f8185e0971f", size = 106507, upload-time = "2026-07-20T02:07:14.173Z" }, + { url = "https://files.pythonhosted.org/packages/33/90/d4d2d73ee78229cc889872eb8e085d8f5c6f51abdb178409fd9b23cf74fd/yarl-1.24.5-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:4377407001ca3c057773f44d8ddd6358fa5f691407c1ba92210bd3cf8d9e4c95", size = 99219, upload-time = "2026-07-20T02:07:16.019Z" }, + { url = "https://files.pythonhosted.org/packages/3e/fa/a6df1a9bccd644eec00abee0dff4277416222cec435330fd1f2858523ec1/yarl-1.24.5-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7c0494a31a1ac5461a226e7947a9c9b78c44e1dc7185164fa7e9651557a5d9bc", size = 111804, upload-time = "2026-07-20T02:07:18.141Z" }, + { url = "https://files.pythonhosted.org/packages/8a/9e/7b2a1f4bcc20e9447156dd2b1c4d01f70d9df0759025ee7d09a84ffae134/yarl-1.24.5-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a7cff474ab7cd149765bb784cf6d78b32e18e20473fb7bda860bce98ab58e9da", size = 110943, upload-time = "2026-07-20T02:07:20.06Z" }, + { url = "https://files.pythonhosted.org/packages/08/ff/22c92affb0f9b623ca753d27d968b5625b868f12c6378d049d55ae247643/yarl-1.24.5-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cbb833ccacdb5519eff9b8b71ee618cc2801c878e77e288775d77c3a2ced858a", size = 108251, upload-time = "2026-07-20T02:07:22.217Z" }, + { url = "https://files.pythonhosted.org/packages/45/44/5769b96298c1e195fb412997b6090af2a84105cf59c17613558a2d011d1f/yarl-1.24.5-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:82f75e05912e84b7a0fe57075d9c59de3cb352b928330f2eb69b2e1f54c3e1f0", size = 106025, upload-time = "2026-07-20T02:07:24.083Z" }, + { url = "https://files.pythonhosted.org/packages/4c/40/009e8e791fd9762c0e1567e69248acb4f49064597e1680874c16dd8bb798/yarl-1.24.5-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:16a2f5010280020e90f5330257e6944bc33e73593b136cc5a241e6c1dc292498", size = 106573, upload-time = "2026-07-20T02:07:26.248Z" }, + { url = "https://files.pythonhosted.org/packages/20/c6/b7480578f8a0a80946f36ad6df547ecec704f9ba69d2de60f8aa6f1c1cbf/yarl-1.24.5-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:ffcd54362564dc1a30fb74d8b8a6e5a6b11ebd5e27266adc3b7427a21a6c9104", size = 100751, upload-time = "2026-07-20T02:07:28.098Z" }, + { url = "https://files.pythonhosted.org/packages/d4/27/4476f3360b91a48c5cf125e91f59a3bd35299d84a431a258d57f5977bb11/yarl-1.24.5-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:0465ec8cedc2349b97a6b595ace64084a50c6e839eca40aa0626f38b8350e331", size = 111643, upload-time = "2026-07-20T02:07:30.88Z" }, + { url = "https://files.pythonhosted.org/packages/4c/4b/5cdd3e5ee944e8af31e52f6cd3d3af5fd7b937e036ccbbba2c9ffebede95/yarl-1.24.5-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:4db9aecb141cb7a5447171b57aa1ed3a8fee06af40b992ffc31206c0b0121550", size = 106312, upload-time = "2026-07-20T02:07:33.06Z" }, + { url = "https://files.pythonhosted.org/packages/18/86/f406b0c2a6f99575de2da671ef47aa06f89a5be83a27a46971c3b86cecdb/yarl-1.24.5-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:f540c013589084679a6c7fac07096b10159737918174f5dfc5e11bf5bca4dfe6", size = 110379, upload-time = "2026-07-20T02:07:35.155Z" }, + { url = "https://files.pythonhosted.org/packages/f0/6c/9f3adfbd3b30b4fa0f7ccb3a83eba2c1152d3fff554d535e640ba0f7ba2b/yarl-1.24.5-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:a61834fb15d81322d872eaafd333838ae7c9cea84067f232656f75965933d047", size = 108497, upload-time = "2026-07-20T02:07:37.35Z" }, + { url = "https://files.pythonhosted.org/packages/dd/37/91eb2e5ca883a529c1b390348a74cd9fc0512171727f547ce70bfe02be5c/yarl-1.24.5-cp314-cp314t-win_amd64.whl", hash = "sha256:5c88e5815a49d289e599f3513aa7fde0bc2092ff188f99c940f007f90f53d104", size = 102450, upload-time = "2026-07-20T02:07:39.578Z" }, + { url = "https://files.pythonhosted.org/packages/bf/f4/ed5c402ac8fde4403ed3366c2716bfddc8a6677ebd59f3d62772cc7fe468/yarl-1.24.5-cp314-cp314t-win_arm64.whl", hash = "sha256:cf139c02f5f23ef6532040a30ff662c00a318c952334f211046b8e60b7f17688", size = 97222, upload-time = "2026-07-20T02:07:41.55Z" }, + { url = "https://files.pythonhosted.org/packages/61/02/962c1cbfc401a30c1d034dc67ff395f64b52302c6d62de556c1fca99acc0/yarl-1.24.5-py3-none-any.whl", hash = "sha256:a33700d13d9b7d84fd10947b09ff69fb9a792e519c8cb9764a3ca70baa6c23a7", size = 58612, upload-time = "2026-07-20T02:07:43.461Z" }, +] + [[package]] name = "zipp" version = "3.23.0" diff --git a/runtime/rust/prompty/src/model/contracts/models/model_options.rs b/runtime/rust/prompty/src/model/contracts/models/model_options.rs index 994382641..fe7e597e0 100644 --- a/runtime/rust/prompty/src/model/contracts/models/model_options.rs +++ b/runtime/rust/prompty/src/model/contracts/models/model_options.rs @@ -12,6 +12,110 @@ use super::super::super::context::{LoadContext, SaveContext}; +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub enum reasoningEffort { + None, + Minimal, + Low, + Medium, + High, + Xhigh, + Max, + /// Unknown variant (open enum — accepts any string). + Other(String), +} + +impl Default for reasoningEffort { + fn default() -> Self { + Self::None + } +} + +impl std::fmt::Display for reasoningEffort { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::None => write!(f, "none"), + Self::Minimal => write!(f, "minimal"), + Self::Low => write!(f, "low"), + Self::Medium => write!(f, "medium"), + Self::High => write!(f, "high"), + Self::Xhigh => write!(f, "xhigh"), + Self::Max => write!(f, "max"), + Self::Other(s) => write!(f, "{}", s), + } + } +} + +impl reasoningEffort { + pub fn from_str_opt(s: &str) -> Option { + match s { + "none" => Some(Self::None), + "minimal" => Some(Self::Minimal), + "low" => Some(Self::Low), + "medium" => Some(Self::Medium), + "high" => Some(Self::High), + "xhigh" => Some(Self::Xhigh), + "max" => Some(Self::Max), + other => Some(Self::Other(other.to_string())), + } + } + + pub fn from_str_ignore_case_opt(s: &str) -> Option { + if s.eq_ignore_ascii_case("none") { + return Some(Self::None); + } + if s.eq_ignore_ascii_case("minimal") { + return Some(Self::Minimal); + } + if s.eq_ignore_ascii_case("low") { + return Some(Self::Low); + } + if s.eq_ignore_ascii_case("medium") { + return Some(Self::Medium); + } + if s.eq_ignore_ascii_case("high") { + return Some(Self::High); + } + if s.eq_ignore_ascii_case("xhigh") { + return Some(Self::Xhigh); + } + if s.eq_ignore_ascii_case("max") { + return Some(Self::Max); + } + Some(Self::Other(s.to_string())) + } + + pub fn as_str(&self) -> &str { + match self { + Self::None => "none", + Self::Minimal => "minimal", + Self::Low => "low", + Self::Medium => "medium", + Self::High => "high", + Self::Xhigh => "xhigh", + Self::Max => "max", + Self::Other(s) => s.as_str(), + } + } +} + +#[cfg(feature = "serde")] +impl serde::Serialize for reasoningEffort { + fn serialize(&self, serializer: S) -> Result { + serializer.serialize_str(self.as_str()) + } +} + +#[cfg(feature = "serde")] +impl<'de> serde::Deserialize<'de> for reasoningEffort { + fn deserialize>(deserializer: D) -> Result { + let s = ::deserialize(deserializer)?; + Self::from_str_opt(&s).ok_or_else(|| { + serde::de::Error::custom(format!("invalid reasoningEffort value: {}", s)) + }) + } +} + /// Options for configuring the behavior of the AI model. #[derive(Debug, Clone, Default, PartialEq)] pub struct ModelOptions { @@ -21,6 +125,8 @@ pub struct ModelOptions { pub max_output_tokens: Option, /// The presence penalty to apply to the model's output pub presence_penalty: Option, + /// The reasoning effort level for reasoning-capable models + pub reasoning_effort: Option, /// A random seed for deterministic output pub seed: Option, /// The temperature to use for sampling @@ -92,6 +198,10 @@ impl ModelOptions { .get("presencePenalty") .and_then(|v| v.as_f64()) .map(|v| v as f32), + reasoning_effort: value + .get("reasoningEffort") + .and_then(|v| v.as_str()) + .and_then(|s| reasoningEffort::from_str_opt(s)), seed: value.get("seed").and_then(|v| v.as_i64()).map(|v| v as i32), temperature: value .get("temperature") @@ -149,6 +259,12 @@ impl ModelOptions { .unwrap_or(serde_json::Value::Null), ); } + if let Some(val) = self.reasoning_effort.as_ref() { + result.insert( + "reasoningEffort".to_string(), + serde_json::Value::String(val.to_string()), + ); + } if let Some(val) = self.seed.as_ref() { result.insert( "seed".to_string(), @@ -230,6 +346,13 @@ impl ModelOptions { "presencePenalty", std::collections::HashMap::from([("openai", "presence_penalty")]), ), + ( + "reasoningEffort", + std::collections::HashMap::from([ + ("openai", "reasoning_effort"), + ("responses", "reasoning_effort"), + ]), + ), ( "seed", std::collections::HashMap::from([("openai", "seed")]), @@ -298,6 +421,13 @@ impl ModelOptions { "presencePenalty", std::collections::HashMap::from([("openai", "presence_penalty")]), ), + ( + "reasoningEffort", + std::collections::HashMap::from([ + ("openai", "reasoning_effort"), + ("responses", "reasoning_effort"), + ]), + ), ( "seed", std::collections::HashMap::from([("openai", "seed")]), diff --git a/runtime/rust/prompty/tests/model/agent_test.rs b/runtime/rust/prompty/tests/model/agent_test.rs index 966fc40d4..20f73eed4 100644 --- a/runtime/rust/prompty/tests/model/agent_test.rs +++ b/runtime/rust/prompty/tests/model/agent_test.rs @@ -3642,3 +3642,12 @@ fn test_agent_serde_roundtrip_7() { "keyed collection loaded from a MAP must re-serialize to the canonical name-keyed map" ); } + +#[test] +fn test_agent_from_json_invalid() { + let ctx = LoadContext::default(); + assert!( + Agent::from_json("{", &ctx).is_err(), + "malformed JSON must be rejected instead of silently defaulting" + ); +} diff --git a/runtime/rust/prompty/tests/model/contracts/connectivity/connection_test.rs b/runtime/rust/prompty/tests/model/contracts/connectivity/connection_test.rs index b8f4c2877..b388f25f8 100644 --- a/runtime/rust/prompty/tests/model/contracts/connectivity/connection_test.rs +++ b/runtime/rust/prompty/tests/model/contracts/connectivity/connection_test.rs @@ -62,3 +62,12 @@ fn test_connection_roundtrip() { let result = Connection::from_json(json, &load_ctx); assert!(result.is_ok(), "Failed to load: {:?}", result.err()); } + +#[test] +fn test_connection_from_json_invalid() { + let ctx = LoadContext::default(); + assert!( + Connection::from_json("{", &ctx).is_err(), + "malformed JSON must be rejected instead of silently defaulting" + ); +} diff --git a/runtime/rust/prompty/tests/model/contracts/connectivity/device_authorization_test.rs b/runtime/rust/prompty/tests/model/contracts/connectivity/device_authorization_test.rs index e9ed15588..ba3db18e0 100644 --- a/runtime/rust/prompty/tests/model/contracts/connectivity/device_authorization_test.rs +++ b/runtime/rust/prompty/tests/model/contracts/connectivity/device_authorization_test.rs @@ -118,3 +118,79 @@ fn test_device_authorization_serde_roundtrip() { serde_json::from_value(value).expect("serde should re-deserialize"); assert_eq!(instance, reparsed, "serde round-trip must be stable"); } + +#[test] +fn test_device_authorization_wire_conversion() { + let json = r####" +{ + "deviceCode": "sample", + "userCode": "sample", + "verificationUri": "sample", + "expiresIn": 1, + "interval": 1 +} +"####; + let ctx = LoadContext::default(); + let instance = DeviceAuthorization::from_json(json, &ctx).expect("load should succeed"); + let foundry_wire = instance.to_wire("foundry"); + let foundry_obj = foundry_wire.as_object().expect("to_wire returns an object"); + assert!( + foundry_obj.contains_key("device_code"), + "Expected foundry wire output to include device_code" + ); + assert!( + !foundry_obj.contains_key("deviceCode"), + "Expected foundry wire output to omit deviceCode" + ); + assert!( + foundry_obj.contains_key("user_code"), + "Expected foundry wire output to include user_code" + ); + assert!( + !foundry_obj.contains_key("userCode"), + "Expected foundry wire output to omit userCode" + ); + assert!( + foundry_obj.contains_key("verification_uri"), + "Expected foundry wire output to include verification_uri" + ); + assert!( + !foundry_obj.contains_key("verificationUri"), + "Expected foundry wire output to omit verificationUri" + ); + assert!( + foundry_obj.contains_key("expires_in"), + "Expected foundry wire output to include expires_in" + ); + assert!( + !foundry_obj.contains_key("expiresIn"), + "Expected foundry wire output to omit expiresIn" + ); + assert!( + foundry_obj.contains_key("interval"), + "Expected foundry wire output to include interval" + ); + let foundry_restored = DeviceAuthorization::from_wire("foundry", &foundry_wire, &ctx); + let foundry_round = foundry_restored.to_wire("foundry"); + let mut foundry_expected: Vec<&String> = foundry_obj.keys().collect(); + let mut foundry_actual: Vec<&String> = foundry_round + .as_object() + .expect("to_wire returns an object") + .keys() + .collect(); + foundry_expected.sort(); + foundry_actual.sort(); + assert_eq!( + foundry_expected, foundry_actual, + "Expected foundry round-trip to preserve wire keys" + ); +} + +#[test] +fn test_device_authorization_from_json_invalid() { + let ctx = LoadContext::default(); + assert!( + DeviceAuthorization::from_json("{", &ctx).is_err(), + "malformed JSON must be rejected instead of silently defaulting" + ); +} diff --git a/runtime/rust/prompty/tests/model/contracts/connectivity/o_auth_token_test.rs b/runtime/rust/prompty/tests/model/contracts/connectivity/o_auth_token_test.rs index 5b6d85763..beae6a060 100644 --- a/runtime/rust/prompty/tests/model/contracts/connectivity/o_auth_token_test.rs +++ b/runtime/rust/prompty/tests/model/contracts/connectivity/o_auth_token_test.rs @@ -113,3 +113,52 @@ fn test_o_auth_token_serde_roundtrip() { let reparsed: OAuthToken = serde_json::from_value(value).expect("serde should re-deserialize"); assert_eq!(instance, reparsed, "serde round-trip must be stable"); } + +#[test] +fn test_o_auth_token_wire_conversion() { + let json = r####" +{ + "refreshToken": "0.AXoAoffline-refresh-token-value", + "scope": "https://cognitiveservices.azure.com/.default offline_access" +} +"####; + let ctx = LoadContext::default(); + let instance = OAuthToken::from_json(json, &ctx).expect("load should succeed"); + let foundry_wire = instance.to_wire("foundry"); + let foundry_obj = foundry_wire.as_object().expect("to_wire returns an object"); + assert!( + foundry_obj.contains_key("refresh_token"), + "Expected foundry wire output to include refresh_token" + ); + assert!( + !foundry_obj.contains_key("refreshToken"), + "Expected foundry wire output to omit refreshToken" + ); + assert!( + foundry_obj.contains_key("scope"), + "Expected foundry wire output to include scope" + ); + let foundry_restored = OAuthToken::from_wire("foundry", &foundry_wire, &ctx); + let foundry_round = foundry_restored.to_wire("foundry"); + let mut foundry_expected: Vec<&String> = foundry_obj.keys().collect(); + let mut foundry_actual: Vec<&String> = foundry_round + .as_object() + .expect("to_wire returns an object") + .keys() + .collect(); + foundry_expected.sort(); + foundry_actual.sort(); + assert_eq!( + foundry_expected, foundry_actual, + "Expected foundry round-trip to preserve wire keys" + ); +} + +#[test] +fn test_o_auth_token_from_json_invalid() { + let ctx = LoadContext::default(); + assert!( + OAuthToken::from_json("{", &ctx).is_err(), + "malformed JSON must be rejected instead of silently defaulting" + ); +} diff --git a/runtime/rust/prompty/tests/model/contracts/conversation/content_part_test.rs b/runtime/rust/prompty/tests/model/contracts/conversation/content_part_test.rs index ee4c8878e..207d12205 100644 --- a/runtime/rust/prompty/tests/model/contracts/conversation/content_part_test.rs +++ b/runtime/rust/prompty/tests/model/contracts/conversation/content_part_test.rs @@ -58,3 +58,12 @@ fn test_content_part_roundtrip() { let result = ContentPart::from_json(json, &load_ctx); assert!(result.is_ok(), "Failed to load: {:?}", result.err()); } + +#[test] +fn test_content_part_from_json_invalid() { + let ctx = LoadContext::default(); + assert!( + ContentPart::from_json("{", &ctx).is_err(), + "malformed JSON must be rejected instead of silently defaulting" + ); +} diff --git a/runtime/rust/prompty/tests/model/contracts/conversation/message_test.rs b/runtime/rust/prompty/tests/model/contracts/conversation/message_test.rs index b4f7e9db2..7ec16a763 100644 --- a/runtime/rust/prompty/tests/model/contracts/conversation/message_test.rs +++ b/runtime/rust/prompty/tests/model/contracts/conversation/message_test.rs @@ -131,6 +131,15 @@ fn test_message_serde_roundtrip() { assert_eq!(instance, reparsed, "serde round-trip must be stable"); } +#[test] +fn test_message_from_json_invalid() { + let ctx = LoadContext::default(); + assert!( + Message::from_json("{", &ctx).is_err(), + "malformed JSON must be rejected instead of silently defaulting" + ); +} + #[test] fn test_message_factory_assistant() { let instance = Message::assistant("test".to_string()); diff --git a/runtime/rust/prompty/tests/model/contracts/conversation/tool_call_test.rs b/runtime/rust/prompty/tests/model/contracts/conversation/tool_call_test.rs index b8bad0995..1042e8bb2 100644 --- a/runtime/rust/prompty/tests/model/contracts/conversation/tool_call_test.rs +++ b/runtime/rust/prompty/tests/model/contracts/conversation/tool_call_test.rs @@ -109,3 +109,12 @@ fn test_tool_call_serde_roundtrip() { let reparsed: ToolCall = serde_json::from_value(value).expect("serde should re-deserialize"); assert_eq!(instance, reparsed, "serde round-trip must be stable"); } + +#[test] +fn test_tool_call_from_json_invalid() { + let ctx = LoadContext::default(); + assert!( + ToolCall::from_json("{", &ctx).is_err(), + "malformed JSON must be rejected instead of silently defaulting" + ); +} diff --git a/runtime/rust/prompty/tests/model/contracts/conversation/tool_result_test.rs b/runtime/rust/prompty/tests/model/contracts/conversation/tool_result_test.rs index c8f4bcb96..6a15adc92 100644 --- a/runtime/rust/prompty/tests/model/contracts/conversation/tool_result_test.rs +++ b/runtime/rust/prompty/tests/model/contracts/conversation/tool_result_test.rs @@ -152,6 +152,15 @@ fn test_tool_result_serde_roundtrip() { assert_eq!(instance, reparsed, "serde round-trip must be stable"); } +#[test] +fn test_tool_result_from_json_invalid() { + let ctx = LoadContext::default(); + assert!( + ToolResult::from_json("{", &ctx).is_err(), + "malformed JSON must be rejected instead of silently defaulting" + ); +} + #[test] fn test_tool_result_factory_text() { let instance = ToolResult::text("test".to_string()); diff --git a/runtime/rust/prompty/tests/model/contracts/core/property_test.rs b/runtime/rust/prompty/tests/model/contracts/core/property_test.rs index 84db93886..9d9b215be 100644 --- a/runtime/rust/prompty/tests/model/contracts/core/property_test.rs +++ b/runtime/rust/prompty/tests/model/contracts/core/property_test.rs @@ -88,6 +88,15 @@ fn test_property_roundtrip() { assert!(result.is_ok(), "Failed to load: {:?}", result.err()); } +#[test] +fn test_property_from_json_invalid() { + let ctx = LoadContext::default(); + assert!( + Property::from_json("{", &ctx).is_err(), + "malformed JSON must be rejected instead of silently defaulting" + ); +} + #[test] fn test_property_from_input() { let value = serde_json::json!(false); diff --git a/runtime/rust/prompty/tests/model/contracts/events/checkpoint_test.rs b/runtime/rust/prompty/tests/model/contracts/events/checkpoint_test.rs index e224045ea..68bb16afc 100644 --- a/runtime/rust/prompty/tests/model/contracts/events/checkpoint_test.rs +++ b/runtime/rust/prompty/tests/model/contracts/events/checkpoint_test.rs @@ -153,3 +153,12 @@ fn test_checkpoint_serde_roundtrip() { let reparsed: Checkpoint = serde_json::from_value(value).expect("serde should re-deserialize"); assert_eq!(instance, reparsed, "serde round-trip must be stable"); } + +#[test] +fn test_checkpoint_from_json_invalid() { + let ctx = LoadContext::default(); + assert!( + Checkpoint::from_json("{", &ctx).is_err(), + "malformed JSON must be rejected instead of silently defaulting" + ); +} diff --git a/runtime/rust/prompty/tests/model/contracts/events/host_tool_request_test.rs b/runtime/rust/prompty/tests/model/contracts/events/host_tool_request_test.rs index a018dcf31..a6b884603 100644 --- a/runtime/rust/prompty/tests/model/contracts/events/host_tool_request_test.rs +++ b/runtime/rust/prompty/tests/model/contracts/events/host_tool_request_test.rs @@ -140,3 +140,12 @@ fn test_host_tool_request_serde_roundtrip() { serde_json::from_value(value).expect("serde should re-deserialize"); assert_eq!(instance, reparsed, "serde round-trip must be stable"); } + +#[test] +fn test_host_tool_request_from_json_invalid() { + let ctx = LoadContext::default(); + assert!( + HostToolRequest::from_json("{", &ctx).is_err(), + "malformed JSON must be rejected instead of silently defaulting" + ); +} diff --git a/runtime/rust/prompty/tests/model/contracts/events/host_tool_result_test.rs b/runtime/rust/prompty/tests/model/contracts/events/host_tool_result_test.rs index 77eef62d4..95af38b73 100644 --- a/runtime/rust/prompty/tests/model/contracts/events/host_tool_result_test.rs +++ b/runtime/rust/prompty/tests/model/contracts/events/host_tool_result_test.rs @@ -165,3 +165,12 @@ fn test_host_tool_result_serde_roundtrip() { serde_json::from_value(value).expect("serde should re-deserialize"); assert_eq!(instance, reparsed, "serde round-trip must be stable"); } + +#[test] +fn test_host_tool_result_from_json_invalid() { + let ctx = LoadContext::default(); + assert!( + HostToolResult::from_json("{", &ctx).is_err(), + "malformed JSON must be rejected instead of silently defaulting" + ); +} diff --git a/runtime/rust/prompty/tests/model/contracts/events/permission_decision_test.rs b/runtime/rust/prompty/tests/model/contracts/events/permission_decision_test.rs index cd6267e29..e2f6a5809 100644 --- a/runtime/rust/prompty/tests/model/contracts/events/permission_decision_test.rs +++ b/runtime/rust/prompty/tests/model/contracts/events/permission_decision_test.rs @@ -137,3 +137,12 @@ fn test_permission_decision_serde_roundtrip() { serde_json::from_value(value).expect("serde should re-deserialize"); assert_eq!(instance, reparsed, "serde round-trip must be stable"); } + +#[test] +fn test_permission_decision_from_json_invalid() { + let ctx = LoadContext::default(); + assert!( + PermissionDecision::from_json("{", &ctx).is_err(), + "malformed JSON must be rejected instead of silently defaulting" + ); +} diff --git a/runtime/rust/prompty/tests/model/contracts/events/permission_request_test.rs b/runtime/rust/prompty/tests/model/contracts/events/permission_request_test.rs index 40a57d58a..88c7003c2 100644 --- a/runtime/rust/prompty/tests/model/contracts/events/permission_request_test.rs +++ b/runtime/rust/prompty/tests/model/contracts/events/permission_request_test.rs @@ -147,3 +147,12 @@ fn test_permission_request_serde_roundtrip() { serde_json::from_value(value).expect("serde should re-deserialize"); assert_eq!(instance, reparsed, "serde round-trip must be stable"); } + +#[test] +fn test_permission_request_from_json_invalid() { + let ctx = LoadContext::default(); + assert!( + PermissionRequest::from_json("{", &ctx).is_err(), + "malformed JSON must be rejected instead of silently defaulting" + ); +} diff --git a/runtime/rust/prompty/tests/model/contracts/events/redacted_field_test.rs b/runtime/rust/prompty/tests/model/contracts/events/redacted_field_test.rs index f06ed6cee..4894d89eb 100644 --- a/runtime/rust/prompty/tests/model/contracts/events/redacted_field_test.rs +++ b/runtime/rust/prompty/tests/model/contracts/events/redacted_field_test.rs @@ -112,3 +112,12 @@ fn test_redacted_field_serde_roundtrip() { serde_json::from_value(value).expect("serde should re-deserialize"); assert_eq!(instance, reparsed, "serde round-trip must be stable"); } + +#[test] +fn test_redacted_field_from_json_invalid() { + let ctx = LoadContext::default(); + assert!( + RedactedField::from_json("{", &ctx).is_err(), + "malformed JSON must be rejected instead of silently defaulting" + ); +} diff --git a/runtime/rust/prompty/tests/model/contracts/events/redaction_metadata_test.rs b/runtime/rust/prompty/tests/model/contracts/events/redaction_metadata_test.rs index bfa027fdb..808d637b5 100644 --- a/runtime/rust/prompty/tests/model/contracts/events/redaction_metadata_test.rs +++ b/runtime/rust/prompty/tests/model/contracts/events/redaction_metadata_test.rs @@ -112,3 +112,12 @@ fn test_redaction_metadata_serde_roundtrip() { serde_json::from_value(value).expect("serde should re-deserialize"); assert_eq!(instance, reparsed, "serde round-trip must be stable"); } + +#[test] +fn test_redaction_metadata_from_json_invalid() { + let ctx = LoadContext::default(); + assert!( + RedactionMetadata::from_json("{", &ctx).is_err(), + "malformed JSON must be rejected instead of silently defaulting" + ); +} diff --git a/runtime/rust/prompty/tests/model/contracts/events/session_event_test.rs b/runtime/rust/prompty/tests/model/contracts/events/session_event_test.rs index a4471daed..3cebeffac 100644 --- a/runtime/rust/prompty/tests/model/contracts/events/session_event_test.rs +++ b/runtime/rust/prompty/tests/model/contracts/events/session_event_test.rs @@ -141,3 +141,12 @@ fn test_session_event_serde_roundtrip() { serde_json::from_value(value).expect("serde should re-deserialize"); assert_eq!(instance, reparsed, "serde round-trip must be stable"); } + +#[test] +fn test_session_event_from_json_invalid() { + let ctx = LoadContext::default(); + assert!( + SessionEvent::from_json("{", &ctx).is_err(), + "malformed JSON must be rejected instead of silently defaulting" + ); +} diff --git a/runtime/rust/prompty/tests/model/contracts/events/session_summary_test.rs b/runtime/rust/prompty/tests/model/contracts/events/session_summary_test.rs index ac512bb4b..a05a97f1d 100644 --- a/runtime/rust/prompty/tests/model/contracts/events/session_summary_test.rs +++ b/runtime/rust/prompty/tests/model/contracts/events/session_summary_test.rs @@ -138,3 +138,12 @@ fn test_session_summary_serde_roundtrip() { serde_json::from_value(value).expect("serde should re-deserialize"); assert_eq!(instance, reparsed, "serde round-trip must be stable"); } + +#[test] +fn test_session_summary_from_json_invalid() { + let ctx = LoadContext::default(); + assert!( + SessionSummary::from_json("{", &ctx).is_err(), + "malformed JSON must be rejected instead of silently defaulting" + ); +} diff --git a/runtime/rust/prompty/tests/model/contracts/events/stream_chunk_test.rs b/runtime/rust/prompty/tests/model/contracts/events/stream_chunk_test.rs index e3f83bab7..af7b4ba6f 100644 --- a/runtime/rust/prompty/tests/model/contracts/events/stream_chunk_test.rs +++ b/runtime/rust/prompty/tests/model/contracts/events/stream_chunk_test.rs @@ -58,3 +58,12 @@ fn test_stream_chunk_roundtrip() { let result = StreamChunk::from_json(json, &load_ctx); assert!(result.is_ok(), "Failed to load: {:?}", result.err()); } + +#[test] +fn test_stream_chunk_from_json_invalid() { + let ctx = LoadContext::default(); + assert!( + StreamChunk::from_json("{", &ctx).is_err(), + "malformed JSON must be rejected instead of silently defaulting" + ); +} diff --git a/runtime/rust/prompty/tests/model/contracts/events/stream_failure_test.rs b/runtime/rust/prompty/tests/model/contracts/events/stream_failure_test.rs index ad4ba1fc6..4e4004e14 100644 --- a/runtime/rust/prompty/tests/model/contracts/events/stream_failure_test.rs +++ b/runtime/rust/prompty/tests/model/contracts/events/stream_failure_test.rs @@ -105,3 +105,12 @@ fn test_stream_failure_serde_roundtrip() { serde_json::from_value(value).expect("serde should re-deserialize"); assert_eq!(instance, reparsed, "serde round-trip must be stable"); } + +#[test] +fn test_stream_failure_from_json_invalid() { + let ctx = LoadContext::default(); + assert!( + StreamFailure::from_json("{", &ctx).is_err(), + "malformed JSON must be rejected instead of silently defaulting" + ); +} diff --git a/runtime/rust/prompty/tests/model/contracts/events/turn_event_test.rs b/runtime/rust/prompty/tests/model/contracts/events/turn_event_test.rs index 127683476..1a00ffa2b 100644 --- a/runtime/rust/prompty/tests/model/contracts/events/turn_event_test.rs +++ b/runtime/rust/prompty/tests/model/contracts/events/turn_event_test.rs @@ -140,3 +140,12 @@ fn test_turn_event_serde_roundtrip() { let reparsed: TurnEvent = serde_json::from_value(value).expect("serde should re-deserialize"); assert_eq!(instance, reparsed, "serde round-trip must be stable"); } + +#[test] +fn test_turn_event_from_json_invalid() { + let ctx = LoadContext::default(); + assert!( + TurnEvent::from_json("{", &ctx).is_err(), + "malformed JSON must be rejected instead of silently defaulting" + ); +} diff --git a/runtime/rust/prompty/tests/model/contracts/memory/memory_entry_test.rs b/runtime/rust/prompty/tests/model/contracts/memory/memory_entry_test.rs index d5dce7802..941e20dbd 100644 --- a/runtime/rust/prompty/tests/model/contracts/memory/memory_entry_test.rs +++ b/runtime/rust/prompty/tests/model/contracts/memory/memory_entry_test.rs @@ -135,3 +135,12 @@ fn test_memory_entry_serde_roundtrip() { let reparsed: MemoryEntry = serde_json::from_value(value).expect("serde should re-deserialize"); assert_eq!(instance, reparsed, "serde round-trip must be stable"); } + +#[test] +fn test_memory_entry_from_json_invalid() { + let ctx = LoadContext::default(); + assert!( + MemoryEntry::from_json("{", &ctx).is_err(), + "malformed JSON must be rejected instead of silently defaulting" + ); +} diff --git a/runtime/rust/prompty/tests/model/contracts/memory/memory_store_test.rs b/runtime/rust/prompty/tests/model/contracts/memory/memory_store_test.rs index 344301c31..f422f3457 100644 --- a/runtime/rust/prompty/tests/model/contracts/memory/memory_store_test.rs +++ b/runtime/rust/prompty/tests/model/contracts/memory/memory_store_test.rs @@ -93,3 +93,12 @@ fn test_memory_store_serde_roundtrip() { let reparsed: MemoryStore = serde_json::from_value(value).expect("serde should re-deserialize"); assert_eq!(instance, reparsed, "serde round-trip must be stable"); } + +#[test] +fn test_memory_store_from_json_invalid() { + let ctx = LoadContext::default(); + assert!( + MemoryStore::from_json("{", &ctx).is_err(), + "malformed JSON must be rejected instead of silently defaulting" + ); +} diff --git a/runtime/rust/prompty/tests/model/contracts/models/invocation_usage_test.rs b/runtime/rust/prompty/tests/model/contracts/models/invocation_usage_test.rs index 0d37d0cac..1e800cc00 100644 --- a/runtime/rust/prompty/tests/model/contracts/models/invocation_usage_test.rs +++ b/runtime/rust/prompty/tests/model/contracts/models/invocation_usage_test.rs @@ -110,3 +110,99 @@ fn test_invocation_usage_serde_roundtrip() { serde_json::from_value(value).expect("serde should re-deserialize"); assert_eq!(instance, reparsed, "serde round-trip must be stable"); } + +#[test] +fn test_invocation_usage_wire_conversion() { + let json = r####" +{ + "inputTokens": 150, + "outputTokens": 42, + "totalTokens": 192 +} +"####; + let ctx = LoadContext::default(); + let instance = InvocationUsage::from_json(json, &ctx).expect("load should succeed"); + let openai_wire = instance.to_wire("openai"); + let openai_obj = openai_wire.as_object().expect("to_wire returns an object"); + assert!( + openai_obj.contains_key("prompt_tokens"), + "Expected openai wire output to include prompt_tokens" + ); + assert!( + !openai_obj.contains_key("inputTokens"), + "Expected openai wire output to omit inputTokens" + ); + assert!( + openai_obj.contains_key("completion_tokens"), + "Expected openai wire output to include completion_tokens" + ); + assert!( + !openai_obj.contains_key("outputTokens"), + "Expected openai wire output to omit outputTokens" + ); + assert!( + openai_obj.contains_key("total_tokens"), + "Expected openai wire output to include total_tokens" + ); + assert!( + !openai_obj.contains_key("totalTokens"), + "Expected openai wire output to omit totalTokens" + ); + let openai_restored = InvocationUsage::from_wire("openai", &openai_wire, &ctx); + let openai_round = openai_restored.to_wire("openai"); + let mut openai_expected: Vec<&String> = openai_obj.keys().collect(); + let mut openai_actual: Vec<&String> = openai_round + .as_object() + .expect("to_wire returns an object") + .keys() + .collect(); + openai_expected.sort(); + openai_actual.sort(); + assert_eq!( + openai_expected, openai_actual, + "Expected openai round-trip to preserve wire keys" + ); + let anthropic_wire = instance.to_wire("anthropic"); + let anthropic_obj = anthropic_wire + .as_object() + .expect("to_wire returns an object"); + assert!( + anthropic_obj.contains_key("input_tokens"), + "Expected anthropic wire output to include input_tokens" + ); + assert!( + !anthropic_obj.contains_key("inputTokens"), + "Expected anthropic wire output to omit inputTokens" + ); + assert!( + anthropic_obj.contains_key("output_tokens"), + "Expected anthropic wire output to include output_tokens" + ); + assert!( + !anthropic_obj.contains_key("outputTokens"), + "Expected anthropic wire output to omit outputTokens" + ); + let anthropic_restored = InvocationUsage::from_wire("anthropic", &anthropic_wire, &ctx); + let anthropic_round = anthropic_restored.to_wire("anthropic"); + let mut anthropic_expected: Vec<&String> = anthropic_obj.keys().collect(); + let mut anthropic_actual: Vec<&String> = anthropic_round + .as_object() + .expect("to_wire returns an object") + .keys() + .collect(); + anthropic_expected.sort(); + anthropic_actual.sort(); + assert_eq!( + anthropic_expected, anthropic_actual, + "Expected anthropic round-trip to preserve wire keys" + ); +} + +#[test] +fn test_invocation_usage_from_json_invalid() { + let ctx = LoadContext::default(); + assert!( + InvocationUsage::from_json("{", &ctx).is_err(), + "malformed JSON must be rejected instead of silently defaulting" + ); +} diff --git a/runtime/rust/prompty/tests/model/contracts/models/model_info_test.rs b/runtime/rust/prompty/tests/model/contracts/models/model_info_test.rs index ace7112f3..16a2a84eb 100644 --- a/runtime/rust/prompty/tests/model/contracts/models/model_info_test.rs +++ b/runtime/rust/prompty/tests/model/contracts/models/model_info_test.rs @@ -167,3 +167,118 @@ fn test_model_info_serde_roundtrip() { let reparsed: ModelInfo = serde_json::from_value(value).expect("serde should re-deserialize"); assert_eq!(instance, reparsed, "serde round-trip must be stable"); } + +#[test] +fn test_model_info_wire_conversion() { + let json = r####" +{ + "id": "gpt-4o", + "displayName": "GPT-4o", + "ownedBy": "openai", + "contextWindow": 128000, + "inputModalities": [ + "text", + "image" + ], + "outputModalities": [ + "text" + ], + "additionalProperties": { + "supportsStreaming": true + } +} +"####; + let ctx = LoadContext::default(); + let instance = ModelInfo::from_json(json, &ctx).expect("load should succeed"); + let openai_wire = instance.to_wire("openai"); + let openai_obj = openai_wire.as_object().expect("to_wire returns an object"); + assert!( + openai_obj.contains_key("id"), + "Expected openai wire output to include id" + ); + assert!( + openai_obj.contains_key("owned_by"), + "Expected openai wire output to include owned_by" + ); + assert!( + !openai_obj.contains_key("ownedBy"), + "Expected openai wire output to omit ownedBy" + ); + let openai_restored = ModelInfo::from_wire("openai", &openai_wire, &ctx); + let openai_round = openai_restored.to_wire("openai"); + let mut openai_expected: Vec<&String> = openai_obj.keys().collect(); + let mut openai_actual: Vec<&String> = openai_round + .as_object() + .expect("to_wire returns an object") + .keys() + .collect(); + openai_expected.sort(); + openai_actual.sort(); + assert_eq!( + openai_expected, openai_actual, + "Expected openai round-trip to preserve wire keys" + ); + let anthropic_wire = instance.to_wire("anthropic"); + let anthropic_obj = anthropic_wire + .as_object() + .expect("to_wire returns an object"); + assert!( + anthropic_obj.contains_key("id"), + "Expected anthropic wire output to include id" + ); + assert!( + anthropic_obj.contains_key("display_name"), + "Expected anthropic wire output to include display_name" + ); + assert!( + !anthropic_obj.contains_key("displayName"), + "Expected anthropic wire output to omit displayName" + ); + assert!( + anthropic_obj.contains_key("context_length"), + "Expected anthropic wire output to include context_length" + ); + assert!( + !anthropic_obj.contains_key("contextWindow"), + "Expected anthropic wire output to omit contextWindow" + ); + assert!( + anthropic_obj.contains_key("input_modalities"), + "Expected anthropic wire output to include input_modalities" + ); + assert!( + !anthropic_obj.contains_key("inputModalities"), + "Expected anthropic wire output to omit inputModalities" + ); + assert!( + anthropic_obj.contains_key("output_modalities"), + "Expected anthropic wire output to include output_modalities" + ); + assert!( + !anthropic_obj.contains_key("outputModalities"), + "Expected anthropic wire output to omit outputModalities" + ); + let anthropic_restored = ModelInfo::from_wire("anthropic", &anthropic_wire, &ctx); + let anthropic_round = anthropic_restored.to_wire("anthropic"); + let mut anthropic_expected: Vec<&String> = anthropic_obj.keys().collect(); + let mut anthropic_actual: Vec<&String> = anthropic_round + .as_object() + .expect("to_wire returns an object") + .keys() + .collect(); + anthropic_expected.sort(); + anthropic_actual.sort(); + assert_eq!( + anthropic_expected, anthropic_actual, + "Expected anthropic round-trip to preserve wire keys" + ); +} + +#[test] +fn test_model_info_from_json_invalid() { + let ctx = LoadContext::default(); + assert!( + ModelInfo::from_json("{", &ctx).is_err(), + "malformed JSON must be rejected instead of silently defaulting" + ); +} diff --git a/runtime/rust/prompty/tests/model/contracts/models/model_options_test.rs b/runtime/rust/prompty/tests/model/contracts/models/model_options_test.rs index c5f455c51..5b0f771b8 100644 --- a/runtime/rust/prompty/tests/model/contracts/models/model_options_test.rs +++ b/runtime/rust/prompty/tests/model/contracts/models/model_options_test.rs @@ -12,6 +12,7 @@ use prompty::model::ModelOptions; use prompty::model::context::{LoadContext, SaveContext}; +use prompty::model::reasoningEffort; #[test] fn test_model_options_load_json() { @@ -20,6 +21,7 @@ fn test_model_options_load_json() { "frequencyPenalty": 0.5, "maxOutputTokens": 2048, "presencePenalty": 0.3, + "reasoningEffort": "medium", "seed": 42, "temperature": 0.7, "topK": 40, @@ -58,6 +60,14 @@ fn test_model_options_load_json() { "Expected presence_penalty to be Some" ); assert_eq!(instance.presence_penalty.as_ref().unwrap(), &0.3); + assert!( + instance.reasoning_effort.is_some(), + "Expected reasoning_effort to be Some" + ); + assert_eq!( + instance.reasoning_effort.as_ref().unwrap(), + &reasoningEffort::Medium + ); assert!(instance.seed.is_some(), "Expected seed to be Some"); assert_eq!(instance.seed.as_ref().unwrap(), &42); assert!( @@ -82,6 +92,7 @@ fn test_model_options_load_yaml() { frequencyPenalty: 0.5 maxOutputTokens: 2048 presencePenalty: 0.3 +reasoningEffort: medium seed: 42 temperature: 0.7 topK: 40 @@ -115,6 +126,10 @@ additionalProperties: instance.presence_penalty.is_some(), "Expected presence_penalty to be Some" ); + assert!( + instance.reasoning_effort.is_some(), + "Expected reasoning_effort to be Some" + ); assert!(instance.seed.is_some(), "Expected seed to be Some"); assert!( instance.temperature.is_some(), @@ -135,6 +150,7 @@ fn test_model_options_roundtrip() { "frequencyPenalty": 0.5, "maxOutputTokens": 2048, "presencePenalty": 0.3, + "reasoningEffort": "medium", "seed": 42, "temperature": 0.7, "topK": 40, @@ -171,6 +187,7 @@ fn test_model_options_serde_roundtrip() { "frequencyPenalty": 0.5, "maxOutputTokens": 2048, "presencePenalty": 0.3, + "reasoningEffort": "medium", "seed": 42, "temperature": 0.7, "topK": 40, @@ -204,3 +221,227 @@ fn test_model_options_serde_roundtrip() { serde_json::from_value(value).expect("serde should re-deserialize"); assert_eq!(instance, reparsed, "serde round-trip must be stable"); } + +#[test] +fn test_model_options_wire_conversion() { + let json = r####" +{ + "frequencyPenalty": 0.5, + "maxOutputTokens": 2048, + "presencePenalty": 0.3, + "reasoningEffort": "medium", + "seed": 42, + "temperature": 0.7, + "topK": 40, + "topP": 0.9, + "stopSequences": [ + "\n", + "###" + ], + "allowMultipleToolCalls": true, + "additionalProperties": { + "customProperty": "value", + "anotherProperty": "anotherValue" + } +} +"####; + let ctx = LoadContext::default(); + let instance = ModelOptions::from_json(json, &ctx).expect("load should succeed"); + let openai_wire = instance.to_wire("openai"); + let openai_obj = openai_wire.as_object().expect("to_wire returns an object"); + assert!( + openai_obj.contains_key("frequency_penalty"), + "Expected openai wire output to include frequency_penalty" + ); + assert!( + !openai_obj.contains_key("frequencyPenalty"), + "Expected openai wire output to omit frequencyPenalty" + ); + assert!( + openai_obj.contains_key("max_completion_tokens"), + "Expected openai wire output to include max_completion_tokens" + ); + assert!( + !openai_obj.contains_key("maxOutputTokens"), + "Expected openai wire output to omit maxOutputTokens" + ); + assert!( + openai_obj.contains_key("presence_penalty"), + "Expected openai wire output to include presence_penalty" + ); + assert!( + !openai_obj.contains_key("presencePenalty"), + "Expected openai wire output to omit presencePenalty" + ); + assert!( + openai_obj.contains_key("reasoning_effort"), + "Expected openai wire output to include reasoning_effort" + ); + assert!( + !openai_obj.contains_key("reasoningEffort"), + "Expected openai wire output to omit reasoningEffort" + ); + assert!( + openai_obj.contains_key("seed"), + "Expected openai wire output to include seed" + ); + assert!( + openai_obj.contains_key("temperature"), + "Expected openai wire output to include temperature" + ); + assert!( + openai_obj.contains_key("top_k"), + "Expected openai wire output to include top_k" + ); + assert!( + !openai_obj.contains_key("topK"), + "Expected openai wire output to omit topK" + ); + assert!( + openai_obj.contains_key("top_p"), + "Expected openai wire output to include top_p" + ); + assert!( + !openai_obj.contains_key("topP"), + "Expected openai wire output to omit topP" + ); + assert!( + openai_obj.contains_key("stop"), + "Expected openai wire output to include stop" + ); + assert!( + !openai_obj.contains_key("stopSequences"), + "Expected openai wire output to omit stopSequences" + ); + assert!( + openai_obj.contains_key("parallel_tool_calls"), + "Expected openai wire output to include parallel_tool_calls" + ); + assert!( + !openai_obj.contains_key("allowMultipleToolCalls"), + "Expected openai wire output to omit allowMultipleToolCalls" + ); + let openai_restored = ModelOptions::from_wire("openai", &openai_wire, &ctx); + let openai_round = openai_restored.to_wire("openai"); + let mut openai_expected: Vec<&String> = openai_obj.keys().collect(); + let mut openai_actual: Vec<&String> = openai_round + .as_object() + .expect("to_wire returns an object") + .keys() + .collect(); + openai_expected.sort(); + openai_actual.sort(); + assert_eq!( + openai_expected, openai_actual, + "Expected openai round-trip to preserve wire keys" + ); + let responses_wire = instance.to_wire("responses"); + let responses_obj = responses_wire + .as_object() + .expect("to_wire returns an object"); + assert!( + responses_obj.contains_key("max_output_tokens"), + "Expected responses wire output to include max_output_tokens" + ); + assert!( + !responses_obj.contains_key("maxOutputTokens"), + "Expected responses wire output to omit maxOutputTokens" + ); + assert!( + responses_obj.contains_key("reasoning_effort"), + "Expected responses wire output to include reasoning_effort" + ); + assert!( + !responses_obj.contains_key("reasoningEffort"), + "Expected responses wire output to omit reasoningEffort" + ); + assert!( + responses_obj.contains_key("temperature"), + "Expected responses wire output to include temperature" + ); + assert!( + responses_obj.contains_key("top_p"), + "Expected responses wire output to include top_p" + ); + assert!( + !responses_obj.contains_key("topP"), + "Expected responses wire output to omit topP" + ); + let responses_restored = ModelOptions::from_wire("responses", &responses_wire, &ctx); + let responses_round = responses_restored.to_wire("responses"); + let mut responses_expected: Vec<&String> = responses_obj.keys().collect(); + let mut responses_actual: Vec<&String> = responses_round + .as_object() + .expect("to_wire returns an object") + .keys() + .collect(); + responses_expected.sort(); + responses_actual.sort(); + assert_eq!( + responses_expected, responses_actual, + "Expected responses round-trip to preserve wire keys" + ); + let anthropic_wire = instance.to_wire("anthropic"); + let anthropic_obj = anthropic_wire + .as_object() + .expect("to_wire returns an object"); + assert!( + anthropic_obj.contains_key("max_tokens"), + "Expected anthropic wire output to include max_tokens" + ); + assert!( + !anthropic_obj.contains_key("maxOutputTokens"), + "Expected anthropic wire output to omit maxOutputTokens" + ); + assert!( + anthropic_obj.contains_key("temperature"), + "Expected anthropic wire output to include temperature" + ); + assert!( + anthropic_obj.contains_key("top_k"), + "Expected anthropic wire output to include top_k" + ); + assert!( + !anthropic_obj.contains_key("topK"), + "Expected anthropic wire output to omit topK" + ); + assert!( + anthropic_obj.contains_key("top_p"), + "Expected anthropic wire output to include top_p" + ); + assert!( + !anthropic_obj.contains_key("topP"), + "Expected anthropic wire output to omit topP" + ); + assert!( + anthropic_obj.contains_key("stop_sequences"), + "Expected anthropic wire output to include stop_sequences" + ); + assert!( + !anthropic_obj.contains_key("stopSequences"), + "Expected anthropic wire output to omit stopSequences" + ); + let anthropic_restored = ModelOptions::from_wire("anthropic", &anthropic_wire, &ctx); + let anthropic_round = anthropic_restored.to_wire("anthropic"); + let mut anthropic_expected: Vec<&String> = anthropic_obj.keys().collect(); + let mut anthropic_actual: Vec<&String> = anthropic_round + .as_object() + .expect("to_wire returns an object") + .keys() + .collect(); + anthropic_expected.sort(); + anthropic_actual.sort(); + assert_eq!( + anthropic_expected, anthropic_actual, + "Expected anthropic round-trip to preserve wire keys" + ); +} + +#[test] +fn test_model_options_from_json_invalid() { + let ctx = LoadContext::default(); + assert!( + ModelOptions::from_json("{", &ctx).is_err(), + "malformed JSON must be rejected instead of silently defaulting" + ); +} diff --git a/runtime/rust/prompty/tests/model/contracts/models/model_test.rs b/runtime/rust/prompty/tests/model/contracts/models/model_test.rs index 51f575a54..291f80a19 100644 --- a/runtime/rust/prompty/tests/model/contracts/models/model_test.rs +++ b/runtime/rust/prompty/tests/model/contracts/models/model_test.rs @@ -91,6 +91,15 @@ fn test_model_roundtrip() { assert!(result.is_ok(), "Failed to load: {:?}", result.err()); } +#[test] +fn test_model_from_json_invalid() { + let ctx = LoadContext::default(); + assert!( + Model::from_json("{", &ctx).is_err(), + "malformed JSON must be rejected instead of silently defaulting" + ); +} + #[test] fn test_model_from_model() { let value = serde_json::json!("example"); diff --git a/runtime/rust/prompty/tests/model/contracts/models/token_usage_test.rs b/runtime/rust/prompty/tests/model/contracts/models/token_usage_test.rs index 6f0e8d8c4..dc559cb55 100644 --- a/runtime/rust/prompty/tests/model/contracts/models/token_usage_test.rs +++ b/runtime/rust/prompty/tests/model/contracts/models/token_usage_test.rs @@ -130,3 +130,99 @@ fn test_token_usage_serde_roundtrip() { let reparsed: TokenUsage = serde_json::from_value(value).expect("serde should re-deserialize"); assert_eq!(instance, reparsed, "serde round-trip must be stable"); } + +#[test] +fn test_token_usage_wire_conversion() { + let json = r####" +{ + "promptTokens": 150, + "completionTokens": 42, + "totalTokens": 192 +} +"####; + let ctx = LoadContext::default(); + let instance = TokenUsage::from_json(json, &ctx).expect("load should succeed"); + let openai_wire = instance.to_wire("openai"); + let openai_obj = openai_wire.as_object().expect("to_wire returns an object"); + assert!( + openai_obj.contains_key("prompt_tokens"), + "Expected openai wire output to include prompt_tokens" + ); + assert!( + !openai_obj.contains_key("promptTokens"), + "Expected openai wire output to omit promptTokens" + ); + assert!( + openai_obj.contains_key("completion_tokens"), + "Expected openai wire output to include completion_tokens" + ); + assert!( + !openai_obj.contains_key("completionTokens"), + "Expected openai wire output to omit completionTokens" + ); + assert!( + openai_obj.contains_key("total_tokens"), + "Expected openai wire output to include total_tokens" + ); + assert!( + !openai_obj.contains_key("totalTokens"), + "Expected openai wire output to omit totalTokens" + ); + let openai_restored = TokenUsage::from_wire("openai", &openai_wire, &ctx); + let openai_round = openai_restored.to_wire("openai"); + let mut openai_expected: Vec<&String> = openai_obj.keys().collect(); + let mut openai_actual: Vec<&String> = openai_round + .as_object() + .expect("to_wire returns an object") + .keys() + .collect(); + openai_expected.sort(); + openai_actual.sort(); + assert_eq!( + openai_expected, openai_actual, + "Expected openai round-trip to preserve wire keys" + ); + let anthropic_wire = instance.to_wire("anthropic"); + let anthropic_obj = anthropic_wire + .as_object() + .expect("to_wire returns an object"); + assert!( + anthropic_obj.contains_key("input_tokens"), + "Expected anthropic wire output to include input_tokens" + ); + assert!( + !anthropic_obj.contains_key("promptTokens"), + "Expected anthropic wire output to omit promptTokens" + ); + assert!( + anthropic_obj.contains_key("output_tokens"), + "Expected anthropic wire output to include output_tokens" + ); + assert!( + !anthropic_obj.contains_key("completionTokens"), + "Expected anthropic wire output to omit completionTokens" + ); + let anthropic_restored = TokenUsage::from_wire("anthropic", &anthropic_wire, &ctx); + let anthropic_round = anthropic_restored.to_wire("anthropic"); + let mut anthropic_expected: Vec<&String> = anthropic_obj.keys().collect(); + let mut anthropic_actual: Vec<&String> = anthropic_round + .as_object() + .expect("to_wire returns an object") + .keys() + .collect(); + anthropic_expected.sort(); + anthropic_actual.sort(); + assert_eq!( + anthropic_expected, anthropic_actual, + "Expected anthropic round-trip to preserve wire keys" + ); +} + +#[test] +fn test_token_usage_from_json_invalid() { + let ctx = LoadContext::default(); + assert!( + TokenUsage::from_json("{", &ctx).is_err(), + "malformed JSON must be rejected instead of silently defaulting" + ); +} diff --git a/runtime/rust/prompty/tests/model/contracts/pipeline/delegated_state_reference_test.rs b/runtime/rust/prompty/tests/model/contracts/pipeline/delegated_state_reference_test.rs index 1087292bf..eb516c94c 100644 --- a/runtime/rust/prompty/tests/model/contracts/pipeline/delegated_state_reference_test.rs +++ b/runtime/rust/prompty/tests/model/contracts/pipeline/delegated_state_reference_test.rs @@ -110,3 +110,12 @@ fn test_delegated_state_reference_serde_roundtrip() { serde_json::from_value(value).expect("serde should re-deserialize"); assert_eq!(instance, reparsed, "serde round-trip must be stable"); } + +#[test] +fn test_delegated_state_reference_from_json_invalid() { + let ctx = LoadContext::default(); + assert!( + DelegatedStateReference::from_json("{", &ctx).is_err(), + "malformed JSON must be rejected instead of silently defaulting" + ); +} diff --git a/runtime/rust/prompty/tests/model/contracts/pipeline/engine_checkpoint_test.rs b/runtime/rust/prompty/tests/model/contracts/pipeline/engine_checkpoint_test.rs index 47e00cfe5..5884f826b 100644 --- a/runtime/rust/prompty/tests/model/contracts/pipeline/engine_checkpoint_test.rs +++ b/runtime/rust/prompty/tests/model/contracts/pipeline/engine_checkpoint_test.rs @@ -165,3 +165,12 @@ fn test_engine_checkpoint_serde_roundtrip() { serde_json::from_value(value).expect("serde should re-deserialize"); assert_eq!(instance, reparsed, "serde round-trip must be stable"); } + +#[test] +fn test_engine_checkpoint_from_json_invalid() { + let ctx = LoadContext::default(); + assert!( + EngineCheckpoint::from_json("{", &ctx).is_err(), + "malformed JSON must be rejected instead of silently defaulting" + ); +} diff --git a/runtime/rust/prompty/tests/model/contracts/pipeline/engine_event_test.rs b/runtime/rust/prompty/tests/model/contracts/pipeline/engine_event_test.rs index 49bc0c97e..f650408ef 100644 --- a/runtime/rust/prompty/tests/model/contracts/pipeline/engine_event_test.rs +++ b/runtime/rust/prompty/tests/model/contracts/pipeline/engine_event_test.rs @@ -118,3 +118,12 @@ fn test_engine_event_serde_roundtrip() { let reparsed: EngineEvent = serde_json::from_value(value).expect("serde should re-deserialize"); assert_eq!(instance, reparsed, "serde round-trip must be stable"); } + +#[test] +fn test_engine_event_from_json_invalid() { + let ctx = LoadContext::default(); + assert!( + EngineEvent::from_json("{", &ctx).is_err(), + "malformed JSON must be rejected instead of silently defaulting" + ); +} diff --git a/runtime/rust/prompty/tests/model/contracts/pipeline/engine_permission_decision_test.rs b/runtime/rust/prompty/tests/model/contracts/pipeline/engine_permission_decision_test.rs index 5f9be1e7b..941e907ab 100644 --- a/runtime/rust/prompty/tests/model/contracts/pipeline/engine_permission_decision_test.rs +++ b/runtime/rust/prompty/tests/model/contracts/pipeline/engine_permission_decision_test.rs @@ -98,3 +98,12 @@ fn test_engine_permission_decision_serde_roundtrip() { serde_json::from_value(value).expect("serde should re-deserialize"); assert_eq!(instance, reparsed, "serde round-trip must be stable"); } + +#[test] +fn test_engine_permission_decision_from_json_invalid() { + let ctx = LoadContext::default(); + assert!( + EnginePermissionDecision::from_json("{", &ctx).is_err(), + "malformed JSON must be rejected instead of silently defaulting" + ); +} diff --git a/runtime/rust/prompty/tests/model/contracts/pipeline/invocation_context_decision_test.rs b/runtime/rust/prompty/tests/model/contracts/pipeline/invocation_context_decision_test.rs index e7aedc91a..e83b6a607 100644 --- a/runtime/rust/prompty/tests/model/contracts/pipeline/invocation_context_decision_test.rs +++ b/runtime/rust/prompty/tests/model/contracts/pipeline/invocation_context_decision_test.rs @@ -101,3 +101,12 @@ fn test_invocation_context_decision_serde_roundtrip() { serde_json::from_value(value).expect("serde should re-deserialize"); assert_eq!(instance, reparsed, "serde round-trip must be stable"); } + +#[test] +fn test_invocation_context_decision_from_json_invalid() { + let ctx = LoadContext::default(); + assert!( + InvocationContextDecision::from_json("{", &ctx).is_err(), + "malformed JSON must be rejected instead of silently defaulting" + ); +} diff --git a/runtime/rust/prompty/tests/model/contracts/pipeline/invocation_context_state_test.rs b/runtime/rust/prompty/tests/model/contracts/pipeline/invocation_context_state_test.rs index 26ba70b24..07b99d461 100644 --- a/runtime/rust/prompty/tests/model/contracts/pipeline/invocation_context_state_test.rs +++ b/runtime/rust/prompty/tests/model/contracts/pipeline/invocation_context_state_test.rs @@ -89,3 +89,12 @@ fn test_invocation_context_state_serde_roundtrip() { serde_json::from_value(value).expect("serde should re-deserialize"); assert_eq!(instance, reparsed, "serde round-trip must be stable"); } + +#[test] +fn test_invocation_context_state_from_json_invalid() { + let ctx = LoadContext::default(); + assert!( + InvocationContextState::from_json("{", &ctx).is_err(), + "malformed JSON must be rejected instead of silently defaulting" + ); +} diff --git a/runtime/rust/prompty/tests/model/contracts/pipeline/model_invocation_context_snapshot_test.rs b/runtime/rust/prompty/tests/model/contracts/pipeline/model_invocation_context_snapshot_test.rs index e8c09cb84..fa400e9b3 100644 --- a/runtime/rust/prompty/tests/model/contracts/pipeline/model_invocation_context_snapshot_test.rs +++ b/runtime/rust/prompty/tests/model/contracts/pipeline/model_invocation_context_snapshot_test.rs @@ -165,3 +165,12 @@ fn test_model_invocation_context_snapshot_serde_roundtrip() { serde_json::from_value(value).expect("serde should re-deserialize"); assert_eq!(instance, reparsed, "serde round-trip must be stable"); } + +#[test] +fn test_model_invocation_context_snapshot_from_json_invalid() { + let ctx = LoadContext::default(); + assert!( + ModelInvocationContextSnapshot::from_json("{", &ctx).is_err(), + "malformed JSON must be rejected instead of silently defaulting" + ); +} diff --git a/runtime/rust/prompty/tests/model/contracts/pipeline/model_invocation_request_test.rs b/runtime/rust/prompty/tests/model/contracts/pipeline/model_invocation_request_test.rs index f0adaedfd..0ff207059 100644 --- a/runtime/rust/prompty/tests/model/contracts/pipeline/model_invocation_request_test.rs +++ b/runtime/rust/prompty/tests/model/contracts/pipeline/model_invocation_request_test.rs @@ -125,3 +125,12 @@ fn test_model_invocation_request_serde_roundtrip() { serde_json::from_value(value).expect("serde should re-deserialize"); assert_eq!(instance, reparsed, "serde round-trip must be stable"); } + +#[test] +fn test_model_invocation_request_from_json_invalid() { + let ctx = LoadContext::default(); + assert!( + ModelInvocationRequest::from_json("{", &ctx).is_err(), + "malformed JSON must be rejected instead of silently defaulting" + ); +} diff --git a/runtime/rust/prompty/tests/model/contracts/pipeline/model_invocation_response_test.rs b/runtime/rust/prompty/tests/model/contracts/pipeline/model_invocation_response_test.rs index e9f5f5503..57bacd8d1 100644 --- a/runtime/rust/prompty/tests/model/contracts/pipeline/model_invocation_response_test.rs +++ b/runtime/rust/prompty/tests/model/contracts/pipeline/model_invocation_response_test.rs @@ -92,3 +92,12 @@ fn test_model_invocation_response_serde_roundtrip() { serde_json::from_value(value).expect("serde should re-deserialize"); assert_eq!(instance, reparsed, "serde round-trip must be stable"); } + +#[test] +fn test_model_invocation_response_from_json_invalid() { + let ctx = LoadContext::default(); + assert!( + ModelInvocationResponse::from_json("{", &ctx).is_err(), + "malformed JSON must be rejected instead of silently defaulting" + ); +} diff --git a/runtime/rust/prompty/tests/model/contracts/pipeline/model_reconciliation_state_test.rs b/runtime/rust/prompty/tests/model/contracts/pipeline/model_reconciliation_state_test.rs index 3f01f88e3..b6d06c1c7 100644 --- a/runtime/rust/prompty/tests/model/contracts/pipeline/model_reconciliation_state_test.rs +++ b/runtime/rust/prompty/tests/model/contracts/pipeline/model_reconciliation_state_test.rs @@ -148,3 +148,12 @@ fn test_model_reconciliation_state_serde_roundtrip() { serde_json::from_value(value).expect("serde should re-deserialize"); assert_eq!(instance, reparsed, "serde round-trip must be stable"); } + +#[test] +fn test_model_reconciliation_state_from_json_invalid() { + let ctx = LoadContext::default(); + assert!( + ModelReconciliationState::from_json("{", &ctx).is_err(), + "malformed JSON must be rejected instead of silently defaulting" + ); +} diff --git a/runtime/rust/prompty/tests/model/contracts/pipeline/model_tool_request_test.rs b/runtime/rust/prompty/tests/model/contracts/pipeline/model_tool_request_test.rs index 09b05f4f1..8a1300f4f 100644 --- a/runtime/rust/prompty/tests/model/contracts/pipeline/model_tool_request_test.rs +++ b/runtime/rust/prompty/tests/model/contracts/pipeline/model_tool_request_test.rs @@ -104,3 +104,12 @@ fn test_model_tool_request_serde_roundtrip() { serde_json::from_value(value).expect("serde should re-deserialize"); assert_eq!(instance, reparsed, "serde round-trip must be stable"); } + +#[test] +fn test_model_tool_request_from_json_invalid() { + let ctx = LoadContext::default(); + assert!( + ModelToolRequest::from_json("{", &ctx).is_err(), + "malformed JSON must be rejected instead of silently defaulting" + ); +} diff --git a/runtime/rust/prompty/tests/model/contracts/pipeline/model_tool_result_test.rs b/runtime/rust/prompty/tests/model/contracts/pipeline/model_tool_result_test.rs index f9b8deeb2..d5ab46eec 100644 --- a/runtime/rust/prompty/tests/model/contracts/pipeline/model_tool_result_test.rs +++ b/runtime/rust/prompty/tests/model/contracts/pipeline/model_tool_result_test.rs @@ -101,3 +101,12 @@ fn test_model_tool_result_serde_roundtrip() { serde_json::from_value(value).expect("serde should re-deserialize"); assert_eq!(instance, reparsed, "serde round-trip must be stable"); } + +#[test] +fn test_model_tool_result_from_json_invalid() { + let ctx = LoadContext::default(); + assert!( + ModelToolResult::from_json("{", &ctx).is_err(), + "malformed JSON must be rejected instead of silently defaulting" + ); +} diff --git a/runtime/rust/prompty/tests/model/contracts/pipeline/replay_journal_record_test.rs b/runtime/rust/prompty/tests/model/contracts/pipeline/replay_journal_record_test.rs index 260bb0ed6..463d45a65 100644 --- a/runtime/rust/prompty/tests/model/contracts/pipeline/replay_journal_record_test.rs +++ b/runtime/rust/prompty/tests/model/contracts/pipeline/replay_journal_record_test.rs @@ -100,3 +100,12 @@ fn test_replay_journal_record_serde_roundtrip() { serde_json::from_value(value).expect("serde should re-deserialize"); assert_eq!(instance, reparsed, "serde round-trip must be stable"); } + +#[test] +fn test_replay_journal_record_from_json_invalid() { + let ctx = LoadContext::default(); + assert!( + ReplayJournalRecord::from_json("{", &ctx).is_err(), + "malformed JSON must be rejected instead of silently defaulting" + ); +} diff --git a/runtime/rust/prompty/tests/model/contracts/pipeline/resume_context_test.rs b/runtime/rust/prompty/tests/model/contracts/pipeline/resume_context_test.rs index 2bdc39117..9720a38ab 100644 --- a/runtime/rust/prompty/tests/model/contracts/pipeline/resume_context_test.rs +++ b/runtime/rust/prompty/tests/model/contracts/pipeline/resume_context_test.rs @@ -133,3 +133,12 @@ fn test_resume_context_serde_roundtrip() { serde_json::from_value(value).expect("serde should re-deserialize"); assert_eq!(instance, reparsed, "serde round-trip must be stable"); } + +#[test] +fn test_resume_context_from_json_invalid() { + let ctx = LoadContext::default(); + assert!( + ResumeContext::from_json("{", &ctx).is_err(), + "malformed JSON must be rejected instead of silently defaulting" + ); +} diff --git a/runtime/rust/prompty/tests/model/contracts/pipeline/turn_commit_test.rs b/runtime/rust/prompty/tests/model/contracts/pipeline/turn_commit_test.rs index 731732476..4c884c9d1 100644 --- a/runtime/rust/prompty/tests/model/contracts/pipeline/turn_commit_test.rs +++ b/runtime/rust/prompty/tests/model/contracts/pipeline/turn_commit_test.rs @@ -153,3 +153,12 @@ fn test_turn_commit_serde_roundtrip() { let reparsed: TurnCommit = serde_json::from_value(value).expect("serde should re-deserialize"); assert_eq!(instance, reparsed, "serde round-trip must be stable"); } + +#[test] +fn test_turn_commit_from_json_invalid() { + let ctx = LoadContext::default(); + assert!( + TurnCommit::from_json("{", &ctx).is_err(), + "malformed JSON must be rejected instead of silently defaulting" + ); +} diff --git a/runtime/rust/prompty/tests/model/contracts/templates/format_config_test.rs b/runtime/rust/prompty/tests/model/contracts/templates/format_config_test.rs index 5d49509c6..35505e8d6 100644 --- a/runtime/rust/prompty/tests/model/contracts/templates/format_config_test.rs +++ b/runtime/rust/prompty/tests/model/contracts/templates/format_config_test.rs @@ -67,6 +67,15 @@ fn test_format_config_roundtrip() { assert!(result.is_ok(), "Failed to load: {:?}", result.err()); } +#[test] +fn test_format_config_from_json_invalid() { + let ctx = LoadContext::default(); + assert!( + FormatConfig::from_json("{", &ctx).is_err(), + "malformed JSON must be rejected instead of silently defaulting" + ); +} + #[test] fn test_format_config_from_format() { let value = serde_json::json!("example"); diff --git a/runtime/rust/prompty/tests/model/contracts/templates/parser_config_test.rs b/runtime/rust/prompty/tests/model/contracts/templates/parser_config_test.rs index 8e6a68ba3..b19b87359 100644 --- a/runtime/rust/prompty/tests/model/contracts/templates/parser_config_test.rs +++ b/runtime/rust/prompty/tests/model/contracts/templates/parser_config_test.rs @@ -64,6 +64,15 @@ fn test_parser_config_roundtrip() { assert!(result.is_ok(), "Failed to load: {:?}", result.err()); } +#[test] +fn test_parser_config_from_json_invalid() { + let ctx = LoadContext::default(); + assert!( + ParserConfig::from_json("{", &ctx).is_err(), + "malformed JSON must be rejected instead of silently defaulting" + ); +} + #[test] fn test_parser_config_from_parser() { let value = serde_json::json!("example"); diff --git a/runtime/rust/prompty/tests/model/contracts/templates/template_test.rs b/runtime/rust/prompty/tests/model/contracts/templates/template_test.rs index 207a43554..6479f23da 100644 --- a/runtime/rust/prompty/tests/model/contracts/templates/template_test.rs +++ b/runtime/rust/prompty/tests/model/contracts/templates/template_test.rs @@ -111,3 +111,12 @@ fn test_template_serde_roundtrip() { let reparsed: Template = serde_json::from_value(value).expect("serde should re-deserialize"); assert_eq!(instance, reparsed, "serde round-trip must be stable"); } + +#[test] +fn test_template_from_json_invalid() { + let ctx = LoadContext::default(); + assert!( + Template::from_json("{", &ctx).is_err(), + "malformed JSON must be rejected instead of silently defaulting" + ); +} diff --git a/runtime/rust/prompty/tests/model/contracts/tooling/binding_test.rs b/runtime/rust/prompty/tests/model/contracts/tooling/binding_test.rs index 4f2d075df..539e3072b 100644 --- a/runtime/rust/prompty/tests/model/contracts/tooling/binding_test.rs +++ b/runtime/rust/prompty/tests/model/contracts/tooling/binding_test.rs @@ -100,6 +100,15 @@ fn test_binding_serde_roundtrip() { assert_eq!(instance, reparsed, "serde round-trip must be stable"); } +#[test] +fn test_binding_from_json_invalid() { + let ctx = LoadContext::default(); + assert!( + Binding::from_json("{", &ctx).is_err(), + "malformed JSON must be rejected instead of silently defaulting" + ); +} + #[test] fn test_binding_from_string() { let value = serde_json::json!("example"); diff --git a/runtime/rust/prompty/tests/model/contracts/tooling/mcp_approval_mode_test.rs b/runtime/rust/prompty/tests/model/contracts/tooling/mcp_approval_mode_test.rs index ab6f45828..883b8a53e 100644 --- a/runtime/rust/prompty/tests/model/contracts/tooling/mcp_approval_mode_test.rs +++ b/runtime/rust/prompty/tests/model/contracts/tooling/mcp_approval_mode_test.rs @@ -118,6 +118,15 @@ fn test_mcp_approval_mode_serde_roundtrip() { assert_eq!(instance, reparsed, "serde round-trip must be stable"); } +#[test] +fn test_mcp_approval_mode_from_json_invalid() { + let ctx = LoadContext::default(); + assert!( + McpApprovalMode::from_json("{", &ctx).is_err(), + "malformed JSON must be rejected instead of silently defaulting" + ); +} + #[test] fn test_mcp_approval_mode_from_kind() { let value = serde_json::json!("never"); diff --git a/runtime/rust/prompty/tests/model/contracts/tooling/tool_test.rs b/runtime/rust/prompty/tests/model/contracts/tooling/tool_test.rs index 705d11af1..6b5858eb0 100644 --- a/runtime/rust/prompty/tests/model/contracts/tooling/tool_test.rs +++ b/runtime/rust/prompty/tests/model/contracts/tooling/tool_test.rs @@ -69,3 +69,12 @@ fn test_tool_roundtrip() { let result = Tool::from_json(json, &load_ctx); assert!(result.is_ok(), "Failed to load: {:?}", result.err()); } + +#[test] +fn test_tool_from_json_invalid() { + let ctx = LoadContext::default(); + assert!( + Tool::from_json("{", &ctx).is_err(), + "malformed JSON must be rejected instead of silently defaulting" + ); +} diff --git a/runtime/rust/prompty/tests/model/operations/pipeline/render_segment_test.rs b/runtime/rust/prompty/tests/model/operations/pipeline/render_segment_test.rs index 0fbecef4e..7bfb028a9 100644 --- a/runtime/rust/prompty/tests/model/operations/pipeline/render_segment_test.rs +++ b/runtime/rust/prompty/tests/model/operations/pipeline/render_segment_test.rs @@ -105,3 +105,12 @@ fn test_render_segment_serde_roundtrip() { serde_json::from_value(value).expect("serde should re-deserialize"); assert_eq!(instance, reparsed, "serde round-trip must be stable"); } + +#[test] +fn test_render_segment_from_json_invalid() { + let ctx = LoadContext::default(); + assert!( + RenderSegment::from_json("{", &ctx).is_err(), + "malformed JSON must be rejected instead of silently defaulting" + ); +} diff --git a/runtime/swift/prompty-model/Sources/PromptyModel/Contracts/Models/model_options.swift b/runtime/swift/prompty-model/Sources/PromptyModel/Contracts/Models/model_options.swift index 9356ef797..524274e40 100644 --- a/runtime/swift/prompty-model/Sources/PromptyModel/Contracts/Models/model_options.swift +++ b/runtime/swift/prompty-model/Sources/PromptyModel/Contracts/Models/model_options.swift @@ -3,12 +3,37 @@ import Foundation +public struct ReasoningEffort: RawRepresentable, Equatable, Hashable, Codable { + public let rawValue: String + public init(rawValue: String) { self.rawValue = rawValue } + public static let `none` = ReasoningEffort(rawValue: "none") + public static let minimal = ReasoningEffort(rawValue: "minimal") + public static let low = ReasoningEffort(rawValue: "low") + public static let medium = ReasoningEffort(rawValue: "medium") + public static let high = ReasoningEffort(rawValue: "high") + public static let xhigh = ReasoningEffort(rawValue: "xhigh") + public static let max = ReasoningEffort(rawValue: "max") + public static func parse(_ value: String) throws -> ReasoningEffort { + switch value { + case "none": return .`none` + case "minimal": return .minimal + case "low": return .low + case "medium": return .medium + case "high": return .high + case "xhigh": return .xhigh + case "max": return .max + default: return ReasoningEffort(rawValue: value) + } + } +} + /// Options for configuring the behavior of the AI model. public struct ModelOptions: TypraModel { public static let shorthandProperty: String? = nil public var frequencyPenalty: Float? = nil public var maxOutputTokens: Int32? = nil public var presencePenalty: Float? = nil + public var reasoningEffort: ReasoningEffort? = nil public var seed: Int32? = nil public var temperature: Float? = nil public var topK: Int32? = nil @@ -17,10 +42,11 @@ public struct ModelOptions: TypraModel { public var allowMultipleToolCalls: Bool? = nil public var additionalProperties: [String: Any]? = nil - public init(frequencyPenalty: Float? = nil, maxOutputTokens: Int32? = nil, presencePenalty: Float? = nil, seed: Int32? = nil, temperature: Float? = nil, topK: Int32? = nil, topP: Float? = nil, stopSequences: [String]? = nil, allowMultipleToolCalls: Bool? = nil, additionalProperties: [String: Any]? = nil) { + public init(frequencyPenalty: Float? = nil, maxOutputTokens: Int32? = nil, presencePenalty: Float? = nil, reasoningEffort: ReasoningEffort? = nil, seed: Int32? = nil, temperature: Float? = nil, topK: Int32? = nil, topP: Float? = nil, stopSequences: [String]? = nil, allowMultipleToolCalls: Bool? = nil, additionalProperties: [String: Any]? = nil) { self.frequencyPenalty = frequencyPenalty self.maxOutputTokens = maxOutputTokens self.presencePenalty = presencePenalty + self.reasoningEffort = reasoningEffort self.seed = seed self.temperature = temperature self.topK = topK @@ -42,6 +68,9 @@ public struct ModelOptions: TypraModel { if let value = object["presencePenalty"] { instance.presencePenalty = try TypraRuntime.float(value, field: "presencePenalty") } + if let value = object["reasoningEffort"] { + instance.reasoningEffort = try ReasoningEffort.parse(try TypraRuntime.string(value, field: "reasoningEffort")) + } if let value = object["seed"] { instance.seed = try TypraRuntime.int32(value, field: "seed") } @@ -77,6 +106,9 @@ public struct ModelOptions: TypraModel { if let value = self.presencePenalty { result["presencePenalty"] = value } + if let value = self.reasoningEffort { + result["reasoningEffort"] = value.rawValue + } if let value = self.seed { result["seed"] = value } @@ -123,6 +155,13 @@ public struct ModelOptions: TypraModel { default: wireNamePresencePenalty = nil } if let wireKey = wireNamePresencePenalty, let value = self.presencePenalty { result[wireKey] = value } + let wireNameReasoningEffort: String? + switch provider { + case "openai": wireNameReasoningEffort = "reasoning_effort" + case "responses": wireNameReasoningEffort = "reasoning_effort" + default: wireNameReasoningEffort = nil + } + if let wireKey = wireNameReasoningEffort, let value = self.reasoningEffort { result[wireKey] = value.rawValue } let wireNameSeed: String? switch provider { case "openai": wireNameSeed = "seed" @@ -173,6 +212,7 @@ public struct ModelOptions: TypraModel { "frequencyPenalty": ["openai": "frequency_penalty"], "maxOutputTokens": ["openai": "max_completion_tokens", "responses": "max_output_tokens", "anthropic": "max_tokens"], "presencePenalty": ["openai": "presence_penalty"], + "reasoningEffort": ["openai": "reasoning_effort", "responses": "reasoning_effort"], "seed": ["openai": "seed"], "temperature": ["openai": "temperature", "responses": "temperature", "anthropic": "temperature"], "topK": ["openai": "top_k", "anthropic": "top_k"], diff --git a/runtime/swift/prompty-model/Tests/PromptyModelTests/AgentTests.swift b/runtime/swift/prompty-model/Tests/PromptyModelTests/AgentTests.swift index e0eb05402..0804de59a 100644 --- a/runtime/swift/prompty-model/Tests/PromptyModelTests/AgentTests.swift +++ b/runtime/swift/prompty-model/Tests/PromptyModelTests/AgentTests.swift @@ -1498,4 +1498,9 @@ instructions: "system:\\nYou are an AI assistant who helps people find informati XCTAssertEqual(try (try XCTUnwrap(reloaded.template)).parser.save()["kind"] as? String, "prompty") } + // Invalid-input test (malformed JSON must be rejected, issue #328 class) + func testFromJSONInvalid() throws { + XCTAssertThrowsError(try Agent.fromJSON("{")) + } + } diff --git a/runtime/swift/prompty-model/Tests/PromptyModelTests/Contracts/Connectivity/ConnectionTests.swift b/runtime/swift/prompty-model/Tests/PromptyModelTests/Contracts/Connectivity/ConnectionTests.swift index f0f1fc471..c1cd6947b 100644 --- a/runtime/swift/prompty-model/Tests/PromptyModelTests/Contracts/Connectivity/ConnectionTests.swift +++ b/runtime/swift/prompty-model/Tests/PromptyModelTests/Contracts/Connectivity/ConnectionTests.swift @@ -53,4 +53,9 @@ usageDescription: This will allow the agent to respond to an email on your behal } } + // Invalid-input test (malformed JSON must be rejected, issue #328 class) + func testFromJSONInvalid() throws { + XCTAssertThrowsError(try Connection.fromJSON("{")) + } + } diff --git a/runtime/swift/prompty-model/Tests/PromptyModelTests/Contracts/Connectivity/DeviceAuthorizationTests.swift b/runtime/swift/prompty-model/Tests/PromptyModelTests/Contracts/Connectivity/DeviceAuthorizationTests.swift index 8dd7bd1ea..738d9f1ad 100644 --- a/runtime/swift/prompty-model/Tests/PromptyModelTests/Contracts/Connectivity/DeviceAuthorizationTests.swift +++ b/runtime/swift/prompty-model/Tests/PromptyModelTests/Contracts/Connectivity/DeviceAuthorizationTests.swift @@ -53,4 +53,34 @@ interval: 1 XCTAssertEqual(reloaded.interval, 1) } + + func testWireConversion() throws { + let json = """ +{ + "deviceCode": "sample", + "userCode": "sample", + "verificationUri": "sample", + "expiresIn": 1, + "interval": 1 +} +""" + let instance = try DeviceAuthorization.fromJSON(json) + let foundryWire = try instance.toWire("foundry") + XCTAssertNotNil(foundryWire["device_code"]) + XCTAssertNil(foundryWire["deviceCode"]) + XCTAssertNotNil(foundryWire["user_code"]) + XCTAssertNil(foundryWire["userCode"]) + XCTAssertNotNil(foundryWire["verification_uri"]) + XCTAssertNil(foundryWire["verificationUri"]) + XCTAssertNotNil(foundryWire["expires_in"]) + XCTAssertNil(foundryWire["expiresIn"]) + XCTAssertNotNil(foundryWire["interval"]) + let foundryRestored = try DeviceAuthorization.fromWire("foundry", foundryWire) + XCTAssertEqual(Set(try foundryRestored.toWire("foundry").keys), Set(foundryWire.keys)) + } + // Invalid-input test (malformed JSON must be rejected, issue #328 class) + func testFromJSONInvalid() throws { + XCTAssertThrowsError(try DeviceAuthorization.fromJSON("{")) + } + } diff --git a/runtime/swift/prompty-model/Tests/PromptyModelTests/Contracts/Connectivity/OAuthTokenTests.swift b/runtime/swift/prompty-model/Tests/PromptyModelTests/Contracts/Connectivity/OAuthTokenTests.swift index 142ec70d3..d3a87b084 100644 --- a/runtime/swift/prompty-model/Tests/PromptyModelTests/Contracts/Connectivity/OAuthTokenTests.swift +++ b/runtime/swift/prompty-model/Tests/PromptyModelTests/Contracts/Connectivity/OAuthTokenTests.swift @@ -35,4 +35,25 @@ scope: "https://cognitiveservices.azure.com/.default offline_access" XCTAssertEqual((try XCTUnwrap(reloaded.scope)), "https://cognitiveservices.azure.com/.default offline_access") } + + func testWireConversion() throws { + let json = """ +{ + "refreshToken": "0.AXoAoffline-refresh-token-value", + "scope": "https://cognitiveservices.azure.com/.default offline_access" +} +""" + let instance = try OAuthToken.fromJSON(json) + let foundryWire = try instance.toWire("foundry") + XCTAssertNotNil(foundryWire["refresh_token"]) + XCTAssertNil(foundryWire["refreshToken"]) + XCTAssertNotNil(foundryWire["scope"]) + let foundryRestored = try OAuthToken.fromWire("foundry", foundryWire) + XCTAssertEqual(Set(try foundryRestored.toWire("foundry").keys), Set(foundryWire.keys)) + } + // Invalid-input test (malformed JSON must be rejected, issue #328 class) + func testFromJSONInvalid() throws { + XCTAssertThrowsError(try OAuthToken.fromJSON("{")) + } + } diff --git a/runtime/swift/prompty-model/Tests/PromptyModelTests/Contracts/Conversation/ContentPartTests.swift b/runtime/swift/prompty-model/Tests/PromptyModelTests/Contracts/Conversation/ContentPartTests.swift index 02a432119..895947128 100644 --- a/runtime/swift/prompty-model/Tests/PromptyModelTests/Contracts/Conversation/ContentPartTests.swift +++ b/runtime/swift/prompty-model/Tests/PromptyModelTests/Contracts/Conversation/ContentPartTests.swift @@ -47,4 +47,9 @@ value: Hello, world! } } + // Invalid-input test (malformed JSON must be rejected, issue #328 class) + func testFromJSONInvalid() throws { + XCTAssertThrowsError(try ContentPart.fromJSON("{")) + } + } diff --git a/runtime/swift/prompty-model/Tests/PromptyModelTests/Contracts/Conversation/MessageTests.swift b/runtime/swift/prompty-model/Tests/PromptyModelTests/Contracts/Conversation/MessageTests.swift index ae674d46f..6f05bcaf2 100644 --- a/runtime/swift/prompty-model/Tests/PromptyModelTests/Contracts/Conversation/MessageTests.swift +++ b/runtime/swift/prompty-model/Tests/PromptyModelTests/Contracts/Conversation/MessageTests.swift @@ -51,4 +51,9 @@ metadata: XCTAssertNotNil(reloaded.metadata) } + // Invalid-input test (malformed JSON must be rejected, issue #328 class) + func testFromJSONInvalid() throws { + XCTAssertThrowsError(try Message.fromJSON("{")) + } + } diff --git a/runtime/swift/prompty-model/Tests/PromptyModelTests/Contracts/Conversation/ToolCallTests.swift b/runtime/swift/prompty-model/Tests/PromptyModelTests/Contracts/Conversation/ToolCallTests.swift index ee8b6b132..d7014d8bd 100644 --- a/runtime/swift/prompty-model/Tests/PromptyModelTests/Contracts/Conversation/ToolCallTests.swift +++ b/runtime/swift/prompty-model/Tests/PromptyModelTests/Contracts/Conversation/ToolCallTests.swift @@ -41,4 +41,9 @@ arguments: "{\\"city\\": \\"Paris\\"}" XCTAssertEqual(reloaded.arguments, "{\"city\": \"Paris\"}") } + // Invalid-input test (malformed JSON must be rejected, issue #328 class) + func testFromJSONInvalid() throws { + XCTAssertThrowsError(try ToolCall.fromJSON("{")) + } + } diff --git a/runtime/swift/prompty-model/Tests/PromptyModelTests/Contracts/Conversation/ToolResultTests.swift b/runtime/swift/prompty-model/Tests/PromptyModelTests/Contracts/Conversation/ToolResultTests.swift index cbc099b78..f8d8e8ff3 100644 --- a/runtime/swift/prompty-model/Tests/PromptyModelTests/Contracts/Conversation/ToolResultTests.swift +++ b/runtime/swift/prompty-model/Tests/PromptyModelTests/Contracts/Conversation/ToolResultTests.swift @@ -54,4 +54,9 @@ durationMs: 42 XCTAssertEqual(reloaded.parts.count, 1) } + // Invalid-input test (malformed JSON must be rejected, issue #328 class) + func testFromJSONInvalid() throws { + XCTAssertThrowsError(try ToolResult.fromJSON("{")) + } + } diff --git a/runtime/swift/prompty-model/Tests/PromptyModelTests/Contracts/Core/PropertyTests.swift b/runtime/swift/prompty-model/Tests/PromptyModelTests/Contracts/Core/PropertyTests.swift index 1f322ad35..847a0fb8f 100644 --- a/runtime/swift/prompty-model/Tests/PromptyModelTests/Contracts/Core/PropertyTests.swift +++ b/runtime/swift/prompty-model/Tests/PromptyModelTests/Contracts/Core/PropertyTests.swift @@ -130,4 +130,9 @@ enumValues: } } + // Invalid-input test (malformed JSON must be rejected, issue #328 class) + func testFromJSONInvalid() throws { + XCTAssertThrowsError(try Property.fromJSON("{")) + } + } diff --git a/runtime/swift/prompty-model/Tests/PromptyModelTests/Contracts/Events/CheckpointTests.swift b/runtime/swift/prompty-model/Tests/PromptyModelTests/Contracts/Events/CheckpointTests.swift index 1c225384d..4783c0a83 100644 --- a/runtime/swift/prompty-model/Tests/PromptyModelTests/Contracts/Events/CheckpointTests.swift +++ b/runtime/swift/prompty-model/Tests/PromptyModelTests/Contracts/Events/CheckpointTests.swift @@ -59,4 +59,9 @@ createdAt: "2026-06-09T20:00:00Z" XCTAssertEqual((try XCTUnwrap(reloaded.createdAt)), "2026-06-09T20:00:00Z") } + // Invalid-input test (malformed JSON must be rejected, issue #328 class) + func testFromJSONInvalid() throws { + XCTAssertThrowsError(try Checkpoint.fromJSON("{")) + } + } diff --git a/runtime/swift/prompty-model/Tests/PromptyModelTests/Contracts/Events/HostToolRequestTests.swift b/runtime/swift/prompty-model/Tests/PromptyModelTests/Contracts/Events/HostToolRequestTests.swift index 10bd3b5e9..9fbc0c56d 100644 --- a/runtime/swift/prompty-model/Tests/PromptyModelTests/Contracts/Events/HostToolRequestTests.swift +++ b/runtime/swift/prompty-model/Tests/PromptyModelTests/Contracts/Events/HostToolRequestTests.swift @@ -47,4 +47,9 @@ workingDirectory: /workspace/project XCTAssertEqual((try XCTUnwrap(reloaded.workingDirectory)), "/workspace/project") } + // Invalid-input test (malformed JSON must be rejected, issue #328 class) + func testFromJSONInvalid() throws { + XCTAssertThrowsError(try HostToolRequest.fromJSON("{")) + } + } diff --git a/runtime/swift/prompty-model/Tests/PromptyModelTests/Contracts/Events/HostToolResultTests.swift b/runtime/swift/prompty-model/Tests/PromptyModelTests/Contracts/Events/HostToolResultTests.swift index a4d1e80d4..5fec15489 100644 --- a/runtime/swift/prompty-model/Tests/PromptyModelTests/Contracts/Events/HostToolResultTests.swift +++ b/runtime/swift/prompty-model/Tests/PromptyModelTests/Contracts/Events/HostToolResultTests.swift @@ -65,4 +65,9 @@ errorKind: timeout XCTAssertEqual((try XCTUnwrap(reloaded.errorKind)), "timeout") } + // Invalid-input test (malformed JSON must be rejected, issue #328 class) + func testFromJSONInvalid() throws { + XCTAssertThrowsError(try HostToolResult.fromJSON("{")) + } + } diff --git a/runtime/swift/prompty-model/Tests/PromptyModelTests/Contracts/Events/PermissionDecisionTests.swift b/runtime/swift/prompty-model/Tests/PromptyModelTests/Contracts/Events/PermissionDecisionTests.swift index a9ac6e1eb..81c27f84a 100644 --- a/runtime/swift/prompty-model/Tests/PromptyModelTests/Contracts/Events/PermissionDecisionTests.swift +++ b/runtime/swift/prompty-model/Tests/PromptyModelTests/Contracts/Events/PermissionDecisionTests.swift @@ -53,4 +53,9 @@ reason: user_approved XCTAssertEqual((try XCTUnwrap(reloaded.reason)), "user_approved") } + // Invalid-input test (malformed JSON must be rejected, issue #328 class) + func testFromJSONInvalid() throws { + XCTAssertThrowsError(try PermissionDecision.fromJSON("{")) + } + } diff --git a/runtime/swift/prompty-model/Tests/PromptyModelTests/Contracts/Events/PermissionRequestTests.swift b/runtime/swift/prompty-model/Tests/PromptyModelTests/Contracts/Events/PermissionRequestTests.swift index fec49fc3d..e1ed49ab7 100644 --- a/runtime/swift/prompty-model/Tests/PromptyModelTests/Contracts/Events/PermissionRequestTests.swift +++ b/runtime/swift/prompty-model/Tests/PromptyModelTests/Contracts/Events/PermissionRequestTests.swift @@ -53,4 +53,9 @@ promptRequest: Allow shell to run tests? XCTAssertEqual((try XCTUnwrap(reloaded.promptRequest)), "Allow shell to run tests?") } + // Invalid-input test (malformed JSON must be rejected, issue #328 class) + func testFromJSONInvalid() throws { + XCTAssertThrowsError(try PermissionRequest.fromJSON("{")) + } + } diff --git a/runtime/swift/prompty-model/Tests/PromptyModelTests/Contracts/Events/RedactedFieldTests.swift b/runtime/swift/prompty-model/Tests/PromptyModelTests/Contracts/Events/RedactedFieldTests.swift index 233a2fbe5..c1985ebb9 100644 --- a/runtime/swift/prompty-model/Tests/PromptyModelTests/Contracts/Events/RedactedFieldTests.swift +++ b/runtime/swift/prompty-model/Tests/PromptyModelTests/Contracts/Events/RedactedFieldTests.swift @@ -41,4 +41,9 @@ reason: secret XCTAssertEqual((try XCTUnwrap(reloaded.reason)), "secret") } + // Invalid-input test (malformed JSON must be rejected, issue #328 class) + func testFromJSONInvalid() throws { + XCTAssertThrowsError(try RedactedField.fromJSON("{")) + } + } diff --git a/runtime/swift/prompty-model/Tests/PromptyModelTests/Contracts/Events/RedactionMetadataTests.swift b/runtime/swift/prompty-model/Tests/PromptyModelTests/Contracts/Events/RedactionMetadataTests.swift index aec38b712..283168a13 100644 --- a/runtime/swift/prompty-model/Tests/PromptyModelTests/Contracts/Events/RedactionMetadataTests.swift +++ b/runtime/swift/prompty-model/Tests/PromptyModelTests/Contracts/Events/RedactionMetadataTests.swift @@ -35,4 +35,9 @@ policy: default-v1 XCTAssertEqual((try XCTUnwrap(reloaded.policy)), "default-v1") } + // Invalid-input test (malformed JSON must be rejected, issue #328 class) + func testFromJSONInvalid() throws { + XCTAssertThrowsError(try RedactionMetadata.fromJSON("{")) + } + } diff --git a/runtime/swift/prompty-model/Tests/PromptyModelTests/Contracts/Events/SessionEventTests.swift b/runtime/swift/prompty-model/Tests/PromptyModelTests/Contracts/Events/SessionEventTests.swift index a7f0d225b..d17b006cd 100644 --- a/runtime/swift/prompty-model/Tests/PromptyModelTests/Contracts/Events/SessionEventTests.swift +++ b/runtime/swift/prompty-model/Tests/PromptyModelTests/Contracts/Events/SessionEventTests.swift @@ -59,4 +59,9 @@ spanId: span_hook_001 XCTAssertEqual((try XCTUnwrap(reloaded.spanId)), "span_hook_001") } + // Invalid-input test (malformed JSON must be rejected, issue #328 class) + func testFromJSONInvalid() throws { + XCTAssertThrowsError(try SessionEvent.fromJSON("{")) + } + } diff --git a/runtime/swift/prompty-model/Tests/PromptyModelTests/Contracts/Events/SessionSummaryTests.swift b/runtime/swift/prompty-model/Tests/PromptyModelTests/Contracts/Events/SessionSummaryTests.swift index 8934221be..fb0a330b9 100644 --- a/runtime/swift/prompty-model/Tests/PromptyModelTests/Contracts/Events/SessionSummaryTests.swift +++ b/runtime/swift/prompty-model/Tests/PromptyModelTests/Contracts/Events/SessionSummaryTests.swift @@ -53,4 +53,9 @@ durationMs: 12500 XCTAssertEqual((try XCTUnwrap(reloaded.durationMs)), 12500) } + // Invalid-input test (malformed JSON must be rejected, issue #328 class) + func testFromJSONInvalid() throws { + XCTAssertThrowsError(try SessionSummary.fromJSON("{")) + } + } diff --git a/runtime/swift/prompty-model/Tests/PromptyModelTests/Contracts/Events/StreamChunkTests.swift b/runtime/swift/prompty-model/Tests/PromptyModelTests/Contracts/Events/StreamChunkTests.swift index 91b3804ea..e3ff7c240 100644 --- a/runtime/swift/prompty-model/Tests/PromptyModelTests/Contracts/Events/StreamChunkTests.swift +++ b/runtime/swift/prompty-model/Tests/PromptyModelTests/Contracts/Events/StreamChunkTests.swift @@ -47,4 +47,9 @@ value: Hello } } + // Invalid-input test (malformed JSON must be rejected, issue #328 class) + func testFromJSONInvalid() throws { + XCTAssertThrowsError(try StreamChunk.fromJSON("{")) + } + } diff --git a/runtime/swift/prompty-model/Tests/PromptyModelTests/Contracts/Events/StreamFailureTests.swift b/runtime/swift/prompty-model/Tests/PromptyModelTests/Contracts/Events/StreamFailureTests.swift index 68d8d6e62..7abe85546 100644 --- a/runtime/swift/prompty-model/Tests/PromptyModelTests/Contracts/Events/StreamFailureTests.swift +++ b/runtime/swift/prompty-model/Tests/PromptyModelTests/Contracts/Events/StreamFailureTests.swift @@ -35,4 +35,9 @@ message: "SSE stream error: connection reset" XCTAssertEqual(reloaded.message, "SSE stream error: connection reset") } + // Invalid-input test (malformed JSON must be rejected, issue #328 class) + func testFromJSONInvalid() throws { + XCTAssertThrowsError(try StreamFailure.fromJSON("{")) + } + } diff --git a/runtime/swift/prompty-model/Tests/PromptyModelTests/Contracts/Events/TurnEventTests.swift b/runtime/swift/prompty-model/Tests/PromptyModelTests/Contracts/Events/TurnEventTests.swift index 736eb8b3c..8ed26ec44 100644 --- a/runtime/swift/prompty-model/Tests/PromptyModelTests/Contracts/Events/TurnEventTests.swift +++ b/runtime/swift/prompty-model/Tests/PromptyModelTests/Contracts/Events/TurnEventTests.swift @@ -59,4 +59,9 @@ spanId: span_tool_001 XCTAssertEqual((try XCTUnwrap(reloaded.spanId)), "span_tool_001") } + // Invalid-input test (malformed JSON must be rejected, issue #328 class) + func testFromJSONInvalid() throws { + XCTAssertThrowsError(try TurnEvent.fromJSON("{")) + } + } diff --git a/runtime/swift/prompty-model/Tests/PromptyModelTests/Contracts/Memory/MemoryEntryTests.swift b/runtime/swift/prompty-model/Tests/PromptyModelTests/Contracts/Memory/MemoryEntryTests.swift index 0c0fc952a..2f6098af8 100644 --- a/runtime/swift/prompty-model/Tests/PromptyModelTests/Contracts/Memory/MemoryEntryTests.swift +++ b/runtime/swift/prompty-model/Tests/PromptyModelTests/Contracts/Memory/MemoryEntryTests.swift @@ -52,4 +52,9 @@ tags: XCTAssertEqual((try XCTUnwrap(reloaded.tags)).count, 2) } + // Invalid-input test (malformed JSON must be rejected, issue #328 class) + func testFromJSONInvalid() throws { + XCTAssertThrowsError(try MemoryEntry.fromJSON("{")) + } + } diff --git a/runtime/swift/prompty-model/Tests/PromptyModelTests/Contracts/Memory/MemoryStoreTests.swift b/runtime/swift/prompty-model/Tests/PromptyModelTests/Contracts/Memory/MemoryStoreTests.swift index 6c997447f..bcb2e6ace 100644 --- a/runtime/swift/prompty-model/Tests/PromptyModelTests/Contracts/Memory/MemoryStoreTests.swift +++ b/runtime/swift/prompty-model/Tests/PromptyModelTests/Contracts/Memory/MemoryStoreTests.swift @@ -29,4 +29,9 @@ entries: [] XCTAssertEqual(reloaded.entries.count, 0) } + // Invalid-input test (malformed JSON must be rejected, issue #328 class) + func testFromJSONInvalid() throws { + XCTAssertThrowsError(try MemoryStore.fromJSON("{")) + } + } diff --git a/runtime/swift/prompty-model/Tests/PromptyModelTests/Contracts/Models/InvocationUsageTests.swift b/runtime/swift/prompty-model/Tests/PromptyModelTests/Contracts/Models/InvocationUsageTests.swift index e8878d0c4..42bfbdabe 100644 --- a/runtime/swift/prompty-model/Tests/PromptyModelTests/Contracts/Models/InvocationUsageTests.swift +++ b/runtime/swift/prompty-model/Tests/PromptyModelTests/Contracts/Models/InvocationUsageTests.swift @@ -41,4 +41,36 @@ totalTokens: 192 XCTAssertEqual(reloaded.totalTokens, 192) } + + func testWireConversion() throws { + let json = """ +{ + "inputTokens": 150, + "outputTokens": 42, + "totalTokens": 192 +} +""" + let instance = try InvocationUsage.fromJSON(json) + let openaiWire = try instance.toWire("openai") + XCTAssertNotNil(openaiWire["prompt_tokens"]) + XCTAssertNil(openaiWire["inputTokens"]) + XCTAssertNotNil(openaiWire["completion_tokens"]) + XCTAssertNil(openaiWire["outputTokens"]) + XCTAssertNotNil(openaiWire["total_tokens"]) + XCTAssertNil(openaiWire["totalTokens"]) + let openaiRestored = try InvocationUsage.fromWire("openai", openaiWire) + XCTAssertEqual(Set(try openaiRestored.toWire("openai").keys), Set(openaiWire.keys)) + let anthropicWire = try instance.toWire("anthropic") + XCTAssertNotNil(anthropicWire["input_tokens"]) + XCTAssertNil(anthropicWire["inputTokens"]) + XCTAssertNotNil(anthropicWire["output_tokens"]) + XCTAssertNil(anthropicWire["outputTokens"]) + let anthropicRestored = try InvocationUsage.fromWire("anthropic", anthropicWire) + XCTAssertEqual(Set(try anthropicRestored.toWire("anthropic").keys), Set(anthropicWire.keys)) + } + // Invalid-input test (malformed JSON must be rejected, issue #328 class) + func testFromJSONInvalid() throws { + XCTAssertThrowsError(try InvocationUsage.fromJSON("{")) + } + } diff --git a/runtime/swift/prompty-model/Tests/PromptyModelTests/Contracts/Models/ModelInfoTests.swift b/runtime/swift/prompty-model/Tests/PromptyModelTests/Contracts/Models/ModelInfoTests.swift index 3fe35f3be..ea203f873 100644 --- a/runtime/swift/prompty-model/Tests/PromptyModelTests/Contracts/Models/ModelInfoTests.swift +++ b/runtime/swift/prompty-model/Tests/PromptyModelTests/Contracts/Models/ModelInfoTests.swift @@ -76,4 +76,49 @@ additionalProperties: XCTAssertNotNil(reloaded.additionalProperties) } + + func testWireConversion() throws { + let json = """ +{ + "id": "gpt-4o", + "displayName": "GPT-4o", + "ownedBy": "openai", + "contextWindow": 128000, + "inputModalities": [ + "text", + "image" + ], + "outputModalities": [ + "text" + ], + "additionalProperties": { + "supportsStreaming": true + } +} +""" + let instance = try ModelInfo.fromJSON(json) + let openaiWire = try instance.toWire("openai") + XCTAssertNotNil(openaiWire["id"]) + XCTAssertNotNil(openaiWire["owned_by"]) + XCTAssertNil(openaiWire["ownedBy"]) + let openaiRestored = try ModelInfo.fromWire("openai", openaiWire) + XCTAssertEqual(Set(try openaiRestored.toWire("openai").keys), Set(openaiWire.keys)) + let anthropicWire = try instance.toWire("anthropic") + XCTAssertNotNil(anthropicWire["id"]) + XCTAssertNotNil(anthropicWire["display_name"]) + XCTAssertNil(anthropicWire["displayName"]) + XCTAssertNotNil(anthropicWire["context_length"]) + XCTAssertNil(anthropicWire["contextWindow"]) + XCTAssertNotNil(anthropicWire["input_modalities"]) + XCTAssertNil(anthropicWire["inputModalities"]) + XCTAssertNotNil(anthropicWire["output_modalities"]) + XCTAssertNil(anthropicWire["outputModalities"]) + let anthropicRestored = try ModelInfo.fromWire("anthropic", anthropicWire) + XCTAssertEqual(Set(try anthropicRestored.toWire("anthropic").keys), Set(anthropicWire.keys)) + } + // Invalid-input test (malformed JSON must be rejected, issue #328 class) + func testFromJSONInvalid() throws { + XCTAssertThrowsError(try ModelInfo.fromJSON("{")) + } + } diff --git a/runtime/swift/prompty-model/Tests/PromptyModelTests/Contracts/Models/ModelOptionsTests.swift b/runtime/swift/prompty-model/Tests/PromptyModelTests/Contracts/Models/ModelOptionsTests.swift index 4bb6fd443..6c838c6d6 100644 --- a/runtime/swift/prompty-model/Tests/PromptyModelTests/Contracts/Models/ModelOptionsTests.swift +++ b/runtime/swift/prompty-model/Tests/PromptyModelTests/Contracts/Models/ModelOptionsTests.swift @@ -12,6 +12,7 @@ final class ModelOptionsTests: XCTestCase { "frequencyPenalty": 0.5, "maxOutputTokens": 2048, "presencePenalty": 0.3, + "reasoningEffort": "medium", "seed": 42, "temperature": 0.7, "topK": 40, @@ -31,6 +32,7 @@ final class ModelOptionsTests: XCTestCase { XCTAssertEqual((try XCTUnwrap(instance.frequencyPenalty)), 0.5) XCTAssertEqual((try XCTUnwrap(instance.maxOutputTokens)), 2048) XCTAssertEqual((try XCTUnwrap(instance.presencePenalty)), 0.3) + XCTAssertEqual((try XCTUnwrap(instance.reasoningEffort)), ReasoningEffort(rawValue: "medium")) XCTAssertEqual((try XCTUnwrap(instance.seed)), 42) XCTAssertEqual((try XCTUnwrap(instance.temperature)), 0.7) XCTAssertEqual((try XCTUnwrap(instance.topK)), 40) @@ -42,6 +44,7 @@ final class ModelOptionsTests: XCTestCase { XCTAssertEqual((try XCTUnwrap(reloaded.frequencyPenalty)), 0.5) XCTAssertEqual((try XCTUnwrap(reloaded.maxOutputTokens)), 2048) XCTAssertEqual((try XCTUnwrap(reloaded.presencePenalty)), 0.3) + XCTAssertEqual((try XCTUnwrap(reloaded.reasoningEffort)), ReasoningEffort(rawValue: "medium")) XCTAssertEqual((try XCTUnwrap(reloaded.seed)), 42) XCTAssertEqual((try XCTUnwrap(reloaded.temperature)), 0.7) XCTAssertEqual((try XCTUnwrap(reloaded.topK)), 40) @@ -56,6 +59,7 @@ final class ModelOptionsTests: XCTestCase { frequencyPenalty: 0.5 maxOutputTokens: 2048 presencePenalty: 0.3 +reasoningEffort: medium seed: 42 temperature: 0.7 topK: 40 @@ -73,6 +77,7 @@ additionalProperties: XCTAssertEqual((try XCTUnwrap(instance.frequencyPenalty)), 0.5) XCTAssertEqual((try XCTUnwrap(instance.maxOutputTokens)), 2048) XCTAssertEqual((try XCTUnwrap(instance.presencePenalty)), 0.3) + XCTAssertEqual((try XCTUnwrap(instance.reasoningEffort)), ReasoningEffort(rawValue: "medium")) XCTAssertEqual((try XCTUnwrap(instance.seed)), 42) XCTAssertEqual((try XCTUnwrap(instance.temperature)), 0.7) XCTAssertEqual((try XCTUnwrap(instance.topK)), 40) @@ -84,6 +89,7 @@ additionalProperties: XCTAssertEqual((try XCTUnwrap(reloaded.frequencyPenalty)), 0.5) XCTAssertEqual((try XCTUnwrap(reloaded.maxOutputTokens)), 2048) XCTAssertEqual((try XCTUnwrap(reloaded.presencePenalty)), 0.3) + XCTAssertEqual((try XCTUnwrap(reloaded.reasoningEffort)), ReasoningEffort(rawValue: "medium")) XCTAssertEqual((try XCTUnwrap(reloaded.seed)), 42) XCTAssertEqual((try XCTUnwrap(reloaded.temperature)), 0.7) XCTAssertEqual((try XCTUnwrap(reloaded.topK)), 40) @@ -93,4 +99,77 @@ additionalProperties: XCTAssertNotNil(reloaded.additionalProperties) } + + func testWireConversion() throws { + let json = """ +{ + "frequencyPenalty": 0.5, + "maxOutputTokens": 2048, + "presencePenalty": 0.3, + "reasoningEffort": "medium", + "seed": 42, + "temperature": 0.7, + "topK": 40, + "topP": 0.9, + "stopSequences": [ + "\\n", + "###" + ], + "allowMultipleToolCalls": true, + "additionalProperties": { + "customProperty": "value", + "anotherProperty": "anotherValue" + } +} +""" + let instance = try ModelOptions.fromJSON(json) + let openaiWire = try instance.toWire("openai") + XCTAssertNotNil(openaiWire["frequency_penalty"]) + XCTAssertNil(openaiWire["frequencyPenalty"]) + XCTAssertNotNil(openaiWire["max_completion_tokens"]) + XCTAssertNil(openaiWire["maxOutputTokens"]) + XCTAssertNotNil(openaiWire["presence_penalty"]) + XCTAssertNil(openaiWire["presencePenalty"]) + XCTAssertNotNil(openaiWire["reasoning_effort"]) + XCTAssertNil(openaiWire["reasoningEffort"]) + XCTAssertNotNil(openaiWire["seed"]) + XCTAssertNotNil(openaiWire["temperature"]) + XCTAssertNotNil(openaiWire["top_k"]) + XCTAssertNil(openaiWire["topK"]) + XCTAssertNotNil(openaiWire["top_p"]) + XCTAssertNil(openaiWire["topP"]) + XCTAssertNotNil(openaiWire["stop"]) + XCTAssertNil(openaiWire["stopSequences"]) + XCTAssertNotNil(openaiWire["parallel_tool_calls"]) + XCTAssertNil(openaiWire["allowMultipleToolCalls"]) + let openaiRestored = try ModelOptions.fromWire("openai", openaiWire) + XCTAssertEqual(Set(try openaiRestored.toWire("openai").keys), Set(openaiWire.keys)) + let responsesWire = try instance.toWire("responses") + XCTAssertNotNil(responsesWire["max_output_tokens"]) + XCTAssertNil(responsesWire["maxOutputTokens"]) + XCTAssertNotNil(responsesWire["reasoning_effort"]) + XCTAssertNil(responsesWire["reasoningEffort"]) + XCTAssertNotNil(responsesWire["temperature"]) + XCTAssertNotNil(responsesWire["top_p"]) + XCTAssertNil(responsesWire["topP"]) + let responsesRestored = try ModelOptions.fromWire("responses", responsesWire) + XCTAssertEqual(Set(try responsesRestored.toWire("responses").keys), Set(responsesWire.keys)) + let anthropicWire = try instance.toWire("anthropic") + XCTAssertNotNil(anthropicWire["max_tokens"]) + XCTAssertNil(anthropicWire["maxOutputTokens"]) + XCTAssertNotNil(anthropicWire["temperature"]) + XCTAssertNotNil(anthropicWire["top_k"]) + XCTAssertNil(anthropicWire["topK"]) + XCTAssertNotNil(anthropicWire["top_p"]) + XCTAssertNil(anthropicWire["topP"]) + XCTAssertNotNil(anthropicWire["stop_sequences"]) + XCTAssertNil(anthropicWire["stopSequences"]) + let anthropicRestored = try ModelOptions.fromWire("anthropic", anthropicWire) + XCTAssertEqual(Set(try anthropicRestored.toWire("anthropic").keys), Set(anthropicWire.keys)) + } + // Invalid-input test (malformed JSON must be rejected, issue #328 class) + func testFromJSONInvalid() throws { + XCTAssertThrowsError(try ModelOptions.fromJSON("{")) + } + } diff --git a/runtime/swift/prompty-model/Tests/PromptyModelTests/Contracts/Models/ModelTests.swift b/runtime/swift/prompty-model/Tests/PromptyModelTests/Contracts/Models/ModelTests.swift index 8b26bc525..50eec9f6e 100644 --- a/runtime/swift/prompty-model/Tests/PromptyModelTests/Contracts/Models/ModelTests.swift +++ b/runtime/swift/prompty-model/Tests/PromptyModelTests/Contracts/Models/ModelTests.swift @@ -80,4 +80,9 @@ options: } } + // Invalid-input test (malformed JSON must be rejected, issue #328 class) + func testFromJSONInvalid() throws { + XCTAssertThrowsError(try Model.fromJSON("{")) + } + } diff --git a/runtime/swift/prompty-model/Tests/PromptyModelTests/Contracts/Models/TokenUsageTests.swift b/runtime/swift/prompty-model/Tests/PromptyModelTests/Contracts/Models/TokenUsageTests.swift index 78ce11d28..d71943243 100644 --- a/runtime/swift/prompty-model/Tests/PromptyModelTests/Contracts/Models/TokenUsageTests.swift +++ b/runtime/swift/prompty-model/Tests/PromptyModelTests/Contracts/Models/TokenUsageTests.swift @@ -41,4 +41,36 @@ totalTokens: 192 XCTAssertEqual((try XCTUnwrap(reloaded.totalTokens)), 192) } + + func testWireConversion() throws { + let json = """ +{ + "promptTokens": 150, + "completionTokens": 42, + "totalTokens": 192 +} +""" + let instance = try TokenUsage.fromJSON(json) + let openaiWire = try instance.toWire("openai") + XCTAssertNotNil(openaiWire["prompt_tokens"]) + XCTAssertNil(openaiWire["promptTokens"]) + XCTAssertNotNil(openaiWire["completion_tokens"]) + XCTAssertNil(openaiWire["completionTokens"]) + XCTAssertNotNil(openaiWire["total_tokens"]) + XCTAssertNil(openaiWire["totalTokens"]) + let openaiRestored = try TokenUsage.fromWire("openai", openaiWire) + XCTAssertEqual(Set(try openaiRestored.toWire("openai").keys), Set(openaiWire.keys)) + let anthropicWire = try instance.toWire("anthropic") + XCTAssertNotNil(anthropicWire["input_tokens"]) + XCTAssertNil(anthropicWire["promptTokens"]) + XCTAssertNotNil(anthropicWire["output_tokens"]) + XCTAssertNil(anthropicWire["completionTokens"]) + let anthropicRestored = try TokenUsage.fromWire("anthropic", anthropicWire) + XCTAssertEqual(Set(try anthropicRestored.toWire("anthropic").keys), Set(anthropicWire.keys)) + } + // Invalid-input test (malformed JSON must be rejected, issue #328 class) + func testFromJSONInvalid() throws { + XCTAssertThrowsError(try TokenUsage.fromJSON("{")) + } + } diff --git a/runtime/swift/prompty-model/Tests/PromptyModelTests/Contracts/Pipeline/DelegatedStateReferenceTests.swift b/runtime/swift/prompty-model/Tests/PromptyModelTests/Contracts/Pipeline/DelegatedStateReferenceTests.swift index 4d6d78022..5b549da7c 100644 --- a/runtime/swift/prompty-model/Tests/PromptyModelTests/Contracts/Pipeline/DelegatedStateReferenceTests.swift +++ b/runtime/swift/prompty-model/Tests/PromptyModelTests/Contracts/Pipeline/DelegatedStateReferenceTests.swift @@ -41,4 +41,9 @@ id: resp_abc123 XCTAssertEqual(reloaded.id, "resp_abc123") } + // Invalid-input test (malformed JSON must be rejected, issue #328 class) + func testFromJSONInvalid() throws { + XCTAssertThrowsError(try DelegatedStateReference.fromJSON("{")) + } + } diff --git a/runtime/swift/prompty-model/Tests/PromptyModelTests/Contracts/Pipeline/EngineCheckpointTests.swift b/runtime/swift/prompty-model/Tests/PromptyModelTests/Contracts/Pipeline/EngineCheckpointTests.swift index bf45f9759..0e235b6f6 100644 --- a/runtime/swift/prompty-model/Tests/PromptyModelTests/Contracts/Pipeline/EngineCheckpointTests.swift +++ b/runtime/swift/prompty-model/Tests/PromptyModelTests/Contracts/Pipeline/EngineCheckpointTests.swift @@ -74,4 +74,9 @@ contextState: {} XCTAssertEqual(reloaded.messages.count, 1) } + // Invalid-input test (malformed JSON must be rejected, issue #328 class) + func testFromJSONInvalid() throws { + XCTAssertThrowsError(try EngineCheckpoint.fromJSON("{")) + } + } diff --git a/runtime/swift/prompty-model/Tests/PromptyModelTests/Contracts/Pipeline/EngineEventTests.swift b/runtime/swift/prompty-model/Tests/PromptyModelTests/Contracts/Pipeline/EngineEventTests.swift index 7f17caacf..7a4b77787 100644 --- a/runtime/swift/prompty-model/Tests/PromptyModelTests/Contracts/Pipeline/EngineEventTests.swift +++ b/runtime/swift/prompty-model/Tests/PromptyModelTests/Contracts/Pipeline/EngineEventTests.swift @@ -53,4 +53,9 @@ runId: run_abc123 XCTAssertEqual(reloaded.runId, "run_abc123") } + // Invalid-input test (malformed JSON must be rejected, issue #328 class) + func testFromJSONInvalid() throws { + XCTAssertThrowsError(try EngineEvent.fromJSON("{")) + } + } diff --git a/runtime/swift/prompty-model/Tests/PromptyModelTests/Contracts/Pipeline/EnginePermissionDecisionTests.swift b/runtime/swift/prompty-model/Tests/PromptyModelTests/Contracts/Pipeline/EnginePermissionDecisionTests.swift index 510d0aa0e..f4c8ebb0d 100644 --- a/runtime/swift/prompty-model/Tests/PromptyModelTests/Contracts/Pipeline/EnginePermissionDecisionTests.swift +++ b/runtime/swift/prompty-model/Tests/PromptyModelTests/Contracts/Pipeline/EnginePermissionDecisionTests.swift @@ -29,4 +29,9 @@ approved: true XCTAssertEqual(reloaded.approved, true) } + // Invalid-input test (malformed JSON must be rejected, issue #328 class) + func testFromJSONInvalid() throws { + XCTAssertThrowsError(try EnginePermissionDecision.fromJSON("{")) + } + } diff --git a/runtime/swift/prompty-model/Tests/PromptyModelTests/Contracts/Pipeline/InvocationContextDecisionTests.swift b/runtime/swift/prompty-model/Tests/PromptyModelTests/Contracts/Pipeline/InvocationContextDecisionTests.swift index ca9fa4c87..b464e5058 100644 --- a/runtime/swift/prompty-model/Tests/PromptyModelTests/Contracts/Pipeline/InvocationContextDecisionTests.swift +++ b/runtime/swift/prompty-model/Tests/PromptyModelTests/Contracts/Pipeline/InvocationContextDecisionTests.swift @@ -35,4 +35,9 @@ reason: included by relevance ranking XCTAssertEqual(reloaded.reason, "included by relevance ranking") } + // Invalid-input test (malformed JSON must be rejected, issue #328 class) + func testFromJSONInvalid() throws { + XCTAssertThrowsError(try InvocationContextDecision.fromJSON("{")) + } + } diff --git a/runtime/swift/prompty-model/Tests/PromptyModelTests/Contracts/Pipeline/InvocationContextStateTests.swift b/runtime/swift/prompty-model/Tests/PromptyModelTests/Contracts/Pipeline/InvocationContextStateTests.swift index 496d69d0f..f853cc9c6 100644 --- a/runtime/swift/prompty-model/Tests/PromptyModelTests/Contracts/Pipeline/InvocationContextStateTests.swift +++ b/runtime/swift/prompty-model/Tests/PromptyModelTests/Contracts/Pipeline/InvocationContextStateTests.swift @@ -25,4 +25,9 @@ final class InvocationContextStateTests: XCTestCase { _ = reloaded } + // Invalid-input test (malformed JSON must be rejected, issue #328 class) + func testFromJSONInvalid() throws { + XCTAssertThrowsError(try InvocationContextState.fromJSON("{")) + } + } diff --git a/runtime/swift/prompty-model/Tests/PromptyModelTests/Contracts/Pipeline/ModelInvocationContextSnapshotTests.swift b/runtime/swift/prompty-model/Tests/PromptyModelTests/Contracts/Pipeline/ModelInvocationContextSnapshotTests.swift index 23f086eff..02d6a9049 100644 --- a/runtime/swift/prompty-model/Tests/PromptyModelTests/Contracts/Pipeline/ModelInvocationContextSnapshotTests.swift +++ b/runtime/swift/prompty-model/Tests/PromptyModelTests/Contracts/Pipeline/ModelInvocationContextSnapshotTests.swift @@ -74,4 +74,9 @@ contextState: {} XCTAssertEqual(reloaded.messages.count, 1) } + // Invalid-input test (malformed JSON must be rejected, issue #328 class) + func testFromJSONInvalid() throws { + XCTAssertThrowsError(try ModelInvocationContextSnapshot.fromJSON("{")) + } + } diff --git a/runtime/swift/prompty-model/Tests/PromptyModelTests/Contracts/Pipeline/ModelInvocationRequestTests.swift b/runtime/swift/prompty-model/Tests/PromptyModelTests/Contracts/Pipeline/ModelInvocationRequestTests.swift index 754c80c3c..0abef6093 100644 --- a/runtime/swift/prompty-model/Tests/PromptyModelTests/Contracts/Pipeline/ModelInvocationRequestTests.swift +++ b/runtime/swift/prompty-model/Tests/PromptyModelTests/Contracts/Pipeline/ModelInvocationRequestTests.swift @@ -60,4 +60,9 @@ context: XCTAssertEqual(reloaded.context.iteration, 1) } + // Invalid-input test (malformed JSON must be rejected, issue #328 class) + func testFromJSONInvalid() throws { + XCTAssertThrowsError(try ModelInvocationRequest.fromJSON("{")) + } + } diff --git a/runtime/swift/prompty-model/Tests/PromptyModelTests/Contracts/Pipeline/ModelInvocationResponseTests.swift b/runtime/swift/prompty-model/Tests/PromptyModelTests/Contracts/Pipeline/ModelInvocationResponseTests.swift index e861e6606..aee63e62f 100644 --- a/runtime/swift/prompty-model/Tests/PromptyModelTests/Contracts/Pipeline/ModelInvocationResponseTests.swift +++ b/runtime/swift/prompty-model/Tests/PromptyModelTests/Contracts/Pipeline/ModelInvocationResponseTests.swift @@ -25,4 +25,9 @@ final class ModelInvocationResponseTests: XCTestCase { _ = reloaded } + // Invalid-input test (malformed JSON must be rejected, issue #328 class) + func testFromJSONInvalid() throws { + XCTAssertThrowsError(try ModelInvocationResponse.fromJSON("{")) + } + } diff --git a/runtime/swift/prompty-model/Tests/PromptyModelTests/Contracts/Pipeline/ModelReconciliationStateTests.swift b/runtime/swift/prompty-model/Tests/PromptyModelTests/Contracts/Pipeline/ModelReconciliationStateTests.swift index 9d032e8ce..70f35e7e0 100644 --- a/runtime/swift/prompty-model/Tests/PromptyModelTests/Contracts/Pipeline/ModelReconciliationStateTests.swift +++ b/runtime/swift/prompty-model/Tests/PromptyModelTests/Contracts/Pipeline/ModelReconciliationStateTests.swift @@ -55,4 +55,9 @@ request: XCTAssertEqual(reloaded.message, "provider connection dropped after request was sent") } + // Invalid-input test (malformed JSON must be rejected, issue #328 class) + func testFromJSONInvalid() throws { + XCTAssertThrowsError(try ModelReconciliationState.fromJSON("{")) + } + } diff --git a/runtime/swift/prompty-model/Tests/PromptyModelTests/Contracts/Pipeline/ModelToolRequestTests.swift b/runtime/swift/prompty-model/Tests/PromptyModelTests/Contracts/Pipeline/ModelToolRequestTests.swift index d22276ff8..e1fbc982a 100644 --- a/runtime/swift/prompty-model/Tests/PromptyModelTests/Contracts/Pipeline/ModelToolRequestTests.swift +++ b/runtime/swift/prompty-model/Tests/PromptyModelTests/Contracts/Pipeline/ModelToolRequestTests.swift @@ -35,4 +35,9 @@ name: get_weather XCTAssertEqual(reloaded.name, "get_weather") } + // Invalid-input test (malformed JSON must be rejected, issue #328 class) + func testFromJSONInvalid() throws { + XCTAssertThrowsError(try ModelToolRequest.fromJSON("{")) + } + } diff --git a/runtime/swift/prompty-model/Tests/PromptyModelTests/Contracts/Pipeline/ModelToolResultTests.swift b/runtime/swift/prompty-model/Tests/PromptyModelTests/Contracts/Pipeline/ModelToolResultTests.swift index 1e7f4aa53..a875cfb64 100644 --- a/runtime/swift/prompty-model/Tests/PromptyModelTests/Contracts/Pipeline/ModelToolResultTests.swift +++ b/runtime/swift/prompty-model/Tests/PromptyModelTests/Contracts/Pipeline/ModelToolResultTests.swift @@ -35,4 +35,9 @@ name: get_weather XCTAssertEqual(reloaded.name, "get_weather") } + // Invalid-input test (malformed JSON must be rejected, issue #328 class) + func testFromJSONInvalid() throws { + XCTAssertThrowsError(try ModelToolResult.fromJSON("{")) + } + } diff --git a/runtime/swift/prompty-model/Tests/PromptyModelTests/Contracts/Pipeline/ReplayJournalRecordTests.swift b/runtime/swift/prompty-model/Tests/PromptyModelTests/Contracts/Pipeline/ReplayJournalRecordTests.swift index 0c9d5cdaa..8e623343d 100644 --- a/runtime/swift/prompty-model/Tests/PromptyModelTests/Contracts/Pipeline/ReplayJournalRecordTests.swift +++ b/runtime/swift/prompty-model/Tests/PromptyModelTests/Contracts/Pipeline/ReplayJournalRecordTests.swift @@ -29,4 +29,9 @@ kind: session XCTAssertEqual(reloaded.kind, ReplayRecordKind.session) } + // Invalid-input test (malformed JSON must be rejected, issue #328 class) + func testFromJSONInvalid() throws { + XCTAssertThrowsError(try ReplayJournalRecord.fromJSON("{")) + } + } diff --git a/runtime/swift/prompty-model/Tests/PromptyModelTests/Contracts/Pipeline/ResumeContextTests.swift b/runtime/swift/prompty-model/Tests/PromptyModelTests/Contracts/Pipeline/ResumeContextTests.swift index 544a93c6d..6143d68e5 100644 --- a/runtime/swift/prompty-model/Tests/PromptyModelTests/Contracts/Pipeline/ResumeContextTests.swift +++ b/runtime/swift/prompty-model/Tests/PromptyModelTests/Contracts/Pipeline/ResumeContextTests.swift @@ -72,4 +72,9 @@ checkpoint: XCTAssertEqual(reloaded.checkpoint.lastSequence, 1) } + // Invalid-input test (malformed JSON must be rejected, issue #328 class) + func testFromJSONInvalid() throws { + XCTAssertThrowsError(try ResumeContext.fromJSON("{")) + } + } diff --git a/runtime/swift/prompty-model/Tests/PromptyModelTests/Contracts/Pipeline/TurnCommitTests.swift b/runtime/swift/prompty-model/Tests/PromptyModelTests/Contracts/Pipeline/TurnCommitTests.swift index 92ca8ee5f..451382fad 100644 --- a/runtime/swift/prompty-model/Tests/PromptyModelTests/Contracts/Pipeline/TurnCommitTests.swift +++ b/runtime/swift/prompty-model/Tests/PromptyModelTests/Contracts/Pipeline/TurnCommitTests.swift @@ -62,4 +62,9 @@ contextState: {} XCTAssertEqual(reloaded.messages.count, 1) } + // Invalid-input test (malformed JSON must be rejected, issue #328 class) + func testFromJSONInvalid() throws { + XCTAssertThrowsError(try TurnCommit.fromJSON("{")) + } + } diff --git a/runtime/swift/prompty-model/Tests/PromptyModelTests/Contracts/Templates/FormatConfigTests.swift b/runtime/swift/prompty-model/Tests/PromptyModelTests/Contracts/Templates/FormatConfigTests.swift index 895d3b293..b4cfb7306 100644 --- a/runtime/swift/prompty-model/Tests/PromptyModelTests/Contracts/Templates/FormatConfigTests.swift +++ b/runtime/swift/prompty-model/Tests/PromptyModelTests/Contracts/Templates/FormatConfigTests.swift @@ -60,4 +60,9 @@ options: } } + // Invalid-input test (malformed JSON must be rejected, issue #328 class) + func testFromJSONInvalid() throws { + XCTAssertThrowsError(try FormatConfig.fromJSON("{")) + } + } diff --git a/runtime/swift/prompty-model/Tests/PromptyModelTests/Contracts/Templates/ParserConfigTests.swift b/runtime/swift/prompty-model/Tests/PromptyModelTests/Contracts/Templates/ParserConfigTests.swift index e95eea933..f0bb28eab 100644 --- a/runtime/swift/prompty-model/Tests/PromptyModelTests/Contracts/Templates/ParserConfigTests.swift +++ b/runtime/swift/prompty-model/Tests/PromptyModelTests/Contracts/Templates/ParserConfigTests.swift @@ -54,4 +54,9 @@ options: } } + // Invalid-input test (malformed JSON must be rejected, issue #328 class) + func testFromJSONInvalid() throws { + XCTAssertThrowsError(try ParserConfig.fromJSON("{")) + } + } diff --git a/runtime/swift/prompty-model/Tests/PromptyModelTests/Contracts/Templates/TemplateTests.swift b/runtime/swift/prompty-model/Tests/PromptyModelTests/Contracts/Templates/TemplateTests.swift index 0f9f880fe..ea766ffd7 100644 --- a/runtime/swift/prompty-model/Tests/PromptyModelTests/Contracts/Templates/TemplateTests.swift +++ b/runtime/swift/prompty-model/Tests/PromptyModelTests/Contracts/Templates/TemplateTests.swift @@ -65,4 +65,9 @@ parser: } } + // Invalid-input test (malformed JSON must be rejected, issue #328 class) + func testFromJSONInvalid() throws { + XCTAssertThrowsError(try Template.fromJSON("{")) + } + } diff --git a/runtime/swift/prompty-model/Tests/PromptyModelTests/Contracts/Tooling/BindingTests.swift b/runtime/swift/prompty-model/Tests/PromptyModelTests/Contracts/Tooling/BindingTests.swift index 18ac1b08e..2e5c777bf 100644 --- a/runtime/swift/prompty-model/Tests/PromptyModelTests/Contracts/Tooling/BindingTests.swift +++ b/runtime/swift/prompty-model/Tests/PromptyModelTests/Contracts/Tooling/BindingTests.swift @@ -40,4 +40,9 @@ input: input-variable XCTAssertEqual(instance.input, "example") } + // Invalid-input test (malformed JSON must be rejected, issue #328 class) + func testFromJSONInvalid() throws { + XCTAssertThrowsError(try Binding.fromJSON("{")) + } + } diff --git a/runtime/swift/prompty-model/Tests/PromptyModelTests/Contracts/Tooling/McpApprovalModeTests.swift b/runtime/swift/prompty-model/Tests/PromptyModelTests/Contracts/Tooling/McpApprovalModeTests.swift index 027598e6c..143a76c3c 100644 --- a/runtime/swift/prompty-model/Tests/PromptyModelTests/Contracts/Tooling/McpApprovalModeTests.swift +++ b/runtime/swift/prompty-model/Tests/PromptyModelTests/Contracts/Tooling/McpApprovalModeTests.swift @@ -52,4 +52,9 @@ neverRequireApprovalTools: XCTAssertEqual(instance.kind, McpApprovalModeKind.never) } + // Invalid-input test (malformed JSON must be rejected, issue #328 class) + func testFromJSONInvalid() throws { + XCTAssertThrowsError(try McpApprovalMode.fromJSON("{")) + } + } diff --git a/runtime/swift/prompty-model/Tests/PromptyModelTests/Contracts/Tooling/ToolTests.swift b/runtime/swift/prompty-model/Tests/PromptyModelTests/Contracts/Tooling/ToolTests.swift index 584430980..76c9b96fa 100644 --- a/runtime/swift/prompty-model/Tests/PromptyModelTests/Contracts/Tooling/ToolTests.swift +++ b/runtime/swift/prompty-model/Tests/PromptyModelTests/Contracts/Tooling/ToolTests.swift @@ -58,4 +58,9 @@ bindings: } } + // Invalid-input test (malformed JSON must be rejected, issue #328 class) + func testFromJSONInvalid() throws { + XCTAssertThrowsError(try Tool.fromJSON("{")) + } + } diff --git a/runtime/swift/prompty-model/Tests/PromptyModelTests/Operations/Pipeline/RenderSegmentTests.swift b/runtime/swift/prompty-model/Tests/PromptyModelTests/Operations/Pipeline/RenderSegmentTests.swift index 893b9a413..a9d1640a1 100644 --- a/runtime/swift/prompty-model/Tests/PromptyModelTests/Operations/Pipeline/RenderSegmentTests.swift +++ b/runtime/swift/prompty-model/Tests/PromptyModelTests/Operations/Pipeline/RenderSegmentTests.swift @@ -35,4 +35,9 @@ text: sample XCTAssertEqual(reloaded.text, "sample") } + // Invalid-input test (malformed JSON must be rejected, issue #328 class) + func testFromJSONInvalid() throws { + XCTAssertThrowsError(try RenderSegment.fromJSON("{")) + } + } diff --git a/runtime/typescript/packages/core/src/model/contracts/models/model-options.ts b/runtime/typescript/packages/core/src/model/contracts/models/model-options.ts index c256e312b..681af235a 100644 --- a/runtime/typescript/packages/core/src/model/contracts/models/model-options.ts +++ b/runtime/typescript/packages/core/src/model/contracts/models/model-options.ts @@ -4,12 +4,23 @@ import { LoadContext, SaveContext } from "../../context"; +export type reasoningEffort = + | "none" + | "minimal" + | "low" + | "medium" + | "high" + | "xhigh" + | "max" + | (string & {}); + export class ModelOptions { static readonly shorthandProperty: string | undefined = undefined; frequencyPenalty?: number | undefined; maxOutputTokens?: number | undefined; presencePenalty?: number | undefined; + reasoningEffort?: reasoningEffort | undefined; seed?: number | undefined; temperature?: number | undefined; topK?: number | undefined; @@ -28,6 +39,9 @@ export class ModelOptions { if (init?.presencePenalty !== undefined) { this.presencePenalty = init.presencePenalty; } + if (init?.reasoningEffort !== undefined) { + this.reasoningEffort = init.reasoningEffort; + } if (init?.seed !== undefined) { this.seed = init.seed; } @@ -82,6 +96,14 @@ export class ModelOptions { ) { instance.presencePenalty = Number(data["presencePenalty"]); } + if ( + data["reasoningEffort"] !== undefined && + data["reasoningEffort"] !== null + ) { + instance.reasoningEffort = String( + data["reasoningEffort"], + ) as reasoningEffort; + } if (data["seed"] !== undefined && data["seed"] !== null) { instance.seed = Number(data["seed"]); } @@ -142,6 +164,9 @@ export class ModelOptions { if (obj.presencePenalty !== undefined && obj.presencePenalty !== null) { result["presencePenalty"] = obj.presencePenalty; } + if (obj.reasoningEffort !== undefined && obj.reasoningEffort !== null) { + result["reasoningEffort"] = obj.reasoningEffort; + } if (obj.seed !== undefined && obj.seed !== null) { result["seed"] = obj.seed; } @@ -187,6 +212,10 @@ export class ModelOptions { anthropic: "max_tokens", }, presencePenalty: { openai: "presence_penalty" }, + reasoningEffort: { + openai: "reasoning_effort", + responses: "reasoning_effort", + }, seed: { openai: "seed" }, temperature: { openai: "temperature", @@ -220,6 +249,10 @@ export class ModelOptions { anthropic: "max_tokens", }, presencePenalty: { openai: "presence_penalty" }, + reasoningEffort: { + openai: "reasoning_effort", + responses: "reasoning_effort", + }, seed: { openai: "seed" }, temperature: { openai: "temperature", diff --git a/runtime/typescript/packages/core/tests/model/agent.test.ts b/runtime/typescript/packages/core/tests/model/agent.test.ts index 113d3c37d..976908a10 100644 --- a/runtime/typescript/packages/core/tests/model/agent.test.ts +++ b/runtime/typescript/packages/core/tests/model/agent.test.ts @@ -210,6 +210,16 @@ describe("Agent", () => { expect(reloaded.description).toEqual(instance.description); expect(reloaded.instructions).toEqual(instance.instructions); }); + + it("should reject malformed JSON", () => { + let threw = false; + try { + Agent.fromJson("{"); + } catch { + threw = true; + } + expect(threw).toBe(true); + }); }); describe("YAML serialization", () => { diff --git a/runtime/typescript/packages/core/tests/model/contracts/connectivity/anonymous-connection.test.ts b/runtime/typescript/packages/core/tests/model/contracts/connectivity/anonymous-connection.test.ts index aaa29fe2a..8b05cfad4 100644 --- a/runtime/typescript/packages/core/tests/model/contracts/connectivity/anonymous-connection.test.ts +++ b/runtime/typescript/packages/core/tests/model/contracts/connectivity/anonymous-connection.test.ts @@ -36,6 +36,16 @@ describe("AnonymousConnection", () => { expect(reloaded.kind).toEqual(instance.kind); expect(reloaded.endpoint).toEqual(instance.endpoint); }); + + it("should reject malformed JSON", () => { + let threw = false; + try { + AnonymousConnection.fromJson("{"); + } catch { + threw = true; + } + expect(threw).toBe(true); + }); }); describe("YAML serialization", () => { diff --git a/runtime/typescript/packages/core/tests/model/contracts/connectivity/api-key-connection.test.ts b/runtime/typescript/packages/core/tests/model/contracts/connectivity/api-key-connection.test.ts index a27224f5b..cbfe8132c 100644 --- a/runtime/typescript/packages/core/tests/model/contracts/connectivity/api-key-connection.test.ts +++ b/runtime/typescript/packages/core/tests/model/contracts/connectivity/api-key-connection.test.ts @@ -37,6 +37,16 @@ describe("ApiKeyConnection", () => { expect(reloaded.kind).toEqual(instance.kind); expect(reloaded.endpoint).toEqual(instance.endpoint); }); + + it("should reject malformed JSON", () => { + let threw = false; + try { + ApiKeyConnection.fromJson("{"); + } catch { + threw = true; + } + expect(threw).toBe(true); + }); }); describe("YAML serialization", () => { diff --git a/runtime/typescript/packages/core/tests/model/contracts/connectivity/connection.test.ts b/runtime/typescript/packages/core/tests/model/contracts/connectivity/connection.test.ts index fc2553d20..13c3daf9b 100644 --- a/runtime/typescript/packages/core/tests/model/contracts/connectivity/connection.test.ts +++ b/runtime/typescript/packages/core/tests/model/contracts/connectivity/connection.test.ts @@ -26,6 +26,16 @@ describe("Connection", () => { expect(reloaded.authenticationMode).toEqual(instance.authenticationMode); expect(reloaded.usageDescription).toEqual(instance.usageDescription); }); + + it("should reject malformed JSON", () => { + let threw = false; + try { + Connection.fromJson("{"); + } catch { + threw = true; + } + expect(threw).toBe(true); + }); }); describe("YAML serialization", () => { diff --git a/runtime/typescript/packages/core/tests/model/contracts/connectivity/device-authorization.test.ts b/runtime/typescript/packages/core/tests/model/contracts/connectivity/device-authorization.test.ts index d46e5954c..42ad32a3e 100644 --- a/runtime/typescript/packages/core/tests/model/contracts/connectivity/device-authorization.test.ts +++ b/runtime/typescript/packages/core/tests/model/contracts/connectivity/device-authorization.test.ts @@ -40,6 +40,16 @@ describe("DeviceAuthorization", () => { expect(reloaded.expiresIn).toEqual(instance.expiresIn); expect(reloaded.interval).toEqual(instance.interval); }); + + it("should reject malformed JSON", () => { + let threw = false; + try { + DeviceAuthorization.fromJson("{"); + } catch { + threw = true; + } + expect(threw).toBe(true); + }); }); describe("YAML serialization", () => { @@ -67,6 +77,30 @@ describe("DeviceAuthorization", () => { }); }); + describe("wire conversion", () => { + it("should apply provider wire field names", () => { + const json = `{\n "deviceCode": "sample",\n "userCode": "sample",\n "verificationUri": "sample",\n "expiresIn": 1,\n "interval": 1\n}`; + const instance = DeviceAuthorization.fromJson(json); + const foundryWire = instance.toWire("foundry"); + expect(Object.keys(foundryWire).includes("device_code")).toBe(true); + expect(Object.keys(foundryWire).includes("deviceCode")).toBe(false); + expect(Object.keys(foundryWire).includes("user_code")).toBe(true); + expect(Object.keys(foundryWire).includes("userCode")).toBe(false); + expect(Object.keys(foundryWire).includes("verification_uri")).toBe(true); + expect(Object.keys(foundryWire).includes("verificationUri")).toBe(false); + expect(Object.keys(foundryWire).includes("expires_in")).toBe(true); + expect(Object.keys(foundryWire).includes("expiresIn")).toBe(false); + expect(Object.keys(foundryWire).includes("interval")).toBe(true); + const foundryRestored = DeviceAuthorization.fromWire( + "foundry", + foundryWire, + ); + expect(Object.keys(foundryRestored.toWire("foundry")).sort()).toEqual( + Object.keys(foundryWire).sort(), + ); + }); + }); + describe("load and save", () => { it("should load from dictionary", () => { const data = JSON.parse( diff --git a/runtime/typescript/packages/core/tests/model/contracts/connectivity/foundry-connection.test.ts b/runtime/typescript/packages/core/tests/model/contracts/connectivity/foundry-connection.test.ts index 7ccd48560..efc58828b 100644 --- a/runtime/typescript/packages/core/tests/model/contracts/connectivity/foundry-connection.test.ts +++ b/runtime/typescript/packages/core/tests/model/contracts/connectivity/foundry-connection.test.ts @@ -40,6 +40,16 @@ describe("FoundryConnection", () => { expect(reloaded.name).toEqual(instance.name); expect(reloaded.connectionType).toEqual(instance.connectionType); }); + + it("should reject malformed JSON", () => { + let threw = false; + try { + FoundryConnection.fromJson("{"); + } catch { + threw = true; + } + expect(threw).toBe(true); + }); }); describe("YAML serialization", () => { diff --git a/runtime/typescript/packages/core/tests/model/contracts/connectivity/o-auth-connection.test.ts b/runtime/typescript/packages/core/tests/model/contracts/connectivity/o-auth-connection.test.ts index dd3598b37..0aad47de3 100644 --- a/runtime/typescript/packages/core/tests/model/contracts/connectivity/o-auth-connection.test.ts +++ b/runtime/typescript/packages/core/tests/model/contracts/connectivity/o-auth-connection.test.ts @@ -42,6 +42,16 @@ describe("OAuthConnection", () => { expect(reloaded.clientSecret).toEqual(instance.clientSecret); expect(reloaded.tokenUrl).toEqual(instance.tokenUrl); }); + + it("should reject malformed JSON", () => { + let threw = false; + try { + OAuthConnection.fromJson("{"); + } catch { + threw = true; + } + expect(threw).toBe(true); + }); }); describe("YAML serialization", () => { diff --git a/runtime/typescript/packages/core/tests/model/contracts/connectivity/o-auth-token.test.ts b/runtime/typescript/packages/core/tests/model/contracts/connectivity/o-auth-token.test.ts index 749c11ad0..6ba62ef35 100644 --- a/runtime/typescript/packages/core/tests/model/contracts/connectivity/o-auth-token.test.ts +++ b/runtime/typescript/packages/core/tests/model/contracts/connectivity/o-auth-token.test.ts @@ -38,6 +38,16 @@ describe("OAuthToken", () => { expect(reloaded.refreshToken).toEqual(instance.refreshToken); expect(reloaded.scope).toEqual(instance.scope); }); + + it("should reject malformed JSON", () => { + let threw = false; + try { + OAuthToken.fromJson("{"); + } catch { + threw = true; + } + expect(threw).toBe(true); + }); }); describe("YAML serialization", () => { @@ -63,6 +73,21 @@ describe("OAuthToken", () => { }); }); + describe("wire conversion", () => { + it("should apply provider wire field names", () => { + const json = `{\n "refreshToken": "0.AXoAoffline-refresh-token-value",\n "scope": "https://cognitiveservices.azure.com/.default offline_access"\n}`; + const instance = OAuthToken.fromJson(json); + const foundryWire = instance.toWire("foundry"); + expect(Object.keys(foundryWire).includes("refresh_token")).toBe(true); + expect(Object.keys(foundryWire).includes("refreshToken")).toBe(false); + expect(Object.keys(foundryWire).includes("scope")).toBe(true); + const foundryRestored = OAuthToken.fromWire("foundry", foundryWire); + expect(Object.keys(foundryRestored.toWire("foundry")).sort()).toEqual( + Object.keys(foundryWire).sort(), + ); + }); + }); + describe("load and save", () => { it("should load from dictionary", () => { const data = JSON.parse( diff --git a/runtime/typescript/packages/core/tests/model/contracts/connectivity/reference-connection.test.ts b/runtime/typescript/packages/core/tests/model/contracts/connectivity/reference-connection.test.ts index 22a8f175c..badadf972 100644 --- a/runtime/typescript/packages/core/tests/model/contracts/connectivity/reference-connection.test.ts +++ b/runtime/typescript/packages/core/tests/model/contracts/connectivity/reference-connection.test.ts @@ -36,6 +36,16 @@ describe("ReferenceConnection", () => { expect(reloaded.name).toEqual(instance.name); expect(reloaded.target).toEqual(instance.target); }); + + it("should reject malformed JSON", () => { + let threw = false; + try { + ReferenceConnection.fromJson("{"); + } catch { + threw = true; + } + expect(threw).toBe(true); + }); }); describe("YAML serialization", () => { diff --git a/runtime/typescript/packages/core/tests/model/contracts/connectivity/remote-connection.test.ts b/runtime/typescript/packages/core/tests/model/contracts/connectivity/remote-connection.test.ts index a1a982ab6..66e5ba61c 100644 --- a/runtime/typescript/packages/core/tests/model/contracts/connectivity/remote-connection.test.ts +++ b/runtime/typescript/packages/core/tests/model/contracts/connectivity/remote-connection.test.ts @@ -38,6 +38,16 @@ describe("RemoteConnection", () => { expect(reloaded.name).toEqual(instance.name); expect(reloaded.endpoint).toEqual(instance.endpoint); }); + + it("should reject malformed JSON", () => { + let threw = false; + try { + RemoteConnection.fromJson("{"); + } catch { + threw = true; + } + expect(threw).toBe(true); + }); }); describe("YAML serialization", () => { diff --git a/runtime/typescript/packages/core/tests/model/contracts/conversation/audio-part.test.ts b/runtime/typescript/packages/core/tests/model/contracts/conversation/audio-part.test.ts index 234a06710..5700c0f79 100644 --- a/runtime/typescript/packages/core/tests/model/contracts/conversation/audio-part.test.ts +++ b/runtime/typescript/packages/core/tests/model/contracts/conversation/audio-part.test.ts @@ -34,6 +34,16 @@ describe("AudioPart", () => { expect(reloaded.source).toEqual(instance.source); expect(reloaded.mediaType).toEqual(instance.mediaType); }); + + it("should reject malformed JSON", () => { + let threw = false; + try { + AudioPart.fromJson("{"); + } catch { + threw = true; + } + expect(threw).toBe(true); + }); }); describe("YAML serialization", () => { diff --git a/runtime/typescript/packages/core/tests/model/contracts/conversation/content-part.test.ts b/runtime/typescript/packages/core/tests/model/contracts/conversation/content-part.test.ts index 093a664f8..47cff63dc 100644 --- a/runtime/typescript/packages/core/tests/model/contracts/conversation/content-part.test.ts +++ b/runtime/typescript/packages/core/tests/model/contracts/conversation/content-part.test.ts @@ -20,6 +20,16 @@ describe("ContentPart", () => { const reloaded = ContentPart.fromJson(output); expect(reloaded.kind).toEqual(instance.kind); }); + + it("should reject malformed JSON", () => { + let threw = false; + try { + ContentPart.fromJson("{"); + } catch { + threw = true; + } + expect(threw).toBe(true); + }); }); describe("YAML serialization", () => { diff --git a/runtime/typescript/packages/core/tests/model/contracts/conversation/file-part.test.ts b/runtime/typescript/packages/core/tests/model/contracts/conversation/file-part.test.ts index 0a6b9b695..7222a031b 100644 --- a/runtime/typescript/packages/core/tests/model/contracts/conversation/file-part.test.ts +++ b/runtime/typescript/packages/core/tests/model/contracts/conversation/file-part.test.ts @@ -34,6 +34,16 @@ describe("FilePart", () => { expect(reloaded.source).toEqual(instance.source); expect(reloaded.mediaType).toEqual(instance.mediaType); }); + + it("should reject malformed JSON", () => { + let threw = false; + try { + FilePart.fromJson("{"); + } catch { + threw = true; + } + expect(threw).toBe(true); + }); }); describe("YAML serialization", () => { diff --git a/runtime/typescript/packages/core/tests/model/contracts/conversation/image-part.test.ts b/runtime/typescript/packages/core/tests/model/contracts/conversation/image-part.test.ts index 5a54decd7..6f2cc6cf7 100644 --- a/runtime/typescript/packages/core/tests/model/contracts/conversation/image-part.test.ts +++ b/runtime/typescript/packages/core/tests/model/contracts/conversation/image-part.test.ts @@ -36,6 +36,16 @@ describe("ImagePart", () => { expect(reloaded.detail).toEqual(instance.detail); expect(reloaded.mediaType).toEqual(instance.mediaType); }); + + it("should reject malformed JSON", () => { + let threw = false; + try { + ImagePart.fromJson("{"); + } catch { + threw = true; + } + expect(threw).toBe(true); + }); }); describe("YAML serialization", () => { diff --git a/runtime/typescript/packages/core/tests/model/contracts/conversation/message.test.ts b/runtime/typescript/packages/core/tests/model/contracts/conversation/message.test.ts index 6af962edb..a734b062f 100644 --- a/runtime/typescript/packages/core/tests/model/contracts/conversation/message.test.ts +++ b/runtime/typescript/packages/core/tests/model/contracts/conversation/message.test.ts @@ -32,6 +32,16 @@ describe("Message", () => { const reloaded = Message.fromJson(output); expect(reloaded.role).toEqual(instance.role); }); + + it("should reject malformed JSON", () => { + let threw = false; + try { + Message.fromJson("{"); + } catch { + threw = true; + } + expect(threw).toBe(true); + }); }); describe("YAML serialization", () => { diff --git a/runtime/typescript/packages/core/tests/model/contracts/conversation/text-part.test.ts b/runtime/typescript/packages/core/tests/model/contracts/conversation/text-part.test.ts index b13926ce5..e7a9c221b 100644 --- a/runtime/typescript/packages/core/tests/model/contracts/conversation/text-part.test.ts +++ b/runtime/typescript/packages/core/tests/model/contracts/conversation/text-part.test.ts @@ -32,6 +32,16 @@ describe("TextPart", () => { const reloaded = TextPart.fromJson(output); expect(reloaded.value).toEqual(instance.value); }); + + it("should reject malformed JSON", () => { + let threw = false; + try { + TextPart.fromJson("{"); + } catch { + threw = true; + } + expect(threw).toBe(true); + }); }); describe("YAML serialization", () => { diff --git a/runtime/typescript/packages/core/tests/model/contracts/conversation/tool-call.test.ts b/runtime/typescript/packages/core/tests/model/contracts/conversation/tool-call.test.ts index 13d007950..f9d81a4ed 100644 --- a/runtime/typescript/packages/core/tests/model/contracts/conversation/tool-call.test.ts +++ b/runtime/typescript/packages/core/tests/model/contracts/conversation/tool-call.test.ts @@ -36,6 +36,16 @@ describe("ToolCall", () => { expect(reloaded.name).toEqual(instance.name); expect(reloaded.arguments).toEqual(instance.arguments); }); + + it("should reject malformed JSON", () => { + let threw = false; + try { + ToolCall.fromJson("{"); + } catch { + threw = true; + } + expect(threw).toBe(true); + }); }); describe("YAML serialization", () => { diff --git a/runtime/typescript/packages/core/tests/model/contracts/conversation/tool-result.test.ts b/runtime/typescript/packages/core/tests/model/contracts/conversation/tool-result.test.ts index 49338652c..32945ce33 100644 --- a/runtime/typescript/packages/core/tests/model/contracts/conversation/tool-result.test.ts +++ b/runtime/typescript/packages/core/tests/model/contracts/conversation/tool-result.test.ts @@ -38,6 +38,16 @@ describe("ToolResult", () => { expect(reloaded.errorMessage).toEqual(instance.errorMessage); expect(reloaded.durationMs).toEqual(instance.durationMs); }); + + it("should reject malformed JSON", () => { + let threw = false; + try { + ToolResult.fromJson("{"); + } catch { + threw = true; + } + expect(threw).toBe(true); + }); }); describe("YAML serialization", () => { diff --git a/runtime/typescript/packages/core/tests/model/contracts/core/array-property.test.ts b/runtime/typescript/packages/core/tests/model/contracts/core/array-property.test.ts index 21c28fc59..bd49662b7 100644 --- a/runtime/typescript/packages/core/tests/model/contracts/core/array-property.test.ts +++ b/runtime/typescript/packages/core/tests/model/contracts/core/array-property.test.ts @@ -31,6 +31,16 @@ describe("ArrayProperty", () => { const reloaded = ArrayProperty.fromJson(output); expect(reloaded).toBeDefined(); }); + + it("should reject malformed JSON", () => { + let threw = false; + try { + ArrayProperty.fromJson("{"); + } catch { + threw = true; + } + expect(threw).toBe(true); + }); }); describe("YAML serialization", () => { diff --git a/runtime/typescript/packages/core/tests/model/contracts/core/object-property.test.ts b/runtime/typescript/packages/core/tests/model/contracts/core/object-property.test.ts index 0274046ef..17e1a527a 100644 --- a/runtime/typescript/packages/core/tests/model/contracts/core/object-property.test.ts +++ b/runtime/typescript/packages/core/tests/model/contracts/core/object-property.test.ts @@ -31,6 +31,16 @@ describe("ObjectProperty", () => { const reloaded = ObjectProperty.fromJson(output); expect(reloaded).toBeDefined(); }); + + it("should reject malformed JSON", () => { + let threw = false; + try { + ObjectProperty.fromJson("{"); + } catch { + threw = true; + } + expect(threw).toBe(true); + }); }); describe("YAML serialization", () => { diff --git a/runtime/typescript/packages/core/tests/model/contracts/core/property.test.ts b/runtime/typescript/packages/core/tests/model/contracts/core/property.test.ts index 18523d799..ab044daed 100644 --- a/runtime/typescript/packages/core/tests/model/contracts/core/property.test.ts +++ b/runtime/typescript/packages/core/tests/model/contracts/core/property.test.ts @@ -46,6 +46,16 @@ describe("Property", () => { expect(reloaded.default).toEqual(instance.default); expect(reloaded.example).toEqual(instance.example); }); + + it("should reject malformed JSON", () => { + let threw = false; + try { + Property.fromJson("{"); + } catch { + threw = true; + } + expect(threw).toBe(true); + }); }); describe("YAML serialization", () => { diff --git a/runtime/typescript/packages/core/tests/model/contracts/core/union-property.test.ts b/runtime/typescript/packages/core/tests/model/contracts/core/union-property.test.ts index c965ac46c..bf275ec82 100644 --- a/runtime/typescript/packages/core/tests/model/contracts/core/union-property.test.ts +++ b/runtime/typescript/packages/core/tests/model/contracts/core/union-property.test.ts @@ -31,6 +31,16 @@ describe("UnionProperty", () => { const reloaded = UnionProperty.fromJson(output); expect(reloaded).toBeDefined(); }); + + it("should reject malformed JSON", () => { + let threw = false; + try { + UnionProperty.fromJson("{"); + } catch { + threw = true; + } + expect(threw).toBe(true); + }); }); describe("YAML serialization", () => { diff --git a/runtime/typescript/packages/core/tests/model/contracts/events/checkpoint.test.ts b/runtime/typescript/packages/core/tests/model/contracts/events/checkpoint.test.ts index b83a04779..6b0cf8f77 100644 --- a/runtime/typescript/packages/core/tests/model/contracts/events/checkpoint.test.ts +++ b/runtime/typescript/packages/core/tests/model/contracts/events/checkpoint.test.ts @@ -42,6 +42,16 @@ describe("Checkpoint", () => { expect(reloaded.title).toEqual(instance.title); expect(reloaded.createdAt).toEqual(instance.createdAt); }); + + it("should reject malformed JSON", () => { + let threw = false; + try { + Checkpoint.fromJson("{"); + } catch { + threw = true; + } + expect(threw).toBe(true); + }); }); describe("YAML serialization", () => { diff --git a/runtime/typescript/packages/core/tests/model/contracts/events/error-chunk.test.ts b/runtime/typescript/packages/core/tests/model/contracts/events/error-chunk.test.ts index d6cc2202a..b5fc67c16 100644 --- a/runtime/typescript/packages/core/tests/model/contracts/events/error-chunk.test.ts +++ b/runtime/typescript/packages/core/tests/model/contracts/events/error-chunk.test.ts @@ -32,6 +32,16 @@ describe("ErrorChunk", () => { const reloaded = ErrorChunk.fromJson(output); expect(reloaded.message).toEqual(instance.message); }); + + it("should reject malformed JSON", () => { + let threw = false; + try { + ErrorChunk.fromJson("{"); + } catch { + threw = true; + } + expect(threw).toBe(true); + }); }); describe("YAML serialization", () => { diff --git a/runtime/typescript/packages/core/tests/model/contracts/events/failure-chunk.test.ts b/runtime/typescript/packages/core/tests/model/contracts/events/failure-chunk.test.ts index c6ac814aa..e3071e2b6 100644 --- a/runtime/typescript/packages/core/tests/model/contracts/events/failure-chunk.test.ts +++ b/runtime/typescript/packages/core/tests/model/contracts/events/failure-chunk.test.ts @@ -31,6 +31,16 @@ describe("FailureChunk", () => { const reloaded = FailureChunk.fromJson(output); expect(reloaded).toBeDefined(); }); + + it("should reject malformed JSON", () => { + let threw = false; + try { + FailureChunk.fromJson("{"); + } catch { + threw = true; + } + expect(threw).toBe(true); + }); }); describe("YAML serialization", () => { diff --git a/runtime/typescript/packages/core/tests/model/contracts/events/host-tool-request.test.ts b/runtime/typescript/packages/core/tests/model/contracts/events/host-tool-request.test.ts index d74f2db14..cba80a547 100644 --- a/runtime/typescript/packages/core/tests/model/contracts/events/host-tool-request.test.ts +++ b/runtime/typescript/packages/core/tests/model/contracts/events/host-tool-request.test.ts @@ -38,6 +38,16 @@ describe("HostToolRequest", () => { expect(reloaded.toolName).toEqual(instance.toolName); expect(reloaded.workingDirectory).toEqual(instance.workingDirectory); }); + + it("should reject malformed JSON", () => { + let threw = false; + try { + HostToolRequest.fromJson("{"); + } catch { + threw = true; + } + expect(threw).toBe(true); + }); }); describe("YAML serialization", () => { diff --git a/runtime/typescript/packages/core/tests/model/contracts/events/host-tool-result.test.ts b/runtime/typescript/packages/core/tests/model/contracts/events/host-tool-result.test.ts index d352c6462..834bbd7a7 100644 --- a/runtime/typescript/packages/core/tests/model/contracts/events/host-tool-result.test.ts +++ b/runtime/typescript/packages/core/tests/model/contracts/events/host-tool-result.test.ts @@ -44,6 +44,16 @@ describe("HostToolResult", () => { expect(reloaded.durationMs).toEqual(instance.durationMs); expect(reloaded.errorKind).toEqual(instance.errorKind); }); + + it("should reject malformed JSON", () => { + let threw = false; + try { + HostToolResult.fromJson("{"); + } catch { + threw = true; + } + expect(threw).toBe(true); + }); }); describe("YAML serialization", () => { diff --git a/runtime/typescript/packages/core/tests/model/contracts/events/permission-decision.test.ts b/runtime/typescript/packages/core/tests/model/contracts/events/permission-decision.test.ts index bb3f73ca1..1db8eab60 100644 --- a/runtime/typescript/packages/core/tests/model/contracts/events/permission-decision.test.ts +++ b/runtime/typescript/packages/core/tests/model/contracts/events/permission-decision.test.ts @@ -40,6 +40,16 @@ describe("PermissionDecision", () => { expect(reloaded.approved).toEqual(instance.approved); expect(reloaded.reason).toEqual(instance.reason); }); + + it("should reject malformed JSON", () => { + let threw = false; + try { + PermissionDecision.fromJson("{"); + } catch { + threw = true; + } + expect(threw).toBe(true); + }); }); describe("YAML serialization", () => { diff --git a/runtime/typescript/packages/core/tests/model/contracts/events/permission-request.test.ts b/runtime/typescript/packages/core/tests/model/contracts/events/permission-request.test.ts index f394491f9..c4f6587d2 100644 --- a/runtime/typescript/packages/core/tests/model/contracts/events/permission-request.test.ts +++ b/runtime/typescript/packages/core/tests/model/contracts/events/permission-request.test.ts @@ -40,6 +40,16 @@ describe("PermissionRequest", () => { expect(reloaded.target).toEqual(instance.target); expect(reloaded.promptRequest).toEqual(instance.promptRequest); }); + + it("should reject malformed JSON", () => { + let threw = false; + try { + PermissionRequest.fromJson("{"); + } catch { + threw = true; + } + expect(threw).toBe(true); + }); }); describe("YAML serialization", () => { diff --git a/runtime/typescript/packages/core/tests/model/contracts/events/redacted-field.test.ts b/runtime/typescript/packages/core/tests/model/contracts/events/redacted-field.test.ts index a7445f3e7..b26b9533b 100644 --- a/runtime/typescript/packages/core/tests/model/contracts/events/redacted-field.test.ts +++ b/runtime/typescript/packages/core/tests/model/contracts/events/redacted-field.test.ts @@ -36,6 +36,16 @@ describe("RedactedField", () => { expect(reloaded.mode).toEqual(instance.mode); expect(reloaded.reason).toEqual(instance.reason); }); + + it("should reject malformed JSON", () => { + let threw = false; + try { + RedactedField.fromJson("{"); + } catch { + threw = true; + } + expect(threw).toBe(true); + }); }); describe("YAML serialization", () => { diff --git a/runtime/typescript/packages/core/tests/model/contracts/events/redaction-metadata.test.ts b/runtime/typescript/packages/core/tests/model/contracts/events/redaction-metadata.test.ts index 6b5f373b8..ad3253205 100644 --- a/runtime/typescript/packages/core/tests/model/contracts/events/redaction-metadata.test.ts +++ b/runtime/typescript/packages/core/tests/model/contracts/events/redaction-metadata.test.ts @@ -34,6 +34,16 @@ describe("RedactionMetadata", () => { expect(reloaded.sanitized).toEqual(instance.sanitized); expect(reloaded.policy).toEqual(instance.policy); }); + + it("should reject malformed JSON", () => { + let threw = false; + try { + RedactionMetadata.fromJson("{"); + } catch { + threw = true; + } + expect(threw).toBe(true); + }); }); describe("YAML serialization", () => { diff --git a/runtime/typescript/packages/core/tests/model/contracts/events/session-event.test.ts b/runtime/typescript/packages/core/tests/model/contracts/events/session-event.test.ts index ee8abc60b..de47048d9 100644 --- a/runtime/typescript/packages/core/tests/model/contracts/events/session-event.test.ts +++ b/runtime/typescript/packages/core/tests/model/contracts/events/session-event.test.ts @@ -42,6 +42,16 @@ describe("SessionEvent", () => { expect(reloaded.parentId).toEqual(instance.parentId); expect(reloaded.spanId).toEqual(instance.spanId); }); + + it("should reject malformed JSON", () => { + let threw = false; + try { + SessionEvent.fromJson("{"); + } catch { + threw = true; + } + expect(threw).toBe(true); + }); }); describe("YAML serialization", () => { diff --git a/runtime/typescript/packages/core/tests/model/contracts/events/session-summary.test.ts b/runtime/typescript/packages/core/tests/model/contracts/events/session-summary.test.ts index 46b849873..4f3bda3e1 100644 --- a/runtime/typescript/packages/core/tests/model/contracts/events/session-summary.test.ts +++ b/runtime/typescript/packages/core/tests/model/contracts/events/session-summary.test.ts @@ -40,6 +40,16 @@ describe("SessionSummary", () => { expect(reloaded.checkpoints).toEqual(instance.checkpoints); expect(reloaded.durationMs).toEqual(instance.durationMs); }); + + it("should reject malformed JSON", () => { + let threw = false; + try { + SessionSummary.fromJson("{"); + } catch { + threw = true; + } + expect(threw).toBe(true); + }); }); describe("YAML serialization", () => { diff --git a/runtime/typescript/packages/core/tests/model/contracts/events/stream-chunk.test.ts b/runtime/typescript/packages/core/tests/model/contracts/events/stream-chunk.test.ts index 44dc1b8dd..260b3d526 100644 --- a/runtime/typescript/packages/core/tests/model/contracts/events/stream-chunk.test.ts +++ b/runtime/typescript/packages/core/tests/model/contracts/events/stream-chunk.test.ts @@ -20,6 +20,16 @@ describe("StreamChunk", () => { const reloaded = StreamChunk.fromJson(output); expect(reloaded.kind).toEqual(instance.kind); }); + + it("should reject malformed JSON", () => { + let threw = false; + try { + StreamChunk.fromJson("{"); + } catch { + threw = true; + } + expect(threw).toBe(true); + }); }); describe("YAML serialization", () => { diff --git a/runtime/typescript/packages/core/tests/model/contracts/events/stream-failure.test.ts b/runtime/typescript/packages/core/tests/model/contracts/events/stream-failure.test.ts index 48c8897b7..ff00bff71 100644 --- a/runtime/typescript/packages/core/tests/model/contracts/events/stream-failure.test.ts +++ b/runtime/typescript/packages/core/tests/model/contracts/events/stream-failure.test.ts @@ -34,6 +34,16 @@ describe("StreamFailure", () => { expect(reloaded.outcome).toEqual(instance.outcome); expect(reloaded.message).toEqual(instance.message); }); + + it("should reject malformed JSON", () => { + let threw = false; + try { + StreamFailure.fromJson("{"); + } catch { + threw = true; + } + expect(threw).toBe(true); + }); }); describe("YAML serialization", () => { diff --git a/runtime/typescript/packages/core/tests/model/contracts/events/text-chunk.test.ts b/runtime/typescript/packages/core/tests/model/contracts/events/text-chunk.test.ts index 4fdd204f1..fe5b17dbd 100644 --- a/runtime/typescript/packages/core/tests/model/contracts/events/text-chunk.test.ts +++ b/runtime/typescript/packages/core/tests/model/contracts/events/text-chunk.test.ts @@ -32,6 +32,16 @@ describe("TextChunk", () => { const reloaded = TextChunk.fromJson(output); expect(reloaded.value).toEqual(instance.value); }); + + it("should reject malformed JSON", () => { + let threw = false; + try { + TextChunk.fromJson("{"); + } catch { + threw = true; + } + expect(threw).toBe(true); + }); }); describe("YAML serialization", () => { diff --git a/runtime/typescript/packages/core/tests/model/contracts/events/thinking-chunk.test.ts b/runtime/typescript/packages/core/tests/model/contracts/events/thinking-chunk.test.ts index ed5799207..0a3672c96 100644 --- a/runtime/typescript/packages/core/tests/model/contracts/events/thinking-chunk.test.ts +++ b/runtime/typescript/packages/core/tests/model/contracts/events/thinking-chunk.test.ts @@ -32,6 +32,16 @@ describe("ThinkingChunk", () => { const reloaded = ThinkingChunk.fromJson(output); expect(reloaded.value).toEqual(instance.value); }); + + it("should reject malformed JSON", () => { + let threw = false; + try { + ThinkingChunk.fromJson("{"); + } catch { + threw = true; + } + expect(threw).toBe(true); + }); }); describe("YAML serialization", () => { diff --git a/runtime/typescript/packages/core/tests/model/contracts/events/tool-chunk.test.ts b/runtime/typescript/packages/core/tests/model/contracts/events/tool-chunk.test.ts index bc149e634..24d2da36f 100644 --- a/runtime/typescript/packages/core/tests/model/contracts/events/tool-chunk.test.ts +++ b/runtime/typescript/packages/core/tests/model/contracts/events/tool-chunk.test.ts @@ -31,6 +31,16 @@ describe("ToolChunk", () => { const reloaded = ToolChunk.fromJson(output); expect(reloaded).toBeDefined(); }); + + it("should reject malformed JSON", () => { + let threw = false; + try { + ToolChunk.fromJson("{"); + } catch { + threw = true; + } + expect(threw).toBe(true); + }); }); describe("YAML serialization", () => { diff --git a/runtime/typescript/packages/core/tests/model/contracts/events/turn-event.test.ts b/runtime/typescript/packages/core/tests/model/contracts/events/turn-event.test.ts index 7e92e6327..de33c99dd 100644 --- a/runtime/typescript/packages/core/tests/model/contracts/events/turn-event.test.ts +++ b/runtime/typescript/packages/core/tests/model/contracts/events/turn-event.test.ts @@ -42,6 +42,16 @@ describe("TurnEvent", () => { expect(reloaded.parentId).toEqual(instance.parentId); expect(reloaded.spanId).toEqual(instance.spanId); }); + + it("should reject malformed JSON", () => { + let threw = false; + try { + TurnEvent.fromJson("{"); + } catch { + threw = true; + } + expect(threw).toBe(true); + }); }); describe("YAML serialization", () => { diff --git a/runtime/typescript/packages/core/tests/model/contracts/events/usage-chunk.test.ts b/runtime/typescript/packages/core/tests/model/contracts/events/usage-chunk.test.ts index 05616a502..9971473b8 100644 --- a/runtime/typescript/packages/core/tests/model/contracts/events/usage-chunk.test.ts +++ b/runtime/typescript/packages/core/tests/model/contracts/events/usage-chunk.test.ts @@ -32,6 +32,16 @@ describe("UsageChunk", () => { const reloaded = UsageChunk.fromJson(output); expect(reloaded.kind).toEqual(instance.kind); }); + + it("should reject malformed JSON", () => { + let threw = false; + try { + UsageChunk.fromJson("{"); + } catch { + threw = true; + } + expect(threw).toBe(true); + }); }); describe("YAML serialization", () => { diff --git a/runtime/typescript/packages/core/tests/model/contracts/memory/memory-entry.test.ts b/runtime/typescript/packages/core/tests/model/contracts/memory/memory-entry.test.ts index 1f51af940..8d113f5f4 100644 --- a/runtime/typescript/packages/core/tests/model/contracts/memory/memory-entry.test.ts +++ b/runtime/typescript/packages/core/tests/model/contracts/memory/memory-entry.test.ts @@ -36,6 +36,16 @@ describe("MemoryEntry", () => { expect(reloaded.category).toEqual(instance.category); expect(reloaded.createdAt).toEqual(instance.createdAt); }); + + it("should reject malformed JSON", () => { + let threw = false; + try { + MemoryEntry.fromJson("{"); + } catch { + threw = true; + } + expect(threw).toBe(true); + }); }); describe("YAML serialization", () => { diff --git a/runtime/typescript/packages/core/tests/model/contracts/memory/memory-store.test.ts b/runtime/typescript/packages/core/tests/model/contracts/memory/memory-store.test.ts index 1d4e0ecc0..2b846d2dc 100644 --- a/runtime/typescript/packages/core/tests/model/contracts/memory/memory-store.test.ts +++ b/runtime/typescript/packages/core/tests/model/contracts/memory/memory-store.test.ts @@ -31,6 +31,16 @@ describe("MemoryStore", () => { const reloaded = MemoryStore.fromJson(output); expect(reloaded).toBeDefined(); }); + + it("should reject malformed JSON", () => { + let threw = false; + try { + MemoryStore.fromJson("{"); + } catch { + threw = true; + } + expect(threw).toBe(true); + }); }); describe("YAML serialization", () => { diff --git a/runtime/typescript/packages/core/tests/model/contracts/models/azure-model.test.ts b/runtime/typescript/packages/core/tests/model/contracts/models/azure-model.test.ts index bbc3acace..a74d87578 100644 --- a/runtime/typescript/packages/core/tests/model/contracts/models/azure-model.test.ts +++ b/runtime/typescript/packages/core/tests/model/contracts/models/azure-model.test.ts @@ -32,6 +32,16 @@ describe("AzureModel", () => { const reloaded = AzureModel.fromJson(output); expect(reloaded.provider).toEqual(instance.provider); }); + + it("should reject malformed JSON", () => { + let threw = false; + try { + AzureModel.fromJson("{"); + } catch { + threw = true; + } + expect(threw).toBe(true); + }); }); describe("YAML serialization", () => { diff --git a/runtime/typescript/packages/core/tests/model/contracts/models/custom-model.test.ts b/runtime/typescript/packages/core/tests/model/contracts/models/custom-model.test.ts index bf49bb336..bc433b8d7 100644 --- a/runtime/typescript/packages/core/tests/model/contracts/models/custom-model.test.ts +++ b/runtime/typescript/packages/core/tests/model/contracts/models/custom-model.test.ts @@ -32,6 +32,16 @@ describe("CustomModel", () => { const reloaded = CustomModel.fromJson(output); expect(reloaded.provider).toEqual(instance.provider); }); + + it("should reject malformed JSON", () => { + let threw = false; + try { + CustomModel.fromJson("{"); + } catch { + threw = true; + } + expect(threw).toBe(true); + }); }); describe("YAML serialization", () => { diff --git a/runtime/typescript/packages/core/tests/model/contracts/models/invocation-usage.test.ts b/runtime/typescript/packages/core/tests/model/contracts/models/invocation-usage.test.ts index 7b1636013..5ba7fe1d8 100644 --- a/runtime/typescript/packages/core/tests/model/contracts/models/invocation-usage.test.ts +++ b/runtime/typescript/packages/core/tests/model/contracts/models/invocation-usage.test.ts @@ -36,6 +36,16 @@ describe("InvocationUsage", () => { expect(reloaded.outputTokens).toEqual(instance.outputTokens); expect(reloaded.totalTokens).toEqual(instance.totalTokens); }); + + it("should reject malformed JSON", () => { + let threw = false; + try { + InvocationUsage.fromJson("{"); + } catch { + threw = true; + } + expect(threw).toBe(true); + }); }); describe("YAML serialization", () => { @@ -59,6 +69,36 @@ describe("InvocationUsage", () => { }); }); + describe("wire conversion", () => { + it("should apply provider wire field names", () => { + const json = `{\n "inputTokens": 150,\n "outputTokens": 42,\n "totalTokens": 192\n}`; + const instance = InvocationUsage.fromJson(json); + const openaiWire = instance.toWire("openai"); + expect(Object.keys(openaiWire).includes("prompt_tokens")).toBe(true); + expect(Object.keys(openaiWire).includes("inputTokens")).toBe(false); + expect(Object.keys(openaiWire).includes("completion_tokens")).toBe(true); + expect(Object.keys(openaiWire).includes("outputTokens")).toBe(false); + expect(Object.keys(openaiWire).includes("total_tokens")).toBe(true); + expect(Object.keys(openaiWire).includes("totalTokens")).toBe(false); + const openaiRestored = InvocationUsage.fromWire("openai", openaiWire); + expect(Object.keys(openaiRestored.toWire("openai")).sort()).toEqual( + Object.keys(openaiWire).sort(), + ); + const anthropicWire = instance.toWire("anthropic"); + expect(Object.keys(anthropicWire).includes("input_tokens")).toBe(true); + expect(Object.keys(anthropicWire).includes("inputTokens")).toBe(false); + expect(Object.keys(anthropicWire).includes("output_tokens")).toBe(true); + expect(Object.keys(anthropicWire).includes("outputTokens")).toBe(false); + const anthropicRestored = InvocationUsage.fromWire( + "anthropic", + anthropicWire, + ); + expect(Object.keys(anthropicRestored.toWire("anthropic")).sort()).toEqual( + Object.keys(anthropicWire).sort(), + ); + }); + }); + describe("load and save", () => { it("should load from dictionary", () => { const data = JSON.parse( diff --git a/runtime/typescript/packages/core/tests/model/contracts/models/model-info.test.ts b/runtime/typescript/packages/core/tests/model/contracts/models/model-info.test.ts index 0616cc1cc..594e83a19 100644 --- a/runtime/typescript/packages/core/tests/model/contracts/models/model-info.test.ts +++ b/runtime/typescript/packages/core/tests/model/contracts/models/model-info.test.ts @@ -38,6 +38,16 @@ describe("ModelInfo", () => { expect(reloaded.ownedBy).toEqual(instance.ownedBy); expect(reloaded.contextWindow).toEqual(instance.contextWindow); }); + + it("should reject malformed JSON", () => { + let threw = false; + try { + ModelInfo.fromJson("{"); + } catch { + threw = true; + } + expect(threw).toBe(true); + }); }); describe("YAML serialization", () => { @@ -63,6 +73,43 @@ describe("ModelInfo", () => { }); }); + describe("wire conversion", () => { + it("should apply provider wire field names", () => { + const json = `{\n "id": "gpt-4o",\n "displayName": "GPT-4o",\n "ownedBy": "openai",\n "contextWindow": 128000,\n "inputModalities": [\n "text",\n "image"\n ],\n "outputModalities": [\n "text"\n ],\n "additionalProperties": {\n "supportsStreaming": true\n }\n}`; + const instance = ModelInfo.fromJson(json); + const openaiWire = instance.toWire("openai"); + expect(Object.keys(openaiWire).includes("id")).toBe(true); + expect(Object.keys(openaiWire).includes("owned_by")).toBe(true); + expect(Object.keys(openaiWire).includes("ownedBy")).toBe(false); + const openaiRestored = ModelInfo.fromWire("openai", openaiWire); + expect(Object.keys(openaiRestored.toWire("openai")).sort()).toEqual( + Object.keys(openaiWire).sort(), + ); + const anthropicWire = instance.toWire("anthropic"); + expect(Object.keys(anthropicWire).includes("id")).toBe(true); + expect(Object.keys(anthropicWire).includes("display_name")).toBe(true); + expect(Object.keys(anthropicWire).includes("displayName")).toBe(false); + expect(Object.keys(anthropicWire).includes("context_length")).toBe(true); + expect(Object.keys(anthropicWire).includes("contextWindow")).toBe(false); + expect(Object.keys(anthropicWire).includes("input_modalities")).toBe( + true, + ); + expect(Object.keys(anthropicWire).includes("inputModalities")).toBe( + false, + ); + expect(Object.keys(anthropicWire).includes("output_modalities")).toBe( + true, + ); + expect(Object.keys(anthropicWire).includes("outputModalities")).toBe( + false, + ); + const anthropicRestored = ModelInfo.fromWire("anthropic", anthropicWire); + expect(Object.keys(anthropicRestored.toWire("anthropic")).sort()).toEqual( + Object.keys(anthropicWire).sort(), + ); + }); + }); + describe("load and save", () => { it("should load from dictionary", () => { const data = JSON.parse( diff --git a/runtime/typescript/packages/core/tests/model/contracts/models/model-options.test.ts b/runtime/typescript/packages/core/tests/model/contracts/models/model-options.test.ts index f599d52cd..2ae430377 100644 --- a/runtime/typescript/packages/core/tests/model/contracts/models/model-options.test.ts +++ b/runtime/typescript/packages/core/tests/model/contracts/models/model-options.test.ts @@ -19,12 +19,13 @@ describe("ModelOptions", () => { describe("JSON serialization", () => { it("should load from JSON - example 1", () => { - const json = `{\n "frequencyPenalty": 0.5,\n "maxOutputTokens": 2048,\n "presencePenalty": 0.3,\n "seed": 42,\n "temperature": 0.7,\n "topK": 40,\n "topP": 0.9,\n "stopSequences": [\n "\\n",\n "###"\n ],\n "allowMultipleToolCalls": true,\n "additionalProperties": {\n "customProperty": "value",\n "anotherProperty": "anotherValue"\n }\n}`; + const json = `{\n "frequencyPenalty": 0.5,\n "maxOutputTokens": 2048,\n "presencePenalty": 0.3,\n "reasoningEffort": "medium",\n "seed": 42,\n "temperature": 0.7,\n "topK": 40,\n "topP": 0.9,\n "stopSequences": [\n "\\n",\n "###"\n ],\n "allowMultipleToolCalls": true,\n "additionalProperties": {\n "customProperty": "value",\n "anotherProperty": "anotherValue"\n }\n}`; const instance = ModelOptions.fromJson(json); expect(instance).toBeDefined(); expect(instance.frequencyPenalty).toEqual(0.5); expect(instance.maxOutputTokens).toEqual(2048); expect(instance.presencePenalty).toEqual(0.3); + expect(instance.reasoningEffort).toEqual("medium"); expect(instance.seed).toEqual(42); expect(instance.temperature).toEqual(0.7); expect(instance.topK).toEqual(40); @@ -33,13 +34,14 @@ describe("ModelOptions", () => { }); it("should round-trip JSON - example 1", () => { - const json = `{\n "frequencyPenalty": 0.5,\n "maxOutputTokens": 2048,\n "presencePenalty": 0.3,\n "seed": 42,\n "temperature": 0.7,\n "topK": 40,\n "topP": 0.9,\n "stopSequences": [\n "\\n",\n "###"\n ],\n "allowMultipleToolCalls": true,\n "additionalProperties": {\n "customProperty": "value",\n "anotherProperty": "anotherValue"\n }\n}`; + const json = `{\n "frequencyPenalty": 0.5,\n "maxOutputTokens": 2048,\n "presencePenalty": 0.3,\n "reasoningEffort": "medium",\n "seed": 42,\n "temperature": 0.7,\n "topK": 40,\n "topP": 0.9,\n "stopSequences": [\n "\\n",\n "###"\n ],\n "allowMultipleToolCalls": true,\n "additionalProperties": {\n "customProperty": "value",\n "anotherProperty": "anotherValue"\n }\n}`; const instance = ModelOptions.fromJson(json); const output = instance.toJson(); const reloaded = ModelOptions.fromJson(output); expect(reloaded.frequencyPenalty).toEqual(instance.frequencyPenalty); expect(reloaded.maxOutputTokens).toEqual(instance.maxOutputTokens); expect(reloaded.presencePenalty).toEqual(instance.presencePenalty); + expect(reloaded.reasoningEffort).toEqual(instance.reasoningEffort); expect(reloaded.seed).toEqual(instance.seed); expect(reloaded.temperature).toEqual(instance.temperature); expect(reloaded.topK).toEqual(instance.topK); @@ -48,16 +50,27 @@ describe("ModelOptions", () => { instance.allowMultipleToolCalls, ); }); + + it("should reject malformed JSON", () => { + let threw = false; + try { + ModelOptions.fromJson("{"); + } catch { + threw = true; + } + expect(threw).toBe(true); + }); }); describe("YAML serialization", () => { it("should load from YAML - example 1", () => { - const yaml = `frequencyPenalty: 0.5\nmaxOutputTokens: 2048\npresencePenalty: 0.3\nseed: 42\ntemperature: 0.7\ntopK: 40\ntopP: 0.9\nstopSequences:\n - "\\n"\n - "###"\nallowMultipleToolCalls: true\nadditionalProperties:\n customProperty: value\n anotherProperty: anotherValue\n`; + const yaml = `frequencyPenalty: 0.5\nmaxOutputTokens: 2048\npresencePenalty: 0.3\nreasoningEffort: medium\nseed: 42\ntemperature: 0.7\ntopK: 40\ntopP: 0.9\nstopSequences:\n - "\\n"\n - "###"\nallowMultipleToolCalls: true\nadditionalProperties:\n customProperty: value\n anotherProperty: anotherValue\n`; const instance = ModelOptions.fromYaml(yaml); expect(instance).toBeDefined(); expect(instance.frequencyPenalty).toEqual(0.5); expect(instance.maxOutputTokens).toEqual(2048); expect(instance.presencePenalty).toEqual(0.3); + expect(instance.reasoningEffort).toEqual("medium"); expect(instance.seed).toEqual(42); expect(instance.temperature).toEqual(0.7); expect(instance.topK).toEqual(40); @@ -66,13 +79,14 @@ describe("ModelOptions", () => { }); it("should round-trip YAML - example 1", () => { - const yaml = `frequencyPenalty: 0.5\nmaxOutputTokens: 2048\npresencePenalty: 0.3\nseed: 42\ntemperature: 0.7\ntopK: 40\ntopP: 0.9\nstopSequences:\n - "\\n"\n - "###"\nallowMultipleToolCalls: true\nadditionalProperties:\n customProperty: value\n anotherProperty: anotherValue\n`; + const yaml = `frequencyPenalty: 0.5\nmaxOutputTokens: 2048\npresencePenalty: 0.3\nreasoningEffort: medium\nseed: 42\ntemperature: 0.7\ntopK: 40\ntopP: 0.9\nstopSequences:\n - "\\n"\n - "###"\nallowMultipleToolCalls: true\nadditionalProperties:\n customProperty: value\n anotherProperty: anotherValue\n`; const instance = ModelOptions.fromYaml(yaml); const output = instance.toYaml(); const reloaded = ModelOptions.fromYaml(output); expect(reloaded.frequencyPenalty).toEqual(instance.frequencyPenalty); expect(reloaded.maxOutputTokens).toEqual(instance.maxOutputTokens); expect(reloaded.presencePenalty).toEqual(instance.presencePenalty); + expect(reloaded.reasoningEffort).toEqual(instance.reasoningEffort); expect(reloaded.seed).toEqual(instance.seed); expect(reloaded.temperature).toEqual(instance.temperature); expect(reloaded.topK).toEqual(instance.topK); @@ -83,10 +97,88 @@ describe("ModelOptions", () => { }); }); + describe("wire conversion", () => { + it("should apply provider wire field names", () => { + const json = `{\n "frequencyPenalty": 0.5,\n "maxOutputTokens": 2048,\n "presencePenalty": 0.3,\n "reasoningEffort": "medium",\n "seed": 42,\n "temperature": 0.7,\n "topK": 40,\n "topP": 0.9,\n "stopSequences": [\n "\\n",\n "###"\n ],\n "allowMultipleToolCalls": true,\n "additionalProperties": {\n "customProperty": "value",\n "anotherProperty": "anotherValue"\n }\n}`; + const instance = ModelOptions.fromJson(json); + const openaiWire = instance.toWire("openai"); + expect(Object.keys(openaiWire).includes("frequency_penalty")).toBe(true); + expect(Object.keys(openaiWire).includes("frequencyPenalty")).toBe(false); + expect(Object.keys(openaiWire).includes("max_completion_tokens")).toBe( + true, + ); + expect(Object.keys(openaiWire).includes("maxOutputTokens")).toBe(false); + expect(Object.keys(openaiWire).includes("presence_penalty")).toBe(true); + expect(Object.keys(openaiWire).includes("presencePenalty")).toBe(false); + expect(Object.keys(openaiWire).includes("reasoning_effort")).toBe(true); + expect(Object.keys(openaiWire).includes("reasoningEffort")).toBe(false); + expect(Object.keys(openaiWire).includes("seed")).toBe(true); + expect(Object.keys(openaiWire).includes("temperature")).toBe(true); + expect(Object.keys(openaiWire).includes("top_k")).toBe(true); + expect(Object.keys(openaiWire).includes("topK")).toBe(false); + expect(Object.keys(openaiWire).includes("top_p")).toBe(true); + expect(Object.keys(openaiWire).includes("topP")).toBe(false); + expect(Object.keys(openaiWire).includes("stop")).toBe(true); + expect(Object.keys(openaiWire).includes("stopSequences")).toBe(false); + expect(Object.keys(openaiWire).includes("parallel_tool_calls")).toBe( + true, + ); + expect(Object.keys(openaiWire).includes("allowMultipleToolCalls")).toBe( + false, + ); + const openaiRestored = ModelOptions.fromWire("openai", openaiWire); + expect(Object.keys(openaiRestored.toWire("openai")).sort()).toEqual( + Object.keys(openaiWire).sort(), + ); + const responsesWire = instance.toWire("responses"); + expect(Object.keys(responsesWire).includes("max_output_tokens")).toBe( + true, + ); + expect(Object.keys(responsesWire).includes("maxOutputTokens")).toBe( + false, + ); + expect(Object.keys(responsesWire).includes("reasoning_effort")).toBe( + true, + ); + expect(Object.keys(responsesWire).includes("reasoningEffort")).toBe( + false, + ); + expect(Object.keys(responsesWire).includes("temperature")).toBe(true); + expect(Object.keys(responsesWire).includes("top_p")).toBe(true); + expect(Object.keys(responsesWire).includes("topP")).toBe(false); + const responsesRestored = ModelOptions.fromWire( + "responses", + responsesWire, + ); + expect(Object.keys(responsesRestored.toWire("responses")).sort()).toEqual( + Object.keys(responsesWire).sort(), + ); + const anthropicWire = instance.toWire("anthropic"); + expect(Object.keys(anthropicWire).includes("max_tokens")).toBe(true); + expect(Object.keys(anthropicWire).includes("maxOutputTokens")).toBe( + false, + ); + expect(Object.keys(anthropicWire).includes("temperature")).toBe(true); + expect(Object.keys(anthropicWire).includes("top_k")).toBe(true); + expect(Object.keys(anthropicWire).includes("topK")).toBe(false); + expect(Object.keys(anthropicWire).includes("top_p")).toBe(true); + expect(Object.keys(anthropicWire).includes("topP")).toBe(false); + expect(Object.keys(anthropicWire).includes("stop_sequences")).toBe(true); + expect(Object.keys(anthropicWire).includes("stopSequences")).toBe(false); + const anthropicRestored = ModelOptions.fromWire( + "anthropic", + anthropicWire, + ); + expect(Object.keys(anthropicRestored.toWire("anthropic")).sort()).toEqual( + Object.keys(anthropicWire).sort(), + ); + }); + }); + describe("load and save", () => { it("should load from dictionary", () => { const data = JSON.parse( - `{\n "frequencyPenalty": 0.5,\n "maxOutputTokens": 2048,\n "presencePenalty": 0.3,\n "seed": 42,\n "temperature": 0.7,\n "topK": 40,\n "topP": 0.9,\n "stopSequences": [\n "\\n",\n "###"\n ],\n "allowMultipleToolCalls": true,\n "additionalProperties": {\n "customProperty": "value",\n "anotherProperty": "anotherValue"\n }\n}`, + `{\n "frequencyPenalty": 0.5,\n "maxOutputTokens": 2048,\n "presencePenalty": 0.3,\n "reasoningEffort": "medium",\n "seed": 42,\n "temperature": 0.7,\n "topK": 40,\n "topP": 0.9,\n "stopSequences": [\n "\\n",\n "###"\n ],\n "allowMultipleToolCalls": true,\n "additionalProperties": {\n "customProperty": "value",\n "anotherProperty": "anotherValue"\n }\n}`, ) as Record; const instance = ModelOptions.load(data); expect(instance).toBeDefined(); diff --git a/runtime/typescript/packages/core/tests/model/contracts/models/model.test.ts b/runtime/typescript/packages/core/tests/model/contracts/models/model.test.ts index 6bf11858f..55f33f4ea 100644 --- a/runtime/typescript/packages/core/tests/model/contracts/models/model.test.ts +++ b/runtime/typescript/packages/core/tests/model/contracts/models/model.test.ts @@ -36,6 +36,16 @@ describe("Model", () => { expect(reloaded.provider).toEqual(instance.provider); expect(reloaded.apiType).toEqual(instance.apiType); }); + + it("should reject malformed JSON", () => { + let threw = false; + try { + Model.fromJson("{"); + } catch { + threw = true; + } + expect(threw).toBe(true); + }); }); describe("YAML serialization", () => { diff --git a/runtime/typescript/packages/core/tests/model/contracts/models/open-ai-model.test.ts b/runtime/typescript/packages/core/tests/model/contracts/models/open-ai-model.test.ts index 48c23d9a5..5c687f882 100644 --- a/runtime/typescript/packages/core/tests/model/contracts/models/open-ai-model.test.ts +++ b/runtime/typescript/packages/core/tests/model/contracts/models/open-ai-model.test.ts @@ -32,6 +32,16 @@ describe("OpenAIModel", () => { const reloaded = OpenAIModel.fromJson(output); expect(reloaded.provider).toEqual(instance.provider); }); + + it("should reject malformed JSON", () => { + let threw = false; + try { + OpenAIModel.fromJson("{"); + } catch { + threw = true; + } + expect(threw).toBe(true); + }); }); describe("YAML serialization", () => { diff --git a/runtime/typescript/packages/core/tests/model/contracts/models/token-usage.test.ts b/runtime/typescript/packages/core/tests/model/contracts/models/token-usage.test.ts index 57f2112e1..1fb904790 100644 --- a/runtime/typescript/packages/core/tests/model/contracts/models/token-usage.test.ts +++ b/runtime/typescript/packages/core/tests/model/contracts/models/token-usage.test.ts @@ -36,6 +36,16 @@ describe("TokenUsage", () => { expect(reloaded.completionTokens).toEqual(instance.completionTokens); expect(reloaded.totalTokens).toEqual(instance.totalTokens); }); + + it("should reject malformed JSON", () => { + let threw = false; + try { + TokenUsage.fromJson("{"); + } catch { + threw = true; + } + expect(threw).toBe(true); + }); }); describe("YAML serialization", () => { @@ -59,6 +69,35 @@ describe("TokenUsage", () => { }); }); + describe("wire conversion", () => { + it("should apply provider wire field names", () => { + const json = `{\n "promptTokens": 150,\n "completionTokens": 42,\n "totalTokens": 192\n}`; + const instance = TokenUsage.fromJson(json); + const openaiWire = instance.toWire("openai"); + expect(Object.keys(openaiWire).includes("prompt_tokens")).toBe(true); + expect(Object.keys(openaiWire).includes("promptTokens")).toBe(false); + expect(Object.keys(openaiWire).includes("completion_tokens")).toBe(true); + expect(Object.keys(openaiWire).includes("completionTokens")).toBe(false); + expect(Object.keys(openaiWire).includes("total_tokens")).toBe(true); + expect(Object.keys(openaiWire).includes("totalTokens")).toBe(false); + const openaiRestored = TokenUsage.fromWire("openai", openaiWire); + expect(Object.keys(openaiRestored.toWire("openai")).sort()).toEqual( + Object.keys(openaiWire).sort(), + ); + const anthropicWire = instance.toWire("anthropic"); + expect(Object.keys(anthropicWire).includes("input_tokens")).toBe(true); + expect(Object.keys(anthropicWire).includes("promptTokens")).toBe(false); + expect(Object.keys(anthropicWire).includes("output_tokens")).toBe(true); + expect(Object.keys(anthropicWire).includes("completionTokens")).toBe( + false, + ); + const anthropicRestored = TokenUsage.fromWire("anthropic", anthropicWire); + expect(Object.keys(anthropicRestored.toWire("anthropic")).sort()).toEqual( + Object.keys(anthropicWire).sort(), + ); + }); + }); + describe("load and save", () => { it("should load from dictionary", () => { const data = JSON.parse( diff --git a/runtime/typescript/packages/core/tests/model/contracts/pipeline/delegated-state-reference.test.ts b/runtime/typescript/packages/core/tests/model/contracts/pipeline/delegated-state-reference.test.ts index e0189ffba..224a9518a 100644 --- a/runtime/typescript/packages/core/tests/model/contracts/pipeline/delegated-state-reference.test.ts +++ b/runtime/typescript/packages/core/tests/model/contracts/pipeline/delegated-state-reference.test.ts @@ -36,6 +36,16 @@ describe("DelegatedStateReference", () => { expect(reloaded.kind).toEqual(instance.kind); expect(reloaded.id).toEqual(instance.id); }); + + it("should reject malformed JSON", () => { + let threw = false; + try { + DelegatedStateReference.fromJson("{"); + } catch { + threw = true; + } + expect(threw).toBe(true); + }); }); describe("YAML serialization", () => { diff --git a/runtime/typescript/packages/core/tests/model/contracts/pipeline/engine-checkpoint.test.ts b/runtime/typescript/packages/core/tests/model/contracts/pipeline/engine-checkpoint.test.ts index c4950fac8..647c45e0c 100644 --- a/runtime/typescript/packages/core/tests/model/contracts/pipeline/engine-checkpoint.test.ts +++ b/runtime/typescript/packages/core/tests/model/contracts/pipeline/engine-checkpoint.test.ts @@ -38,6 +38,16 @@ describe("EngineCheckpoint", () => { expect(reloaded.turnId).toEqual(instance.turnId); expect(reloaded.runId).toEqual(instance.runId); }); + + it("should reject malformed JSON", () => { + let threw = false; + try { + EngineCheckpoint.fromJson("{"); + } catch { + threw = true; + } + expect(threw).toBe(true); + }); }); describe("YAML serialization", () => { diff --git a/runtime/typescript/packages/core/tests/model/contracts/pipeline/engine-event.test.ts b/runtime/typescript/packages/core/tests/model/contracts/pipeline/engine-event.test.ts index 6de141e32..c6746ccc9 100644 --- a/runtime/typescript/packages/core/tests/model/contracts/pipeline/engine-event.test.ts +++ b/runtime/typescript/packages/core/tests/model/contracts/pipeline/engine-event.test.ts @@ -40,6 +40,16 @@ describe("EngineEvent", () => { expect(reloaded.turnId).toEqual(instance.turnId); expect(reloaded.runId).toEqual(instance.runId); }); + + it("should reject malformed JSON", () => { + let threw = false; + try { + EngineEvent.fromJson("{"); + } catch { + threw = true; + } + expect(threw).toBe(true); + }); }); describe("YAML serialization", () => { diff --git a/runtime/typescript/packages/core/tests/model/contracts/pipeline/engine-permission-decision.test.ts b/runtime/typescript/packages/core/tests/model/contracts/pipeline/engine-permission-decision.test.ts index 9a3e6a315..9d3f0979b 100644 --- a/runtime/typescript/packages/core/tests/model/contracts/pipeline/engine-permission-decision.test.ts +++ b/runtime/typescript/packages/core/tests/model/contracts/pipeline/engine-permission-decision.test.ts @@ -32,6 +32,16 @@ describe("EnginePermissionDecision", () => { const reloaded = EnginePermissionDecision.fromJson(output); expect(reloaded.approved).toEqual(instance.approved); }); + + it("should reject malformed JSON", () => { + let threw = false; + try { + EnginePermissionDecision.fromJson("{"); + } catch { + threw = true; + } + expect(threw).toBe(true); + }); }); describe("YAML serialization", () => { diff --git a/runtime/typescript/packages/core/tests/model/contracts/pipeline/invocation-context-decision.test.ts b/runtime/typescript/packages/core/tests/model/contracts/pipeline/invocation-context-decision.test.ts index 84d85a9e9..c231da8d6 100644 --- a/runtime/typescript/packages/core/tests/model/contracts/pipeline/invocation-context-decision.test.ts +++ b/runtime/typescript/packages/core/tests/model/contracts/pipeline/invocation-context-decision.test.ts @@ -34,6 +34,16 @@ describe("InvocationContextDecision", () => { expect(reloaded.candidateId).toEqual(instance.candidateId); expect(reloaded.reason).toEqual(instance.reason); }); + + it("should reject malformed JSON", () => { + let threw = false; + try { + InvocationContextDecision.fromJson("{"); + } catch { + threw = true; + } + expect(threw).toBe(true); + }); }); describe("YAML serialization", () => { diff --git a/runtime/typescript/packages/core/tests/model/contracts/pipeline/invocation-context-state.test.ts b/runtime/typescript/packages/core/tests/model/contracts/pipeline/invocation-context-state.test.ts index 61919bd86..7ca2fce0e 100644 --- a/runtime/typescript/packages/core/tests/model/contracts/pipeline/invocation-context-state.test.ts +++ b/runtime/typescript/packages/core/tests/model/contracts/pipeline/invocation-context-state.test.ts @@ -31,6 +31,16 @@ describe("InvocationContextState", () => { const reloaded = InvocationContextState.fromJson(output); expect(reloaded).toBeDefined(); }); + + it("should reject malformed JSON", () => { + let threw = false; + try { + InvocationContextState.fromJson("{"); + } catch { + threw = true; + } + expect(threw).toBe(true); + }); }); describe("YAML serialization", () => { diff --git a/runtime/typescript/packages/core/tests/model/contracts/pipeline/model-invocation-context-snapshot.test.ts b/runtime/typescript/packages/core/tests/model/contracts/pipeline/model-invocation-context-snapshot.test.ts index 83b17a355..68da1ff59 100644 --- a/runtime/typescript/packages/core/tests/model/contracts/pipeline/model-invocation-context-snapshot.test.ts +++ b/runtime/typescript/packages/core/tests/model/contracts/pipeline/model-invocation-context-snapshot.test.ts @@ -38,6 +38,16 @@ describe("ModelInvocationContextSnapshot", () => { expect(reloaded.turnId).toEqual(instance.turnId); expect(reloaded.invocationId).toEqual(instance.invocationId); }); + + it("should reject malformed JSON", () => { + let threw = false; + try { + ModelInvocationContextSnapshot.fromJson("{"); + } catch { + threw = true; + } + expect(threw).toBe(true); + }); }); describe("YAML serialization", () => { diff --git a/runtime/typescript/packages/core/tests/model/contracts/pipeline/model-invocation-request.test.ts b/runtime/typescript/packages/core/tests/model/contracts/pipeline/model-invocation-request.test.ts index ee5ed1d84..b3dae7a12 100644 --- a/runtime/typescript/packages/core/tests/model/contracts/pipeline/model-invocation-request.test.ts +++ b/runtime/typescript/packages/core/tests/model/contracts/pipeline/model-invocation-request.test.ts @@ -31,6 +31,16 @@ describe("ModelInvocationRequest", () => { const reloaded = ModelInvocationRequest.fromJson(output); expect(reloaded).toBeDefined(); }); + + it("should reject malformed JSON", () => { + let threw = false; + try { + ModelInvocationRequest.fromJson("{"); + } catch { + threw = true; + } + expect(threw).toBe(true); + }); }); describe("YAML serialization", () => { diff --git a/runtime/typescript/packages/core/tests/model/contracts/pipeline/model-invocation-response.test.ts b/runtime/typescript/packages/core/tests/model/contracts/pipeline/model-invocation-response.test.ts index 40356706c..a8fb6f510 100644 --- a/runtime/typescript/packages/core/tests/model/contracts/pipeline/model-invocation-response.test.ts +++ b/runtime/typescript/packages/core/tests/model/contracts/pipeline/model-invocation-response.test.ts @@ -31,6 +31,16 @@ describe("ModelInvocationResponse", () => { const reloaded = ModelInvocationResponse.fromJson(output); expect(reloaded).toBeDefined(); }); + + it("should reject malformed JSON", () => { + let threw = false; + try { + ModelInvocationResponse.fromJson("{"); + } catch { + threw = true; + } + expect(threw).toBe(true); + }); }); describe("YAML serialization", () => { diff --git a/runtime/typescript/packages/core/tests/model/contracts/pipeline/model-reconciliation-state.test.ts b/runtime/typescript/packages/core/tests/model/contracts/pipeline/model-reconciliation-state.test.ts index 2fbe9faa3..1eaeba2ce 100644 --- a/runtime/typescript/packages/core/tests/model/contracts/pipeline/model-reconciliation-state.test.ts +++ b/runtime/typescript/packages/core/tests/model/contracts/pipeline/model-reconciliation-state.test.ts @@ -36,6 +36,16 @@ describe("ModelReconciliationState", () => { expect(reloaded.invocationId).toEqual(instance.invocationId); expect(reloaded.message).toEqual(instance.message); }); + + it("should reject malformed JSON", () => { + let threw = false; + try { + ModelReconciliationState.fromJson("{"); + } catch { + threw = true; + } + expect(threw).toBe(true); + }); }); describe("YAML serialization", () => { diff --git a/runtime/typescript/packages/core/tests/model/contracts/pipeline/model-tool-request.test.ts b/runtime/typescript/packages/core/tests/model/contracts/pipeline/model-tool-request.test.ts index 9b20bfeb1..d8a7764fa 100644 --- a/runtime/typescript/packages/core/tests/model/contracts/pipeline/model-tool-request.test.ts +++ b/runtime/typescript/packages/core/tests/model/contracts/pipeline/model-tool-request.test.ts @@ -34,6 +34,16 @@ describe("ModelToolRequest", () => { expect(reloaded.id).toEqual(instance.id); expect(reloaded.name).toEqual(instance.name); }); + + it("should reject malformed JSON", () => { + let threw = false; + try { + ModelToolRequest.fromJson("{"); + } catch { + threw = true; + } + expect(threw).toBe(true); + }); }); describe("YAML serialization", () => { diff --git a/runtime/typescript/packages/core/tests/model/contracts/pipeline/model-tool-result.test.ts b/runtime/typescript/packages/core/tests/model/contracts/pipeline/model-tool-result.test.ts index 5fa6a6d51..c7155f92d 100644 --- a/runtime/typescript/packages/core/tests/model/contracts/pipeline/model-tool-result.test.ts +++ b/runtime/typescript/packages/core/tests/model/contracts/pipeline/model-tool-result.test.ts @@ -34,6 +34,16 @@ describe("ModelToolResult", () => { expect(reloaded.requestId).toEqual(instance.requestId); expect(reloaded.name).toEqual(instance.name); }); + + it("should reject malformed JSON", () => { + let threw = false; + try { + ModelToolResult.fromJson("{"); + } catch { + threw = true; + } + expect(threw).toBe(true); + }); }); describe("YAML serialization", () => { diff --git a/runtime/typescript/packages/core/tests/model/contracts/pipeline/replay-journal-record.test.ts b/runtime/typescript/packages/core/tests/model/contracts/pipeline/replay-journal-record.test.ts index 7e6b0bfbf..198352321 100644 --- a/runtime/typescript/packages/core/tests/model/contracts/pipeline/replay-journal-record.test.ts +++ b/runtime/typescript/packages/core/tests/model/contracts/pipeline/replay-journal-record.test.ts @@ -32,6 +32,16 @@ describe("ReplayJournalRecord", () => { const reloaded = ReplayJournalRecord.fromJson(output); expect(reloaded.kind).toEqual(instance.kind); }); + + it("should reject malformed JSON", () => { + let threw = false; + try { + ReplayJournalRecord.fromJson("{"); + } catch { + threw = true; + } + expect(threw).toBe(true); + }); }); describe("YAML serialization", () => { diff --git a/runtime/typescript/packages/core/tests/model/contracts/pipeline/resume-context.test.ts b/runtime/typescript/packages/core/tests/model/contracts/pipeline/resume-context.test.ts index aee6d162f..c3cbf1c14 100644 --- a/runtime/typescript/packages/core/tests/model/contracts/pipeline/resume-context.test.ts +++ b/runtime/typescript/packages/core/tests/model/contracts/pipeline/resume-context.test.ts @@ -34,6 +34,16 @@ describe("ResumeContext", () => { instance.lastJournalSequence, ); }); + + it("should reject malformed JSON", () => { + let threw = false; + try { + ResumeContext.fromJson("{"); + } catch { + threw = true; + } + expect(threw).toBe(true); + }); }); describe("YAML serialization", () => { diff --git a/runtime/typescript/packages/core/tests/model/contracts/pipeline/turn-commit.test.ts b/runtime/typescript/packages/core/tests/model/contracts/pipeline/turn-commit.test.ts index d213b0ce4..2b1580d8c 100644 --- a/runtime/typescript/packages/core/tests/model/contracts/pipeline/turn-commit.test.ts +++ b/runtime/typescript/packages/core/tests/model/contracts/pipeline/turn-commit.test.ts @@ -34,6 +34,16 @@ describe("TurnCommit", () => { expect(reloaded.sessionId).toEqual(instance.sessionId); expect(reloaded.turnId).toEqual(instance.turnId); }); + + it("should reject malformed JSON", () => { + let threw = false; + try { + TurnCommit.fromJson("{"); + } catch { + threw = true; + } + expect(threw).toBe(true); + }); }); describe("YAML serialization", () => { diff --git a/runtime/typescript/packages/core/tests/model/contracts/templates/custom-format.test.ts b/runtime/typescript/packages/core/tests/model/contracts/templates/custom-format.test.ts index ca1044b60..7b90455ac 100644 --- a/runtime/typescript/packages/core/tests/model/contracts/templates/custom-format.test.ts +++ b/runtime/typescript/packages/core/tests/model/contracts/templates/custom-format.test.ts @@ -32,6 +32,16 @@ describe("CustomFormat", () => { const reloaded = CustomFormat.fromJson(output); expect(reloaded.kind).toEqual(instance.kind); }); + + it("should reject malformed JSON", () => { + let threw = false; + try { + CustomFormat.fromJson("{"); + } catch { + threw = true; + } + expect(threw).toBe(true); + }); }); describe("YAML serialization", () => { diff --git a/runtime/typescript/packages/core/tests/model/contracts/templates/custom-parser.test.ts b/runtime/typescript/packages/core/tests/model/contracts/templates/custom-parser.test.ts index 0e27a5699..4e7c91098 100644 --- a/runtime/typescript/packages/core/tests/model/contracts/templates/custom-parser.test.ts +++ b/runtime/typescript/packages/core/tests/model/contracts/templates/custom-parser.test.ts @@ -32,6 +32,16 @@ describe("CustomParser", () => { const reloaded = CustomParser.fromJson(output); expect(reloaded.kind).toEqual(instance.kind); }); + + it("should reject malformed JSON", () => { + let threw = false; + try { + CustomParser.fromJson("{"); + } catch { + threw = true; + } + expect(threw).toBe(true); + }); }); describe("YAML serialization", () => { diff --git a/runtime/typescript/packages/core/tests/model/contracts/templates/format-config.test.ts b/runtime/typescript/packages/core/tests/model/contracts/templates/format-config.test.ts index 76e309215..03727bc1c 100644 --- a/runtime/typescript/packages/core/tests/model/contracts/templates/format-config.test.ts +++ b/runtime/typescript/packages/core/tests/model/contracts/templates/format-config.test.ts @@ -34,6 +34,16 @@ describe("FormatConfig", () => { expect(reloaded.kind).toEqual(instance.kind); expect(reloaded.strict).toEqual(instance.strict); }); + + it("should reject malformed JSON", () => { + let threw = false; + try { + FormatConfig.fromJson("{"); + } catch { + threw = true; + } + expect(threw).toBe(true); + }); }); describe("YAML serialization", () => { diff --git a/runtime/typescript/packages/core/tests/model/contracts/templates/jinja2format.test.ts b/runtime/typescript/packages/core/tests/model/contracts/templates/jinja2format.test.ts index 346e904ca..a31d9998b 100644 --- a/runtime/typescript/packages/core/tests/model/contracts/templates/jinja2format.test.ts +++ b/runtime/typescript/packages/core/tests/model/contracts/templates/jinja2format.test.ts @@ -32,6 +32,16 @@ describe("Jinja2Format", () => { const reloaded = Jinja2Format.fromJson(output); expect(reloaded.kind).toEqual(instance.kind); }); + + it("should reject malformed JSON", () => { + let threw = false; + try { + Jinja2Format.fromJson("{"); + } catch { + threw = true; + } + expect(threw).toBe(true); + }); }); describe("YAML serialization", () => { diff --git a/runtime/typescript/packages/core/tests/model/contracts/templates/mustache-format.test.ts b/runtime/typescript/packages/core/tests/model/contracts/templates/mustache-format.test.ts index 310d1dc77..6059f0986 100644 --- a/runtime/typescript/packages/core/tests/model/contracts/templates/mustache-format.test.ts +++ b/runtime/typescript/packages/core/tests/model/contracts/templates/mustache-format.test.ts @@ -32,6 +32,16 @@ describe("MustacheFormat", () => { const reloaded = MustacheFormat.fromJson(output); expect(reloaded.kind).toEqual(instance.kind); }); + + it("should reject malformed JSON", () => { + let threw = false; + try { + MustacheFormat.fromJson("{"); + } catch { + threw = true; + } + expect(threw).toBe(true); + }); }); describe("YAML serialization", () => { diff --git a/runtime/typescript/packages/core/tests/model/contracts/templates/parser-config.test.ts b/runtime/typescript/packages/core/tests/model/contracts/templates/parser-config.test.ts index 2b9685a04..aee88459f 100644 --- a/runtime/typescript/packages/core/tests/model/contracts/templates/parser-config.test.ts +++ b/runtime/typescript/packages/core/tests/model/contracts/templates/parser-config.test.ts @@ -32,6 +32,16 @@ describe("ParserConfig", () => { const reloaded = ParserConfig.fromJson(output); expect(reloaded.kind).toEqual(instance.kind); }); + + it("should reject malformed JSON", () => { + let threw = false; + try { + ParserConfig.fromJson("{"); + } catch { + threw = true; + } + expect(threw).toBe(true); + }); }); describe("YAML serialization", () => { diff --git a/runtime/typescript/packages/core/tests/model/contracts/templates/prompty-parser.test.ts b/runtime/typescript/packages/core/tests/model/contracts/templates/prompty-parser.test.ts index 0ebb282c0..180d9af91 100644 --- a/runtime/typescript/packages/core/tests/model/contracts/templates/prompty-parser.test.ts +++ b/runtime/typescript/packages/core/tests/model/contracts/templates/prompty-parser.test.ts @@ -32,6 +32,16 @@ describe("PromptyParser", () => { const reloaded = PromptyParser.fromJson(output); expect(reloaded.kind).toEqual(instance.kind); }); + + it("should reject malformed JSON", () => { + let threw = false; + try { + PromptyParser.fromJson("{"); + } catch { + threw = true; + } + expect(threw).toBe(true); + }); }); describe("YAML serialization", () => { diff --git a/runtime/typescript/packages/core/tests/model/contracts/templates/template.test.ts b/runtime/typescript/packages/core/tests/model/contracts/templates/template.test.ts index c3a648a40..182f2aab4 100644 --- a/runtime/typescript/packages/core/tests/model/contracts/templates/template.test.ts +++ b/runtime/typescript/packages/core/tests/model/contracts/templates/template.test.ts @@ -31,6 +31,16 @@ describe("Template", () => { const reloaded = Template.fromJson(output); expect(reloaded).toBeDefined(); }); + + it("should reject malformed JSON", () => { + let threw = false; + try { + Template.fromJson("{"); + } catch { + threw = true; + } + expect(threw).toBe(true); + }); }); describe("YAML serialization", () => { diff --git a/runtime/typescript/packages/core/tests/model/contracts/tooling/binding.test.ts b/runtime/typescript/packages/core/tests/model/contracts/tooling/binding.test.ts index 7e694a031..d32cec42a 100644 --- a/runtime/typescript/packages/core/tests/model/contracts/tooling/binding.test.ts +++ b/runtime/typescript/packages/core/tests/model/contracts/tooling/binding.test.ts @@ -34,6 +34,16 @@ describe("Binding", () => { expect(reloaded.name).toEqual(instance.name); expect(reloaded.input).toEqual(instance.input); }); + + it("should reject malformed JSON", () => { + let threw = false; + try { + Binding.fromJson("{"); + } catch { + threw = true; + } + expect(threw).toBe(true); + }); }); describe("YAML serialization", () => { diff --git a/runtime/typescript/packages/core/tests/model/contracts/tooling/custom-tool.test.ts b/runtime/typescript/packages/core/tests/model/contracts/tooling/custom-tool.test.ts index dc8a8e4c3..848c9b810 100644 --- a/runtime/typescript/packages/core/tests/model/contracts/tooling/custom-tool.test.ts +++ b/runtime/typescript/packages/core/tests/model/contracts/tooling/custom-tool.test.ts @@ -31,6 +31,16 @@ describe("CustomTool", () => { const reloaded = CustomTool.fromJson(output); expect(reloaded).toBeDefined(); }); + + it("should reject malformed JSON", () => { + let threw = false; + try { + CustomTool.fromJson("{"); + } catch { + threw = true; + } + expect(threw).toBe(true); + }); }); describe("YAML serialization", () => { diff --git a/runtime/typescript/packages/core/tests/model/contracts/tooling/function-tool.test.ts b/runtime/typescript/packages/core/tests/model/contracts/tooling/function-tool.test.ts index c9fadd454..90d576340 100644 --- a/runtime/typescript/packages/core/tests/model/contracts/tooling/function-tool.test.ts +++ b/runtime/typescript/packages/core/tests/model/contracts/tooling/function-tool.test.ts @@ -50,6 +50,16 @@ describe("FunctionTool", () => { expect(reloaded.kind).toEqual(instance.kind); expect(reloaded.strict).toEqual(instance.strict); }); + + it("should reject malformed JSON", () => { + let threw = false; + try { + FunctionTool.fromJson("{"); + } catch { + threw = true; + } + expect(threw).toBe(true); + }); }); describe("YAML serialization", () => { diff --git a/runtime/typescript/packages/core/tests/model/contracts/tooling/mcp-approval-mode.test.ts b/runtime/typescript/packages/core/tests/model/contracts/tooling/mcp-approval-mode.test.ts index 0dc5491d0..3508351b0 100644 --- a/runtime/typescript/packages/core/tests/model/contracts/tooling/mcp-approval-mode.test.ts +++ b/runtime/typescript/packages/core/tests/model/contracts/tooling/mcp-approval-mode.test.ts @@ -32,6 +32,16 @@ describe("McpApprovalMode", () => { const reloaded = McpApprovalMode.fromJson(output); expect(reloaded.kind).toEqual(instance.kind); }); + + it("should reject malformed JSON", () => { + let threw = false; + try { + McpApprovalMode.fromJson("{"); + } catch { + threw = true; + } + expect(threw).toBe(true); + }); }); describe("YAML serialization", () => { diff --git a/runtime/typescript/packages/core/tests/model/contracts/tooling/mcp-tool.test.ts b/runtime/typescript/packages/core/tests/model/contracts/tooling/mcp-tool.test.ts index bab60a9db..6d7c86105 100644 --- a/runtime/typescript/packages/core/tests/model/contracts/tooling/mcp-tool.test.ts +++ b/runtime/typescript/packages/core/tests/model/contracts/tooling/mcp-tool.test.ts @@ -38,6 +38,16 @@ describe("McpTool", () => { expect(reloaded.serverName).toEqual(instance.serverName); expect(reloaded.serverDescription).toEqual(instance.serverDescription); }); + + it("should reject malformed JSON", () => { + let threw = false; + try { + McpTool.fromJson("{"); + } catch { + threw = true; + } + expect(threw).toBe(true); + }); }); describe("YAML serialization", () => { diff --git a/runtime/typescript/packages/core/tests/model/contracts/tooling/open-api-tool.test.ts b/runtime/typescript/packages/core/tests/model/contracts/tooling/open-api-tool.test.ts index b9ffeb398..49d49de97 100644 --- a/runtime/typescript/packages/core/tests/model/contracts/tooling/open-api-tool.test.ts +++ b/runtime/typescript/packages/core/tests/model/contracts/tooling/open-api-tool.test.ts @@ -34,6 +34,16 @@ describe("OpenApiTool", () => { expect(reloaded.kind).toEqual(instance.kind); expect(reloaded.specification).toEqual(instance.specification); }); + + it("should reject malformed JSON", () => { + let threw = false; + try { + OpenApiTool.fromJson("{"); + } catch { + threw = true; + } + expect(threw).toBe(true); + }); }); describe("YAML serialization", () => { diff --git a/runtime/typescript/packages/core/tests/model/contracts/tooling/tool.test.ts b/runtime/typescript/packages/core/tests/model/contracts/tooling/tool.test.ts index 770b1ba28..3199b3ab5 100644 --- a/runtime/typescript/packages/core/tests/model/contracts/tooling/tool.test.ts +++ b/runtime/typescript/packages/core/tests/model/contracts/tooling/tool.test.ts @@ -24,6 +24,16 @@ describe("Tool", () => { expect(reloaded.kind).toEqual(instance.kind); expect(reloaded.description).toEqual(instance.description); }); + + it("should reject malformed JSON", () => { + let threw = false; + try { + Tool.fromJson("{"); + } catch { + threw = true; + } + expect(threw).toBe(true); + }); }); describe("YAML serialization", () => { diff --git a/runtime/typescript/packages/core/tests/model/operations/pipeline/render-segment.test.ts b/runtime/typescript/packages/core/tests/model/operations/pipeline/render-segment.test.ts index af4880a61..e876f39f7 100644 --- a/runtime/typescript/packages/core/tests/model/operations/pipeline/render-segment.test.ts +++ b/runtime/typescript/packages/core/tests/model/operations/pipeline/render-segment.test.ts @@ -34,6 +34,16 @@ describe("RenderSegment", () => { expect(reloaded.kind).toEqual(instance.kind); expect(reloaded.text).toEqual(instance.text); }); + + it("should reject malformed JSON", () => { + let threw = false; + try { + RenderSegment.fromJson("{"); + } catch { + threw = true; + } + expect(threw).toBe(true); + }); }); describe("YAML serialization", () => { diff --git a/schema/model/conformance/samples/model.tsp b/schema/model/conformance/samples/model.tsp index aafc6e25e..2afa0f82d 100644 --- a/schema/model/conformance/samples/model.tsp +++ b/schema/model/conformance/samples/model.tsp @@ -13,6 +13,7 @@ namespace Prompty.Conformance.Samples; const ModelOptionsFrequencyPenaltySample = #{ frequencyPenalty: 0.5 }; const ModelOptionsMaxOutputTokensSample = #{ maxOutputTokens: 2048 }; const ModelOptionsPresencePenaltySample = #{ presencePenalty: 0.3 }; +const ModelOptionsReasoningEffortSample = #{ reasoningEffort: "medium" }; const ModelOptionsSeedSample = #{ seed: 42 }; const ModelOptionsTemperatureSample = #{ temperature: 0.7 }; const ModelOptionsTopKSample = #{ topK: 40 }; diff --git a/schema/model/contracts/models/model.tsp b/schema/model/contracts/models/model.tsp index 47abf0818..24768b21c 100644 --- a/schema/model/contracts/models/model.tsp +++ b/schema/model/contracts/models/model.tsp @@ -6,6 +6,27 @@ using Prompty.Contracts.Connectivity; namespace Prompty.Contracts.Models; alias apiType = "chat" | "embedding" | "image" | "responses" | string; + +/** + * Reasoning effort levels for frontier/reasoning models. Mirrors the OpenAI + * `reasoning_effort` enum. The bare `string` member keeps the set OPEN so + * provider-specific or future values pass through untyped rather than erroring. + * + * Newer reasoning models increasingly gate output quality on this control + * rather than scalar sampling (`temperature`/`topP`/`topK`), which several + * providers now reject on those models. Executors are responsible for dropping + * unsupported sampling params per target model. + */ +alias reasoningEffort = + | "none" + | "minimal" + | "low" + | "medium" + | "high" + | "xhigh" + | "max" + | string; + /** * Options for configuring the behavior of the AI model. */ @@ -22,6 +43,10 @@ model ModelOptions { @sample(ModelOptionsPresencePenaltySample) presencePenalty?: float32; + @doc("The reasoning effort level for reasoning-capable models") + @sample(ModelOptionsReasoningEffortSample) + reasoningEffort?: reasoningEffort; + @doc("A random seed for deterministic output") @sample(ModelOptionsSeedSample) seed?: int32; diff --git a/schema/model/wire/anthropic.tsp b/schema/model/wire/anthropic.tsp index 75e5cd6f7..0875f046b 100644 --- a/schema/model/wire/anthropic.tsp +++ b/schema/model/wire/anthropic.tsp @@ -19,7 +19,17 @@ using Prompty.Contracts.Models; // ============================================================================ // ModelOptions → Anthropic parameter names -// Identity mappings included so strict toWire() knows which fields Anthropic supports +// Identity mappings included so strict toWire() knows which fields Anthropic supports. +// +// NOTE: temperature/topK/topP are DEPRECATED by Anthropic for models released +// after Claude Opus 4.6 (only no-op values accepted; others → 400). They remain +// mapped here because older models still honor them; the executor is responsible +// for dropping unsupported sampling params per target model. +// +// reasoningEffort is intentionally NOT mapped for Anthropic: its reasoning +// control is the nested `thinking` object (`{type, budget_tokens}`), not a flat +// scalar rename, so it cannot be expressed via @@knownAs. The Anthropic executor +// translates reasoningEffort → thinking config when needed. @@knownAs(ModelOptions.temperature, "anthropic", "temperature"); @@knownAs(ModelOptions.maxOutputTokens, "anthropic", "max_tokens"); @@knownAs(ModelOptions.topK, "anthropic", "top_k"); diff --git a/schema/model/wire/openai.tsp b/schema/model/wire/openai.tsp index 7c848878a..a3810d757 100644 --- a/schema/model/wire/openai.tsp +++ b/schema/model/wire/openai.tsp @@ -26,11 +26,13 @@ namespace Prompty; @@knownAs(ModelOptions.stopSequences, "openai", "stop"); @@knownAs(ModelOptions.seed, "openai", "seed"); @@knownAs(ModelOptions.allowMultipleToolCalls, "openai", "parallel_tool_calls"); +@@knownAs(ModelOptions.reasoningEffort, "openai", "reasoning_effort"); // ModelOptions → OpenAI Responses API parameter names @@knownAs(ModelOptions.temperature, "responses", "temperature"); @@knownAs(ModelOptions.maxOutputTokens, "responses", "max_output_tokens"); @@knownAs(ModelOptions.topP, "responses", "top_p"); +@@knownAs(ModelOptions.reasoningEffort, "responses", "reasoning_effort"); // TokenUsage → OpenAI usage field names @@knownAs(TokenUsage.promptTokens, "openai", "prompt_tokens"); diff --git a/schema/package-lock.json b/schema/package-lock.json index fbf5fb122..9da56ed72 100644 --- a/schema/package-lock.json +++ b/schema/package-lock.json @@ -9,7 +9,7 @@ "@typespec/compiler": "^1.15.0", "@typespec/http": "^1.15.0", "@typespec/json-schema": "^1.15.0", - "@typra/emitter": "^2.1.6" + "@typra/emitter": "^2.1.8" } }, "node_modules/@babel/code-frame": { @@ -448,9 +448,9 @@ } }, "node_modules/@typra/emitter": { - "version": "2.1.6", - "resolved": "https://registry.npmjs.org/@typra/emitter/-/emitter-2.1.6.tgz", - "integrity": "sha512-QgCAc9R7ZxOaglJaFQNuyzsR8s/f3fpRw4TGTtYa7XcL7COnPV5v4futeH/5nKR9pUvpT+FK20oybHHA/TAuYQ==", + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/@typra/emitter/-/emitter-2.1.8.tgz", + "integrity": "sha512-+2w5fn7ylf9XtR7TIL8G25dmEysYqZBOpG65UUBM0Oc5++YUCtM96dEHIwHwEggtF2eyA/9SMA3tFMEn5heLow==", "license": "MIT", "dependencies": { "semver": "^7.7.0", diff --git a/schema/package.json b/schema/package.json index ea977e207..7aa14ef46 100644 --- a/schema/package.json +++ b/schema/package.json @@ -10,12 +10,13 @@ "lint:fixtures": "node scripts/lint-fixtures.mjs", "verify:typra": "node scripts/verify-typra.mjs", "smoke:emit": "node scripts/smoke-emit.mjs", + "check:coverage": "node scripts/check-vector-coverage.mjs", "build": "npm run lint:fixtures && npm run format:tsp && npm run generate" }, "dependencies": { "@typespec/compiler": "^1.15.0", "@typespec/http": "^1.15.0", "@typespec/json-schema": "^1.15.0", - "@typra/emitter": "^2.1.6" + "@typra/emitter": "^2.1.8" } } diff --git a/schema/scripts/check-vector-coverage.mjs b/schema/scripts/check-vector-coverage.mjs new file mode 100644 index 000000000..6fdf7f936 --- /dev/null +++ b/schema/scripts/check-vector-coverage.mjs @@ -0,0 +1,104 @@ +// Forcing function: fail the build unless every member of the turn engine's +// observable TypeSpec surface is exercised by at least one shared @vector. +// +// The turn engine's observable vocabulary (event kinds, terminal statuses, and +// context portability) is defined once in TypeSpec and flows to every runtime. +// Hand-written per-runtime turn tests can silently cover behaviors the shared +// vector corpus does not, which breaks cross-runtime parity. This check pins the +// canonical enum members (parsed from the .tsp source of truth) against what the +// emitted vector corpus (vectors.json) actually asserts. Any enum member with no +// covering vector is a hard failure — the red list IS the vector-authoring backlog. +// +// No emitter change is required: both sides are plain in-repo files regenerated +// deterministically by `npm run generate`. + +import { execFileSync } from "node:child_process"; +import { readFileSync } from "node:fs"; +import { join } from "node:path"; + +const repoRoot = execFileSync("git", ["rev-parse", "--show-toplevel"], { encoding: "utf8" }).trim(); + +const VECTORS_PATH = join(repoRoot, "schema/tsp-output/.typra-generated/vectors.json"); + +/** + * Each dimension pins a canonical TypeSpec alias (the full set of legal members) + * against the set of values the vector corpus actually asserts (the covered set). + * `observe` extracts the covered values from a single vector's `expected` block. + */ +const DIMENSIONS = [ + { + name: "EngineEventKind", + aliasFile: "schema/model/contracts/pipeline/engine-events.tsp", + aliasName: "EngineEventKind", + observe: (expected) => expected?.eventKinds ?? [], + }, +]; + +/** Parse the string-literal members of a `alias Name = "a" | "b" | ...;` union. */ +function parseAliasMembers(absFile, aliasName) { + const src = readFileSync(absFile, "utf8"); + const start = src.indexOf(`alias ${aliasName}`); + if (start === -1) { + throw new Error(`Could not find 'alias ${aliasName}' in ${absFile}`); + } + const eq = src.indexOf("=", start); + const semi = src.indexOf(";", eq); + if (eq === -1 || semi === -1) { + throw new Error(`Malformed alias ${aliasName} in ${absFile}`); + } + const body = src.slice(eq + 1, semi); + const members = [...body.matchAll(/"([^"]+)"/g)].map((m) => m[1]); + if (members.length === 0) { + throw new Error(`Alias ${aliasName} has no string-literal members in ${absFile}`); + } + return members; +} + +function collectCovered(vectors, observe) { + const covered = new Set(); + for (const entry of vectors) { + const expected = entry?.vector?.expected; + for (const value of observe(expected)) { + covered.add(value); + } + } + return covered; +} + +function main() { + const corpus = JSON.parse(readFileSync(VECTORS_PATH, "utf8")); + const vectors = corpus.vectors ?? []; + + let failed = false; + const lines = []; + + for (const dim of DIMENSIONS) { + const members = parseAliasMembers(join(repoRoot, dim.aliasFile), dim.aliasName); + const covered = collectCovered(vectors, dim.observe); + const uncovered = members.filter((m) => !covered.has(m)); + + lines.push(`\n${dim.name}: ${members.length - uncovered.length}/${members.length} members covered by @vector`); + if (uncovered.length > 0) { + failed = true; + for (const m of uncovered) { + lines.push(` UNCOVERED ${m}`); + } + } + } + + process.stdout.write(lines.join("\n") + "\n"); + + if (failed) { + process.stdout.write( + "\nVector coverage gate FAILED: the members above are part of the turn engine's\n" + + "observable TypeSpec surface but no shared @vector asserts them, so they are not\n" + + "verified for cross-runtime parity. Add covering vectors (extending the runTurn/\n" + + "replay seam contract where the behavior is not yet expressible).\n", + ); + process.exit(1); + } + + process.stdout.write("\nVector coverage gate PASSED: every observable member is covered.\n"); +} + +main(); diff --git a/schema/scripts/normalize-typra-output.mjs b/schema/scripts/normalize-typra-output.mjs index 9523fee56..1b6ee2e87 100644 --- a/schema/scripts/normalize-typra-output.mjs +++ b/schema/scripts/normalize-typra-output.mjs @@ -24,6 +24,19 @@ fixSwiftProcessorProviderCollision( "ProcessorConformanceTests.swift", ), ); +fixSwiftReasoningEffortWireValue( + join( + "..", + "runtime", + "swift", + "prompty-model", + "Sources", + "PromptyModel", + "Contracts", + "Models", + "model_options.swift", + ), +); restoreSwiftPackageResources(join("..", "runtime", "swift", "prompty-model", "Package.swift")); // Note: the dead `if ctx == nil { ctx = NewLoadContext() }` guard in leaf Go @@ -86,10 +99,11 @@ function trimTrailingWhitespace(root) { // Verified empirically: moving Resources to the test target makes `swift test` // fail to compile the main target with "type 'Bundle' has no member 'module'", // because SwiftPM only synthesizes `Bundle.module` for a target that owns -// resources. The emitter exposes no main-target resources option, so this hand -// re-injection remains load-bearing. Fully removing it requires relocating -// Discovery + the capabilities resource into a package the emitter does not -// regenerate (tracked with the Swift split-package work in #487). +// resources. The emitter exposes no main-target resources option (feature +// requested upstream in sethjuarez/typra#332), so this hand re-injection remains +// load-bearing. Fully removing it requires either that emitter option or +// relocating Discovery + the capabilities resource into a package the emitter +// does not regenerate (tracked with the Swift split-package work in #487). function restoreSwiftPackageResources(packagePath) { if (!existsSync(packagePath)) { return; @@ -123,7 +137,9 @@ function restoreSwiftPackageResources(packagePath) { // reproducible without depending on goimports being installed. Only removes a // standalone `"fmt"` import line when the file has no `fmt.` reference, so it is // a no-op on every file that legitimately uses fmt. Remove once the emitter no -// longer emits the unused import (tracked on sethjuarez/typra). +// longer emits the unused import (tracked on sethjuarez/typra#305; appears fixed +// as of @typra/emitter 2.1.8 — raw output no longer emits the dead import, so +// this is now a no-op guarding against regression until #305 is confirmed/closed). function stripUnusedFmtImport(root) { if (!existsSync(root)) { return; @@ -161,7 +177,7 @@ function stripUnusedFmtImport(root) { // longer shadows the registry accessor. Deterministic exact-substring rewrites; // the `provider:` argument label, the `["provider"]` lookup, and the `provider()` // call are all left untouched. Remove once the emitter names the discriminator -// local distinctly from the registry accessor (tracked on sethjuarez/typra). +// local distinctly from the registry accessor (tracked on sethjuarez/typra#304). function fixSwiftProcessorProviderCollision(path) { if (!existsSync(path)) { return; @@ -180,4 +196,33 @@ function fixSwiftProcessorProviderCollision(path) { if (patched !== content) { writeFileSync(path, patched); } +} + +// WORKAROUND (typra 2.1.8 emitter bug): the Swift backend emits the provider +// wire-mapping (`toWire`) assignment for the `reasoningEffort` option as +// `result[wireKey] = value`, storing the `ReasoningEffort` RawRepresentable +// struct itself rather than its backing string. `reasoningEffort` is the only +// ModelOptions field with a named string-union (alias) type; every other field +// is a wire-primitive (Int/Double/Bool/[String]) that needs no unwrapping, so +// only this field is affected. The plain `save()` path correctly emits +// `value.rawValue`, but the provider `toWire` path does not, so a wire +// round-trip produces a dict whose `reasoning_effort` value is a struct and the +// matching `fromWire` -> `load()` throws "Expected string for field +// reasoningEffort." (Newly exposed by the 2.1.8 wire-conversion tests.) Append +// `.rawValue` to that single assignment so the wire value is the backing string. +// Deterministic exact-substring rewrite; unique to this field. Remove once the +// emitter unwraps RawRepresentable union types in the Swift toWire path (tracked +// on sethjuarez/typra#331). +function fixSwiftReasoningEffortWireValue(path) { + if (!existsSync(path)) { + return; + } + const content = readFileSync(path, "utf8"); + const patched = content.replaceAll( + "let value = self.reasoningEffort { result[wireKey] = value }", + "let value = self.reasoningEffort { result[wireKey] = value.rawValue }", + ); + if (patched !== content) { + writeFileSync(path, patched); + } } \ No newline at end of file diff --git a/schema/tsp-output/.typra-generated/export-surfaces.json b/schema/tsp-output/.typra-generated/export-surfaces.json index bf06d676b..78c245b37 100644 --- a/schema/tsp-output/.typra-generated/export-surfaces.json +++ b/schema/tsp-output/.typra-generated/export-surfaces.json @@ -17,8 +17,8 @@ }, { "name": "@typra/emitter", - "version": "2.1.6", - "supportedRange": "2.1.6", + "version": "2.1.8", + "supportedRange": "2.1.8", "supported": true } ] diff --git a/schema/tsp-output/json-ast/model.json b/schema/tsp-output/json-ast/model.json index 9f398abd0..d5a31b904 100644 --- a/schema/tsp-output/json-ast/model.json +++ b/schema/tsp-output/json-ast/model.json @@ -4988,6 +4988,55 @@ "isOpenEnum": false, "isNamedCollection": false }, + { + "name": "reasoningEffort", + "typeName": { + "namespace": "", + "name": "string" + }, + "description": "The reasoning effort level for reasoning-capable models", + "samples": [ + { + "sample": { + "reasoningEffort": "medium" + }, + "title": "", + "description": "" + } + ], + "knownAs": [ + { + "provider": "openai", + "name": "reasoning_effort" + }, + { + "provider": "responses", + "name": "reasoning_effort" + } + ], + "defaultFor": [], + "isScalar": true, + "isOptional": true, + "isCollection": false, + "isAny": false, + "isDict": false, + "dictValueType": null, + "defaultValue": "null", + "hasExplicitDefault": false, + "allowedValues": [ + "none", + "minimal", + "low", + "medium", + "high", + "xhigh", + "max" + ], + "parseAliases": {}, + "enumName": "reasoningEffort", + "isOpenEnum": true, + "isNamedCollection": false + }, { "name": "seed", "typeName": { diff --git a/vscode/prompty/schemas/ModelOptions.yaml b/vscode/prompty/schemas/ModelOptions.yaml index 0a624b13f..e54bdbca6 100644 --- a/vscode/prompty/schemas/ModelOptions.yaml +++ b/vscode/prompty/schemas/ModelOptions.yaml @@ -13,6 +13,24 @@ properties: presencePenalty: type: number description: The presence penalty to apply to the model's output + reasoningEffort: + anyOf: + - type: string + const: none + - type: string + const: minimal + - type: string + const: low + - type: string + const: medium + - type: string + const: high + - type: string + const: xhigh + - type: string + const: max + - type: string + description: The reasoning effort level for reasoning-capable models seed: type: integer minimum: -2147483648 diff --git a/web/src/content/docs/reference/Model.md b/web/src/content/docs/reference/Model.md index 62e90b4e4..c02f152ee 100644 --- a/web/src/content/docs/reference/Model.md +++ b/web/src/content/docs/reference/Model.md @@ -56,6 +56,7 @@ classDiagram +float32 frequencyPenalty +int32 maxOutputTokens +float32 presencePenalty + +string reasoningEffort +int32 seed +float32 temperature +int32 topK diff --git a/web/src/content/docs/reference/ModelOptions.md b/web/src/content/docs/reference/ModelOptions.md index 8dc73424d..e52b1c3ae 100644 --- a/web/src/content/docs/reference/ModelOptions.md +++ b/web/src/content/docs/reference/ModelOptions.md @@ -23,6 +23,7 @@ classDiagram +float32 frequencyPenalty +int32 maxOutputTokens +float32 presencePenalty + +string reasoningEffort +int32 seed +float32 temperature +int32 topK @@ -39,6 +40,7 @@ classDiagram frequencyPenalty: 0.5 maxOutputTokens: 2048 presencePenalty: 0.3 +reasoningEffort: medium seed: 42 temperature: 0.7 topK: 40 @@ -60,6 +62,7 @@ additionalProperties: | frequencyPenalty | float32 | The frequency penalty to apply to the model's output | | maxOutputTokens | int32 | The maximum number of tokens to generate in the output | | presencePenalty | float32 | The presence penalty to apply to the model's output | +| reasoningEffort | string | The reasoning effort level for reasoning-capable models | | seed | int32 | A random seed for deterministic output | | temperature | float32 | The temperature to use for sampling | | topK | int32 | The top-K sampling value | diff --git a/web/src/content/docs/reference/index.md b/web/src/content/docs/reference/index.md index f95db5e25..6a62819da 100644 --- a/web/src/content/docs/reference/index.md +++ b/web/src/content/docs/reference/index.md @@ -116,6 +116,7 @@ classDiagram +float32 frequencyPenalty +int32 maxOutputTokens +float32 presencePenalty + +string reasoningEffort +int32 seed +float32 temperature +int32 topK