From fc07e784bbd4e1d4aa18ccff7383b6ad483f1e7f Mon Sep 17 00:00:00 2001 From: Seth Juarez Date: Tue, 8 Sep 2026 08:35:42 -0700 Subject: [PATCH 01/10] feat(model): add reasoningEffort to ModelOptions Add a first-class reasoningEffort field to ModelOptions in the TypeSpec model with per-provider wire mappings (OpenAI chat/responses, Anthropic), and regenerate all runtimes (Python, C#, Go, Java, Rust, Swift, TypeScript). Frontier reasoning models deprecate scalar sampling params (temperature/top_p/top_k); reasoningEffort is the forward-compatible control surface. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../Models/ModelOptionsConversionTests.cs | 10 ++ .../Model/Contracts/Models/ModelOptions.cs | 18 +++ runtime/go/prompty/model/model_options.go | 23 ++++ .../go/prompty/model/model_options_test.go | 41 ++++++ .../microsoft/prompty/model/ModelOptions.java | 17 +++ .../model/ModelOptionsGeneratedTest.java | 10 +- .../model/contracts/models/_ModelOptions.py | 19 ++- .../contracts/models/test_model_options.py | 8 ++ .../model/contracts/models/model_options.rs | 130 ++++++++++++++++++ .../contracts/models/model_options_test.rs | 17 +++ .../Contracts/Models/model_options.swift | 42 +++++- .../Contracts/Models/ModelOptionsTests.swift | 6 + .../contracts/connectivity/connection.ts | 5 +- .../model/contracts/conversation/message.ts | 21 ++- .../model/contracts/events/redacted-field.ts | 3 +- .../contracts/events/session-end-payload.ts | 3 +- .../model/contracts/events/session-event.ts | 9 +- .../model/contracts/events/session-summary.ts | 3 +- .../src/model/contracts/events/turn-event.ts | 26 +--- .../core/src/model/contracts/models/index.ts | 7 +- .../model/contracts/models/model-options.ts | 24 ++++ .../core/src/model/contracts/models/model.ts | 3 +- .../contracts/pipeline/engine-checkpoint.ts | 10 +- .../model/contracts/pipeline/engine-event.ts | 25 +--- .../pipeline/invocation-context-decision.ts | 5 +- .../pipeline/invocation-context-state.ts | 5 +- .../model/contracts/pipeline/turn-commit.ts | 3 +- .../src/model/contracts/templates/index.ts | 6 +- .../core/src/model/contracts/tooling/index.ts | 8 +- .../contracts/tooling/mcp-approval-mode.ts | 10 +- .../core/src/model/parser-resolver.ts | 5 +- .../core/src/model/processor-resolver.ts | 5 +- .../core/src/model/renderer-resolver.ts | 5 +- .../core/src/model/vector-conformance.ts | 15 +- .../contracts/models/model-options.test.ts | 14 +- schema/model/conformance/samples/model.tsp | 1 + schema/model/contracts/models/model.tsp | 25 ++++ schema/model/wire/anthropic.tsp | 12 +- schema/model/wire/openai.tsp | 2 + schema/tsp-output/json-ast/model.json | 49 +++++++ 40 files changed, 520 insertions(+), 130 deletions(-) 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..258c0c80f 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 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/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/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/tests/model/contracts/models/test_model_options.py b/runtime/python/prompty/tests/model/contracts/models/test_model_options.py index a11fa313d..2df14839f 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, 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/contracts/models/model_options_test.rs b/runtime/rust/prompty/tests/model/contracts/models/model_options_test.rs index c5f455c51..d34df5e55 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, 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..0ac7ddc38 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 } 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/Contracts/Models/ModelOptionsTests.swift b/runtime/swift/prompty-model/Tests/PromptyModelTests/Contracts/Models/ModelOptionsTests.swift index 4bb6fd443..3f3e4351f 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) diff --git a/runtime/typescript/packages/core/src/model/contracts/connectivity/connection.ts b/runtime/typescript/packages/core/src/model/contracts/connectivity/connection.ts index 6dacccae9..74abb8723 100644 --- a/runtime/typescript/packages/core/src/model/contracts/connectivity/connection.ts +++ b/runtime/typescript/packages/core/src/model/contracts/connectivity/connection.ts @@ -61,9 +61,8 @@ export abstract class Connection { data["authenticationMode"] !== undefined && data["authenticationMode"] !== null ) { - instance.authenticationMode = String( - data["authenticationMode"], - ) as AuthenticationMode; + instance.authenticationMode = + String(data["authenticationMode"]) as AuthenticationMode; } if ( data["usageDescription"] !== undefined && diff --git a/runtime/typescript/packages/core/src/model/contracts/conversation/message.ts b/runtime/typescript/packages/core/src/model/contracts/conversation/message.ts index 9c8572005..db5ba5a3a 100644 --- a/runtime/typescript/packages/core/src/model/contracts/conversation/message.ts +++ b/runtime/typescript/packages/core/src/model/contracts/conversation/message.ts @@ -144,23 +144,20 @@ export class Message { //#endregion static assistant(text: string): Message { - return new Message({ - role: "assistant", - parts: [new TextPart({ value: text })], - }); + return new Message( + { role: "assistant", parts: [new TextPart({ value: text })] }, + ); } static system(text: string): Message { - return new Message({ - role: "system", - parts: [new TextPart({ value: text })], - }); + return new Message( + { role: "system", parts: [new TextPart({ value: text })] }, + ); } static user(text: string): Message { - return new Message({ - role: "user", - parts: [new TextPart({ value: text })], - }); + return new Message( + { role: "user", parts: [new TextPart({ value: text })] }, + ); } } diff --git a/runtime/typescript/packages/core/src/model/contracts/events/redacted-field.ts b/runtime/typescript/packages/core/src/model/contracts/events/redacted-field.ts index 892b36b02..1b6f95479 100644 --- a/runtime/typescript/packages/core/src/model/contracts/events/redacted-field.ts +++ b/runtime/typescript/packages/core/src/model/contracts/events/redacted-field.ts @@ -4,8 +4,7 @@ import { LoadContext, SaveContext } from "../../context"; -export type RedactionMode = - "none" | "redacted" | "hashed" | "summary" | "reference"; +export type RedactionMode = "none" | "redacted" | "hashed" | "summary" | "reference"; export class RedactedField { static readonly shorthandProperty: string | undefined = undefined; diff --git a/runtime/typescript/packages/core/src/model/contracts/events/session-end-payload.ts b/runtime/typescript/packages/core/src/model/contracts/events/session-end-payload.ts index a0d9d6973..90a2c517e 100644 --- a/runtime/typescript/packages/core/src/model/contracts/events/session-end-payload.ts +++ b/runtime/typescript/packages/core/src/model/contracts/events/session-end-payload.ts @@ -4,8 +4,7 @@ import { LoadContext, SaveContext } from "../../context"; -export type SessionEndStatus = - "success" | "error" | "cancelled" | "interrupted"; +export type SessionEndStatus = "success" | "error" | "cancelled" | "interrupted"; export class SessionEndPayload { static readonly shorthandProperty: string | undefined = undefined; diff --git a/runtime/typescript/packages/core/src/model/contracts/events/session-event.ts b/runtime/typescript/packages/core/src/model/contracts/events/session-event.ts index ebd0eacfe..dc8e4317e 100644 --- a/runtime/typescript/packages/core/src/model/contracts/events/session-event.ts +++ b/runtime/typescript/packages/core/src/model/contracts/events/session-event.ts @@ -5,14 +5,7 @@ import { LoadContext, SaveContext } from "../../context"; import { RedactionMetadata } from "./redaction-metadata"; -export type SessionEventType = - | "session_start" - | "session_end" - | "session_warning" - | "session_hook_start" - | "session_hook_end" - | "checkpoint_created" - | "trajectory_event"; +export type SessionEventType = "session_start" | "session_end" | "session_warning" | "session_hook_start" | "session_hook_end" | "checkpoint_created" | "trajectory_event"; export class SessionEvent { static readonly shorthandProperty: string | undefined = undefined; diff --git a/runtime/typescript/packages/core/src/model/contracts/events/session-summary.ts b/runtime/typescript/packages/core/src/model/contracts/events/session-summary.ts index ad2f83202..17330238a 100644 --- a/runtime/typescript/packages/core/src/model/contracts/events/session-summary.ts +++ b/runtime/typescript/packages/core/src/model/contracts/events/session-summary.ts @@ -5,8 +5,7 @@ import { LoadContext, SaveContext } from "../../context"; import { TokenUsage } from "../../contracts/models/token-usage"; -export type SessionSummaryStatus = - "success" | "error" | "cancelled" | "interrupted"; +export type SessionSummaryStatus = "success" | "error" | "cancelled" | "interrupted"; export class SessionSummary { static readonly shorthandProperty: string | undefined = undefined; diff --git a/runtime/typescript/packages/core/src/model/contracts/events/turn-event.ts b/runtime/typescript/packages/core/src/model/contracts/events/turn-event.ts index 4515239d5..77f516f1a 100644 --- a/runtime/typescript/packages/core/src/model/contracts/events/turn-event.ts +++ b/runtime/typescript/packages/core/src/model/contracts/events/turn-event.ts @@ -4,31 +4,7 @@ import { LoadContext, SaveContext } from "../../context"; -export type TurnEventType = - | "turn_start" - | "turn_end" - | "llm_start" - | "llm_complete" - | "retry" - | "permission_requested" - | "permission_completed" - | "token" - | "thinking" - | "tool_call_start" - | "tool_call_complete" - | "tool_execution_start" - | "tool_execution_complete" - | "tool_result" - | "hook_start" - | "hook_end" - | "status" - | "messages_updated" - | "done" - | "error" - | "cancelled" - | "compaction_start" - | "compaction_complete" - | "compaction_failed"; +export type TurnEventType = "turn_start" | "turn_end" | "llm_start" | "llm_complete" | "retry" | "permission_requested" | "permission_completed" | "token" | "thinking" | "tool_call_start" | "tool_call_complete" | "tool_execution_start" | "tool_execution_complete" | "tool_result" | "hook_start" | "hook_end" | "status" | "messages_updated" | "done" | "error" | "cancelled" | "compaction_start" | "compaction_complete" | "compaction_failed"; export class TurnEvent { static readonly shorthandProperty: string | undefined = undefined; diff --git a/runtime/typescript/packages/core/src/model/contracts/models/index.ts b/runtime/typescript/packages/core/src/model/contracts/models/index.ts index a48476723..bee83fb63 100644 --- a/runtime/typescript/packages/core/src/model/contracts/models/index.ts +++ b/runtime/typescript/packages/core/src/model/contracts/models/index.ts @@ -3,7 +3,12 @@ // WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. export { ModelOptions } from "./model-options"; -export { Model, OpenAIModel, AzureModel, CustomModel } from "./model"; +export { + Model, + OpenAIModel, + AzureModel, + CustomModel, +} from "./model"; export { TokenUsage } from "./token-usage"; export { InvocationUsage } from "./invocation-usage"; export { ModelInfo } from "./model-info"; 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..f6fae97d1 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,15 @@ 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 +31,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 +88,13 @@ 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 +155,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 +203,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 +240,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/src/model/contracts/models/model.ts b/runtime/typescript/packages/core/src/model/contracts/models/model.ts index 9fecad0f5..650a8e8dc 100644 --- a/runtime/typescript/packages/core/src/model/contracts/models/model.ts +++ b/runtime/typescript/packages/core/src/model/contracts/models/model.ts @@ -6,8 +6,7 @@ import { LoadContext, SaveContext } from "../../context"; import { Connection } from "../../contracts/connectivity/connection"; import { ModelOptions } from "./model-options"; -export type apiType = - "chat" | "embedding" | "image" | "responses" | (string & {}); +export type apiType = "chat" | "embedding" | "image" | "responses" | (string & {}); export class Model { static readonly shorthandProperty: string | undefined = "id"; diff --git a/runtime/typescript/packages/core/src/model/contracts/pipeline/engine-checkpoint.ts b/runtime/typescript/packages/core/src/model/contracts/pipeline/engine-checkpoint.ts index a56a2034a..dc0dbc7a0 100644 --- a/runtime/typescript/packages/core/src/model/contracts/pipeline/engine-checkpoint.ts +++ b/runtime/typescript/packages/core/src/model/contracts/pipeline/engine-checkpoint.ts @@ -173,9 +173,8 @@ export class EngineCheckpoint { data["completedModelIterations"] !== undefined && data["completedModelIterations"] !== null ) { - instance.completedModelIterations = Number( - data["completedModelIterations"], - ); + instance.completedModelIterations = + Number(data["completedModelIterations"]); } if ( data["reconciliationRequired"] !== undefined && @@ -220,9 +219,8 @@ export class EngineCheckpoint { data["policyAppliedForIteration"] !== undefined && data["policyAppliedForIteration"] !== null ) { - instance.policyAppliedForIteration = Boolean( - data["policyAppliedForIteration"], - ); + instance.policyAppliedForIteration = + Boolean(data["policyAppliedForIteration"]); } if (data["contextState"] !== undefined && data["contextState"] !== null) { instance.contextState = InvocationContextState.load( diff --git a/runtime/typescript/packages/core/src/model/contracts/pipeline/engine-event.ts b/runtime/typescript/packages/core/src/model/contracts/pipeline/engine-event.ts index 43daac8d1..935a66084 100644 --- a/runtime/typescript/packages/core/src/model/contracts/pipeline/engine-event.ts +++ b/runtime/typescript/packages/core/src/model/contracts/pipeline/engine-event.ts @@ -4,30 +4,7 @@ import { LoadContext, SaveContext } from "../../context"; -export type EngineEventKind = - | "turn_started" - | "policy_applied" - | "context_prepared" - | "model_invocation_started" - | "model_invocation_completed" - | "model_invocation_failed" - | "model_reconciliation_required" - | "model_invocation_reconciled" - | "permission_requested" - | "permission_resolved" - | "tool_execution_started" - | "tool_execution_completed" - | "tool_result_committed" - | "tool_result_reconciled" - | "conversation_updated" - | "checkpoint_created" - | "turn_committed" - | "turn_cancelled" - | "turn_failed" - | "turn_reconciliation_required" - | "post_commit_started" - | "post_commit_completed" - | "post_commit_failed"; +export type EngineEventKind = "turn_started" | "policy_applied" | "context_prepared" | "model_invocation_started" | "model_invocation_completed" | "model_invocation_failed" | "model_reconciliation_required" | "model_invocation_reconciled" | "permission_requested" | "permission_resolved" | "tool_execution_started" | "tool_execution_completed" | "tool_result_committed" | "tool_result_reconciled" | "conversation_updated" | "checkpoint_created" | "turn_committed" | "turn_cancelled" | "turn_failed" | "turn_reconciliation_required" | "post_commit_started" | "post_commit_completed" | "post_commit_failed"; export class EngineEvent { static readonly shorthandProperty: string | undefined = undefined; diff --git a/runtime/typescript/packages/core/src/model/contracts/pipeline/invocation-context-decision.ts b/runtime/typescript/packages/core/src/model/contracts/pipeline/invocation-context-decision.ts index d8a8700b3..b6a17ec36 100644 --- a/runtime/typescript/packages/core/src/model/contracts/pipeline/invocation-context-decision.ts +++ b/runtime/typescript/packages/core/src/model/contracts/pipeline/invocation-context-decision.ts @@ -48,9 +48,8 @@ export class InvocationContextDecision { instance.candidateId = String(data["candidateId"]); } if (data["disposition"] !== undefined && data["disposition"] !== null) { - instance.disposition = String( - data["disposition"], - ) as InvocationContextDisposition; + instance.disposition = + String(data["disposition"]) as InvocationContextDisposition; } if (data["reason"] !== undefined && data["reason"] !== null) { instance.reason = String(data["reason"]); diff --git a/runtime/typescript/packages/core/src/model/contracts/pipeline/invocation-context-state.ts b/runtime/typescript/packages/core/src/model/contracts/pipeline/invocation-context-state.ts index 286c3f7ae..194427e77 100644 --- a/runtime/typescript/packages/core/src/model/contracts/pipeline/invocation-context-state.ts +++ b/runtime/typescript/packages/core/src/model/contracts/pipeline/invocation-context-state.ts @@ -34,9 +34,8 @@ export class InvocationContextState { const instance = new InvocationContextState(); if (data["portability"] !== undefined && data["portability"] !== null) { - instance.portability = String( - data["portability"], - ) as InvocationContextPortability; + instance.portability = + String(data["portability"]) as InvocationContextPortability; } if ( data["delegatedState"] !== undefined && diff --git a/runtime/typescript/packages/core/src/model/contracts/pipeline/turn-commit.ts b/runtime/typescript/packages/core/src/model/contracts/pipeline/turn-commit.ts index b7e43e1b1..98cfea0b2 100644 --- a/runtime/typescript/packages/core/src/model/contracts/pipeline/turn-commit.ts +++ b/runtime/typescript/packages/core/src/model/contracts/pipeline/turn-commit.ts @@ -7,8 +7,7 @@ import { InvocationContextState } from "./invocation-context-state"; import { Message } from "../../contracts/conversation/message"; import { ModelReconciliationState } from "./model-reconciliation-state"; -export type EngineTurnStatus = - "success" | "failed" | "cancelled" | "reconciliation_required"; +export type EngineTurnStatus = "success" | "failed" | "cancelled" | "reconciliation_required"; export class TurnCommit { static readonly shorthandProperty: string | undefined = undefined; diff --git a/runtime/typescript/packages/core/src/model/contracts/templates/index.ts b/runtime/typescript/packages/core/src/model/contracts/templates/index.ts index 8750a133e..5f232bc4b 100644 --- a/runtime/typescript/packages/core/src/model/contracts/templates/index.ts +++ b/runtime/typescript/packages/core/src/model/contracts/templates/index.ts @@ -8,5 +8,9 @@ export { MustacheFormat, CustomFormat, } from "./format-config"; -export { ParserConfig, PromptyParser, CustomParser } from "./parser-config"; +export { + ParserConfig, + PromptyParser, + CustomParser, +} from "./parser-config"; export { Template } from "./template"; diff --git a/runtime/typescript/packages/core/src/model/contracts/tooling/index.ts b/runtime/typescript/packages/core/src/model/contracts/tooling/index.ts index 1d1d63a52..e3415fbd5 100644 --- a/runtime/typescript/packages/core/src/model/contracts/tooling/index.ts +++ b/runtime/typescript/packages/core/src/model/contracts/tooling/index.ts @@ -3,7 +3,13 @@ // WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. export { Binding } from "./binding"; -export { Tool, FunctionTool, CustomTool, McpTool, OpenApiTool } from "./tool"; +export { + Tool, + FunctionTool, + CustomTool, + McpTool, + OpenApiTool, +} from "./tool"; export { McpApprovalMode } from "./mcp-approval-mode"; export { ToolContext } from "./tool-context"; export { ToolDispatchResult } from "./tool-dispatch-result"; diff --git a/runtime/typescript/packages/core/src/model/contracts/tooling/mcp-approval-mode.ts b/runtime/typescript/packages/core/src/model/contracts/tooling/mcp-approval-mode.ts index f5b7834c2..80f97aa37 100644 --- a/runtime/typescript/packages/core/src/model/contracts/tooling/mcp-approval-mode.ts +++ b/runtime/typescript/packages/core/src/model/contracts/tooling/mcp-approval-mode.ts @@ -53,17 +53,15 @@ export class McpApprovalMode { data["alwaysRequireApprovalTools"] !== undefined && data["alwaysRequireApprovalTools"] !== null ) { - instance.alwaysRequireApprovalTools = ( - data["alwaysRequireApprovalTools"] as unknown[] - ).map((v) => String(v)); + instance.alwaysRequireApprovalTools = + (data["alwaysRequireApprovalTools"] as unknown[]).map((v) => String(v)); } if ( data["neverRequireApprovalTools"] !== undefined && data["neverRequireApprovalTools"] !== null ) { - instance.neverRequireApprovalTools = ( - data["neverRequireApprovalTools"] as unknown[] - ).map((v) => String(v)); + instance.neverRequireApprovalTools = + (data["neverRequireApprovalTools"] as unknown[]).map((v) => String(v)); } if (context) { diff --git a/runtime/typescript/packages/core/src/model/parser-resolver.ts b/runtime/typescript/packages/core/src/model/parser-resolver.ts index 22b2dbed5..7151ce743 100644 --- a/runtime/typescript/packages/core/src/model/parser-resolver.ts +++ b/runtime/typescript/packages/core/src/model/parser-resolver.ts @@ -19,9 +19,8 @@ export type ParserKind = "prompty"; * A Record over ParserKind forces every slot to be DECLARED (a missing key is a * compile error); a null value signals a valid-but-unimplemented variant. */ -export type ParserProvider = Record & { - custom?: Parser | null; -}; +export type ParserProvider = + Record & { custom?: Parser | null }; /** * Map a "kind" discriminator value to the selected Parser impl — the diff --git a/runtime/typescript/packages/core/src/model/processor-resolver.ts b/runtime/typescript/packages/core/src/model/processor-resolver.ts index 41e5c61cc..92c2d49fd 100644 --- a/runtime/typescript/packages/core/src/model/processor-resolver.ts +++ b/runtime/typescript/packages/core/src/model/processor-resolver.ts @@ -19,9 +19,8 @@ export type ProcessorKind = "openai" | "azure"; * A Record over ProcessorKind forces every slot to be DECLARED (a missing key is a * compile error); a null value signals a valid-but-unimplemented variant. */ -export type ProcessorProvider = Record & { - custom?: Processor | null; -}; +export type ProcessorProvider = + Record & { custom?: Processor | null }; /** * Map a "provider" discriminator value to the selected Processor impl — the diff --git a/runtime/typescript/packages/core/src/model/renderer-resolver.ts b/runtime/typescript/packages/core/src/model/renderer-resolver.ts index bce77cf14..45a8457c1 100644 --- a/runtime/typescript/packages/core/src/model/renderer-resolver.ts +++ b/runtime/typescript/packages/core/src/model/renderer-resolver.ts @@ -19,9 +19,8 @@ export type RendererKind = "jinja2" | "mustache"; * A Record over RendererKind forces every slot to be DECLARED (a missing key is a * compile error); a null value signals a valid-but-unimplemented variant. */ -export type RendererProvider = Record & { - custom?: Renderer | null; -}; +export type RendererProvider = + Record & { custom?: Renderer | null }; /** * Map a "kind" discriminator value to the selected Renderer impl — the diff --git a/runtime/typescript/packages/core/src/model/vector-conformance.ts b/runtime/typescript/packages/core/src/model/vector-conformance.ts index b7307b453..6fcab1785 100644 --- a/runtime/typescript/packages/core/src/model/vector-conformance.ts +++ b/runtime/typescript/packages/core/src/model/vector-conformance.ts @@ -362,9 +362,8 @@ export async function runLoadConformanceConformance( const sourceJson = "null"; const source = JSON.parse(sourceJson) as unknown; const actual = await seam.load(source); - const expected = JSON.parse( - '{"model":{"connection":{"kind":"key","endpoint":"hello"}}}', - ); + const expected = + JSON.parse('{"model":{"connection":{"kind":"key","endpoint":"hello"}}}'); seamConformanceAssert( seamConformanceCanonical(JSON.parse(JSON.stringify(actual))) === seamConformanceCanonical(expected), @@ -568,9 +567,8 @@ export async function runLoadConformanceConformance( const sourceJson = "null"; const source = JSON.parse(sourceJson) as unknown; const actual = await seam.load(source); - const expected = JSON.parse( - '{"kind":"prompt","name":"test","model":{"id":"gpt-4o"}}', - ); + const expected = + JSON.parse('{"kind":"prompt","name":"test","model":{"id":"gpt-4o"}}'); seamConformanceAssert( seamConformanceCanonical(JSON.parse(JSON.stringify(actual))) === seamConformanceCanonical(expected), @@ -611,9 +609,8 @@ export async function runLoadConformanceConformance( const sourceJson = "null"; const source = JSON.parse(sourceJson) as unknown; const actual = await seam.load(source); - const expected = JSON.parse( - '{"tools":[{"name":"my_tool","kind":"my_provider"}]}', - ); + const expected = + JSON.parse('{"tools":[{"name":"my_tool","kind":"my_provider"}]}'); seamConformanceAssert( seamConformanceCanonical(JSON.parse(JSON.stringify(actual))) === seamConformanceCanonical(expected), 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..4ad533642 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); @@ -52,12 +54,13 @@ describe("ModelOptions", () => { 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 +69,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); @@ -86,7 +90,7 @@ describe("ModelOptions", () => { 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/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/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": { From 09f5ce206b82eb02c1354ea8455ef4f23f5f74ca Mon Sep 17 00:00:00 2001 From: Seth Juarez Date: Tue, 8 Sep 2026 08:35:50 -0700 Subject: [PATCH 02/10] feat(python): guard deprecated sampling params, translate reasoningEffort Keep the generated to_wire/_build_options mappers pure (deterministic knownAs renames so wire-conformance vectors still assert the rename), and apply capability guards only in the live execute path. OpenAI: drop temperature/top_p/top_k/frequency_penalty/presence_penalty for reasoning models (o-series, gpt-5) in the chat/responses arg builders. Anthropic: drop params the installed SDK no longer accepts and map reasoningEffort into output_config.effort. Adds TestReasoningEffortGuard coverage. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../prompty/providers/anthropic/executor.py | 93 +++++++++++++++++- .../prompty/providers/openai/executor.py | 62 ++++++++++++ .../python/prompty/tests/test_anthropic.py | 96 +++++++++++++++++++ 3 files changed, 250 insertions(+), 1 deletion(-) 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/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/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() From c50c3cc0e0ab481fbdd26ee318ef7cb7ba83a543 Mon Sep 17 00:00:00 2001 From: Seth Juarez Date: Tue, 8 Sep 2026 08:36:24 -0700 Subject: [PATCH 03/10] feat(python): live integration plumbing for real Entra-auth endpoints Foundry: use azure.identity.aio DefaultAzureCredential for the async client. OpenAI: default base_url to OPENAI_BASE_URL or api.openai.com/v1 so key-less connections resolve. Add aiohttp to foundry/azure/all extras (async Entra token provider). Extend integration conftest and model-listing test for live runs. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../prompty/providers/foundry/executor.py | 2 +- .../prompty/providers/openai/models.py | 4 + runtime/python/prompty/pyproject.toml | 5 +- .../prompty/tests/integration/conftest.py | 37 +- .../tests/integration/test_model_listing.py | 24 +- runtime/python/prompty/tests/test_models.py | 6 +- runtime/python/prompty/uv.lock | 587 ++++++++++++++++++ 7 files changed, 647 insertions(+), 18 deletions(-) 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/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/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" From c33ea820a593ddf648d6515c8fbd791a9c8ed8dc Mon Sep 17 00:00:00 2001 From: Seth Juarez Date: Tue, 8 Sep 2026 08:36:24 -0700 Subject: [PATCH 04/10] docs: regenerate reference docs and vscode schema for reasoningEffort Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- vscode/prompty/schemas/ModelOptions.yaml | 18 ++++++++++++++++++ web/src/content/docs/reference/Model.md | 1 + web/src/content/docs/reference/ModelOptions.md | 3 +++ web/src/content/docs/reference/index.md | 1 + 4 files changed, 23 insertions(+) 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 From f0862fb4f29f01fcbe1065797f5349832c8d2593 Mon Sep 17 00:00:00 2001 From: Seth Juarez Date: Tue, 8 Sep 2026 09:52:38 -0700 Subject: [PATCH 05/10] schema: add turn-engine vector coverage gate (forcing function) Anchors cross-runtime turn parity to the canonical EngineEventKind TypeSpec alias (23 members) and fails when any member is unasserted by the shared @vector corpus (vectors.json). Today 15/23 are covered; the 8 uncovered kinds (policy_applied, model_invocation_failed, model_reconciliation_required, model_invocation_reconciled, tool_result_reconciled, turn_failed, turn_reconciliation_required, post_commit_failed) are the vector-authoring backlog -- currently implemented behaviorally only in the Rust engine. Exposed as a strict standalone 'check:coverage' script. Intentionally NOT wired into the blocking 'build' chain yet: it is red until the cross-runtime port + covering vectors land, and blocking now would red unrelated in-flight work on this branch. Flip it into 'build' once green. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- schema/package.json | 1 + schema/scripts/check-vector-coverage.mjs | 104 +++++++++++++++++++++++ 2 files changed, 105 insertions(+) create mode 100644 schema/scripts/check-vector-coverage.mjs diff --git a/schema/package.json b/schema/package.json index ea977e207..3b2a48408 100644 --- a/schema/package.json +++ b/schema/package.json @@ -10,6 +10,7 @@ "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": { 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(); From deab0b6953a22bc39f1df3de3ab012b08746fe13 Mon Sep 17 00:00:00 2001 From: Seth Juarez Date: Wed, 9 Sep 2026 09:21:35 -0700 Subject: [PATCH 06/10] schema: upgrade Typra emitter 2.1.6 -> 2.1.8 and regenerate Typra 2.1.8 fixes the generated conversion-test parity gap reported in sethjuarez/typra#328: wire-conversion tests (toWire/fromWire assertions) are now emitted for every backend, not just Go and Java. Regenerating adds them to Python, C#, Rust, Swift, and TypeScript, and additionally emits invalid-input rejection tests (from_json_invalid / 'should reject malformed JSON') across all runtimes. Verified: Python model suite (766) and full Python suite (1472 passed, 57 integration deselected) green; Go model tests green. Turn-engine vector coverage gate unchanged (15/23 by design; tracked separately in microsoft/prompty#519). Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../Model/AgentConversionTests.cs | 6 + .../AnonymousConnectionConversionTests.cs | 6 + .../ApiKeyConnectionConversionTests.cs | 6 + .../Connectivity/ConnectionConversionTests.cs | 6 + .../DeviceAuthorizationConversionTests.cs | 38 +++ .../FoundryConnectionConversionTests.cs | 6 + .../OAuthConnectionConversionTests.cs | 6 + .../Connectivity/OAuthTokenConversionTests.cs | 29 +++ .../ReferenceConnectionConversionTests.cs | 6 + .../RemoteConnectionConversionTests.cs | 6 + .../Conversation/AudioPartConversionTests.cs | 6 + .../ContentPartConversionTests.cs | 6 + .../Conversation/FilePartConversionTests.cs | 6 + .../Conversation/ImagePartConversionTests.cs | 6 + .../Conversation/MessageConversionTests.cs | 6 + .../Conversation/TextPartConversionTests.cs | 6 + .../Conversation/ToolCallConversionTests.cs | 6 + .../Conversation/ToolResultConversionTests.cs | 6 + .../Core/ArrayPropertyConversionTests.cs | 6 + .../Core/ObjectPropertyConversionTests.cs | 6 + .../Contracts/Core/PropertyConversionTests.cs | 6 + .../Core/UnionPropertyConversionTests.cs | 6 + .../Events/CheckpointConversionTests.cs | 6 + .../Events/ErrorChunkConversionTests.cs | 6 + .../Events/FailureChunkConversionTests.cs | 6 + .../Events/HostToolRequestConversionTests.cs | 6 + .../Events/HostToolResultConversionTests.cs | 6 + .../PermissionDecisionConversionTests.cs | 6 + .../PermissionRequestConversionTests.cs | 6 + .../Events/RedactedFieldConversionTests.cs | 6 + .../RedactionMetadataConversionTests.cs | 6 + .../Events/SessionEventConversionTests.cs | 6 + .../Events/SessionSummaryConversionTests.cs | 6 + .../Events/StreamChunkConversionTests.cs | 6 + .../Events/StreamFailureConversionTests.cs | 6 + .../Events/TextChunkConversionTests.cs | 6 + .../Events/ThinkingChunkConversionTests.cs | 6 + .../Events/ToolChunkConversionTests.cs | 6 + .../Events/TurnEventConversionTests.cs | 6 + .../Events/UsageChunkConversionTests.cs | 6 + .../Memory/MemoryEntryConversionTests.cs | 6 + .../Memory/MemoryStoreConversionTests.cs | 6 + .../Models/AzureModelConversionTests.cs | 6 + .../Models/CustomModelConversionTests.cs | 6 + .../Models/InvocationUsageConversionTests.cs | 43 ++++ .../Contracts/Models/ModelConversionTests.cs | 6 + .../Models/ModelInfoConversionTests.cs | 56 +++++ .../Models/ModelOptionsConversionTests.cs | 87 +++++++ .../Models/OpenAIModelConversionTests.cs | 6 + .../Models/TokenUsageConversionTests.cs | 43 ++++ .../DelegatedStateReferenceConversionTests.cs | 6 + .../EngineCheckpointConversionTests.cs | 6 + .../Pipeline/EngineEventConversionTests.cs | 6 + ...EnginePermissionDecisionConversionTests.cs | 6 + ...nvocationContextDecisionConversionTests.cs | 6 + .../InvocationContextStateConversionTests.cs | 6 + ...nvocationContextSnapshotConversionTests.cs | 6 + .../ModelInvocationRequestConversionTests.cs | 6 + .../ModelInvocationResponseConversionTests.cs | 6 + ...ModelReconciliationStateConversionTests.cs | 6 + .../ModelToolRequestConversionTests.cs | 6 + .../ModelToolResultConversionTests.cs | 6 + .../ReplayJournalRecordConversionTests.cs | 6 + .../Pipeline/ResumeContextConversionTests.cs | 6 + .../Pipeline/TurnCommitConversionTests.cs | 6 + .../Templates/CustomFormatConversionTests.cs | 6 + .../Templates/CustomParserConversionTests.cs | 6 + .../Templates/FormatConfigConversionTests.cs | 6 + .../Templates/Jinja2FormatConversionTests.cs | 6 + .../MustacheFormatConversionTests.cs | 6 + .../Templates/ParserConfigConversionTests.cs | 6 + .../Templates/PromptyParserConversionTests.cs | 6 + .../Templates/TemplateConversionTests.cs | 6 + .../Tooling/BindingConversionTests.cs | 6 + .../Tooling/CustomToolConversionTests.cs | 6 + .../Tooling/FunctionToolConversionTests.cs | 6 + .../Tooling/McpApprovalModeConversionTests.cs | 6 + .../Tooling/McpToolConversionTests.cs | 6 + .../Tooling/OpenApiToolConversionTests.cs | 6 + .../Contracts/Tooling/ToolConversionTests.cs | 6 + .../Pipeline/RenderSegmentConversionTests.cs | 6 + .../DeviceAuthorizationGeneratedTest.java | 1 - .../model/OAuthTokenGeneratedTest.java | 6 - .../connectivity/test_anonymous_connection.py | 10 + .../connectivity/test_api_key_connection.py | 10 + .../contracts/connectivity/test_connection.py | 10 + .../connectivity/test_device_authorization.py | 38 +++ .../connectivity/test_foundry_connection.py | 10 + .../connectivity/test_o_auth_connection.py | 10 + .../connectivity/test_o_auth_token.py | 29 +++ .../connectivity/test_reference_connection.py | 10 + .../connectivity/test_remote_connection.py | 10 + .../contracts/conversation/test_audio_part.py | 10 + .../conversation/test_content_part.py | 10 + .../contracts/conversation/test_file_part.py | 10 + .../contracts/conversation/test_image_part.py | 10 + .../contracts/conversation/test_message.py | 10 + .../contracts/conversation/test_text_part.py | 10 + .../contracts/conversation/test_tool_call.py | 10 + .../conversation/test_tool_result.py | 10 + .../contracts/core/test_array_property.py | 10 + .../contracts/core/test_object_property.py | 10 + .../model/contracts/core/test_property.py | 10 + .../contracts/core/test_union_property.py | 10 + .../model/contracts/events/test_checkpoint.py | 10 + .../contracts/events/test_error_chunk.py | 10 + .../contracts/events/test_failure_chunk.py | 10 + .../events/test_host_tool_request.py | 10 + .../contracts/events/test_host_tool_result.py | 10 + .../events/test_permission_decision.py | 10 + .../events/test_permission_request.py | 10 + .../contracts/events/test_redacted_field.py | 10 + .../events/test_redaction_metadata.py | 10 + .../contracts/events/test_session_event.py | 10 + .../contracts/events/test_session_summary.py | 10 + .../contracts/events/test_stream_chunk.py | 10 + .../contracts/events/test_stream_failure.py | 10 + .../model/contracts/events/test_text_chunk.py | 10 + .../contracts/events/test_thinking_chunk.py | 10 + .../model/contracts/events/test_tool_chunk.py | 10 + .../model/contracts/events/test_turn_event.py | 10 + .../contracts/events/test_usage_chunk.py | 10 + .../contracts/memory/test_memory_entry.py | 10 + .../contracts/memory/test_memory_store.py | 10 + .../contracts/models/test_azure_model.py | 10 + .../contracts/models/test_custom_model.py | 10 + .../contracts/models/test_invocation_usage.py | 41 ++++ .../model/contracts/models/test_model.py | 10 + .../model/contracts/models/test_model_info.py | 54 +++++ .../contracts/models/test_model_options.py | 83 +++++++ .../contracts/models/test_open_ai_model.py | 10 + .../contracts/models/test_token_usage.py | 41 ++++ .../test_delegated_state_reference.py | 10 + .../pipeline/test_engine_checkpoint.py | 10 + .../contracts/pipeline/test_engine_event.py | 10 + .../test_engine_permission_decision.py | 10 + .../test_invocation_context_decision.py | 10 + .../pipeline/test_invocation_context_state.py | 10 + .../test_model_invocation_context_snapshot.py | 10 + .../pipeline/test_model_invocation_request.py | 10 + .../test_model_invocation_response.py | 10 + .../test_model_reconciliation_state.py | 10 + .../pipeline/test_model_tool_request.py | 10 + .../pipeline/test_model_tool_result.py | 10 + .../pipeline/test_replay_journal_record.py | 10 + .../contracts/pipeline/test_resume_context.py | 10 + .../contracts/pipeline/test_turn_commit.py | 10 + .../contracts/templates/test_custom_format.py | 10 + .../contracts/templates/test_custom_parser.py | 10 + .../contracts/templates/test_format_config.py | 10 + .../contracts/templates/test_jinja2format.py | 10 + .../templates/test_mustache_format.py | 10 + .../contracts/templates/test_parser_config.py | 10 + .../templates/test_prompty_parser.py | 10 + .../contracts/templates/test_template.py | 10 + .../model/contracts/tooling/test_binding.py | 10 + .../contracts/tooling/test_custom_tool.py | 10 + .../contracts/tooling/test_function_tool.py | 10 + .../tooling/test_mcp_approval_mode.py | 10 + .../model/contracts/tooling/test_mcp_tool.py | 10 + .../contracts/tooling/test_open_api_tool.py | 10 + .../model/contracts/tooling/test_tool.py | 10 + .../pipeline/test_render_segment.py | 10 + .../python/prompty/tests/model/test_agent.py | 10 + .../rust/prompty/tests/model/agent_test.rs | 9 + .../contracts/connectivity/connection_test.rs | 9 + .../connectivity/device_authorization_test.rs | 76 ++++++ .../connectivity/o_auth_token_test.rs | 49 ++++ .../conversation/content_part_test.rs | 9 + .../contracts/conversation/message_test.rs | 9 + .../contracts/conversation/tool_call_test.rs | 9 + .../conversation/tool_result_test.rs | 9 + .../model/contracts/core/property_test.rs | 9 + .../model/contracts/events/checkpoint_test.rs | 9 + .../events/host_tool_request_test.rs | 9 + .../contracts/events/host_tool_result_test.rs | 9 + .../events/permission_decision_test.rs | 9 + .../events/permission_request_test.rs | 9 + .../contracts/events/redacted_field_test.rs | 9 + .../events/redaction_metadata_test.rs | 9 + .../contracts/events/session_event_test.rs | 9 + .../contracts/events/session_summary_test.rs | 9 + .../contracts/events/stream_chunk_test.rs | 9 + .../contracts/events/stream_failure_test.rs | 9 + .../model/contracts/events/turn_event_test.rs | 9 + .../contracts/memory/memory_entry_test.rs | 9 + .../contracts/memory/memory_store_test.rs | 9 + .../contracts/models/invocation_usage_test.rs | 96 ++++++++ .../model/contracts/models/model_info_test.rs | 115 +++++++++ .../contracts/models/model_options_test.rs | 224 ++++++++++++++++++ .../model/contracts/models/model_test.rs | 9 + .../contracts/models/token_usage_test.rs | 96 ++++++++ .../delegated_state_reference_test.rs | 9 + .../pipeline/engine_checkpoint_test.rs | 9 + .../contracts/pipeline/engine_event_test.rs | 9 + .../engine_permission_decision_test.rs | 9 + .../invocation_context_decision_test.rs | 9 + .../pipeline/invocation_context_state_test.rs | 9 + .../model_invocation_context_snapshot_test.rs | 9 + .../pipeline/model_invocation_request_test.rs | 9 + .../model_invocation_response_test.rs | 9 + .../model_reconciliation_state_test.rs | 9 + .../pipeline/model_tool_request_test.rs | 9 + .../pipeline/model_tool_result_test.rs | 9 + .../pipeline/replay_journal_record_test.rs | 9 + .../contracts/pipeline/resume_context_test.rs | 9 + .../contracts/pipeline/turn_commit_test.rs | 9 + .../contracts/templates/format_config_test.rs | 9 + .../contracts/templates/parser_config_test.rs | 9 + .../contracts/templates/template_test.rs | 9 + .../model/contracts/tooling/binding_test.rs | 9 + .../tooling/mcp_approval_mode_test.rs | 9 + .../model/contracts/tooling/tool_test.rs | 9 + .../pipeline/render_segment_test.rs | 9 + .../Tests/PromptyModelTests/AgentTests.swift | 5 + .../Connectivity/ConnectionTests.swift | 5 + .../DeviceAuthorizationTests.swift | 30 +++ .../Connectivity/OAuthTokenTests.swift | 21 ++ .../Conversation/ContentPartTests.swift | 5 + .../Contracts/Conversation/MessageTests.swift | 5 + .../Conversation/ToolCallTests.swift | 5 + .../Conversation/ToolResultTests.swift | 5 + .../Contracts/Core/PropertyTests.swift | 5 + .../Contracts/Events/CheckpointTests.swift | 5 + .../Events/HostToolRequestTests.swift | 5 + .../Events/HostToolResultTests.swift | 5 + .../Events/PermissionDecisionTests.swift | 5 + .../Events/PermissionRequestTests.swift | 5 + .../Contracts/Events/RedactedFieldTests.swift | 5 + .../Events/RedactionMetadataTests.swift | 5 + .../Contracts/Events/SessionEventTests.swift | 5 + .../Events/SessionSummaryTests.swift | 5 + .../Contracts/Events/StreamChunkTests.swift | 5 + .../Contracts/Events/StreamFailureTests.swift | 5 + .../Contracts/Events/TurnEventTests.swift | 5 + .../Contracts/Memory/MemoryEntryTests.swift | 5 + .../Contracts/Memory/MemoryStoreTests.swift | 5 + .../Models/InvocationUsageTests.swift | 32 +++ .../Contracts/Models/ModelInfoTests.swift | 45 ++++ .../Contracts/Models/ModelOptionsTests.swift | 73 ++++++ .../Contracts/Models/ModelTests.swift | 5 + .../Contracts/Models/TokenUsageTests.swift | 32 +++ .../DelegatedStateReferenceTests.swift | 5 + .../Pipeline/EngineCheckpointTests.swift | 5 + .../Contracts/Pipeline/EngineEventTests.swift | 5 + .../EnginePermissionDecisionTests.swift | 5 + .../InvocationContextDecisionTests.swift | 5 + .../InvocationContextStateTests.swift | 5 + .../ModelInvocationContextSnapshotTests.swift | 5 + .../ModelInvocationRequestTests.swift | 5 + .../ModelInvocationResponseTests.swift | 5 + .../ModelReconciliationStateTests.swift | 5 + .../Pipeline/ModelToolRequestTests.swift | 5 + .../Pipeline/ModelToolResultTests.swift | 5 + .../Pipeline/ReplayJournalRecordTests.swift | 5 + .../Pipeline/ResumeContextTests.swift | 5 + .../Contracts/Pipeline/TurnCommitTests.swift | 5 + .../Templates/FormatConfigTests.swift | 5 + .../Templates/ParserConfigTests.swift | 5 + .../Contracts/Templates/TemplateTests.swift | 5 + .../Contracts/Tooling/BindingTests.swift | 5 + .../Tooling/McpApprovalModeTests.swift | 5 + .../Contracts/Tooling/ToolTests.swift | 5 + .../Pipeline/RenderSegmentTests.swift | 5 + .../packages/core/tests/model/agent.test.ts | 10 + .../connectivity/anonymous-connection.test.ts | 10 + .../connectivity/api-key-connection.test.ts | 10 + .../contracts/connectivity/connection.test.ts | 10 + .../connectivity/device-authorization.test.ts | 34 +++ .../connectivity/foundry-connection.test.ts | 10 + .../connectivity/o-auth-connection.test.ts | 10 + .../connectivity/o-auth-token.test.ts | 25 ++ .../connectivity/reference-connection.test.ts | 10 + .../connectivity/remote-connection.test.ts | 10 + .../contracts/conversation/audio-part.test.ts | 10 + .../conversation/content-part.test.ts | 10 + .../contracts/conversation/file-part.test.ts | 10 + .../contracts/conversation/image-part.test.ts | 10 + .../contracts/conversation/message.test.ts | 10 + .../contracts/conversation/text-part.test.ts | 10 + .../contracts/conversation/tool-call.test.ts | 10 + .../conversation/tool-result.test.ts | 10 + .../contracts/core/array-property.test.ts | 10 + .../contracts/core/object-property.test.ts | 10 + .../model/contracts/core/property.test.ts | 10 + .../contracts/core/union-property.test.ts | 10 + .../model/contracts/events/checkpoint.test.ts | 10 + .../contracts/events/error-chunk.test.ts | 10 + .../contracts/events/failure-chunk.test.ts | 10 + .../events/host-tool-request.test.ts | 10 + .../contracts/events/host-tool-result.test.ts | 10 + .../events/permission-decision.test.ts | 10 + .../events/permission-request.test.ts | 10 + .../contracts/events/redacted-field.test.ts | 10 + .../events/redaction-metadata.test.ts | 10 + .../contracts/events/session-event.test.ts | 10 + .../contracts/events/session-summary.test.ts | 10 + .../contracts/events/stream-chunk.test.ts | 10 + .../contracts/events/stream-failure.test.ts | 10 + .../model/contracts/events/text-chunk.test.ts | 10 + .../contracts/events/thinking-chunk.test.ts | 10 + .../model/contracts/events/tool-chunk.test.ts | 10 + .../model/contracts/events/turn-event.test.ts | 10 + .../contracts/events/usage-chunk.test.ts | 10 + .../contracts/memory/memory-entry.test.ts | 10 + .../contracts/memory/memory-store.test.ts | 10 + .../contracts/models/azure-model.test.ts | 10 + .../contracts/models/custom-model.test.ts | 10 + .../contracts/models/invocation-usage.test.ts | 40 ++++ .../model/contracts/models/model-info.test.ts | 47 ++++ .../contracts/models/model-options.test.ts | 88 +++++++ .../model/contracts/models/model.test.ts | 10 + .../contracts/models/open-ai-model.test.ts | 10 + .../contracts/models/token-usage.test.ts | 39 +++ .../delegated-state-reference.test.ts | 10 + .../pipeline/engine-checkpoint.test.ts | 10 + .../contracts/pipeline/engine-event.test.ts | 10 + .../engine-permission-decision.test.ts | 10 + .../invocation-context-decision.test.ts | 10 + .../pipeline/invocation-context-state.test.ts | 10 + .../model-invocation-context-snapshot.test.ts | 10 + .../pipeline/model-invocation-request.test.ts | 10 + .../model-invocation-response.test.ts | 10 + .../model-reconciliation-state.test.ts | 10 + .../pipeline/model-tool-request.test.ts | 10 + .../pipeline/model-tool-result.test.ts | 10 + .../pipeline/replay-journal-record.test.ts | 10 + .../contracts/pipeline/resume-context.test.ts | 10 + .../contracts/pipeline/turn-commit.test.ts | 10 + .../contracts/templates/custom-format.test.ts | 10 + .../contracts/templates/custom-parser.test.ts | 10 + .../contracts/templates/format-config.test.ts | 10 + .../contracts/templates/jinja2format.test.ts | 10 + .../templates/mustache-format.test.ts | 10 + .../contracts/templates/parser-config.test.ts | 10 + .../templates/prompty-parser.test.ts | 10 + .../contracts/templates/template.test.ts | 10 + .../model/contracts/tooling/binding.test.ts | 10 + .../contracts/tooling/custom-tool.test.ts | 10 + .../contracts/tooling/function-tool.test.ts | 10 + .../tooling/mcp-approval-mode.test.ts | 10 + .../model/contracts/tooling/mcp-tool.test.ts | 10 + .../contracts/tooling/open-api-tool.test.ts | 10 + .../model/contracts/tooling/tool.test.ts | 10 + .../pipeline/render-segment.test.ts | 10 + schema/package-lock.json | 8 +- schema/package.json | 2 +- .../.typra-generated/export-surfaces.json | 4 +- 348 files changed, 4317 insertions(+), 14 deletions(-) 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 258c0c80f..196e8ff8b 100644 --- a/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Models/ModelOptionsConversionTests.cs +++ b/runtime/csharp/Prompty.Core.Tests/Model/Contracts/Models/ModelOptionsConversionTests.cs @@ -234,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/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/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/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 2df14839f..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 @@ -174,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/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 d34df5e55..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 @@ -221,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/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 3f3e4351f..6c838c6d6 100644 --- a/runtime/swift/prompty-model/Tests/PromptyModelTests/Contracts/Models/ModelOptionsTests.swift +++ b/runtime/swift/prompty-model/Tests/PromptyModelTests/Contracts/Models/ModelOptionsTests.swift @@ -99,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/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 4ad533642..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 @@ -50,6 +50,16 @@ 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", () => { @@ -87,6 +97,84 @@ 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( 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/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 3b2a48408..7aa14ef46 100644 --- a/schema/package.json +++ b/schema/package.json @@ -17,6 +17,6 @@ "@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/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 } ] From 850106ea580e600a99ff075d69c3f6707ceb5e06 Mon Sep 17 00:00:00 2001 From: Seth Juarez Date: Wed, 9 Sep 2026 09:42:53 -0700 Subject: [PATCH 07/10] schema: fix Swift reasoningEffort wire value and TS prettier regen drift Two CI regressions surfaced by the Typra 2.1.8 wire-conversion tests on PR #522: 1. Swift ModelOptions.toWire emitted the reasoningEffort RawRepresentable struct instead of its backing string (only field with a named string-union type), so a wire round-trip failed fromWire->load() with 'Expected string for field reasoningEffort.' Add a deterministic normalize-typra-output.mjs workaround appending .rawValue to that single assignment (mirrors the correct save() path). Runs in generate on local + CI, so regen stays reproducible. Verified: all 368 Swift tests pass. 2. TypeScript model sources were committed un-prettified because the TS workspace had no node_modules locally, so the emitter skipped prettier. Regenerated with TS deps installed; the 22 model .ts files now match CI's prettier output, fixing 'regeneration produces no diff.' Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../Contracts/Models/model_options.swift | 2 +- .../contracts/connectivity/connection.ts | 5 ++- .../model/contracts/conversation/message.ts | 21 ++++++---- .../model/contracts/events/redacted-field.ts | 3 +- .../contracts/events/session-end-payload.ts | 3 +- .../model/contracts/events/session-event.ts | 9 +++- .../model/contracts/events/session-summary.ts | 3 +- .../src/model/contracts/events/turn-event.ts | 26 +++++++++++- .../core/src/model/contracts/models/index.ts | 7 +--- .../model/contracts/models/model-options.ts | 15 +++++-- .../core/src/model/contracts/models/model.ts | 3 +- .../contracts/pipeline/engine-checkpoint.ts | 10 +++-- .../model/contracts/pipeline/engine-event.ts | 25 ++++++++++- .../pipeline/invocation-context-decision.ts | 5 ++- .../pipeline/invocation-context-state.ts | 5 ++- .../model/contracts/pipeline/turn-commit.ts | 3 +- .../src/model/contracts/templates/index.ts | 6 +-- .../core/src/model/contracts/tooling/index.ts | 8 +--- .../contracts/tooling/mcp-approval-mode.ts | 10 +++-- .../core/src/model/parser-resolver.ts | 5 ++- .../core/src/model/processor-resolver.ts | 5 ++- .../core/src/model/renderer-resolver.ts | 5 ++- .../core/src/model/vector-conformance.ts | 15 ++++--- schema/scripts/normalize-typra-output.mjs | 42 +++++++++++++++++++ 24 files changed, 176 insertions(+), 65 deletions(-) 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 0ac7ddc38..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 @@ -161,7 +161,7 @@ public struct ModelOptions: TypraModel { case "responses": wireNameReasoningEffort = "reasoning_effort" default: wireNameReasoningEffort = nil } - if let wireKey = wireNameReasoningEffort, let value = self.reasoningEffort { result[wireKey] = value } + if let wireKey = wireNameReasoningEffort, let value = self.reasoningEffort { result[wireKey] = value.rawValue } let wireNameSeed: String? switch provider { case "openai": wireNameSeed = "seed" diff --git a/runtime/typescript/packages/core/src/model/contracts/connectivity/connection.ts b/runtime/typescript/packages/core/src/model/contracts/connectivity/connection.ts index 74abb8723..6dacccae9 100644 --- a/runtime/typescript/packages/core/src/model/contracts/connectivity/connection.ts +++ b/runtime/typescript/packages/core/src/model/contracts/connectivity/connection.ts @@ -61,8 +61,9 @@ export abstract class Connection { data["authenticationMode"] !== undefined && data["authenticationMode"] !== null ) { - instance.authenticationMode = - String(data["authenticationMode"]) as AuthenticationMode; + instance.authenticationMode = String( + data["authenticationMode"], + ) as AuthenticationMode; } if ( data["usageDescription"] !== undefined && diff --git a/runtime/typescript/packages/core/src/model/contracts/conversation/message.ts b/runtime/typescript/packages/core/src/model/contracts/conversation/message.ts index db5ba5a3a..9c8572005 100644 --- a/runtime/typescript/packages/core/src/model/contracts/conversation/message.ts +++ b/runtime/typescript/packages/core/src/model/contracts/conversation/message.ts @@ -144,20 +144,23 @@ export class Message { //#endregion static assistant(text: string): Message { - return new Message( - { role: "assistant", parts: [new TextPart({ value: text })] }, - ); + return new Message({ + role: "assistant", + parts: [new TextPart({ value: text })], + }); } static system(text: string): Message { - return new Message( - { role: "system", parts: [new TextPart({ value: text })] }, - ); + return new Message({ + role: "system", + parts: [new TextPart({ value: text })], + }); } static user(text: string): Message { - return new Message( - { role: "user", parts: [new TextPart({ value: text })] }, - ); + return new Message({ + role: "user", + parts: [new TextPart({ value: text })], + }); } } diff --git a/runtime/typescript/packages/core/src/model/contracts/events/redacted-field.ts b/runtime/typescript/packages/core/src/model/contracts/events/redacted-field.ts index 1b6f95479..892b36b02 100644 --- a/runtime/typescript/packages/core/src/model/contracts/events/redacted-field.ts +++ b/runtime/typescript/packages/core/src/model/contracts/events/redacted-field.ts @@ -4,7 +4,8 @@ import { LoadContext, SaveContext } from "../../context"; -export type RedactionMode = "none" | "redacted" | "hashed" | "summary" | "reference"; +export type RedactionMode = + "none" | "redacted" | "hashed" | "summary" | "reference"; export class RedactedField { static readonly shorthandProperty: string | undefined = undefined; diff --git a/runtime/typescript/packages/core/src/model/contracts/events/session-end-payload.ts b/runtime/typescript/packages/core/src/model/contracts/events/session-end-payload.ts index 90a2c517e..a0d9d6973 100644 --- a/runtime/typescript/packages/core/src/model/contracts/events/session-end-payload.ts +++ b/runtime/typescript/packages/core/src/model/contracts/events/session-end-payload.ts @@ -4,7 +4,8 @@ import { LoadContext, SaveContext } from "../../context"; -export type SessionEndStatus = "success" | "error" | "cancelled" | "interrupted"; +export type SessionEndStatus = + "success" | "error" | "cancelled" | "interrupted"; export class SessionEndPayload { static readonly shorthandProperty: string | undefined = undefined; diff --git a/runtime/typescript/packages/core/src/model/contracts/events/session-event.ts b/runtime/typescript/packages/core/src/model/contracts/events/session-event.ts index dc8e4317e..ebd0eacfe 100644 --- a/runtime/typescript/packages/core/src/model/contracts/events/session-event.ts +++ b/runtime/typescript/packages/core/src/model/contracts/events/session-event.ts @@ -5,7 +5,14 @@ import { LoadContext, SaveContext } from "../../context"; import { RedactionMetadata } from "./redaction-metadata"; -export type SessionEventType = "session_start" | "session_end" | "session_warning" | "session_hook_start" | "session_hook_end" | "checkpoint_created" | "trajectory_event"; +export type SessionEventType = + | "session_start" + | "session_end" + | "session_warning" + | "session_hook_start" + | "session_hook_end" + | "checkpoint_created" + | "trajectory_event"; export class SessionEvent { static readonly shorthandProperty: string | undefined = undefined; diff --git a/runtime/typescript/packages/core/src/model/contracts/events/session-summary.ts b/runtime/typescript/packages/core/src/model/contracts/events/session-summary.ts index 17330238a..ad2f83202 100644 --- a/runtime/typescript/packages/core/src/model/contracts/events/session-summary.ts +++ b/runtime/typescript/packages/core/src/model/contracts/events/session-summary.ts @@ -5,7 +5,8 @@ import { LoadContext, SaveContext } from "../../context"; import { TokenUsage } from "../../contracts/models/token-usage"; -export type SessionSummaryStatus = "success" | "error" | "cancelled" | "interrupted"; +export type SessionSummaryStatus = + "success" | "error" | "cancelled" | "interrupted"; export class SessionSummary { static readonly shorthandProperty: string | undefined = undefined; diff --git a/runtime/typescript/packages/core/src/model/contracts/events/turn-event.ts b/runtime/typescript/packages/core/src/model/contracts/events/turn-event.ts index 77f516f1a..4515239d5 100644 --- a/runtime/typescript/packages/core/src/model/contracts/events/turn-event.ts +++ b/runtime/typescript/packages/core/src/model/contracts/events/turn-event.ts @@ -4,7 +4,31 @@ import { LoadContext, SaveContext } from "../../context"; -export type TurnEventType = "turn_start" | "turn_end" | "llm_start" | "llm_complete" | "retry" | "permission_requested" | "permission_completed" | "token" | "thinking" | "tool_call_start" | "tool_call_complete" | "tool_execution_start" | "tool_execution_complete" | "tool_result" | "hook_start" | "hook_end" | "status" | "messages_updated" | "done" | "error" | "cancelled" | "compaction_start" | "compaction_complete" | "compaction_failed"; +export type TurnEventType = + | "turn_start" + | "turn_end" + | "llm_start" + | "llm_complete" + | "retry" + | "permission_requested" + | "permission_completed" + | "token" + | "thinking" + | "tool_call_start" + | "tool_call_complete" + | "tool_execution_start" + | "tool_execution_complete" + | "tool_result" + | "hook_start" + | "hook_end" + | "status" + | "messages_updated" + | "done" + | "error" + | "cancelled" + | "compaction_start" + | "compaction_complete" + | "compaction_failed"; export class TurnEvent { static readonly shorthandProperty: string | undefined = undefined; diff --git a/runtime/typescript/packages/core/src/model/contracts/models/index.ts b/runtime/typescript/packages/core/src/model/contracts/models/index.ts index bee83fb63..a48476723 100644 --- a/runtime/typescript/packages/core/src/model/contracts/models/index.ts +++ b/runtime/typescript/packages/core/src/model/contracts/models/index.ts @@ -3,12 +3,7 @@ // WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. export { ModelOptions } from "./model-options"; -export { - Model, - OpenAIModel, - AzureModel, - CustomModel, -} from "./model"; +export { Model, OpenAIModel, AzureModel, CustomModel } from "./model"; export { TokenUsage } from "./token-usage"; export { InvocationUsage } from "./invocation-usage"; export { ModelInfo } from "./model-info"; 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 f6fae97d1..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,7 +4,15 @@ import { LoadContext, SaveContext } from "../../context"; -export type reasoningEffort = "none" | "minimal" | "low" | "medium" | "high" | "xhigh" | "max" | (string & {}); +export type reasoningEffort = + | "none" + | "minimal" + | "low" + | "medium" + | "high" + | "xhigh" + | "max" + | (string & {}); export class ModelOptions { static readonly shorthandProperty: string | undefined = undefined; @@ -92,8 +100,9 @@ export class ModelOptions { data["reasoningEffort"] !== undefined && data["reasoningEffort"] !== null ) { - instance.reasoningEffort = - String(data["reasoningEffort"]) as reasoningEffort; + instance.reasoningEffort = String( + data["reasoningEffort"], + ) as reasoningEffort; } if (data["seed"] !== undefined && data["seed"] !== null) { instance.seed = Number(data["seed"]); diff --git a/runtime/typescript/packages/core/src/model/contracts/models/model.ts b/runtime/typescript/packages/core/src/model/contracts/models/model.ts index 650a8e8dc..9fecad0f5 100644 --- a/runtime/typescript/packages/core/src/model/contracts/models/model.ts +++ b/runtime/typescript/packages/core/src/model/contracts/models/model.ts @@ -6,7 +6,8 @@ import { LoadContext, SaveContext } from "../../context"; import { Connection } from "../../contracts/connectivity/connection"; import { ModelOptions } from "./model-options"; -export type apiType = "chat" | "embedding" | "image" | "responses" | (string & {}); +export type apiType = + "chat" | "embedding" | "image" | "responses" | (string & {}); export class Model { static readonly shorthandProperty: string | undefined = "id"; diff --git a/runtime/typescript/packages/core/src/model/contracts/pipeline/engine-checkpoint.ts b/runtime/typescript/packages/core/src/model/contracts/pipeline/engine-checkpoint.ts index dc0dbc7a0..a56a2034a 100644 --- a/runtime/typescript/packages/core/src/model/contracts/pipeline/engine-checkpoint.ts +++ b/runtime/typescript/packages/core/src/model/contracts/pipeline/engine-checkpoint.ts @@ -173,8 +173,9 @@ export class EngineCheckpoint { data["completedModelIterations"] !== undefined && data["completedModelIterations"] !== null ) { - instance.completedModelIterations = - Number(data["completedModelIterations"]); + instance.completedModelIterations = Number( + data["completedModelIterations"], + ); } if ( data["reconciliationRequired"] !== undefined && @@ -219,8 +220,9 @@ export class EngineCheckpoint { data["policyAppliedForIteration"] !== undefined && data["policyAppliedForIteration"] !== null ) { - instance.policyAppliedForIteration = - Boolean(data["policyAppliedForIteration"]); + instance.policyAppliedForIteration = Boolean( + data["policyAppliedForIteration"], + ); } if (data["contextState"] !== undefined && data["contextState"] !== null) { instance.contextState = InvocationContextState.load( diff --git a/runtime/typescript/packages/core/src/model/contracts/pipeline/engine-event.ts b/runtime/typescript/packages/core/src/model/contracts/pipeline/engine-event.ts index 935a66084..43daac8d1 100644 --- a/runtime/typescript/packages/core/src/model/contracts/pipeline/engine-event.ts +++ b/runtime/typescript/packages/core/src/model/contracts/pipeline/engine-event.ts @@ -4,7 +4,30 @@ import { LoadContext, SaveContext } from "../../context"; -export type EngineEventKind = "turn_started" | "policy_applied" | "context_prepared" | "model_invocation_started" | "model_invocation_completed" | "model_invocation_failed" | "model_reconciliation_required" | "model_invocation_reconciled" | "permission_requested" | "permission_resolved" | "tool_execution_started" | "tool_execution_completed" | "tool_result_committed" | "tool_result_reconciled" | "conversation_updated" | "checkpoint_created" | "turn_committed" | "turn_cancelled" | "turn_failed" | "turn_reconciliation_required" | "post_commit_started" | "post_commit_completed" | "post_commit_failed"; +export type EngineEventKind = + | "turn_started" + | "policy_applied" + | "context_prepared" + | "model_invocation_started" + | "model_invocation_completed" + | "model_invocation_failed" + | "model_reconciliation_required" + | "model_invocation_reconciled" + | "permission_requested" + | "permission_resolved" + | "tool_execution_started" + | "tool_execution_completed" + | "tool_result_committed" + | "tool_result_reconciled" + | "conversation_updated" + | "checkpoint_created" + | "turn_committed" + | "turn_cancelled" + | "turn_failed" + | "turn_reconciliation_required" + | "post_commit_started" + | "post_commit_completed" + | "post_commit_failed"; export class EngineEvent { static readonly shorthandProperty: string | undefined = undefined; diff --git a/runtime/typescript/packages/core/src/model/contracts/pipeline/invocation-context-decision.ts b/runtime/typescript/packages/core/src/model/contracts/pipeline/invocation-context-decision.ts index b6a17ec36..d8a8700b3 100644 --- a/runtime/typescript/packages/core/src/model/contracts/pipeline/invocation-context-decision.ts +++ b/runtime/typescript/packages/core/src/model/contracts/pipeline/invocation-context-decision.ts @@ -48,8 +48,9 @@ export class InvocationContextDecision { instance.candidateId = String(data["candidateId"]); } if (data["disposition"] !== undefined && data["disposition"] !== null) { - instance.disposition = - String(data["disposition"]) as InvocationContextDisposition; + instance.disposition = String( + data["disposition"], + ) as InvocationContextDisposition; } if (data["reason"] !== undefined && data["reason"] !== null) { instance.reason = String(data["reason"]); diff --git a/runtime/typescript/packages/core/src/model/contracts/pipeline/invocation-context-state.ts b/runtime/typescript/packages/core/src/model/contracts/pipeline/invocation-context-state.ts index 194427e77..286c3f7ae 100644 --- a/runtime/typescript/packages/core/src/model/contracts/pipeline/invocation-context-state.ts +++ b/runtime/typescript/packages/core/src/model/contracts/pipeline/invocation-context-state.ts @@ -34,8 +34,9 @@ export class InvocationContextState { const instance = new InvocationContextState(); if (data["portability"] !== undefined && data["portability"] !== null) { - instance.portability = - String(data["portability"]) as InvocationContextPortability; + instance.portability = String( + data["portability"], + ) as InvocationContextPortability; } if ( data["delegatedState"] !== undefined && diff --git a/runtime/typescript/packages/core/src/model/contracts/pipeline/turn-commit.ts b/runtime/typescript/packages/core/src/model/contracts/pipeline/turn-commit.ts index 98cfea0b2..b7e43e1b1 100644 --- a/runtime/typescript/packages/core/src/model/contracts/pipeline/turn-commit.ts +++ b/runtime/typescript/packages/core/src/model/contracts/pipeline/turn-commit.ts @@ -7,7 +7,8 @@ import { InvocationContextState } from "./invocation-context-state"; import { Message } from "../../contracts/conversation/message"; import { ModelReconciliationState } from "./model-reconciliation-state"; -export type EngineTurnStatus = "success" | "failed" | "cancelled" | "reconciliation_required"; +export type EngineTurnStatus = + "success" | "failed" | "cancelled" | "reconciliation_required"; export class TurnCommit { static readonly shorthandProperty: string | undefined = undefined; diff --git a/runtime/typescript/packages/core/src/model/contracts/templates/index.ts b/runtime/typescript/packages/core/src/model/contracts/templates/index.ts index 5f232bc4b..8750a133e 100644 --- a/runtime/typescript/packages/core/src/model/contracts/templates/index.ts +++ b/runtime/typescript/packages/core/src/model/contracts/templates/index.ts @@ -8,9 +8,5 @@ export { MustacheFormat, CustomFormat, } from "./format-config"; -export { - ParserConfig, - PromptyParser, - CustomParser, -} from "./parser-config"; +export { ParserConfig, PromptyParser, CustomParser } from "./parser-config"; export { Template } from "./template"; diff --git a/runtime/typescript/packages/core/src/model/contracts/tooling/index.ts b/runtime/typescript/packages/core/src/model/contracts/tooling/index.ts index e3415fbd5..1d1d63a52 100644 --- a/runtime/typescript/packages/core/src/model/contracts/tooling/index.ts +++ b/runtime/typescript/packages/core/src/model/contracts/tooling/index.ts @@ -3,13 +3,7 @@ // WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. export { Binding } from "./binding"; -export { - Tool, - FunctionTool, - CustomTool, - McpTool, - OpenApiTool, -} from "./tool"; +export { Tool, FunctionTool, CustomTool, McpTool, OpenApiTool } from "./tool"; export { McpApprovalMode } from "./mcp-approval-mode"; export { ToolContext } from "./tool-context"; export { ToolDispatchResult } from "./tool-dispatch-result"; diff --git a/runtime/typescript/packages/core/src/model/contracts/tooling/mcp-approval-mode.ts b/runtime/typescript/packages/core/src/model/contracts/tooling/mcp-approval-mode.ts index 80f97aa37..f5b7834c2 100644 --- a/runtime/typescript/packages/core/src/model/contracts/tooling/mcp-approval-mode.ts +++ b/runtime/typescript/packages/core/src/model/contracts/tooling/mcp-approval-mode.ts @@ -53,15 +53,17 @@ export class McpApprovalMode { data["alwaysRequireApprovalTools"] !== undefined && data["alwaysRequireApprovalTools"] !== null ) { - instance.alwaysRequireApprovalTools = - (data["alwaysRequireApprovalTools"] as unknown[]).map((v) => String(v)); + instance.alwaysRequireApprovalTools = ( + data["alwaysRequireApprovalTools"] as unknown[] + ).map((v) => String(v)); } if ( data["neverRequireApprovalTools"] !== undefined && data["neverRequireApprovalTools"] !== null ) { - instance.neverRequireApprovalTools = - (data["neverRequireApprovalTools"] as unknown[]).map((v) => String(v)); + instance.neverRequireApprovalTools = ( + data["neverRequireApprovalTools"] as unknown[] + ).map((v) => String(v)); } if (context) { diff --git a/runtime/typescript/packages/core/src/model/parser-resolver.ts b/runtime/typescript/packages/core/src/model/parser-resolver.ts index 7151ce743..22b2dbed5 100644 --- a/runtime/typescript/packages/core/src/model/parser-resolver.ts +++ b/runtime/typescript/packages/core/src/model/parser-resolver.ts @@ -19,8 +19,9 @@ export type ParserKind = "prompty"; * A Record over ParserKind forces every slot to be DECLARED (a missing key is a * compile error); a null value signals a valid-but-unimplemented variant. */ -export type ParserProvider = - Record & { custom?: Parser | null }; +export type ParserProvider = Record & { + custom?: Parser | null; +}; /** * Map a "kind" discriminator value to the selected Parser impl — the diff --git a/runtime/typescript/packages/core/src/model/processor-resolver.ts b/runtime/typescript/packages/core/src/model/processor-resolver.ts index 92c2d49fd..41e5c61cc 100644 --- a/runtime/typescript/packages/core/src/model/processor-resolver.ts +++ b/runtime/typescript/packages/core/src/model/processor-resolver.ts @@ -19,8 +19,9 @@ export type ProcessorKind = "openai" | "azure"; * A Record over ProcessorKind forces every slot to be DECLARED (a missing key is a * compile error); a null value signals a valid-but-unimplemented variant. */ -export type ProcessorProvider = - Record & { custom?: Processor | null }; +export type ProcessorProvider = Record & { + custom?: Processor | null; +}; /** * Map a "provider" discriminator value to the selected Processor impl — the diff --git a/runtime/typescript/packages/core/src/model/renderer-resolver.ts b/runtime/typescript/packages/core/src/model/renderer-resolver.ts index 45a8457c1..bce77cf14 100644 --- a/runtime/typescript/packages/core/src/model/renderer-resolver.ts +++ b/runtime/typescript/packages/core/src/model/renderer-resolver.ts @@ -19,8 +19,9 @@ export type RendererKind = "jinja2" | "mustache"; * A Record over RendererKind forces every slot to be DECLARED (a missing key is a * compile error); a null value signals a valid-but-unimplemented variant. */ -export type RendererProvider = - Record & { custom?: Renderer | null }; +export type RendererProvider = Record & { + custom?: Renderer | null; +}; /** * Map a "kind" discriminator value to the selected Renderer impl — the diff --git a/runtime/typescript/packages/core/src/model/vector-conformance.ts b/runtime/typescript/packages/core/src/model/vector-conformance.ts index 6fcab1785..b7307b453 100644 --- a/runtime/typescript/packages/core/src/model/vector-conformance.ts +++ b/runtime/typescript/packages/core/src/model/vector-conformance.ts @@ -362,8 +362,9 @@ export async function runLoadConformanceConformance( const sourceJson = "null"; const source = JSON.parse(sourceJson) as unknown; const actual = await seam.load(source); - const expected = - JSON.parse('{"model":{"connection":{"kind":"key","endpoint":"hello"}}}'); + const expected = JSON.parse( + '{"model":{"connection":{"kind":"key","endpoint":"hello"}}}', + ); seamConformanceAssert( seamConformanceCanonical(JSON.parse(JSON.stringify(actual))) === seamConformanceCanonical(expected), @@ -567,8 +568,9 @@ export async function runLoadConformanceConformance( const sourceJson = "null"; const source = JSON.parse(sourceJson) as unknown; const actual = await seam.load(source); - const expected = - JSON.parse('{"kind":"prompt","name":"test","model":{"id":"gpt-4o"}}'); + const expected = JSON.parse( + '{"kind":"prompt","name":"test","model":{"id":"gpt-4o"}}', + ); seamConformanceAssert( seamConformanceCanonical(JSON.parse(JSON.stringify(actual))) === seamConformanceCanonical(expected), @@ -609,8 +611,9 @@ export async function runLoadConformanceConformance( const sourceJson = "null"; const source = JSON.parse(sourceJson) as unknown; const actual = await seam.load(source); - const expected = - JSON.parse('{"tools":[{"name":"my_tool","kind":"my_provider"}]}'); + const expected = JSON.parse( + '{"tools":[{"name":"my_tool","kind":"my_provider"}]}', + ); seamConformanceAssert( seamConformanceCanonical(JSON.parse(JSON.stringify(actual))) === seamConformanceCanonical(expected), diff --git a/schema/scripts/normalize-typra-output.mjs b/schema/scripts/normalize-typra-output.mjs index 9523fee56..befd375c2 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 @@ -180,4 +193,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). +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 From 0d5c3eca95c0fb09979b47f823ae0d502272b18c Mon Sep 17 00:00:00 2001 From: Seth Juarez Date: Wed, 9 Sep 2026 11:33:26 -0700 Subject: [PATCH 08/10] schema: reference sethjuarez/typra#331 in Swift reasoningEffort workaround Link the deterministic .rawValue toWire workaround to the upstream Typra emitter bug it works around, so it can be removed once the Swift backend unwraps RawRepresentable/named-union fields in the wire path. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- schema/scripts/normalize-typra-output.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/schema/scripts/normalize-typra-output.mjs b/schema/scripts/normalize-typra-output.mjs index befd375c2..276832156 100644 --- a/schema/scripts/normalize-typra-output.mjs +++ b/schema/scripts/normalize-typra-output.mjs @@ -209,7 +209,7 @@ function fixSwiftProcessorProviderCollision(path) { // `.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). +// on sethjuarez/typra#331). function fixSwiftReasoningEffortWireValue(path) { if (!existsSync(path)) { return; From 23a40316ef7abb381c9f5ca6eb00adf1ee5e8a2f Mon Sep 17 00:00:00 2001 From: Seth Juarez Date: Wed, 9 Sep 2026 11:38:52 -0700 Subject: [PATCH 09/10] schema: link Swift/Go emitter workarounds to upstream typra issues Add tracking references for the two previously-unnumbered 'tracked on sethjuarez/typra' workarounds: - Swift processor provider() shadowing -> sethjuarez/typra#304 (open, still reproduces on 2.1.8) - Go value-backed coerce-union dead fmt import -> sethjuarez/typra#305 (appears fixed in 2.1.8; workaround now a no-op guarding against regression) Verified against raw 2.1.8 emitter output. No generated-output change. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- schema/scripts/normalize-typra-output.mjs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/schema/scripts/normalize-typra-output.mjs b/schema/scripts/normalize-typra-output.mjs index 276832156..b64b37309 100644 --- a/schema/scripts/normalize-typra-output.mjs +++ b/schema/scripts/normalize-typra-output.mjs @@ -136,7 +136,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; @@ -174,7 +176,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; From fbcbbf196369db224b702af5b0297627caac4529 Mon Sep 17 00:00:00 2001 From: Seth Juarez Date: Wed, 9 Sep 2026 11:39:49 -0700 Subject: [PATCH 10/10] schema: reference sethjuarez/typra#332 for Swift main-target resources gap Link the restoreSwiftPackageResources workaround to the upstream feature request for a main-target resources: emit option (the shipped test-resources option from #260 only covers the test target and breaks Bundle.module for production Discovery code). Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- schema/scripts/normalize-typra-output.mjs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/schema/scripts/normalize-typra-output.mjs b/schema/scripts/normalize-typra-output.mjs index b64b37309..1b6ee2e87 100644 --- a/schema/scripts/normalize-typra-output.mjs +++ b/schema/scripts/normalize-typra-output.mjs @@ -99,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;