diff --git a/.gitignore b/.gitignore index a697e57d..8c4f3b29 100644 --- a/.gitignore +++ b/.gitignore @@ -14,5 +14,11 @@ __pycache__/ # Schema emitter build artifacts schema/emitter/dist/ -schema/tsp-output/ +schema/tsp-output/* +!schema/tsp-output/.typra-generated/ +!schema/tsp-output/.typra-generated/export-surfaces.json +!schema/tsp-output/.typra-generated/hydration-seams.json +!schema/tsp-output/.typra-generated/manifest.json +!schema/tsp-output/json-ast/ +!schema/tsp-output/json-ast/model.json .playwright-mcp/ diff --git a/README.md b/README.md index 214a5466..3a7c2dfd 100644 --- a/README.md +++ b/README.md @@ -65,6 +65,46 @@ console.log(result); **VS Code** — open the `.prompty` file and press **F5**. +### Use an OpenAI-compatible endpoint + +Prompty's `openai` provider can also target OpenAI-compatible control planes, +gateways, or self-hosted model servers by setting `model.connection.endpoint`. +The prompt asset stays portable: switch the endpoint and key at runtime without +changing the prompt body. + +```prompty +--- +name: governed-greeting +model: + id: gpt-4o-mini + provider: openai + connection: + kind: key + endpoint: ${env:OPENAI_BASE_URL:https://api.openai.com/v1} + apiKey: ${env:OPENAI_API_KEY} +template: + format: + kind: jinja2 + parser: + kind: prompty +--- +system: +You are a careful assistant. + +user: +Say hello to {{name}}. +``` + +For example, to route through Tuning Engines: + +```bash +export OPENAI_BASE_URL=https://api.tuningengines.com/v1 +export OPENAI_API_KEY=sk-te-your-inference-key +``` + +This keeps the `.prompty` file unchanged while the endpoint provides routing, +policy, usage tracking, or trace correlation around OpenAI-compatible calls. + ## Contributor hygiene Prompty normalizes text files to LF line endings via `.gitattributes`. Enable the diff --git a/runtime/csharp/Prompty.Core.Tests/AgentExtensionsTests.cs b/runtime/csharp/Prompty.Core.Tests/AgentExtensionsTests.cs index 2a4704c9..9ffe3e37 100644 --- a/runtime/csharp/Prompty.Core.Tests/AgentExtensionsTests.cs +++ b/runtime/csharp/Prompty.Core.Tests/AgentExtensionsTests.cs @@ -26,7 +26,13 @@ public void EmitEvent_CallsCallback_WithCorrectArgs() AgentEvents.EmitEvent(cb, AgentEventType.ToolCallStart, data); Assert.Equal(AgentEventType.ToolCallStart, captured); - Assert.Same(data, capturedData); + Assert.NotNull(capturedData); + Assert.Equal("value", capturedData["key"]); + var turnEvent = Assert.IsType>(capturedData["turnEvent"]); + Assert.IsType(turnEvent["id"]); + Assert.Equal("tool_call_start", turnEvent["type"]); + Assert.IsType(turnEvent["timestamp"]); + Assert.Same(data, turnEvent["payload"]); } [Fact] diff --git a/runtime/csharp/Prompty.Core.Tests/HarnessAdaptersTests.cs b/runtime/csharp/Prompty.Core.Tests/HarnessAdaptersTests.cs new file mode 100644 index 00000000..1884d3f7 --- /dev/null +++ b/runtime/csharp/Prompty.Core.Tests/HarnessAdaptersTests.cs @@ -0,0 +1,182 @@ +// Copyright (c) Microsoft. All rights reserved. + +using System.Text.Json; +using Prompty.Core; + +namespace Prompty.Core.Tests; + +public class HarnessAdaptersTests +{ + [Fact] + public void CollectingEventSink_CapturesEvents() + { + var sink = new CollectingEventSink(); + + Assert.True(sink.EmitTurn(TurnEvent())); + Assert.True(sink.EmitSession(SessionEvent())); + + Assert.Equal("turn-event", sink.TurnEvents.Single().Id); + Assert.Equal("session-event", sink.SessionEvents.Single().Id); + } + + [Fact] + public void JsonlEventJournalWriter_WritesRecords() + { + var directory = Directory.CreateTempSubdirectory("prompty-trace-"); + try + { + var path = Path.Combine(directory.FullName, "trace.jsonl"); + var writer = new JsonlEventJournalWriter(path); + + writer.AppendTurn(TurnEvent()); + writer.AppendSession(SessionEvent()); + writer.Close(new SessionSummary { SessionId = "session-1", Turns = 1 }); + + var lines = File.ReadAllLines(path) + .Select(line => JsonSerializer.Deserialize>(line)!) + .ToList(); + + Assert.Equal(new[] { "turn", "session", "summary" }, lines.Select(line => line["kind"].GetString()).ToArray()); + Assert.Equal("turn-event", lines[0]["event"].GetProperty("id").GetString()); + Assert.Equal("session-event", lines[1]["event"].GetProperty("id").GetString()); + Assert.Equal("session-1", lines[2]["summary"].GetProperty("sessionId").GetString()); + Assert.DoesNotContain("\r\n", File.ReadAllText(path)); + } + finally + { + directory.Delete(recursive: true); + } + } + + [Fact] + public void JsonlEventJournalWriter_ReturnsFalseAfterClose() + { + var directory = Directory.CreateTempSubdirectory("prompty-trace-"); + try + { + var writer = new JsonlEventJournalWriter(Path.Combine(directory.FullName, "trace.jsonl")); + + Assert.True(writer.Close(null)); + Assert.False(writer.AppendTurn(TurnEvent())); + } + finally + { + directory.Delete(recursive: true); + } + } + + [Fact] + public async Task InMemoryCheckpointStore_StoresCheckpoints() + { + var store = new InMemoryCheckpointStore(); + var checkpoint = new Checkpoint { Id = "checkpoint-1", SessionId = "session-1", Title = "First" }; + + Assert.Same(checkpoint, await store.SaveAsync(checkpoint)); + Assert.Same(checkpoint, await store.LoadAsync("session-1", "checkpoint-1")); + Assert.Null(await store.LoadAsync("session-1", "missing")); + Assert.Equal([checkpoint], await store.ListCheckpointsAsync("session-1")); + } + + [Fact] + public async Task InMemoryCheckpointStore_RequiresKeys() + { + var store = new InMemoryCheckpointStore(); + + await Assert.ThrowsAsync(() => + store.SaveAsync(new Checkpoint { Id = "checkpoint-1", Title = "Missing session" })); + await Assert.ThrowsAsync(() => + store.SaveAsync(new Checkpoint { SessionId = "session-1", Title = "Missing id" })); + } + + [Fact] + public async Task PermissionResolvers_ReturnDecisions() + { + var request = new PermissionRequest + { + RequestId = "permission-1", + ToolCallId = "tool-call-1", + Permission = "tool.execute" + }; + + var allow = await new AllowAllPermissionResolver().RequestAsync(request); + var deny = await new DenyAllPermissionResolver().RequestAsync(request); + + Assert.True(allow.Approved); + Assert.Equal("allow_all", allow.Reason); + Assert.Equal("permission-1", allow.RequestId); + Assert.Equal("tool-call-1", allow.ToolCallId); + Assert.False(deny.Approved); + Assert.Equal("deny_all", deny.Reason); + } + + [Fact] + public async Task FunctionHostToolExecutor_ExecutesRegisteredHandlers() + { + var executor = new FunctionHostToolExecutor(new Dictionary + { + ["add"] = (args, _) => Task.FromResult(Convert.ToInt32(args!["a"]) + Convert.ToInt32(args["b"])) + }); + + var result = await executor.ExecuteAsync(new HostToolRequest + { + RequestId = "exec-1", + ToolName = "add", + Arguments = new Dictionary { ["a"] = 2, ["b"] = 3 } + }); + + Assert.True(result.Success); + Assert.Equal("exec-1", result.RequestId); + Assert.Equal("add", result.ToolName); + Assert.Equal(5, result.Result); + } + + [Fact] + public async Task FunctionHostToolExecutor_PassesEmptyArguments() + { + var executor = new FunctionHostToolExecutor(new Dictionary + { + ["count"] = (args, _) => Task.FromResult(args.Count) + }); + + var result = await executor.ExecuteAsync(new HostToolRequest { ToolName = "count" }); + + Assert.True(result.Success); + Assert.Equal(0, result.Result); + } + + [Fact] + public async Task FunctionHostToolExecutor_ReturnsFailureResults() + { + var executor = new FunctionHostToolExecutor(new Dictionary + { + ["fail"] = (_, _) => throw new InvalidOperationException("boom") + }); + + var missing = await executor.ExecuteAsync(new HostToolRequest { ToolName = "missing" }); + var thrown = await executor.ExecuteAsync(new HostToolRequest { ToolName = "fail" }); + + Assert.False(missing.Success); + Assert.Equal("not_found", missing.ErrorKind); + Assert.False(thrown.Success); + Assert.Equal("exception", thrown.ErrorKind); + var result = Assert.IsType>(thrown.Result); + Assert.Equal("boom", result["message"]); + } + + private static TurnEvent TurnEvent() => new() + { + Id = "turn-event", + Type = TurnEventType.TurnStart, + Timestamp = "2026-06-10T00:00:00Z", + Payload = new Dictionary { ["phase"] = "start" } + }; + + private static SessionEvent SessionEvent() => new() + { + Id = "session-event", + Type = SessionEventType.SessionStart, + Timestamp = "2026-06-10T00:00:00Z", + SessionId = "session-1", + Payload = new Dictionary { ["phase"] = "start" } + }; +} diff --git a/runtime/csharp/Prompty.Core.Tests/Model/agent/GuardrailResultConversionTests.cs b/runtime/csharp/Prompty.Core.Tests/Model/agent/GuardrailResultConversionTests.cs index 1f5aa6e3..8417ac6f 100644 --- a/runtime/csharp/Prompty.Core.Tests/Model/agent/GuardrailResultConversionTests.cs +++ b/runtime/csharp/Prompty.Core.Tests/Model/agent/GuardrailResultConversionTests.cs @@ -1,3 +1,4 @@ +// using Xunit; #pragma warning disable IDE0130 diff --git a/runtime/csharp/Prompty.Core.Tests/Model/agent/PromptyConversionTests.cs b/runtime/csharp/Prompty.Core.Tests/Model/agent/PromptyConversionTests.cs index 9b6b2548..677aae3a 100644 --- a/runtime/csharp/Prompty.Core.Tests/Model/agent/PromptyConversionTests.cs +++ b/runtime/csharp/Prompty.Core.Tests/Model/agent/PromptyConversionTests.cs @@ -1,3 +1,4 @@ +// using Xunit; #pragma warning disable IDE0130 diff --git a/runtime/csharp/Prompty.Core.Tests/Model/connection/AnonymousConnectionConversionTests.cs b/runtime/csharp/Prompty.Core.Tests/Model/connection/AnonymousConnectionConversionTests.cs index 874f9da2..f7213647 100644 --- a/runtime/csharp/Prompty.Core.Tests/Model/connection/AnonymousConnectionConversionTests.cs +++ b/runtime/csharp/Prompty.Core.Tests/Model/connection/AnonymousConnectionConversionTests.cs @@ -1,3 +1,4 @@ +// using Xunit; #pragma warning disable IDE0130 diff --git a/runtime/csharp/Prompty.Core.Tests/Model/connection/ApiKeyConnectionConversionTests.cs b/runtime/csharp/Prompty.Core.Tests/Model/connection/ApiKeyConnectionConversionTests.cs index 08653272..c6c0e930 100644 --- a/runtime/csharp/Prompty.Core.Tests/Model/connection/ApiKeyConnectionConversionTests.cs +++ b/runtime/csharp/Prompty.Core.Tests/Model/connection/ApiKeyConnectionConversionTests.cs @@ -1,3 +1,4 @@ +// using Xunit; #pragma warning disable IDE0130 diff --git a/runtime/csharp/Prompty.Core.Tests/Model/connection/ConnectionConversionTests.cs b/runtime/csharp/Prompty.Core.Tests/Model/connection/ConnectionConversionTests.cs index a7a2d46d..ec537940 100644 --- a/runtime/csharp/Prompty.Core.Tests/Model/connection/ConnectionConversionTests.cs +++ b/runtime/csharp/Prompty.Core.Tests/Model/connection/ConnectionConversionTests.cs @@ -1,3 +1,4 @@ +// using Xunit; #pragma warning disable IDE0130 diff --git a/runtime/csharp/Prompty.Core.Tests/Model/connection/FoundryConnectionConversionTests.cs b/runtime/csharp/Prompty.Core.Tests/Model/connection/FoundryConnectionConversionTests.cs index e8b36bf4..273154e6 100644 --- a/runtime/csharp/Prompty.Core.Tests/Model/connection/FoundryConnectionConversionTests.cs +++ b/runtime/csharp/Prompty.Core.Tests/Model/connection/FoundryConnectionConversionTests.cs @@ -1,3 +1,4 @@ +// using Xunit; #pragma warning disable IDE0130 diff --git a/runtime/csharp/Prompty.Core.Tests/Model/connection/OAuthConnectionConversionTests.cs b/runtime/csharp/Prompty.Core.Tests/Model/connection/OAuthConnectionConversionTests.cs index de6b3f4f..01f2f8c6 100644 --- a/runtime/csharp/Prompty.Core.Tests/Model/connection/OAuthConnectionConversionTests.cs +++ b/runtime/csharp/Prompty.Core.Tests/Model/connection/OAuthConnectionConversionTests.cs @@ -1,3 +1,4 @@ +// using Xunit; #pragma warning disable IDE0130 diff --git a/runtime/csharp/Prompty.Core.Tests/Model/connection/ReferenceConnectionConversionTests.cs b/runtime/csharp/Prompty.Core.Tests/Model/connection/ReferenceConnectionConversionTests.cs index 09c63f41..35ea856e 100644 --- a/runtime/csharp/Prompty.Core.Tests/Model/connection/ReferenceConnectionConversionTests.cs +++ b/runtime/csharp/Prompty.Core.Tests/Model/connection/ReferenceConnectionConversionTests.cs @@ -1,3 +1,4 @@ +// using Xunit; #pragma warning disable IDE0130 diff --git a/runtime/csharp/Prompty.Core.Tests/Model/connection/RemoteConnectionConversionTests.cs b/runtime/csharp/Prompty.Core.Tests/Model/connection/RemoteConnectionConversionTests.cs index 8468e835..b5ddf396 100644 --- a/runtime/csharp/Prompty.Core.Tests/Model/connection/RemoteConnectionConversionTests.cs +++ b/runtime/csharp/Prompty.Core.Tests/Model/connection/RemoteConnectionConversionTests.cs @@ -1,3 +1,4 @@ +// using Xunit; #pragma warning disable IDE0130 diff --git a/runtime/csharp/Prompty.Core.Tests/Model/conversation/AudioPartConversionTests.cs b/runtime/csharp/Prompty.Core.Tests/Model/conversation/AudioPartConversionTests.cs index f30c32f9..fbdacb65 100644 --- a/runtime/csharp/Prompty.Core.Tests/Model/conversation/AudioPartConversionTests.cs +++ b/runtime/csharp/Prompty.Core.Tests/Model/conversation/AudioPartConversionTests.cs @@ -1,3 +1,4 @@ +// using Xunit; #pragma warning disable IDE0130 diff --git a/runtime/csharp/Prompty.Core.Tests/Model/conversation/ContentPartConversionTests.cs b/runtime/csharp/Prompty.Core.Tests/Model/conversation/ContentPartConversionTests.cs index 148e1f1d..b06bb557 100644 --- a/runtime/csharp/Prompty.Core.Tests/Model/conversation/ContentPartConversionTests.cs +++ b/runtime/csharp/Prompty.Core.Tests/Model/conversation/ContentPartConversionTests.cs @@ -1,3 +1,4 @@ +// using Xunit; #pragma warning disable IDE0130 diff --git a/runtime/csharp/Prompty.Core.Tests/Model/conversation/FilePartConversionTests.cs b/runtime/csharp/Prompty.Core.Tests/Model/conversation/FilePartConversionTests.cs index 9d5e98dd..7db42fa5 100644 --- a/runtime/csharp/Prompty.Core.Tests/Model/conversation/FilePartConversionTests.cs +++ b/runtime/csharp/Prompty.Core.Tests/Model/conversation/FilePartConversionTests.cs @@ -1,3 +1,4 @@ +// using Xunit; #pragma warning disable IDE0130 diff --git a/runtime/csharp/Prompty.Core.Tests/Model/conversation/ImagePartConversionTests.cs b/runtime/csharp/Prompty.Core.Tests/Model/conversation/ImagePartConversionTests.cs index cbca1f7a..1fb3d388 100644 --- a/runtime/csharp/Prompty.Core.Tests/Model/conversation/ImagePartConversionTests.cs +++ b/runtime/csharp/Prompty.Core.Tests/Model/conversation/ImagePartConversionTests.cs @@ -1,3 +1,4 @@ +// using Xunit; #pragma warning disable IDE0130 diff --git a/runtime/csharp/Prompty.Core.Tests/Model/conversation/MessageConversionTests.cs b/runtime/csharp/Prompty.Core.Tests/Model/conversation/MessageConversionTests.cs index aeecbcbd..7fcb2de5 100644 --- a/runtime/csharp/Prompty.Core.Tests/Model/conversation/MessageConversionTests.cs +++ b/runtime/csharp/Prompty.Core.Tests/Model/conversation/MessageConversionTests.cs @@ -1,3 +1,4 @@ +// using Xunit; #pragma warning disable IDE0130 diff --git a/runtime/csharp/Prompty.Core.Tests/Model/conversation/TextPartConversionTests.cs b/runtime/csharp/Prompty.Core.Tests/Model/conversation/TextPartConversionTests.cs index 43932a86..8fc11359 100644 --- a/runtime/csharp/Prompty.Core.Tests/Model/conversation/TextPartConversionTests.cs +++ b/runtime/csharp/Prompty.Core.Tests/Model/conversation/TextPartConversionTests.cs @@ -1,3 +1,4 @@ +// using Xunit; #pragma warning disable IDE0130 diff --git a/runtime/csharp/Prompty.Core.Tests/Model/conversation/ThreadMarkerConversionTests.cs b/runtime/csharp/Prompty.Core.Tests/Model/conversation/ThreadMarkerConversionTests.cs index 04d59ff4..c5edf1d8 100644 --- a/runtime/csharp/Prompty.Core.Tests/Model/conversation/ThreadMarkerConversionTests.cs +++ b/runtime/csharp/Prompty.Core.Tests/Model/conversation/ThreadMarkerConversionTests.cs @@ -1,3 +1,4 @@ +// using Xunit; #pragma warning disable IDE0130 diff --git a/runtime/csharp/Prompty.Core.Tests/Model/conversation/ToolCallConversionTests.cs b/runtime/csharp/Prompty.Core.Tests/Model/conversation/ToolCallConversionTests.cs index d21b2191..9b7fc0f7 100644 --- a/runtime/csharp/Prompty.Core.Tests/Model/conversation/ToolCallConversionTests.cs +++ b/runtime/csharp/Prompty.Core.Tests/Model/conversation/ToolCallConversionTests.cs @@ -1,3 +1,4 @@ +// using Xunit; #pragma warning disable IDE0130 diff --git a/runtime/csharp/Prompty.Core.Tests/Model/conversation/ToolResultConversionTests.cs b/runtime/csharp/Prompty.Core.Tests/Model/conversation/ToolResultConversionTests.cs index a8566d0c..aaee6c25 100644 --- a/runtime/csharp/Prompty.Core.Tests/Model/conversation/ToolResultConversionTests.cs +++ b/runtime/csharp/Prompty.Core.Tests/Model/conversation/ToolResultConversionTests.cs @@ -1,3 +1,4 @@ +// using Xunit; #pragma warning disable IDE0130 @@ -14,12 +15,18 @@ public void LoadYamlInput() parts: - kind: text value: 72°F and sunny +errorKind: missing_tool +errorMessage: Tool 'get_weather' is not registered +durationMs: 42 """; var instance = ToolResult.FromYaml(yamlData); Assert.NotNull(instance); + Assert.Equal("missing_tool", instance.ErrorKind); + Assert.Equal("Tool 'get_weather' is not registered", instance.ErrorMessage); + Assert.Equal(42, instance.DurationMs); } [Fact] @@ -32,12 +39,18 @@ public void LoadJsonInput() "kind": "text", "value": "72°F and sunny" } - ] + ], + "errorKind": "missing_tool", + "errorMessage": "Tool 'get_weather' is not registered", + "durationMs": 42 } """; var instance = ToolResult.FromJson(jsonData); Assert.NotNull(instance); + Assert.Equal("missing_tool", instance.ErrorKind); + Assert.Equal("Tool 'get_weather' is not registered", instance.ErrorMessage); + Assert.Equal(42, instance.DurationMs); } [Fact] @@ -51,7 +64,10 @@ public void RoundtripJson() "kind": "text", "value": "72°F and sunny" } - ] + ], + "errorKind": "missing_tool", + "errorMessage": "Tool 'get_weather' is not registered", + "durationMs": 42 } """; @@ -63,6 +79,9 @@ public void RoundtripJson() var reloaded = ToolResult.FromJson(json); Assert.NotNull(reloaded); + Assert.Equal("missing_tool", reloaded.ErrorKind); + Assert.Equal("Tool 'get_weather' is not registered", reloaded.ErrorMessage); + Assert.Equal(42, reloaded.DurationMs); } [Fact] @@ -73,6 +92,9 @@ public void RoundtripYaml() parts: - kind: text value: 72°F and sunny +errorKind: missing_tool +errorMessage: Tool 'get_weather' is not registered +durationMs: 42 """; @@ -84,6 +106,9 @@ public void RoundtripYaml() var reloaded = ToolResult.FromYaml(yaml); Assert.NotNull(reloaded); + Assert.Equal("missing_tool", reloaded.ErrorKind); + Assert.Equal("Tool 'get_weather' is not registered", reloaded.ErrorMessage); + Assert.Equal(42, reloaded.DurationMs); } [Fact] @@ -96,7 +121,10 @@ public void ToJsonProducesValidJson() "kind": "text", "value": "72°F and sunny" } - ] + ], + "errorKind": "missing_tool", + "errorMessage": "Tool 'get_weather' is not registered", + "durationMs": 42 } """; @@ -115,6 +143,9 @@ public void ToYamlProducesValidYaml() parts: - kind: text value: 72°F and sunny +errorKind: missing_tool +errorMessage: Tool 'get_weather' is not registered +durationMs: 42 """; diff --git a/runtime/csharp/Prompty.Core.Tests/Model/core/ArrayPropertyConversionTests.cs b/runtime/csharp/Prompty.Core.Tests/Model/core/ArrayPropertyConversionTests.cs index f5c0bd95..0a685540 100644 --- a/runtime/csharp/Prompty.Core.Tests/Model/core/ArrayPropertyConversionTests.cs +++ b/runtime/csharp/Prompty.Core.Tests/Model/core/ArrayPropertyConversionTests.cs @@ -1,3 +1,4 @@ +// using Xunit; #pragma warning disable IDE0130 diff --git a/runtime/csharp/Prompty.Core.Tests/Model/core/FileNotFoundErrorConversionTests.cs b/runtime/csharp/Prompty.Core.Tests/Model/core/FileNotFoundErrorConversionTests.cs index 6e7b73a9..e0b6294d 100644 --- a/runtime/csharp/Prompty.Core.Tests/Model/core/FileNotFoundErrorConversionTests.cs +++ b/runtime/csharp/Prompty.Core.Tests/Model/core/FileNotFoundErrorConversionTests.cs @@ -1,3 +1,4 @@ +// using Xunit; #pragma warning disable IDE0130 diff --git a/runtime/csharp/Prompty.Core.Tests/Model/core/InvokerErrorConversionTests.cs b/runtime/csharp/Prompty.Core.Tests/Model/core/InvokerErrorConversionTests.cs index f57d39f7..3e1b3a87 100644 --- a/runtime/csharp/Prompty.Core.Tests/Model/core/InvokerErrorConversionTests.cs +++ b/runtime/csharp/Prompty.Core.Tests/Model/core/InvokerErrorConversionTests.cs @@ -1,3 +1,4 @@ +// using Xunit; #pragma warning disable IDE0130 diff --git a/runtime/csharp/Prompty.Core.Tests/Model/core/ObjectPropertyConversionTests.cs b/runtime/csharp/Prompty.Core.Tests/Model/core/ObjectPropertyConversionTests.cs index c09c7338..1a7098e2 100644 --- a/runtime/csharp/Prompty.Core.Tests/Model/core/ObjectPropertyConversionTests.cs +++ b/runtime/csharp/Prompty.Core.Tests/Model/core/ObjectPropertyConversionTests.cs @@ -1,3 +1,4 @@ +// using Xunit; #pragma warning disable IDE0130 diff --git a/runtime/csharp/Prompty.Core.Tests/Model/core/PropertyConversionTests.cs b/runtime/csharp/Prompty.Core.Tests/Model/core/PropertyConversionTests.cs index 0119ddf9..6ff19c31 100644 --- a/runtime/csharp/Prompty.Core.Tests/Model/core/PropertyConversionTests.cs +++ b/runtime/csharp/Prompty.Core.Tests/Model/core/PropertyConversionTests.cs @@ -1,3 +1,4 @@ +// using Xunit; #pragma warning disable IDE0130 diff --git a/runtime/csharp/Prompty.Core.Tests/Model/core/ValidationErrorConversionTests.cs b/runtime/csharp/Prompty.Core.Tests/Model/core/ValidationErrorConversionTests.cs index 99d9e33b..9cb2602c 100644 --- a/runtime/csharp/Prompty.Core.Tests/Model/core/ValidationErrorConversionTests.cs +++ b/runtime/csharp/Prompty.Core.Tests/Model/core/ValidationErrorConversionTests.cs @@ -1,3 +1,4 @@ +// using Xunit; #pragma warning disable IDE0130 diff --git a/runtime/csharp/Prompty.Core.Tests/Model/core/ValidationResultConversionTests.cs b/runtime/csharp/Prompty.Core.Tests/Model/core/ValidationResultConversionTests.cs index f8157812..11d70897 100644 --- a/runtime/csharp/Prompty.Core.Tests/Model/core/ValidationResultConversionTests.cs +++ b/runtime/csharp/Prompty.Core.Tests/Model/core/ValidationResultConversionTests.cs @@ -1,3 +1,4 @@ +// using Xunit; #pragma warning disable IDE0130 diff --git a/runtime/csharp/Prompty.Core.Tests/Model/events/CheckpointConversionTests.cs b/runtime/csharp/Prompty.Core.Tests/Model/events/CheckpointConversionTests.cs new file mode 100644 index 00000000..93a9d499 --- /dev/null +++ b/runtime/csharp/Prompty.Core.Tests/Model/events/CheckpointConversionTests.cs @@ -0,0 +1,163 @@ +// +using Xunit; + +#pragma warning disable IDE0130 +namespace Prompty.Core; +#pragma warning restore IDE0130 + + +public class CheckpointConversionTests +{ + [Fact] + public void LoadYamlInput() + { + string yamlData = """ +id: chk_abc123 +sessionId: sess_abc123 +turnId: turn_001 +checkpointNumber: 3 +title: Added harness contracts +createdAt: "2026-06-09T20:00:00Z" + +"""; + + var instance = Checkpoint.FromYaml(yamlData); + + Assert.NotNull(instance); + Assert.Equal("chk_abc123", instance.Id); + Assert.Equal("sess_abc123", instance.SessionId); + Assert.Equal("turn_001", instance.TurnId); + Assert.Equal(3, instance.CheckpointNumber); + Assert.Equal("Added harness contracts", instance.Title); + Assert.Equal("2026-06-09T20:00:00Z", instance.CreatedAt); + } + + [Fact] + public void LoadJsonInput() + { + string jsonData = """ +{ + "id": "chk_abc123", + "sessionId": "sess_abc123", + "turnId": "turn_001", + "checkpointNumber": 3, + "title": "Added harness contracts", + "createdAt": "2026-06-09T20:00:00Z" +} +"""; + + var instance = Checkpoint.FromJson(jsonData); + Assert.NotNull(instance); + Assert.Equal("chk_abc123", instance.Id); + Assert.Equal("sess_abc123", instance.SessionId); + Assert.Equal("turn_001", instance.TurnId); + Assert.Equal(3, instance.CheckpointNumber); + Assert.Equal("Added harness contracts", instance.Title); + Assert.Equal("2026-06-09T20:00:00Z", instance.CreatedAt); + } + + [Fact] + public void RoundtripJson() + { + // Test that FromJson -> ToJson -> FromJson produces equivalent data + string jsonData = """ +{ + "id": "chk_abc123", + "sessionId": "sess_abc123", + "turnId": "turn_001", + "checkpointNumber": 3, + "title": "Added harness contracts", + "createdAt": "2026-06-09T20:00:00Z" +} +"""; + + var original = Checkpoint.FromJson(jsonData); + Assert.NotNull(original); + + var json = original.ToJson(); + Assert.False(string.IsNullOrEmpty(json)); + + var reloaded = Checkpoint.FromJson(json); + Assert.NotNull(reloaded); + Assert.Equal("chk_abc123", reloaded.Id); + Assert.Equal("sess_abc123", reloaded.SessionId); + Assert.Equal("turn_001", reloaded.TurnId); + Assert.Equal(3, reloaded.CheckpointNumber); + Assert.Equal("Added harness contracts", reloaded.Title); + Assert.Equal("2026-06-09T20:00:00Z", reloaded.CreatedAt); + } + + [Fact] + public void RoundtripYaml() + { + // Test that FromYaml -> ToYaml -> FromYaml produces equivalent data + string yamlData = """ +id: chk_abc123 +sessionId: sess_abc123 +turnId: turn_001 +checkpointNumber: 3 +title: Added harness contracts +createdAt: "2026-06-09T20:00:00Z" + +"""; + + var original = Checkpoint.FromYaml(yamlData); + Assert.NotNull(original); + + var yaml = original.ToYaml(); + Assert.False(string.IsNullOrEmpty(yaml)); + + var reloaded = Checkpoint.FromYaml(yaml); + Assert.NotNull(reloaded); + Assert.Equal("chk_abc123", reloaded.Id); + Assert.Equal("sess_abc123", reloaded.SessionId); + Assert.Equal("turn_001", reloaded.TurnId); + Assert.Equal(3, reloaded.CheckpointNumber); + Assert.Equal("Added harness contracts", reloaded.Title); + Assert.Equal("2026-06-09T20:00:00Z", reloaded.CreatedAt); + } + + [Fact] + public void ToJsonProducesValidJson() + { + string jsonData = """ +{ + "id": "chk_abc123", + "sessionId": "sess_abc123", + "turnId": "turn_001", + "checkpointNumber": 3, + "title": "Added harness contracts", + "createdAt": "2026-06-09T20:00:00Z" +} +"""; + + var instance = Checkpoint.FromJson(jsonData); + var json = instance.ToJson(); + + // Verify it's valid JSON by parsing it + var parsed = System.Text.Json.JsonDocument.Parse(json); + Assert.NotNull(parsed); + } + + [Fact] + public void ToYamlProducesValidYaml() + { + string yamlData = """ +id: chk_abc123 +sessionId: sess_abc123 +turnId: turn_001 +checkpointNumber: 3 +title: Added harness contracts +createdAt: "2026-06-09T20:00:00Z" + +"""; + + var instance = Checkpoint.FromYaml(yamlData); + var yaml = instance.ToYaml(); + + // Verify it's valid YAML by parsing it + var deserializer = new YamlDotNet.Serialization.DeserializerBuilder().Build(); + var parsed = deserializer.Deserialize(yaml); + Assert.NotNull(parsed); + } +} diff --git a/runtime/csharp/Prompty.Core.Tests/Model/events/CompactionCompletePayloadConversionTests.cs b/runtime/csharp/Prompty.Core.Tests/Model/events/CompactionCompletePayloadConversionTests.cs index 40f3a6c2..63c74abc 100644 --- a/runtime/csharp/Prompty.Core.Tests/Model/events/CompactionCompletePayloadConversionTests.cs +++ b/runtime/csharp/Prompty.Core.Tests/Model/events/CompactionCompletePayloadConversionTests.cs @@ -1,3 +1,4 @@ +// using Xunit; #pragma warning disable IDE0130 @@ -13,6 +14,7 @@ public void LoadYamlInput() string yamlData = """ removed: 5 remaining: 3 +summaryLength: 1200 """; @@ -21,6 +23,7 @@ public void LoadYamlInput() Assert.NotNull(instance); Assert.Equal(5, instance.Removed); Assert.Equal(3, instance.Remaining); + Assert.Equal(1200, instance.SummaryLength); } [Fact] @@ -29,7 +32,8 @@ public void LoadJsonInput() string jsonData = """ { "removed": 5, - "remaining": 3 + "remaining": 3, + "summaryLength": 1200 } """; @@ -37,6 +41,7 @@ public void LoadJsonInput() Assert.NotNull(instance); Assert.Equal(5, instance.Removed); Assert.Equal(3, instance.Remaining); + Assert.Equal(1200, instance.SummaryLength); } [Fact] @@ -46,7 +51,8 @@ public void RoundtripJson() string jsonData = """ { "removed": 5, - "remaining": 3 + "remaining": 3, + "summaryLength": 1200 } """; @@ -60,6 +66,7 @@ public void RoundtripJson() Assert.NotNull(reloaded); Assert.Equal(5, reloaded.Removed); Assert.Equal(3, reloaded.Remaining); + Assert.Equal(1200, reloaded.SummaryLength); } [Fact] @@ -69,6 +76,7 @@ public void RoundtripYaml() string yamlData = """ removed: 5 remaining: 3 +summaryLength: 1200 """; @@ -82,6 +90,7 @@ public void RoundtripYaml() Assert.NotNull(reloaded); Assert.Equal(5, reloaded.Removed); Assert.Equal(3, reloaded.Remaining); + Assert.Equal(1200, reloaded.SummaryLength); } [Fact] @@ -90,7 +99,8 @@ public void ToJsonProducesValidJson() string jsonData = """ { "removed": 5, - "remaining": 3 + "remaining": 3, + "summaryLength": 1200 } """; @@ -108,6 +118,7 @@ public void ToYamlProducesValidYaml() string yamlData = """ removed: 5 remaining: 3 +summaryLength: 1200 """; diff --git a/runtime/csharp/Prompty.Core.Tests/Model/events/CompactionFailedPayloadConversionTests.cs b/runtime/csharp/Prompty.Core.Tests/Model/events/CompactionFailedPayloadConversionTests.cs index 23093a66..d0dbd17c 100644 --- a/runtime/csharp/Prompty.Core.Tests/Model/events/CompactionFailedPayloadConversionTests.cs +++ b/runtime/csharp/Prompty.Core.Tests/Model/events/CompactionFailedPayloadConversionTests.cs @@ -1,3 +1,4 @@ +// using Xunit; #pragma warning disable IDE0130 diff --git a/runtime/csharp/Prompty.Core.Tests/Model/events/CompactionStartPayloadConversionTests.cs b/runtime/csharp/Prompty.Core.Tests/Model/events/CompactionStartPayloadConversionTests.cs new file mode 100644 index 00000000..f7694b70 --- /dev/null +++ b/runtime/csharp/Prompty.Core.Tests/Model/events/CompactionStartPayloadConversionTests.cs @@ -0,0 +1,113 @@ +// +using Xunit; + +#pragma warning disable IDE0130 +namespace Prompty.Core; +#pragma warning restore IDE0130 + + +public class CompactionStartPayloadConversionTests +{ + [Fact] + public void LoadYamlInput() + { + string yamlData = """ +droppedCount: 5 + +"""; + + var instance = CompactionStartPayload.FromYaml(yamlData); + + Assert.NotNull(instance); + Assert.Equal(5, instance.DroppedCount); + } + + [Fact] + public void LoadJsonInput() + { + string jsonData = """ +{ + "droppedCount": 5 +} +"""; + + var instance = CompactionStartPayload.FromJson(jsonData); + Assert.NotNull(instance); + Assert.Equal(5, instance.DroppedCount); + } + + [Fact] + public void RoundtripJson() + { + // Test that FromJson -> ToJson -> FromJson produces equivalent data + string jsonData = """ +{ + "droppedCount": 5 +} +"""; + + var original = CompactionStartPayload.FromJson(jsonData); + Assert.NotNull(original); + + var json = original.ToJson(); + Assert.False(string.IsNullOrEmpty(json)); + + var reloaded = CompactionStartPayload.FromJson(json); + Assert.NotNull(reloaded); + Assert.Equal(5, reloaded.DroppedCount); + } + + [Fact] + public void RoundtripYaml() + { + // Test that FromYaml -> ToYaml -> FromYaml produces equivalent data + string yamlData = """ +droppedCount: 5 + +"""; + + var original = CompactionStartPayload.FromYaml(yamlData); + Assert.NotNull(original); + + var yaml = original.ToYaml(); + Assert.False(string.IsNullOrEmpty(yaml)); + + var reloaded = CompactionStartPayload.FromYaml(yaml); + Assert.NotNull(reloaded); + Assert.Equal(5, reloaded.DroppedCount); + } + + [Fact] + public void ToJsonProducesValidJson() + { + string jsonData = """ +{ + "droppedCount": 5 +} +"""; + + var instance = CompactionStartPayload.FromJson(jsonData); + var json = instance.ToJson(); + + // Verify it's valid JSON by parsing it + var parsed = System.Text.Json.JsonDocument.Parse(json); + Assert.NotNull(parsed); + } + + [Fact] + public void ToYamlProducesValidYaml() + { + string yamlData = """ +droppedCount: 5 + +"""; + + var instance = CompactionStartPayload.FromYaml(yamlData); + var yaml = instance.ToYaml(); + + // Verify it's valid YAML by parsing it + var deserializer = new YamlDotNet.Serialization.DeserializerBuilder().Build(); + var parsed = deserializer.Deserialize(yaml); + Assert.NotNull(parsed); + } +} diff --git a/runtime/csharp/Prompty.Core.Tests/Model/events/DoneEventPayloadConversionTests.cs b/runtime/csharp/Prompty.Core.Tests/Model/events/DoneEventPayloadConversionTests.cs index 1211f745..e35c00c1 100644 --- a/runtime/csharp/Prompty.Core.Tests/Model/events/DoneEventPayloadConversionTests.cs +++ b/runtime/csharp/Prompty.Core.Tests/Model/events/DoneEventPayloadConversionTests.cs @@ -1,3 +1,4 @@ +// using Xunit; #pragma warning disable IDE0130 @@ -7,106 +8,4 @@ namespace Prompty.Core; public class DoneEventPayloadConversionTests { - [Fact] - public void LoadYamlInput() - { - string yamlData = """ -response: The weather in Paris is 72°F and sunny. - -"""; - - var instance = DoneEventPayload.FromYaml(yamlData); - - Assert.NotNull(instance); - Assert.Equal("The weather in Paris is 72°F and sunny.", instance.Response); - } - - [Fact] - public void LoadJsonInput() - { - string jsonData = """ -{ - "response": "The weather in Paris is 72°F and sunny." -} -"""; - - var instance = DoneEventPayload.FromJson(jsonData); - Assert.NotNull(instance); - Assert.Equal("The weather in Paris is 72°F and sunny.", instance.Response); - } - - [Fact] - public void RoundtripJson() - { - // Test that FromJson -> ToJson -> FromJson produces equivalent data - string jsonData = """ -{ - "response": "The weather in Paris is 72°F and sunny." -} -"""; - - var original = DoneEventPayload.FromJson(jsonData); - Assert.NotNull(original); - - var json = original.ToJson(); - Assert.False(string.IsNullOrEmpty(json)); - - var reloaded = DoneEventPayload.FromJson(json); - Assert.NotNull(reloaded); - Assert.Equal("The weather in Paris is 72°F and sunny.", reloaded.Response); - } - - [Fact] - public void RoundtripYaml() - { - // Test that FromYaml -> ToYaml -> FromYaml produces equivalent data - string yamlData = """ -response: The weather in Paris is 72°F and sunny. - -"""; - - var original = DoneEventPayload.FromYaml(yamlData); - Assert.NotNull(original); - - var yaml = original.ToYaml(); - Assert.False(string.IsNullOrEmpty(yaml)); - - var reloaded = DoneEventPayload.FromYaml(yaml); - Assert.NotNull(reloaded); - Assert.Equal("The weather in Paris is 72°F and sunny.", reloaded.Response); - } - - [Fact] - public void ToJsonProducesValidJson() - { - string jsonData = """ -{ - "response": "The weather in Paris is 72°F and sunny." -} -"""; - - var instance = DoneEventPayload.FromJson(jsonData); - var json = instance.ToJson(); - - // Verify it's valid JSON by parsing it - var parsed = System.Text.Json.JsonDocument.Parse(json); - Assert.NotNull(parsed); - } - - [Fact] - public void ToYamlProducesValidYaml() - { - string yamlData = """ -response: The weather in Paris is 72°F and sunny. - -"""; - - var instance = DoneEventPayload.FromYaml(yamlData); - var yaml = instance.ToYaml(); - - // Verify it's valid YAML by parsing it - var deserializer = new YamlDotNet.Serialization.DeserializerBuilder().Build(); - var parsed = deserializer.Deserialize(yaml); - Assert.NotNull(parsed); - } } diff --git a/runtime/csharp/Prompty.Core.Tests/Model/events/ErrorChunkConversionTests.cs b/runtime/csharp/Prompty.Core.Tests/Model/events/ErrorChunkConversionTests.cs index 16153964..31b0f8a9 100644 --- a/runtime/csharp/Prompty.Core.Tests/Model/events/ErrorChunkConversionTests.cs +++ b/runtime/csharp/Prompty.Core.Tests/Model/events/ErrorChunkConversionTests.cs @@ -1,3 +1,4 @@ +// using Xunit; #pragma warning disable IDE0130 diff --git a/runtime/csharp/Prompty.Core.Tests/Model/events/ErrorEventPayloadConversionTests.cs b/runtime/csharp/Prompty.Core.Tests/Model/events/ErrorEventPayloadConversionTests.cs index d3cc78e2..86ed0e37 100644 --- a/runtime/csharp/Prompty.Core.Tests/Model/events/ErrorEventPayloadConversionTests.cs +++ b/runtime/csharp/Prompty.Core.Tests/Model/events/ErrorEventPayloadConversionTests.cs @@ -1,3 +1,4 @@ +// using Xunit; #pragma warning disable IDE0130 @@ -12,6 +13,8 @@ public void LoadYamlInput() { string yamlData = """ message: Rate limit exceeded +errorKind: rate_limit +phase: llm """; @@ -19,6 +22,8 @@ public void LoadYamlInput() Assert.NotNull(instance); Assert.Equal("Rate limit exceeded", instance.Message); + Assert.Equal("rate_limit", instance.ErrorKind); + Assert.Equal("llm", instance.Phase); } [Fact] @@ -26,13 +31,17 @@ public void LoadJsonInput() { string jsonData = """ { - "message": "Rate limit exceeded" + "message": "Rate limit exceeded", + "errorKind": "rate_limit", + "phase": "llm" } """; var instance = ErrorEventPayload.FromJson(jsonData); Assert.NotNull(instance); Assert.Equal("Rate limit exceeded", instance.Message); + Assert.Equal("rate_limit", instance.ErrorKind); + Assert.Equal("llm", instance.Phase); } [Fact] @@ -41,7 +50,9 @@ public void RoundtripJson() // Test that FromJson -> ToJson -> FromJson produces equivalent data string jsonData = """ { - "message": "Rate limit exceeded" + "message": "Rate limit exceeded", + "errorKind": "rate_limit", + "phase": "llm" } """; @@ -54,6 +65,8 @@ public void RoundtripJson() var reloaded = ErrorEventPayload.FromJson(json); Assert.NotNull(reloaded); Assert.Equal("Rate limit exceeded", reloaded.Message); + Assert.Equal("rate_limit", reloaded.ErrorKind); + Assert.Equal("llm", reloaded.Phase); } [Fact] @@ -62,6 +75,8 @@ public void RoundtripYaml() // Test that FromYaml -> ToYaml -> FromYaml produces equivalent data string yamlData = """ message: Rate limit exceeded +errorKind: rate_limit +phase: llm """; @@ -74,6 +89,8 @@ public void RoundtripYaml() var reloaded = ErrorEventPayload.FromYaml(yaml); Assert.NotNull(reloaded); Assert.Equal("Rate limit exceeded", reloaded.Message); + Assert.Equal("rate_limit", reloaded.ErrorKind); + Assert.Equal("llm", reloaded.Phase); } [Fact] @@ -81,7 +98,9 @@ public void ToJsonProducesValidJson() { string jsonData = """ { - "message": "Rate limit exceeded" + "message": "Rate limit exceeded", + "errorKind": "rate_limit", + "phase": "llm" } """; @@ -98,6 +117,8 @@ public void ToYamlProducesValidYaml() { string yamlData = """ message: Rate limit exceeded +errorKind: rate_limit +phase: llm """; diff --git a/runtime/csharp/Prompty.Core.Tests/Model/events/HarnessContextConversionTests.cs b/runtime/csharp/Prompty.Core.Tests/Model/events/HarnessContextConversionTests.cs new file mode 100644 index 00000000..d5a60b40 --- /dev/null +++ b/runtime/csharp/Prompty.Core.Tests/Model/events/HarnessContextConversionTests.cs @@ -0,0 +1,123 @@ +// +using Xunit; + +#pragma warning disable IDE0130 +namespace Prompty.Core; +#pragma warning restore IDE0130 + + +public class HarnessContextConversionTests +{ + [Fact] + public void LoadYamlInput() + { + string yamlData = """ +cwd: /workspace/project +gitRoot: /workspace/project + +"""; + + var instance = HarnessContext.FromYaml(yamlData); + + Assert.NotNull(instance); + Assert.Equal("/workspace/project", instance.Cwd); + Assert.Equal("/workspace/project", instance.GitRoot); + } + + [Fact] + public void LoadJsonInput() + { + string jsonData = """ +{ + "cwd": "/workspace/project", + "gitRoot": "/workspace/project" +} +"""; + + var instance = HarnessContext.FromJson(jsonData); + Assert.NotNull(instance); + Assert.Equal("/workspace/project", instance.Cwd); + Assert.Equal("/workspace/project", instance.GitRoot); + } + + [Fact] + public void RoundtripJson() + { + // Test that FromJson -> ToJson -> FromJson produces equivalent data + string jsonData = """ +{ + "cwd": "/workspace/project", + "gitRoot": "/workspace/project" +} +"""; + + var original = HarnessContext.FromJson(jsonData); + Assert.NotNull(original); + + var json = original.ToJson(); + Assert.False(string.IsNullOrEmpty(json)); + + var reloaded = HarnessContext.FromJson(json); + Assert.NotNull(reloaded); + Assert.Equal("/workspace/project", reloaded.Cwd); + Assert.Equal("/workspace/project", reloaded.GitRoot); + } + + [Fact] + public void RoundtripYaml() + { + // Test that FromYaml -> ToYaml -> FromYaml produces equivalent data + string yamlData = """ +cwd: /workspace/project +gitRoot: /workspace/project + +"""; + + var original = HarnessContext.FromYaml(yamlData); + Assert.NotNull(original); + + var yaml = original.ToYaml(); + Assert.False(string.IsNullOrEmpty(yaml)); + + var reloaded = HarnessContext.FromYaml(yaml); + Assert.NotNull(reloaded); + Assert.Equal("/workspace/project", reloaded.Cwd); + Assert.Equal("/workspace/project", reloaded.GitRoot); + } + + [Fact] + public void ToJsonProducesValidJson() + { + string jsonData = """ +{ + "cwd": "/workspace/project", + "gitRoot": "/workspace/project" +} +"""; + + var instance = HarnessContext.FromJson(jsonData); + var json = instance.ToJson(); + + // Verify it's valid JSON by parsing it + var parsed = System.Text.Json.JsonDocument.Parse(json); + Assert.NotNull(parsed); + } + + [Fact] + public void ToYamlProducesValidYaml() + { + string yamlData = """ +cwd: /workspace/project +gitRoot: /workspace/project + +"""; + + var instance = HarnessContext.FromYaml(yamlData); + var yaml = instance.ToYaml(); + + // Verify it's valid YAML by parsing it + var deserializer = new YamlDotNet.Serialization.DeserializerBuilder().Build(); + var parsed = deserializer.Deserialize(yaml); + Assert.NotNull(parsed); + } +} diff --git a/runtime/csharp/Prompty.Core.Tests/Model/events/HookEndPayloadConversionTests.cs b/runtime/csharp/Prompty.Core.Tests/Model/events/HookEndPayloadConversionTests.cs new file mode 100644 index 00000000..aa269b82 --- /dev/null +++ b/runtime/csharp/Prompty.Core.Tests/Model/events/HookEndPayloadConversionTests.cs @@ -0,0 +1,153 @@ +// +using Xunit; + +#pragma warning disable IDE0130 +namespace Prompty.Core; +#pragma warning restore IDE0130 + + +public class HookEndPayloadConversionTests +{ + [Fact] + public void LoadYamlInput() + { + string yamlData = """ +hookInvocationId: hook_abc123 +hookType: preToolUse +success: true +durationMs: 12 +error: hook failed + +"""; + + var instance = HookEndPayload.FromYaml(yamlData); + + Assert.NotNull(instance); + Assert.Equal("hook_abc123", instance.HookInvocationId); + Assert.Equal("preToolUse", instance.HookType); + Assert.True(instance.Success); + Assert.Equal(12, instance.DurationMs); + Assert.Equal("hook failed", instance.Error); + } + + [Fact] + public void LoadJsonInput() + { + string jsonData = """ +{ + "hookInvocationId": "hook_abc123", + "hookType": "preToolUse", + "success": true, + "durationMs": 12, + "error": "hook failed" +} +"""; + + var instance = HookEndPayload.FromJson(jsonData); + Assert.NotNull(instance); + Assert.Equal("hook_abc123", instance.HookInvocationId); + Assert.Equal("preToolUse", instance.HookType); + Assert.True(instance.Success); + Assert.Equal(12, instance.DurationMs); + Assert.Equal("hook failed", instance.Error); + } + + [Fact] + public void RoundtripJson() + { + // Test that FromJson -> ToJson -> FromJson produces equivalent data + string jsonData = """ +{ + "hookInvocationId": "hook_abc123", + "hookType": "preToolUse", + "success": true, + "durationMs": 12, + "error": "hook failed" +} +"""; + + var original = HookEndPayload.FromJson(jsonData); + Assert.NotNull(original); + + var json = original.ToJson(); + Assert.False(string.IsNullOrEmpty(json)); + + var reloaded = HookEndPayload.FromJson(json); + Assert.NotNull(reloaded); + Assert.Equal("hook_abc123", reloaded.HookInvocationId); + Assert.Equal("preToolUse", reloaded.HookType); + Assert.True(reloaded.Success); + Assert.Equal(12, reloaded.DurationMs); + Assert.Equal("hook failed", reloaded.Error); + } + + [Fact] + public void RoundtripYaml() + { + // Test that FromYaml -> ToYaml -> FromYaml produces equivalent data + string yamlData = """ +hookInvocationId: hook_abc123 +hookType: preToolUse +success: true +durationMs: 12 +error: hook failed + +"""; + + var original = HookEndPayload.FromYaml(yamlData); + Assert.NotNull(original); + + var yaml = original.ToYaml(); + Assert.False(string.IsNullOrEmpty(yaml)); + + var reloaded = HookEndPayload.FromYaml(yaml); + Assert.NotNull(reloaded); + Assert.Equal("hook_abc123", reloaded.HookInvocationId); + Assert.Equal("preToolUse", reloaded.HookType); + Assert.True(reloaded.Success); + Assert.Equal(12, reloaded.DurationMs); + Assert.Equal("hook failed", reloaded.Error); + } + + [Fact] + public void ToJsonProducesValidJson() + { + string jsonData = """ +{ + "hookInvocationId": "hook_abc123", + "hookType": "preToolUse", + "success": true, + "durationMs": 12, + "error": "hook failed" +} +"""; + + var instance = HookEndPayload.FromJson(jsonData); + var json = instance.ToJson(); + + // Verify it's valid JSON by parsing it + var parsed = System.Text.Json.JsonDocument.Parse(json); + Assert.NotNull(parsed); + } + + [Fact] + public void ToYamlProducesValidYaml() + { + string yamlData = """ +hookInvocationId: hook_abc123 +hookType: preToolUse +success: true +durationMs: 12 +error: hook failed + +"""; + + var instance = HookEndPayload.FromYaml(yamlData); + var yaml = instance.ToYaml(); + + // Verify it's valid YAML by parsing it + var deserializer = new YamlDotNet.Serialization.DeserializerBuilder().Build(); + var parsed = deserializer.Deserialize(yaml); + Assert.NotNull(parsed); + } +} diff --git a/runtime/csharp/Prompty.Core.Tests/Model/events/HookStartPayloadConversionTests.cs b/runtime/csharp/Prompty.Core.Tests/Model/events/HookStartPayloadConversionTests.cs new file mode 100644 index 00000000..5bfed060 --- /dev/null +++ b/runtime/csharp/Prompty.Core.Tests/Model/events/HookStartPayloadConversionTests.cs @@ -0,0 +1,123 @@ +// +using Xunit; + +#pragma warning disable IDE0130 +namespace Prompty.Core; +#pragma warning restore IDE0130 + + +public class HookStartPayloadConversionTests +{ + [Fact] + public void LoadYamlInput() + { + string yamlData = """ +hookInvocationId: hook_abc123 +hookType: preToolUse + +"""; + + var instance = HookStartPayload.FromYaml(yamlData); + + Assert.NotNull(instance); + Assert.Equal("hook_abc123", instance.HookInvocationId); + Assert.Equal("preToolUse", instance.HookType); + } + + [Fact] + public void LoadJsonInput() + { + string jsonData = """ +{ + "hookInvocationId": "hook_abc123", + "hookType": "preToolUse" +} +"""; + + var instance = HookStartPayload.FromJson(jsonData); + Assert.NotNull(instance); + Assert.Equal("hook_abc123", instance.HookInvocationId); + Assert.Equal("preToolUse", instance.HookType); + } + + [Fact] + public void RoundtripJson() + { + // Test that FromJson -> ToJson -> FromJson produces equivalent data + string jsonData = """ +{ + "hookInvocationId": "hook_abc123", + "hookType": "preToolUse" +} +"""; + + var original = HookStartPayload.FromJson(jsonData); + Assert.NotNull(original); + + var json = original.ToJson(); + Assert.False(string.IsNullOrEmpty(json)); + + var reloaded = HookStartPayload.FromJson(json); + Assert.NotNull(reloaded); + Assert.Equal("hook_abc123", reloaded.HookInvocationId); + Assert.Equal("preToolUse", reloaded.HookType); + } + + [Fact] + public void RoundtripYaml() + { + // Test that FromYaml -> ToYaml -> FromYaml produces equivalent data + string yamlData = """ +hookInvocationId: hook_abc123 +hookType: preToolUse + +"""; + + var original = HookStartPayload.FromYaml(yamlData); + Assert.NotNull(original); + + var yaml = original.ToYaml(); + Assert.False(string.IsNullOrEmpty(yaml)); + + var reloaded = HookStartPayload.FromYaml(yaml); + Assert.NotNull(reloaded); + Assert.Equal("hook_abc123", reloaded.HookInvocationId); + Assert.Equal("preToolUse", reloaded.HookType); + } + + [Fact] + public void ToJsonProducesValidJson() + { + string jsonData = """ +{ + "hookInvocationId": "hook_abc123", + "hookType": "preToolUse" +} +"""; + + var instance = HookStartPayload.FromJson(jsonData); + var json = instance.ToJson(); + + // Verify it's valid JSON by parsing it + var parsed = System.Text.Json.JsonDocument.Parse(json); + Assert.NotNull(parsed); + } + + [Fact] + public void ToYamlProducesValidYaml() + { + string yamlData = """ +hookInvocationId: hook_abc123 +hookType: preToolUse + +"""; + + var instance = HookStartPayload.FromYaml(yamlData); + var yaml = instance.ToYaml(); + + // Verify it's valid YAML by parsing it + var deserializer = new YamlDotNet.Serialization.DeserializerBuilder().Build(); + var parsed = deserializer.Deserialize(yaml); + Assert.NotNull(parsed); + } +} diff --git a/runtime/csharp/Prompty.Core.Tests/Model/events/HostToolRequestConversionTests.cs b/runtime/csharp/Prompty.Core.Tests/Model/events/HostToolRequestConversionTests.cs new file mode 100644 index 00000000..d58ddf87 --- /dev/null +++ b/runtime/csharp/Prompty.Core.Tests/Model/events/HostToolRequestConversionTests.cs @@ -0,0 +1,143 @@ +// +using Xunit; + +#pragma warning disable IDE0130 +namespace Prompty.Core; +#pragma warning restore IDE0130 + + +public class HostToolRequestConversionTests +{ + [Fact] + public void LoadYamlInput() + { + string yamlData = """ +requestId: exec_abc123 +toolCallId: call_abc123 +toolName: powershell +workingDirectory: /workspace/project + +"""; + + var instance = HostToolRequest.FromYaml(yamlData); + + Assert.NotNull(instance); + Assert.Equal("exec_abc123", instance.RequestId); + Assert.Equal("call_abc123", instance.ToolCallId); + Assert.Equal("powershell", instance.ToolName); + Assert.Equal("/workspace/project", instance.WorkingDirectory); + } + + [Fact] + public void LoadJsonInput() + { + string jsonData = """ +{ + "requestId": "exec_abc123", + "toolCallId": "call_abc123", + "toolName": "powershell", + "workingDirectory": "/workspace/project" +} +"""; + + var instance = HostToolRequest.FromJson(jsonData); + Assert.NotNull(instance); + Assert.Equal("exec_abc123", instance.RequestId); + Assert.Equal("call_abc123", instance.ToolCallId); + Assert.Equal("powershell", instance.ToolName); + Assert.Equal("/workspace/project", instance.WorkingDirectory); + } + + [Fact] + public void RoundtripJson() + { + // Test that FromJson -> ToJson -> FromJson produces equivalent data + string jsonData = """ +{ + "requestId": "exec_abc123", + "toolCallId": "call_abc123", + "toolName": "powershell", + "workingDirectory": "/workspace/project" +} +"""; + + var original = HostToolRequest.FromJson(jsonData); + Assert.NotNull(original); + + var json = original.ToJson(); + Assert.False(string.IsNullOrEmpty(json)); + + var reloaded = HostToolRequest.FromJson(json); + Assert.NotNull(reloaded); + Assert.Equal("exec_abc123", reloaded.RequestId); + Assert.Equal("call_abc123", reloaded.ToolCallId); + Assert.Equal("powershell", reloaded.ToolName); + Assert.Equal("/workspace/project", reloaded.WorkingDirectory); + } + + [Fact] + public void RoundtripYaml() + { + // Test that FromYaml -> ToYaml -> FromYaml produces equivalent data + string yamlData = """ +requestId: exec_abc123 +toolCallId: call_abc123 +toolName: powershell +workingDirectory: /workspace/project + +"""; + + var original = HostToolRequest.FromYaml(yamlData); + Assert.NotNull(original); + + var yaml = original.ToYaml(); + Assert.False(string.IsNullOrEmpty(yaml)); + + var reloaded = HostToolRequest.FromYaml(yaml); + Assert.NotNull(reloaded); + Assert.Equal("exec_abc123", reloaded.RequestId); + Assert.Equal("call_abc123", reloaded.ToolCallId); + Assert.Equal("powershell", reloaded.ToolName); + Assert.Equal("/workspace/project", reloaded.WorkingDirectory); + } + + [Fact] + public void ToJsonProducesValidJson() + { + string jsonData = """ +{ + "requestId": "exec_abc123", + "toolCallId": "call_abc123", + "toolName": "powershell", + "workingDirectory": "/workspace/project" +} +"""; + + var instance = HostToolRequest.FromJson(jsonData); + var json = instance.ToJson(); + + // Verify it's valid JSON by parsing it + var parsed = System.Text.Json.JsonDocument.Parse(json); + Assert.NotNull(parsed); + } + + [Fact] + public void ToYamlProducesValidYaml() + { + string yamlData = """ +requestId: exec_abc123 +toolCallId: call_abc123 +toolName: powershell +workingDirectory: /workspace/project + +"""; + + var instance = HostToolRequest.FromYaml(yamlData); + var yaml = instance.ToYaml(); + + // Verify it's valid YAML by parsing it + var deserializer = new YamlDotNet.Serialization.DeserializerBuilder().Build(); + var parsed = deserializer.Deserialize(yaml); + Assert.NotNull(parsed); + } +} diff --git a/runtime/csharp/Prompty.Core.Tests/Model/events/HostToolResultConversionTests.cs b/runtime/csharp/Prompty.Core.Tests/Model/events/HostToolResultConversionTests.cs new file mode 100644 index 00000000..e503660f --- /dev/null +++ b/runtime/csharp/Prompty.Core.Tests/Model/events/HostToolResultConversionTests.cs @@ -0,0 +1,173 @@ +// +using Xunit; + +#pragma warning disable IDE0130 +namespace Prompty.Core; +#pragma warning restore IDE0130 + + +public class HostToolResultConversionTests +{ + [Fact] + public void LoadYamlInput() + { + string yamlData = """ +requestId: exec_abc123 +toolCallId: call_abc123 +toolName: powershell +success: true +exitCode: 0 +durationMs: 250 +errorKind: timeout + +"""; + + var instance = HostToolResult.FromYaml(yamlData); + + Assert.NotNull(instance); + Assert.Equal("exec_abc123", instance.RequestId); + Assert.Equal("call_abc123", instance.ToolCallId); + Assert.Equal("powershell", instance.ToolName); + Assert.True(instance.Success); + Assert.Equal(0, instance.ExitCode); + Assert.Equal(250, instance.DurationMs); + Assert.Equal("timeout", instance.ErrorKind); + } + + [Fact] + public void LoadJsonInput() + { + string jsonData = """ +{ + "requestId": "exec_abc123", + "toolCallId": "call_abc123", + "toolName": "powershell", + "success": true, + "exitCode": 0, + "durationMs": 250, + "errorKind": "timeout" +} +"""; + + var instance = HostToolResult.FromJson(jsonData); + Assert.NotNull(instance); + Assert.Equal("exec_abc123", instance.RequestId); + Assert.Equal("call_abc123", instance.ToolCallId); + Assert.Equal("powershell", instance.ToolName); + Assert.True(instance.Success); + Assert.Equal(0, instance.ExitCode); + Assert.Equal(250, instance.DurationMs); + Assert.Equal("timeout", instance.ErrorKind); + } + + [Fact] + public void RoundtripJson() + { + // Test that FromJson -> ToJson -> FromJson produces equivalent data + string jsonData = """ +{ + "requestId": "exec_abc123", + "toolCallId": "call_abc123", + "toolName": "powershell", + "success": true, + "exitCode": 0, + "durationMs": 250, + "errorKind": "timeout" +} +"""; + + var original = HostToolResult.FromJson(jsonData); + Assert.NotNull(original); + + var json = original.ToJson(); + Assert.False(string.IsNullOrEmpty(json)); + + var reloaded = HostToolResult.FromJson(json); + Assert.NotNull(reloaded); + Assert.Equal("exec_abc123", reloaded.RequestId); + Assert.Equal("call_abc123", reloaded.ToolCallId); + Assert.Equal("powershell", reloaded.ToolName); + Assert.True(reloaded.Success); + Assert.Equal(0, reloaded.ExitCode); + Assert.Equal(250, reloaded.DurationMs); + Assert.Equal("timeout", reloaded.ErrorKind); + } + + [Fact] + public void RoundtripYaml() + { + // Test that FromYaml -> ToYaml -> FromYaml produces equivalent data + string yamlData = """ +requestId: exec_abc123 +toolCallId: call_abc123 +toolName: powershell +success: true +exitCode: 0 +durationMs: 250 +errorKind: timeout + +"""; + + var original = HostToolResult.FromYaml(yamlData); + Assert.NotNull(original); + + var yaml = original.ToYaml(); + Assert.False(string.IsNullOrEmpty(yaml)); + + var reloaded = HostToolResult.FromYaml(yaml); + Assert.NotNull(reloaded); + Assert.Equal("exec_abc123", reloaded.RequestId); + Assert.Equal("call_abc123", reloaded.ToolCallId); + Assert.Equal("powershell", reloaded.ToolName); + Assert.True(reloaded.Success); + Assert.Equal(0, reloaded.ExitCode); + Assert.Equal(250, reloaded.DurationMs); + Assert.Equal("timeout", reloaded.ErrorKind); + } + + [Fact] + public void ToJsonProducesValidJson() + { + string jsonData = """ +{ + "requestId": "exec_abc123", + "toolCallId": "call_abc123", + "toolName": "powershell", + "success": true, + "exitCode": 0, + "durationMs": 250, + "errorKind": "timeout" +} +"""; + + var instance = HostToolResult.FromJson(jsonData); + var json = instance.ToJson(); + + // Verify it's valid JSON by parsing it + var parsed = System.Text.Json.JsonDocument.Parse(json); + Assert.NotNull(parsed); + } + + [Fact] + public void ToYamlProducesValidYaml() + { + string yamlData = """ +requestId: exec_abc123 +toolCallId: call_abc123 +toolName: powershell +success: true +exitCode: 0 +durationMs: 250 +errorKind: timeout + +"""; + + var instance = HostToolResult.FromYaml(yamlData); + var yaml = instance.ToYaml(); + + // Verify it's valid YAML by parsing it + var deserializer = new YamlDotNet.Serialization.DeserializerBuilder().Build(); + var parsed = deserializer.Deserialize(yaml); + Assert.NotNull(parsed); + } +} diff --git a/runtime/csharp/Prompty.Core.Tests/Model/events/LlmCompletePayloadConversionTests.cs b/runtime/csharp/Prompty.Core.Tests/Model/events/LlmCompletePayloadConversionTests.cs new file mode 100644 index 00000000..8766f3e4 --- /dev/null +++ b/runtime/csharp/Prompty.Core.Tests/Model/events/LlmCompletePayloadConversionTests.cs @@ -0,0 +1,133 @@ +// +using Xunit; + +#pragma warning disable IDE0130 +namespace Prompty.Core; +#pragma warning restore IDE0130 + + +public class LlmCompletePayloadConversionTests +{ + [Fact] + public void LoadYamlInput() + { + string yamlData = """ +requestId: req_abc123 +serviceRequestId: srv_abc123 +durationMs: 820 + +"""; + + var instance = LlmCompletePayload.FromYaml(yamlData); + + Assert.NotNull(instance); + Assert.Equal("req_abc123", instance.RequestId); + Assert.Equal("srv_abc123", instance.ServiceRequestId); + Assert.Equal(820, instance.DurationMs); + } + + [Fact] + public void LoadJsonInput() + { + string jsonData = """ +{ + "requestId": "req_abc123", + "serviceRequestId": "srv_abc123", + "durationMs": 820 +} +"""; + + var instance = LlmCompletePayload.FromJson(jsonData); + Assert.NotNull(instance); + Assert.Equal("req_abc123", instance.RequestId); + Assert.Equal("srv_abc123", instance.ServiceRequestId); + Assert.Equal(820, instance.DurationMs); + } + + [Fact] + public void RoundtripJson() + { + // Test that FromJson -> ToJson -> FromJson produces equivalent data + string jsonData = """ +{ + "requestId": "req_abc123", + "serviceRequestId": "srv_abc123", + "durationMs": 820 +} +"""; + + var original = LlmCompletePayload.FromJson(jsonData); + Assert.NotNull(original); + + var json = original.ToJson(); + Assert.False(string.IsNullOrEmpty(json)); + + var reloaded = LlmCompletePayload.FromJson(json); + Assert.NotNull(reloaded); + Assert.Equal("req_abc123", reloaded.RequestId); + Assert.Equal("srv_abc123", reloaded.ServiceRequestId); + Assert.Equal(820, reloaded.DurationMs); + } + + [Fact] + public void RoundtripYaml() + { + // Test that FromYaml -> ToYaml -> FromYaml produces equivalent data + string yamlData = """ +requestId: req_abc123 +serviceRequestId: srv_abc123 +durationMs: 820 + +"""; + + var original = LlmCompletePayload.FromYaml(yamlData); + Assert.NotNull(original); + + var yaml = original.ToYaml(); + Assert.False(string.IsNullOrEmpty(yaml)); + + var reloaded = LlmCompletePayload.FromYaml(yaml); + Assert.NotNull(reloaded); + Assert.Equal("req_abc123", reloaded.RequestId); + Assert.Equal("srv_abc123", reloaded.ServiceRequestId); + Assert.Equal(820, reloaded.DurationMs); + } + + [Fact] + public void ToJsonProducesValidJson() + { + string jsonData = """ +{ + "requestId": "req_abc123", + "serviceRequestId": "srv_abc123", + "durationMs": 820 +} +"""; + + var instance = LlmCompletePayload.FromJson(jsonData); + var json = instance.ToJson(); + + // Verify it's valid JSON by parsing it + var parsed = System.Text.Json.JsonDocument.Parse(json); + Assert.NotNull(parsed); + } + + [Fact] + public void ToYamlProducesValidYaml() + { + string yamlData = """ +requestId: req_abc123 +serviceRequestId: srv_abc123 +durationMs: 820 + +"""; + + var instance = LlmCompletePayload.FromYaml(yamlData); + var yaml = instance.ToYaml(); + + // Verify it's valid YAML by parsing it + var deserializer = new YamlDotNet.Serialization.DeserializerBuilder().Build(); + var parsed = deserializer.Deserialize(yaml); + Assert.NotNull(parsed); + } +} diff --git a/runtime/csharp/Prompty.Core.Tests/Model/events/LlmStartPayloadConversionTests.cs b/runtime/csharp/Prompty.Core.Tests/Model/events/LlmStartPayloadConversionTests.cs new file mode 100644 index 00000000..35fde0a5 --- /dev/null +++ b/runtime/csharp/Prompty.Core.Tests/Model/events/LlmStartPayloadConversionTests.cs @@ -0,0 +1,143 @@ +// +using Xunit; + +#pragma warning disable IDE0130 +namespace Prompty.Core; +#pragma warning restore IDE0130 + + +public class LlmStartPayloadConversionTests +{ + [Fact] + public void LoadYamlInput() + { + string yamlData = """ +provider: openai +modelId: gpt-4o-mini +messageCount: 4 +attempt: 0 + +"""; + + var instance = LlmStartPayload.FromYaml(yamlData); + + Assert.NotNull(instance); + Assert.Equal("openai", instance.Provider); + Assert.Equal("gpt-4o-mini", instance.ModelId); + Assert.Equal(4, instance.MessageCount); + Assert.Equal(0, instance.Attempt); + } + + [Fact] + public void LoadJsonInput() + { + string jsonData = """ +{ + "provider": "openai", + "modelId": "gpt-4o-mini", + "messageCount": 4, + "attempt": 0 +} +"""; + + var instance = LlmStartPayload.FromJson(jsonData); + Assert.NotNull(instance); + Assert.Equal("openai", instance.Provider); + Assert.Equal("gpt-4o-mini", instance.ModelId); + Assert.Equal(4, instance.MessageCount); + Assert.Equal(0, instance.Attempt); + } + + [Fact] + public void RoundtripJson() + { + // Test that FromJson -> ToJson -> FromJson produces equivalent data + string jsonData = """ +{ + "provider": "openai", + "modelId": "gpt-4o-mini", + "messageCount": 4, + "attempt": 0 +} +"""; + + var original = LlmStartPayload.FromJson(jsonData); + Assert.NotNull(original); + + var json = original.ToJson(); + Assert.False(string.IsNullOrEmpty(json)); + + var reloaded = LlmStartPayload.FromJson(json); + Assert.NotNull(reloaded); + Assert.Equal("openai", reloaded.Provider); + Assert.Equal("gpt-4o-mini", reloaded.ModelId); + Assert.Equal(4, reloaded.MessageCount); + Assert.Equal(0, reloaded.Attempt); + } + + [Fact] + public void RoundtripYaml() + { + // Test that FromYaml -> ToYaml -> FromYaml produces equivalent data + string yamlData = """ +provider: openai +modelId: gpt-4o-mini +messageCount: 4 +attempt: 0 + +"""; + + var original = LlmStartPayload.FromYaml(yamlData); + Assert.NotNull(original); + + var yaml = original.ToYaml(); + Assert.False(string.IsNullOrEmpty(yaml)); + + var reloaded = LlmStartPayload.FromYaml(yaml); + Assert.NotNull(reloaded); + Assert.Equal("openai", reloaded.Provider); + Assert.Equal("gpt-4o-mini", reloaded.ModelId); + Assert.Equal(4, reloaded.MessageCount); + Assert.Equal(0, reloaded.Attempt); + } + + [Fact] + public void ToJsonProducesValidJson() + { + string jsonData = """ +{ + "provider": "openai", + "modelId": "gpt-4o-mini", + "messageCount": 4, + "attempt": 0 +} +"""; + + var instance = LlmStartPayload.FromJson(jsonData); + var json = instance.ToJson(); + + // Verify it's valid JSON by parsing it + var parsed = System.Text.Json.JsonDocument.Parse(json); + Assert.NotNull(parsed); + } + + [Fact] + public void ToYamlProducesValidYaml() + { + string yamlData = """ +provider: openai +modelId: gpt-4o-mini +messageCount: 4 +attempt: 0 + +"""; + + var instance = LlmStartPayload.FromYaml(yamlData); + var yaml = instance.ToYaml(); + + // Verify it's valid YAML by parsing it + var deserializer = new YamlDotNet.Serialization.DeserializerBuilder().Build(); + var parsed = deserializer.Deserialize(yaml); + Assert.NotNull(parsed); + } +} diff --git a/runtime/csharp/Prompty.Core.Tests/Model/events/MessagesUpdatedPayloadConversionTests.cs b/runtime/csharp/Prompty.Core.Tests/Model/events/MessagesUpdatedPayloadConversionTests.cs index 8a059d2c..8b08f6c1 100644 --- a/runtime/csharp/Prompty.Core.Tests/Model/events/MessagesUpdatedPayloadConversionTests.cs +++ b/runtime/csharp/Prompty.Core.Tests/Model/events/MessagesUpdatedPayloadConversionTests.cs @@ -1,3 +1,4 @@ +// using Xunit; #pragma warning disable IDE0130 @@ -7,4 +8,116 @@ namespace Prompty.Core; public class MessagesUpdatedPayloadConversionTests { + [Fact] + public void LoadYamlInput() + { + string yamlData = """ +reason: tool_results +removed: 2 + +"""; + + var instance = MessagesUpdatedPayload.FromYaml(yamlData); + + Assert.NotNull(instance); + Assert.Equal("tool_results", instance.Reason); + Assert.Equal(2, instance.Removed); + } + + [Fact] + public void LoadJsonInput() + { + string jsonData = """ +{ + "reason": "tool_results", + "removed": 2 +} +"""; + + var instance = MessagesUpdatedPayload.FromJson(jsonData); + Assert.NotNull(instance); + Assert.Equal("tool_results", instance.Reason); + Assert.Equal(2, instance.Removed); + } + + [Fact] + public void RoundtripJson() + { + // Test that FromJson -> ToJson -> FromJson produces equivalent data + string jsonData = """ +{ + "reason": "tool_results", + "removed": 2 +} +"""; + + var original = MessagesUpdatedPayload.FromJson(jsonData); + Assert.NotNull(original); + + var json = original.ToJson(); + Assert.False(string.IsNullOrEmpty(json)); + + var reloaded = MessagesUpdatedPayload.FromJson(json); + Assert.NotNull(reloaded); + Assert.Equal("tool_results", reloaded.Reason); + Assert.Equal(2, reloaded.Removed); + } + + [Fact] + public void RoundtripYaml() + { + // Test that FromYaml -> ToYaml -> FromYaml produces equivalent data + string yamlData = """ +reason: tool_results +removed: 2 + +"""; + + var original = MessagesUpdatedPayload.FromYaml(yamlData); + Assert.NotNull(original); + + var yaml = original.ToYaml(); + Assert.False(string.IsNullOrEmpty(yaml)); + + var reloaded = MessagesUpdatedPayload.FromYaml(yaml); + Assert.NotNull(reloaded); + Assert.Equal("tool_results", reloaded.Reason); + Assert.Equal(2, reloaded.Removed); + } + + [Fact] + public void ToJsonProducesValidJson() + { + string jsonData = """ +{ + "reason": "tool_results", + "removed": 2 +} +"""; + + var instance = MessagesUpdatedPayload.FromJson(jsonData); + var json = instance.ToJson(); + + // Verify it's valid JSON by parsing it + var parsed = System.Text.Json.JsonDocument.Parse(json); + Assert.NotNull(parsed); + } + + [Fact] + public void ToYamlProducesValidYaml() + { + string yamlData = """ +reason: tool_results +removed: 2 + +"""; + + var instance = MessagesUpdatedPayload.FromYaml(yamlData); + var yaml = instance.ToYaml(); + + // Verify it's valid YAML by parsing it + var deserializer = new YamlDotNet.Serialization.DeserializerBuilder().Build(); + var parsed = deserializer.Deserialize(yaml); + Assert.NotNull(parsed); + } } diff --git a/runtime/csharp/Prompty.Core.Tests/Model/events/PermissionCompletedPayloadConversionTests.cs b/runtime/csharp/Prompty.Core.Tests/Model/events/PermissionCompletedPayloadConversionTests.cs new file mode 100644 index 00000000..0571c51f --- /dev/null +++ b/runtime/csharp/Prompty.Core.Tests/Model/events/PermissionCompletedPayloadConversionTests.cs @@ -0,0 +1,153 @@ +// +using Xunit; + +#pragma warning disable IDE0130 +namespace Prompty.Core; +#pragma warning restore IDE0130 + + +public class PermissionCompletedPayloadConversionTests +{ + [Fact] + public void LoadYamlInput() + { + string yamlData = """ +requestId: perm_abc123 +toolCallId: call_abc123 +permission: tool.execute +approved: true +reason: user_approved + +"""; + + var instance = PermissionCompletedPayload.FromYaml(yamlData); + + Assert.NotNull(instance); + Assert.Equal("perm_abc123", instance.RequestId); + Assert.Equal("call_abc123", instance.ToolCallId); + Assert.Equal("tool.execute", instance.Permission); + Assert.True(instance.Approved); + Assert.Equal("user_approved", instance.Reason); + } + + [Fact] + public void LoadJsonInput() + { + string jsonData = """ +{ + "requestId": "perm_abc123", + "toolCallId": "call_abc123", + "permission": "tool.execute", + "approved": true, + "reason": "user_approved" +} +"""; + + var instance = PermissionCompletedPayload.FromJson(jsonData); + Assert.NotNull(instance); + Assert.Equal("perm_abc123", instance.RequestId); + Assert.Equal("call_abc123", instance.ToolCallId); + Assert.Equal("tool.execute", instance.Permission); + Assert.True(instance.Approved); + Assert.Equal("user_approved", instance.Reason); + } + + [Fact] + public void RoundtripJson() + { + // Test that FromJson -> ToJson -> FromJson produces equivalent data + string jsonData = """ +{ + "requestId": "perm_abc123", + "toolCallId": "call_abc123", + "permission": "tool.execute", + "approved": true, + "reason": "user_approved" +} +"""; + + var original = PermissionCompletedPayload.FromJson(jsonData); + Assert.NotNull(original); + + var json = original.ToJson(); + Assert.False(string.IsNullOrEmpty(json)); + + var reloaded = PermissionCompletedPayload.FromJson(json); + Assert.NotNull(reloaded); + Assert.Equal("perm_abc123", reloaded.RequestId); + Assert.Equal("call_abc123", reloaded.ToolCallId); + Assert.Equal("tool.execute", reloaded.Permission); + Assert.True(reloaded.Approved); + Assert.Equal("user_approved", reloaded.Reason); + } + + [Fact] + public void RoundtripYaml() + { + // Test that FromYaml -> ToYaml -> FromYaml produces equivalent data + string yamlData = """ +requestId: perm_abc123 +toolCallId: call_abc123 +permission: tool.execute +approved: true +reason: user_approved + +"""; + + var original = PermissionCompletedPayload.FromYaml(yamlData); + Assert.NotNull(original); + + var yaml = original.ToYaml(); + Assert.False(string.IsNullOrEmpty(yaml)); + + var reloaded = PermissionCompletedPayload.FromYaml(yaml); + Assert.NotNull(reloaded); + Assert.Equal("perm_abc123", reloaded.RequestId); + Assert.Equal("call_abc123", reloaded.ToolCallId); + Assert.Equal("tool.execute", reloaded.Permission); + Assert.True(reloaded.Approved); + Assert.Equal("user_approved", reloaded.Reason); + } + + [Fact] + public void ToJsonProducesValidJson() + { + string jsonData = """ +{ + "requestId": "perm_abc123", + "toolCallId": "call_abc123", + "permission": "tool.execute", + "approved": true, + "reason": "user_approved" +} +"""; + + var instance = PermissionCompletedPayload.FromJson(jsonData); + var json = instance.ToJson(); + + // Verify it's valid JSON by parsing it + var parsed = System.Text.Json.JsonDocument.Parse(json); + Assert.NotNull(parsed); + } + + [Fact] + public void ToYamlProducesValidYaml() + { + string yamlData = """ +requestId: perm_abc123 +toolCallId: call_abc123 +permission: tool.execute +approved: true +reason: user_approved + +"""; + + var instance = PermissionCompletedPayload.FromYaml(yamlData); + var yaml = instance.ToYaml(); + + // Verify it's valid YAML by parsing it + var deserializer = new YamlDotNet.Serialization.DeserializerBuilder().Build(); + var parsed = deserializer.Deserialize(yaml); + Assert.NotNull(parsed); + } +} diff --git a/runtime/csharp/Prompty.Core.Tests/Model/events/PermissionDecisionConversionTests.cs b/runtime/csharp/Prompty.Core.Tests/Model/events/PermissionDecisionConversionTests.cs new file mode 100644 index 00000000..559a177a --- /dev/null +++ b/runtime/csharp/Prompty.Core.Tests/Model/events/PermissionDecisionConversionTests.cs @@ -0,0 +1,153 @@ +// +using Xunit; + +#pragma warning disable IDE0130 +namespace Prompty.Core; +#pragma warning restore IDE0130 + + +public class PermissionDecisionConversionTests +{ + [Fact] + public void LoadYamlInput() + { + string yamlData = """ +requestId: perm_abc123 +toolCallId: call_abc123 +permission: tool.execute +approved: true +reason: user_approved + +"""; + + var instance = PermissionDecision.FromYaml(yamlData); + + Assert.NotNull(instance); + Assert.Equal("perm_abc123", instance.RequestId); + Assert.Equal("call_abc123", instance.ToolCallId); + Assert.Equal("tool.execute", instance.Permission); + Assert.True(instance.Approved); + Assert.Equal("user_approved", instance.Reason); + } + + [Fact] + public void LoadJsonInput() + { + string jsonData = """ +{ + "requestId": "perm_abc123", + "toolCallId": "call_abc123", + "permission": "tool.execute", + "approved": true, + "reason": "user_approved" +} +"""; + + var instance = PermissionDecision.FromJson(jsonData); + Assert.NotNull(instance); + Assert.Equal("perm_abc123", instance.RequestId); + Assert.Equal("call_abc123", instance.ToolCallId); + Assert.Equal("tool.execute", instance.Permission); + Assert.True(instance.Approved); + Assert.Equal("user_approved", instance.Reason); + } + + [Fact] + public void RoundtripJson() + { + // Test that FromJson -> ToJson -> FromJson produces equivalent data + string jsonData = """ +{ + "requestId": "perm_abc123", + "toolCallId": "call_abc123", + "permission": "tool.execute", + "approved": true, + "reason": "user_approved" +} +"""; + + var original = PermissionDecision.FromJson(jsonData); + Assert.NotNull(original); + + var json = original.ToJson(); + Assert.False(string.IsNullOrEmpty(json)); + + var reloaded = PermissionDecision.FromJson(json); + Assert.NotNull(reloaded); + Assert.Equal("perm_abc123", reloaded.RequestId); + Assert.Equal("call_abc123", reloaded.ToolCallId); + Assert.Equal("tool.execute", reloaded.Permission); + Assert.True(reloaded.Approved); + Assert.Equal("user_approved", reloaded.Reason); + } + + [Fact] + public void RoundtripYaml() + { + // Test that FromYaml -> ToYaml -> FromYaml produces equivalent data + string yamlData = """ +requestId: perm_abc123 +toolCallId: call_abc123 +permission: tool.execute +approved: true +reason: user_approved + +"""; + + var original = PermissionDecision.FromYaml(yamlData); + Assert.NotNull(original); + + var yaml = original.ToYaml(); + Assert.False(string.IsNullOrEmpty(yaml)); + + var reloaded = PermissionDecision.FromYaml(yaml); + Assert.NotNull(reloaded); + Assert.Equal("perm_abc123", reloaded.RequestId); + Assert.Equal("call_abc123", reloaded.ToolCallId); + Assert.Equal("tool.execute", reloaded.Permission); + Assert.True(reloaded.Approved); + Assert.Equal("user_approved", reloaded.Reason); + } + + [Fact] + public void ToJsonProducesValidJson() + { + string jsonData = """ +{ + "requestId": "perm_abc123", + "toolCallId": "call_abc123", + "permission": "tool.execute", + "approved": true, + "reason": "user_approved" +} +"""; + + var instance = PermissionDecision.FromJson(jsonData); + var json = instance.ToJson(); + + // Verify it's valid JSON by parsing it + var parsed = System.Text.Json.JsonDocument.Parse(json); + Assert.NotNull(parsed); + } + + [Fact] + public void ToYamlProducesValidYaml() + { + string yamlData = """ +requestId: perm_abc123 +toolCallId: call_abc123 +permission: tool.execute +approved: true +reason: user_approved + +"""; + + var instance = PermissionDecision.FromYaml(yamlData); + var yaml = instance.ToYaml(); + + // Verify it's valid YAML by parsing it + var deserializer = new YamlDotNet.Serialization.DeserializerBuilder().Build(); + var parsed = deserializer.Deserialize(yaml); + Assert.NotNull(parsed); + } +} diff --git a/runtime/csharp/Prompty.Core.Tests/Model/events/PermissionRequestConversionTests.cs b/runtime/csharp/Prompty.Core.Tests/Model/events/PermissionRequestConversionTests.cs new file mode 100644 index 00000000..34c744c9 --- /dev/null +++ b/runtime/csharp/Prompty.Core.Tests/Model/events/PermissionRequestConversionTests.cs @@ -0,0 +1,153 @@ +// +using Xunit; + +#pragma warning disable IDE0130 +namespace Prompty.Core; +#pragma warning restore IDE0130 + + +public class PermissionRequestConversionTests +{ + [Fact] + public void LoadYamlInput() + { + string yamlData = """ +requestId: perm_abc123 +toolCallId: call_abc123 +permission: tool.execute +target: shell +promptRequest: Allow shell to run tests? + +"""; + + var instance = PermissionRequest.FromYaml(yamlData); + + Assert.NotNull(instance); + Assert.Equal("perm_abc123", instance.RequestId); + Assert.Equal("call_abc123", instance.ToolCallId); + Assert.Equal("tool.execute", instance.Permission); + Assert.Equal("shell", instance.Target); + Assert.Equal("Allow shell to run tests?", instance.PromptRequest); + } + + [Fact] + public void LoadJsonInput() + { + string jsonData = """ +{ + "requestId": "perm_abc123", + "toolCallId": "call_abc123", + "permission": "tool.execute", + "target": "shell", + "promptRequest": "Allow shell to run tests?" +} +"""; + + var instance = PermissionRequest.FromJson(jsonData); + Assert.NotNull(instance); + Assert.Equal("perm_abc123", instance.RequestId); + Assert.Equal("call_abc123", instance.ToolCallId); + Assert.Equal("tool.execute", instance.Permission); + Assert.Equal("shell", instance.Target); + Assert.Equal("Allow shell to run tests?", instance.PromptRequest); + } + + [Fact] + public void RoundtripJson() + { + // Test that FromJson -> ToJson -> FromJson produces equivalent data + string jsonData = """ +{ + "requestId": "perm_abc123", + "toolCallId": "call_abc123", + "permission": "tool.execute", + "target": "shell", + "promptRequest": "Allow shell to run tests?" +} +"""; + + var original = PermissionRequest.FromJson(jsonData); + Assert.NotNull(original); + + var json = original.ToJson(); + Assert.False(string.IsNullOrEmpty(json)); + + var reloaded = PermissionRequest.FromJson(json); + Assert.NotNull(reloaded); + Assert.Equal("perm_abc123", reloaded.RequestId); + Assert.Equal("call_abc123", reloaded.ToolCallId); + Assert.Equal("tool.execute", reloaded.Permission); + Assert.Equal("shell", reloaded.Target); + Assert.Equal("Allow shell to run tests?", reloaded.PromptRequest); + } + + [Fact] + public void RoundtripYaml() + { + // Test that FromYaml -> ToYaml -> FromYaml produces equivalent data + string yamlData = """ +requestId: perm_abc123 +toolCallId: call_abc123 +permission: tool.execute +target: shell +promptRequest: Allow shell to run tests? + +"""; + + var original = PermissionRequest.FromYaml(yamlData); + Assert.NotNull(original); + + var yaml = original.ToYaml(); + Assert.False(string.IsNullOrEmpty(yaml)); + + var reloaded = PermissionRequest.FromYaml(yaml); + Assert.NotNull(reloaded); + Assert.Equal("perm_abc123", reloaded.RequestId); + Assert.Equal("call_abc123", reloaded.ToolCallId); + Assert.Equal("tool.execute", reloaded.Permission); + Assert.Equal("shell", reloaded.Target); + Assert.Equal("Allow shell to run tests?", reloaded.PromptRequest); + } + + [Fact] + public void ToJsonProducesValidJson() + { + string jsonData = """ +{ + "requestId": "perm_abc123", + "toolCallId": "call_abc123", + "permission": "tool.execute", + "target": "shell", + "promptRequest": "Allow shell to run tests?" +} +"""; + + var instance = PermissionRequest.FromJson(jsonData); + var json = instance.ToJson(); + + // Verify it's valid JSON by parsing it + var parsed = System.Text.Json.JsonDocument.Parse(json); + Assert.NotNull(parsed); + } + + [Fact] + public void ToYamlProducesValidYaml() + { + string yamlData = """ +requestId: perm_abc123 +toolCallId: call_abc123 +permission: tool.execute +target: shell +promptRequest: Allow shell to run tests? + +"""; + + var instance = PermissionRequest.FromYaml(yamlData); + var yaml = instance.ToYaml(); + + // Verify it's valid YAML by parsing it + var deserializer = new YamlDotNet.Serialization.DeserializerBuilder().Build(); + var parsed = deserializer.Deserialize(yaml); + Assert.NotNull(parsed); + } +} diff --git a/runtime/csharp/Prompty.Core.Tests/Model/events/PermissionRequestedPayloadConversionTests.cs b/runtime/csharp/Prompty.Core.Tests/Model/events/PermissionRequestedPayloadConversionTests.cs new file mode 100644 index 00000000..5a7018aa --- /dev/null +++ b/runtime/csharp/Prompty.Core.Tests/Model/events/PermissionRequestedPayloadConversionTests.cs @@ -0,0 +1,153 @@ +// +using Xunit; + +#pragma warning disable IDE0130 +namespace Prompty.Core; +#pragma warning restore IDE0130 + + +public class PermissionRequestedPayloadConversionTests +{ + [Fact] + public void LoadYamlInput() + { + string yamlData = """ +requestId: perm_abc123 +toolCallId: call_abc123 +permission: tool.execute +target: shell +promptRequest: Allow shell to run tests? + +"""; + + var instance = PermissionRequestedPayload.FromYaml(yamlData); + + Assert.NotNull(instance); + Assert.Equal("perm_abc123", instance.RequestId); + Assert.Equal("call_abc123", instance.ToolCallId); + Assert.Equal("tool.execute", instance.Permission); + Assert.Equal("shell", instance.Target); + Assert.Equal("Allow shell to run tests?", instance.PromptRequest); + } + + [Fact] + public void LoadJsonInput() + { + string jsonData = """ +{ + "requestId": "perm_abc123", + "toolCallId": "call_abc123", + "permission": "tool.execute", + "target": "shell", + "promptRequest": "Allow shell to run tests?" +} +"""; + + var instance = PermissionRequestedPayload.FromJson(jsonData); + Assert.NotNull(instance); + Assert.Equal("perm_abc123", instance.RequestId); + Assert.Equal("call_abc123", instance.ToolCallId); + Assert.Equal("tool.execute", instance.Permission); + Assert.Equal("shell", instance.Target); + Assert.Equal("Allow shell to run tests?", instance.PromptRequest); + } + + [Fact] + public void RoundtripJson() + { + // Test that FromJson -> ToJson -> FromJson produces equivalent data + string jsonData = """ +{ + "requestId": "perm_abc123", + "toolCallId": "call_abc123", + "permission": "tool.execute", + "target": "shell", + "promptRequest": "Allow shell to run tests?" +} +"""; + + var original = PermissionRequestedPayload.FromJson(jsonData); + Assert.NotNull(original); + + var json = original.ToJson(); + Assert.False(string.IsNullOrEmpty(json)); + + var reloaded = PermissionRequestedPayload.FromJson(json); + Assert.NotNull(reloaded); + Assert.Equal("perm_abc123", reloaded.RequestId); + Assert.Equal("call_abc123", reloaded.ToolCallId); + Assert.Equal("tool.execute", reloaded.Permission); + Assert.Equal("shell", reloaded.Target); + Assert.Equal("Allow shell to run tests?", reloaded.PromptRequest); + } + + [Fact] + public void RoundtripYaml() + { + // Test that FromYaml -> ToYaml -> FromYaml produces equivalent data + string yamlData = """ +requestId: perm_abc123 +toolCallId: call_abc123 +permission: tool.execute +target: shell +promptRequest: Allow shell to run tests? + +"""; + + var original = PermissionRequestedPayload.FromYaml(yamlData); + Assert.NotNull(original); + + var yaml = original.ToYaml(); + Assert.False(string.IsNullOrEmpty(yaml)); + + var reloaded = PermissionRequestedPayload.FromYaml(yaml); + Assert.NotNull(reloaded); + Assert.Equal("perm_abc123", reloaded.RequestId); + Assert.Equal("call_abc123", reloaded.ToolCallId); + Assert.Equal("tool.execute", reloaded.Permission); + Assert.Equal("shell", reloaded.Target); + Assert.Equal("Allow shell to run tests?", reloaded.PromptRequest); + } + + [Fact] + public void ToJsonProducesValidJson() + { + string jsonData = """ +{ + "requestId": "perm_abc123", + "toolCallId": "call_abc123", + "permission": "tool.execute", + "target": "shell", + "promptRequest": "Allow shell to run tests?" +} +"""; + + var instance = PermissionRequestedPayload.FromJson(jsonData); + var json = instance.ToJson(); + + // Verify it's valid JSON by parsing it + var parsed = System.Text.Json.JsonDocument.Parse(json); + Assert.NotNull(parsed); + } + + [Fact] + public void ToYamlProducesValidYaml() + { + string yamlData = """ +requestId: perm_abc123 +toolCallId: call_abc123 +permission: tool.execute +target: shell +promptRequest: Allow shell to run tests? + +"""; + + var instance = PermissionRequestedPayload.FromYaml(yamlData); + var yaml = instance.ToYaml(); + + // Verify it's valid YAML by parsing it + var deserializer = new YamlDotNet.Serialization.DeserializerBuilder().Build(); + var parsed = deserializer.Deserialize(yaml); + Assert.NotNull(parsed); + } +} diff --git a/runtime/csharp/Prompty.Core.Tests/Model/events/RedactedFieldConversionTests.cs b/runtime/csharp/Prompty.Core.Tests/Model/events/RedactedFieldConversionTests.cs new file mode 100644 index 00000000..4bfe1f7f --- /dev/null +++ b/runtime/csharp/Prompty.Core.Tests/Model/events/RedactedFieldConversionTests.cs @@ -0,0 +1,133 @@ +// +using Xunit; + +#pragma warning disable IDE0130 +namespace Prompty.Core; +#pragma warning restore IDE0130 + + +public class RedactedFieldConversionTests +{ + [Fact] + public void LoadYamlInput() + { + string yamlData = """ +path: $.arguments.apiKey +mode: redacted +reason: secret + +"""; + + var instance = RedactedField.FromYaml(yamlData); + + Assert.NotNull(instance); + Assert.Equal("$.arguments.apiKey", instance.Path); + Assert.Equal(RedactionMode.Redacted, instance.Mode); + Assert.Equal("secret", instance.Reason); + } + + [Fact] + public void LoadJsonInput() + { + string jsonData = """ +{ + "path": "$.arguments.apiKey", + "mode": "redacted", + "reason": "secret" +} +"""; + + var instance = RedactedField.FromJson(jsonData); + Assert.NotNull(instance); + Assert.Equal("$.arguments.apiKey", instance.Path); + Assert.Equal(RedactionMode.Redacted, instance.Mode); + Assert.Equal("secret", instance.Reason); + } + + [Fact] + public void RoundtripJson() + { + // Test that FromJson -> ToJson -> FromJson produces equivalent data + string jsonData = """ +{ + "path": "$.arguments.apiKey", + "mode": "redacted", + "reason": "secret" +} +"""; + + var original = RedactedField.FromJson(jsonData); + Assert.NotNull(original); + + var json = original.ToJson(); + Assert.False(string.IsNullOrEmpty(json)); + + var reloaded = RedactedField.FromJson(json); + Assert.NotNull(reloaded); + Assert.Equal("$.arguments.apiKey", reloaded.Path); + Assert.Equal(RedactionMode.Redacted, reloaded.Mode); + Assert.Equal("secret", reloaded.Reason); + } + + [Fact] + public void RoundtripYaml() + { + // Test that FromYaml -> ToYaml -> FromYaml produces equivalent data + string yamlData = """ +path: $.arguments.apiKey +mode: redacted +reason: secret + +"""; + + var original = RedactedField.FromYaml(yamlData); + Assert.NotNull(original); + + var yaml = original.ToYaml(); + Assert.False(string.IsNullOrEmpty(yaml)); + + var reloaded = RedactedField.FromYaml(yaml); + Assert.NotNull(reloaded); + Assert.Equal("$.arguments.apiKey", reloaded.Path); + Assert.Equal(RedactionMode.Redacted, reloaded.Mode); + Assert.Equal("secret", reloaded.Reason); + } + + [Fact] + public void ToJsonProducesValidJson() + { + string jsonData = """ +{ + "path": "$.arguments.apiKey", + "mode": "redacted", + "reason": "secret" +} +"""; + + var instance = RedactedField.FromJson(jsonData); + var json = instance.ToJson(); + + // Verify it's valid JSON by parsing it + var parsed = System.Text.Json.JsonDocument.Parse(json); + Assert.NotNull(parsed); + } + + [Fact] + public void ToYamlProducesValidYaml() + { + string yamlData = """ +path: $.arguments.apiKey +mode: redacted +reason: secret + +"""; + + var instance = RedactedField.FromYaml(yamlData); + var yaml = instance.ToYaml(); + + // Verify it's valid YAML by parsing it + var deserializer = new YamlDotNet.Serialization.DeserializerBuilder().Build(); + var parsed = deserializer.Deserialize(yaml); + Assert.NotNull(parsed); + } +} diff --git a/runtime/csharp/Prompty.Core.Tests/Model/events/RedactionMetadataConversionTests.cs b/runtime/csharp/Prompty.Core.Tests/Model/events/RedactionMetadataConversionTests.cs new file mode 100644 index 00000000..ed02a096 --- /dev/null +++ b/runtime/csharp/Prompty.Core.Tests/Model/events/RedactionMetadataConversionTests.cs @@ -0,0 +1,123 @@ +// +using Xunit; + +#pragma warning disable IDE0130 +namespace Prompty.Core; +#pragma warning restore IDE0130 + + +public class RedactionMetadataConversionTests +{ + [Fact] + public void LoadYamlInput() + { + string yamlData = """ +sanitized: true +policy: default-v1 + +"""; + + var instance = RedactionMetadata.FromYaml(yamlData); + + Assert.NotNull(instance); + Assert.True(instance.Sanitized); + Assert.Equal("default-v1", instance.Policy); + } + + [Fact] + public void LoadJsonInput() + { + string jsonData = """ +{ + "sanitized": true, + "policy": "default-v1" +} +"""; + + var instance = RedactionMetadata.FromJson(jsonData); + Assert.NotNull(instance); + Assert.True(instance.Sanitized); + Assert.Equal("default-v1", instance.Policy); + } + + [Fact] + public void RoundtripJson() + { + // Test that FromJson -> ToJson -> FromJson produces equivalent data + string jsonData = """ +{ + "sanitized": true, + "policy": "default-v1" +} +"""; + + var original = RedactionMetadata.FromJson(jsonData); + Assert.NotNull(original); + + var json = original.ToJson(); + Assert.False(string.IsNullOrEmpty(json)); + + var reloaded = RedactionMetadata.FromJson(json); + Assert.NotNull(reloaded); + Assert.True(reloaded.Sanitized); + Assert.Equal("default-v1", reloaded.Policy); + } + + [Fact] + public void RoundtripYaml() + { + // Test that FromYaml -> ToYaml -> FromYaml produces equivalent data + string yamlData = """ +sanitized: true +policy: default-v1 + +"""; + + var original = RedactionMetadata.FromYaml(yamlData); + Assert.NotNull(original); + + var yaml = original.ToYaml(); + Assert.False(string.IsNullOrEmpty(yaml)); + + var reloaded = RedactionMetadata.FromYaml(yaml); + Assert.NotNull(reloaded); + Assert.True(reloaded.Sanitized); + Assert.Equal("default-v1", reloaded.Policy); + } + + [Fact] + public void ToJsonProducesValidJson() + { + string jsonData = """ +{ + "sanitized": true, + "policy": "default-v1" +} +"""; + + var instance = RedactionMetadata.FromJson(jsonData); + var json = instance.ToJson(); + + // Verify it's valid JSON by parsing it + var parsed = System.Text.Json.JsonDocument.Parse(json); + Assert.NotNull(parsed); + } + + [Fact] + public void ToYamlProducesValidYaml() + { + string yamlData = """ +sanitized: true +policy: default-v1 + +"""; + + var instance = RedactionMetadata.FromYaml(yamlData); + var yaml = instance.ToYaml(); + + // Verify it's valid YAML by parsing it + var deserializer = new YamlDotNet.Serialization.DeserializerBuilder().Build(); + var parsed = deserializer.Deserialize(yaml); + Assert.NotNull(parsed); + } +} diff --git a/runtime/csharp/Prompty.Core.Tests/Model/events/RetryPayloadConversionTests.cs b/runtime/csharp/Prompty.Core.Tests/Model/events/RetryPayloadConversionTests.cs new file mode 100644 index 00000000..8a65c910 --- /dev/null +++ b/runtime/csharp/Prompty.Core.Tests/Model/events/RetryPayloadConversionTests.cs @@ -0,0 +1,153 @@ +// +using Xunit; + +#pragma warning disable IDE0130 +namespace Prompty.Core; +#pragma warning restore IDE0130 + + +public class RetryPayloadConversionTests +{ + [Fact] + public void LoadYamlInput() + { + string yamlData = """ +operation: llm +attempt: 2 +maxAttempts: 3 +delayMs: 1250 +reason: rate_limit + +"""; + + var instance = RetryPayload.FromYaml(yamlData); + + Assert.NotNull(instance); + Assert.Equal("llm", instance.Operation); + Assert.Equal(2, instance.Attempt); + Assert.Equal(3, instance.MaxAttempts); + Assert.Equal(1250, instance.DelayMs); + Assert.Equal("rate_limit", instance.Reason); + } + + [Fact] + public void LoadJsonInput() + { + string jsonData = """ +{ + "operation": "llm", + "attempt": 2, + "maxAttempts": 3, + "delayMs": 1250, + "reason": "rate_limit" +} +"""; + + var instance = RetryPayload.FromJson(jsonData); + Assert.NotNull(instance); + Assert.Equal("llm", instance.Operation); + Assert.Equal(2, instance.Attempt); + Assert.Equal(3, instance.MaxAttempts); + Assert.Equal(1250, instance.DelayMs); + Assert.Equal("rate_limit", instance.Reason); + } + + [Fact] + public void RoundtripJson() + { + // Test that FromJson -> ToJson -> FromJson produces equivalent data + string jsonData = """ +{ + "operation": "llm", + "attempt": 2, + "maxAttempts": 3, + "delayMs": 1250, + "reason": "rate_limit" +} +"""; + + var original = RetryPayload.FromJson(jsonData); + Assert.NotNull(original); + + var json = original.ToJson(); + Assert.False(string.IsNullOrEmpty(json)); + + var reloaded = RetryPayload.FromJson(json); + Assert.NotNull(reloaded); + Assert.Equal("llm", reloaded.Operation); + Assert.Equal(2, reloaded.Attempt); + Assert.Equal(3, reloaded.MaxAttempts); + Assert.Equal(1250, reloaded.DelayMs); + Assert.Equal("rate_limit", reloaded.Reason); + } + + [Fact] + public void RoundtripYaml() + { + // Test that FromYaml -> ToYaml -> FromYaml produces equivalent data + string yamlData = """ +operation: llm +attempt: 2 +maxAttempts: 3 +delayMs: 1250 +reason: rate_limit + +"""; + + var original = RetryPayload.FromYaml(yamlData); + Assert.NotNull(original); + + var yaml = original.ToYaml(); + Assert.False(string.IsNullOrEmpty(yaml)); + + var reloaded = RetryPayload.FromYaml(yaml); + Assert.NotNull(reloaded); + Assert.Equal("llm", reloaded.Operation); + Assert.Equal(2, reloaded.Attempt); + Assert.Equal(3, reloaded.MaxAttempts); + Assert.Equal(1250, reloaded.DelayMs); + Assert.Equal("rate_limit", reloaded.Reason); + } + + [Fact] + public void ToJsonProducesValidJson() + { + string jsonData = """ +{ + "operation": "llm", + "attempt": 2, + "maxAttempts": 3, + "delayMs": 1250, + "reason": "rate_limit" +} +"""; + + var instance = RetryPayload.FromJson(jsonData); + var json = instance.ToJson(); + + // Verify it's valid JSON by parsing it + var parsed = System.Text.Json.JsonDocument.Parse(json); + Assert.NotNull(parsed); + } + + [Fact] + public void ToYamlProducesValidYaml() + { + string yamlData = """ +operation: llm +attempt: 2 +maxAttempts: 3 +delayMs: 1250 +reason: rate_limit + +"""; + + var instance = RetryPayload.FromYaml(yamlData); + var yaml = instance.ToYaml(); + + // Verify it's valid YAML by parsing it + var deserializer = new YamlDotNet.Serialization.DeserializerBuilder().Build(); + var parsed = deserializer.Deserialize(yaml); + Assert.NotNull(parsed); + } +} diff --git a/runtime/csharp/Prompty.Core.Tests/Model/events/SessionEndPayloadConversionTests.cs b/runtime/csharp/Prompty.Core.Tests/Model/events/SessionEndPayloadConversionTests.cs new file mode 100644 index 00000000..704cead5 --- /dev/null +++ b/runtime/csharp/Prompty.Core.Tests/Model/events/SessionEndPayloadConversionTests.cs @@ -0,0 +1,143 @@ +// +using Xunit; + +#pragma warning disable IDE0130 +namespace Prompty.Core; +#pragma warning restore IDE0130 + + +public class SessionEndPayloadConversionTests +{ + [Fact] + public void LoadYamlInput() + { + string yamlData = """ +sessionId: sess_abc123 +status: success +reason: complete +durationMs: 12500 + +"""; + + var instance = SessionEndPayload.FromYaml(yamlData); + + Assert.NotNull(instance); + Assert.Equal("sess_abc123", instance.SessionId); + Assert.Equal(SessionEndStatus.Success, instance.Status); + Assert.Equal("complete", instance.Reason); + Assert.Equal(12500, instance.DurationMs); + } + + [Fact] + public void LoadJsonInput() + { + string jsonData = """ +{ + "sessionId": "sess_abc123", + "status": "success", + "reason": "complete", + "durationMs": 12500 +} +"""; + + var instance = SessionEndPayload.FromJson(jsonData); + Assert.NotNull(instance); + Assert.Equal("sess_abc123", instance.SessionId); + Assert.Equal(SessionEndStatus.Success, instance.Status); + Assert.Equal("complete", instance.Reason); + Assert.Equal(12500, instance.DurationMs); + } + + [Fact] + public void RoundtripJson() + { + // Test that FromJson -> ToJson -> FromJson produces equivalent data + string jsonData = """ +{ + "sessionId": "sess_abc123", + "status": "success", + "reason": "complete", + "durationMs": 12500 +} +"""; + + var original = SessionEndPayload.FromJson(jsonData); + Assert.NotNull(original); + + var json = original.ToJson(); + Assert.False(string.IsNullOrEmpty(json)); + + var reloaded = SessionEndPayload.FromJson(json); + Assert.NotNull(reloaded); + Assert.Equal("sess_abc123", reloaded.SessionId); + Assert.Equal(SessionEndStatus.Success, reloaded.Status); + Assert.Equal("complete", reloaded.Reason); + Assert.Equal(12500, reloaded.DurationMs); + } + + [Fact] + public void RoundtripYaml() + { + // Test that FromYaml -> ToYaml -> FromYaml produces equivalent data + string yamlData = """ +sessionId: sess_abc123 +status: success +reason: complete +durationMs: 12500 + +"""; + + var original = SessionEndPayload.FromYaml(yamlData); + Assert.NotNull(original); + + var yaml = original.ToYaml(); + Assert.False(string.IsNullOrEmpty(yaml)); + + var reloaded = SessionEndPayload.FromYaml(yaml); + Assert.NotNull(reloaded); + Assert.Equal("sess_abc123", reloaded.SessionId); + Assert.Equal(SessionEndStatus.Success, reloaded.Status); + Assert.Equal("complete", reloaded.Reason); + Assert.Equal(12500, reloaded.DurationMs); + } + + [Fact] + public void ToJsonProducesValidJson() + { + string jsonData = """ +{ + "sessionId": "sess_abc123", + "status": "success", + "reason": "complete", + "durationMs": 12500 +} +"""; + + var instance = SessionEndPayload.FromJson(jsonData); + var json = instance.ToJson(); + + // Verify it's valid JSON by parsing it + var parsed = System.Text.Json.JsonDocument.Parse(json); + Assert.NotNull(parsed); + } + + [Fact] + public void ToYamlProducesValidYaml() + { + string yamlData = """ +sessionId: sess_abc123 +status: success +reason: complete +durationMs: 12500 + +"""; + + var instance = SessionEndPayload.FromYaml(yamlData); + var yaml = instance.ToYaml(); + + // Verify it's valid YAML by parsing it + var deserializer = new YamlDotNet.Serialization.DeserializerBuilder().Build(); + var parsed = deserializer.Deserialize(yaml); + Assert.NotNull(parsed); + } +} diff --git a/runtime/csharp/Prompty.Core.Tests/Model/events/SessionEventConversionTests.cs b/runtime/csharp/Prompty.Core.Tests/Model/events/SessionEventConversionTests.cs new file mode 100644 index 00000000..3998e042 --- /dev/null +++ b/runtime/csharp/Prompty.Core.Tests/Model/events/SessionEventConversionTests.cs @@ -0,0 +1,163 @@ +// +using Xunit; + +#pragma warning disable IDE0130 +namespace Prompty.Core; +#pragma warning restore IDE0130 + + +public class SessionEventConversionTests +{ + [Fact] + public void LoadYamlInput() + { + string yamlData = """ +id: evt_abc123 +timestamp: "2026-06-09T20:00:00Z" +sessionId: sess_abc123 +turnId: turn_001 +parentId: evt_parent +spanId: span_hook_001 + +"""; + + var instance = SessionEvent.FromYaml(yamlData); + + Assert.NotNull(instance); + Assert.Equal("evt_abc123", instance.Id); + Assert.Equal("2026-06-09T20:00:00Z", instance.Timestamp); + Assert.Equal("sess_abc123", instance.SessionId); + Assert.Equal("turn_001", instance.TurnId); + Assert.Equal("evt_parent", instance.ParentId); + Assert.Equal("span_hook_001", instance.SpanId); + } + + [Fact] + public void LoadJsonInput() + { + string jsonData = """ +{ + "id": "evt_abc123", + "timestamp": "2026-06-09T20:00:00Z", + "sessionId": "sess_abc123", + "turnId": "turn_001", + "parentId": "evt_parent", + "spanId": "span_hook_001" +} +"""; + + var instance = SessionEvent.FromJson(jsonData); + Assert.NotNull(instance); + Assert.Equal("evt_abc123", instance.Id); + Assert.Equal("2026-06-09T20:00:00Z", instance.Timestamp); + Assert.Equal("sess_abc123", instance.SessionId); + Assert.Equal("turn_001", instance.TurnId); + Assert.Equal("evt_parent", instance.ParentId); + Assert.Equal("span_hook_001", instance.SpanId); + } + + [Fact] + public void RoundtripJson() + { + // Test that FromJson -> ToJson -> FromJson produces equivalent data + string jsonData = """ +{ + "id": "evt_abc123", + "timestamp": "2026-06-09T20:00:00Z", + "sessionId": "sess_abc123", + "turnId": "turn_001", + "parentId": "evt_parent", + "spanId": "span_hook_001" +} +"""; + + var original = SessionEvent.FromJson(jsonData); + Assert.NotNull(original); + + var json = original.ToJson(); + Assert.False(string.IsNullOrEmpty(json)); + + var reloaded = SessionEvent.FromJson(json); + Assert.NotNull(reloaded); + Assert.Equal("evt_abc123", reloaded.Id); + Assert.Equal("2026-06-09T20:00:00Z", reloaded.Timestamp); + Assert.Equal("sess_abc123", reloaded.SessionId); + Assert.Equal("turn_001", reloaded.TurnId); + Assert.Equal("evt_parent", reloaded.ParentId); + Assert.Equal("span_hook_001", reloaded.SpanId); + } + + [Fact] + public void RoundtripYaml() + { + // Test that FromYaml -> ToYaml -> FromYaml produces equivalent data + string yamlData = """ +id: evt_abc123 +timestamp: "2026-06-09T20:00:00Z" +sessionId: sess_abc123 +turnId: turn_001 +parentId: evt_parent +spanId: span_hook_001 + +"""; + + var original = SessionEvent.FromYaml(yamlData); + Assert.NotNull(original); + + var yaml = original.ToYaml(); + Assert.False(string.IsNullOrEmpty(yaml)); + + var reloaded = SessionEvent.FromYaml(yaml); + Assert.NotNull(reloaded); + Assert.Equal("evt_abc123", reloaded.Id); + Assert.Equal("2026-06-09T20:00:00Z", reloaded.Timestamp); + Assert.Equal("sess_abc123", reloaded.SessionId); + Assert.Equal("turn_001", reloaded.TurnId); + Assert.Equal("evt_parent", reloaded.ParentId); + Assert.Equal("span_hook_001", reloaded.SpanId); + } + + [Fact] + public void ToJsonProducesValidJson() + { + string jsonData = """ +{ + "id": "evt_abc123", + "timestamp": "2026-06-09T20:00:00Z", + "sessionId": "sess_abc123", + "turnId": "turn_001", + "parentId": "evt_parent", + "spanId": "span_hook_001" +} +"""; + + var instance = SessionEvent.FromJson(jsonData); + var json = instance.ToJson(); + + // Verify it's valid JSON by parsing it + var parsed = System.Text.Json.JsonDocument.Parse(json); + Assert.NotNull(parsed); + } + + [Fact] + public void ToYamlProducesValidYaml() + { + string yamlData = """ +id: evt_abc123 +timestamp: "2026-06-09T20:00:00Z" +sessionId: sess_abc123 +turnId: turn_001 +parentId: evt_parent +spanId: span_hook_001 + +"""; + + var instance = SessionEvent.FromYaml(yamlData); + var yaml = instance.ToYaml(); + + // Verify it's valid YAML by parsing it + var deserializer = new YamlDotNet.Serialization.DeserializerBuilder().Build(); + var parsed = deserializer.Deserialize(yaml); + Assert.NotNull(parsed); + } +} diff --git a/runtime/csharp/Prompty.Core.Tests/Model/events/SessionFileRefConversionTests.cs b/runtime/csharp/Prompty.Core.Tests/Model/events/SessionFileRefConversionTests.cs new file mode 100644 index 00000000..34cb804c --- /dev/null +++ b/runtime/csharp/Prompty.Core.Tests/Model/events/SessionFileRefConversionTests.cs @@ -0,0 +1,153 @@ +// +using Xunit; + +#pragma warning disable IDE0130 +namespace Prompty.Core; +#pragma warning restore IDE0130 + + +public class SessionFileRefConversionTests +{ + [Fact] + public void LoadYamlInput() + { + string yamlData = """ +sessionId: sess_abc123 +path: src/index.ts +toolName: view +turnIndex: 2 +firstSeenAt: "2026-06-09T20:00:00Z" + +"""; + + var instance = SessionFileRef.FromYaml(yamlData); + + Assert.NotNull(instance); + Assert.Equal("sess_abc123", instance.SessionId); + Assert.Equal("src/index.ts", instance.Path); + Assert.Equal("view", instance.ToolName); + Assert.Equal(2, instance.TurnIndex); + Assert.Equal("2026-06-09T20:00:00Z", instance.FirstSeenAt); + } + + [Fact] + public void LoadJsonInput() + { + string jsonData = """ +{ + "sessionId": "sess_abc123", + "path": "src/index.ts", + "toolName": "view", + "turnIndex": 2, + "firstSeenAt": "2026-06-09T20:00:00Z" +} +"""; + + var instance = SessionFileRef.FromJson(jsonData); + Assert.NotNull(instance); + Assert.Equal("sess_abc123", instance.SessionId); + Assert.Equal("src/index.ts", instance.Path); + Assert.Equal("view", instance.ToolName); + Assert.Equal(2, instance.TurnIndex); + Assert.Equal("2026-06-09T20:00:00Z", instance.FirstSeenAt); + } + + [Fact] + public void RoundtripJson() + { + // Test that FromJson -> ToJson -> FromJson produces equivalent data + string jsonData = """ +{ + "sessionId": "sess_abc123", + "path": "src/index.ts", + "toolName": "view", + "turnIndex": 2, + "firstSeenAt": "2026-06-09T20:00:00Z" +} +"""; + + var original = SessionFileRef.FromJson(jsonData); + Assert.NotNull(original); + + var json = original.ToJson(); + Assert.False(string.IsNullOrEmpty(json)); + + var reloaded = SessionFileRef.FromJson(json); + Assert.NotNull(reloaded); + Assert.Equal("sess_abc123", reloaded.SessionId); + Assert.Equal("src/index.ts", reloaded.Path); + Assert.Equal("view", reloaded.ToolName); + Assert.Equal(2, reloaded.TurnIndex); + Assert.Equal("2026-06-09T20:00:00Z", reloaded.FirstSeenAt); + } + + [Fact] + public void RoundtripYaml() + { + // Test that FromYaml -> ToYaml -> FromYaml produces equivalent data + string yamlData = """ +sessionId: sess_abc123 +path: src/index.ts +toolName: view +turnIndex: 2 +firstSeenAt: "2026-06-09T20:00:00Z" + +"""; + + var original = SessionFileRef.FromYaml(yamlData); + Assert.NotNull(original); + + var yaml = original.ToYaml(); + Assert.False(string.IsNullOrEmpty(yaml)); + + var reloaded = SessionFileRef.FromYaml(yaml); + Assert.NotNull(reloaded); + Assert.Equal("sess_abc123", reloaded.SessionId); + Assert.Equal("src/index.ts", reloaded.Path); + Assert.Equal("view", reloaded.ToolName); + Assert.Equal(2, reloaded.TurnIndex); + Assert.Equal("2026-06-09T20:00:00Z", reloaded.FirstSeenAt); + } + + [Fact] + public void ToJsonProducesValidJson() + { + string jsonData = """ +{ + "sessionId": "sess_abc123", + "path": "src/index.ts", + "toolName": "view", + "turnIndex": 2, + "firstSeenAt": "2026-06-09T20:00:00Z" +} +"""; + + var instance = SessionFileRef.FromJson(jsonData); + var json = instance.ToJson(); + + // Verify it's valid JSON by parsing it + var parsed = System.Text.Json.JsonDocument.Parse(json); + Assert.NotNull(parsed); + } + + [Fact] + public void ToYamlProducesValidYaml() + { + string yamlData = """ +sessionId: sess_abc123 +path: src/index.ts +toolName: view +turnIndex: 2 +firstSeenAt: "2026-06-09T20:00:00Z" + +"""; + + var instance = SessionFileRef.FromYaml(yamlData); + var yaml = instance.ToYaml(); + + // Verify it's valid YAML by parsing it + var deserializer = new YamlDotNet.Serialization.DeserializerBuilder().Build(); + var parsed = deserializer.Deserialize(yaml); + Assert.NotNull(parsed); + } +} diff --git a/runtime/csharp/Prompty.Core.Tests/Model/events/SessionRefConversionTests.cs b/runtime/csharp/Prompty.Core.Tests/Model/events/SessionRefConversionTests.cs new file mode 100644 index 00000000..f074aee7 --- /dev/null +++ b/runtime/csharp/Prompty.Core.Tests/Model/events/SessionRefConversionTests.cs @@ -0,0 +1,153 @@ +// +using Xunit; + +#pragma warning disable IDE0130 +namespace Prompty.Core; +#pragma warning restore IDE0130 + + +public class SessionRefConversionTests +{ + [Fact] + public void LoadYamlInput() + { + string yamlData = """ +sessionId: sess_abc123 +refType: issue +refValue: "owner/repo#123" +turnIndex: 2 +createdAt: "2026-06-09T20:00:00Z" + +"""; + + var instance = SessionRef.FromYaml(yamlData); + + Assert.NotNull(instance); + Assert.Equal("sess_abc123", instance.SessionId); + Assert.Equal("issue", instance.RefType); + Assert.Equal("owner/repo#123", instance.RefValue); + Assert.Equal(2, instance.TurnIndex); + Assert.Equal("2026-06-09T20:00:00Z", instance.CreatedAt); + } + + [Fact] + public void LoadJsonInput() + { + string jsonData = """ +{ + "sessionId": "sess_abc123", + "refType": "issue", + "refValue": "owner/repo#123", + "turnIndex": 2, + "createdAt": "2026-06-09T20:00:00Z" +} +"""; + + var instance = SessionRef.FromJson(jsonData); + Assert.NotNull(instance); + Assert.Equal("sess_abc123", instance.SessionId); + Assert.Equal("issue", instance.RefType); + Assert.Equal("owner/repo#123", instance.RefValue); + Assert.Equal(2, instance.TurnIndex); + Assert.Equal("2026-06-09T20:00:00Z", instance.CreatedAt); + } + + [Fact] + public void RoundtripJson() + { + // Test that FromJson -> ToJson -> FromJson produces equivalent data + string jsonData = """ +{ + "sessionId": "sess_abc123", + "refType": "issue", + "refValue": "owner/repo#123", + "turnIndex": 2, + "createdAt": "2026-06-09T20:00:00Z" +} +"""; + + var original = SessionRef.FromJson(jsonData); + Assert.NotNull(original); + + var json = original.ToJson(); + Assert.False(string.IsNullOrEmpty(json)); + + var reloaded = SessionRef.FromJson(json); + Assert.NotNull(reloaded); + Assert.Equal("sess_abc123", reloaded.SessionId); + Assert.Equal("issue", reloaded.RefType); + Assert.Equal("owner/repo#123", reloaded.RefValue); + Assert.Equal(2, reloaded.TurnIndex); + Assert.Equal("2026-06-09T20:00:00Z", reloaded.CreatedAt); + } + + [Fact] + public void RoundtripYaml() + { + // Test that FromYaml -> ToYaml -> FromYaml produces equivalent data + string yamlData = """ +sessionId: sess_abc123 +refType: issue +refValue: "owner/repo#123" +turnIndex: 2 +createdAt: "2026-06-09T20:00:00Z" + +"""; + + var original = SessionRef.FromYaml(yamlData); + Assert.NotNull(original); + + var yaml = original.ToYaml(); + Assert.False(string.IsNullOrEmpty(yaml)); + + var reloaded = SessionRef.FromYaml(yaml); + Assert.NotNull(reloaded); + Assert.Equal("sess_abc123", reloaded.SessionId); + Assert.Equal("issue", reloaded.RefType); + Assert.Equal("owner/repo#123", reloaded.RefValue); + Assert.Equal(2, reloaded.TurnIndex); + Assert.Equal("2026-06-09T20:00:00Z", reloaded.CreatedAt); + } + + [Fact] + public void ToJsonProducesValidJson() + { + string jsonData = """ +{ + "sessionId": "sess_abc123", + "refType": "issue", + "refValue": "owner/repo#123", + "turnIndex": 2, + "createdAt": "2026-06-09T20:00:00Z" +} +"""; + + var instance = SessionRef.FromJson(jsonData); + var json = instance.ToJson(); + + // Verify it's valid JSON by parsing it + var parsed = System.Text.Json.JsonDocument.Parse(json); + Assert.NotNull(parsed); + } + + [Fact] + public void ToYamlProducesValidYaml() + { + string yamlData = """ +sessionId: sess_abc123 +refType: issue +refValue: "owner/repo#123" +turnIndex: 2 +createdAt: "2026-06-09T20:00:00Z" + +"""; + + var instance = SessionRef.FromYaml(yamlData); + var yaml = instance.ToYaml(); + + // Verify it's valid YAML by parsing it + var deserializer = new YamlDotNet.Serialization.DeserializerBuilder().Build(); + var parsed = deserializer.Deserialize(yaml); + Assert.NotNull(parsed); + } +} diff --git a/runtime/csharp/Prompty.Core.Tests/Model/events/SessionStartPayloadConversionTests.cs b/runtime/csharp/Prompty.Core.Tests/Model/events/SessionStartPayloadConversionTests.cs new file mode 100644 index 00000000..1816aab7 --- /dev/null +++ b/runtime/csharp/Prompty.Core.Tests/Model/events/SessionStartPayloadConversionTests.cs @@ -0,0 +1,183 @@ +// +using Xunit; + +#pragma warning disable IDE0130 +namespace Prompty.Core; +#pragma warning restore IDE0130 + + +public class SessionStartPayloadConversionTests +{ + [Fact] + public void LoadYamlInput() + { + string yamlData = """ +sessionId: sess_abc123 +schemaVersion: "1" +producer: prompty-agent +runtime: typescript +promptyVersion: 2.0.0 +startTime: "2026-06-09T20:00:00Z" +selectedModel: gpt-4o-mini +reasoningEffort: medium + +"""; + + var instance = SessionStartPayload.FromYaml(yamlData); + + Assert.NotNull(instance); + Assert.Equal("sess_abc123", instance.SessionId); + Assert.Equal("1", instance.SchemaVersion); + Assert.Equal("prompty-agent", instance.Producer); + Assert.Equal("typescript", instance.Runtime); + Assert.Equal("2.0.0", instance.PromptyVersion); + Assert.Equal("2026-06-09T20:00:00Z", instance.StartTime); + Assert.Equal("gpt-4o-mini", instance.SelectedModel); + Assert.Equal("medium", instance.ReasoningEffort); + } + + [Fact] + public void LoadJsonInput() + { + string jsonData = """ +{ + "sessionId": "sess_abc123", + "schemaVersion": "1", + "producer": "prompty-agent", + "runtime": "typescript", + "promptyVersion": "2.0.0", + "startTime": "2026-06-09T20:00:00Z", + "selectedModel": "gpt-4o-mini", + "reasoningEffort": "medium" +} +"""; + + var instance = SessionStartPayload.FromJson(jsonData); + Assert.NotNull(instance); + Assert.Equal("sess_abc123", instance.SessionId); + Assert.Equal("1", instance.SchemaVersion); + Assert.Equal("prompty-agent", instance.Producer); + Assert.Equal("typescript", instance.Runtime); + Assert.Equal("2.0.0", instance.PromptyVersion); + Assert.Equal("2026-06-09T20:00:00Z", instance.StartTime); + Assert.Equal("gpt-4o-mini", instance.SelectedModel); + Assert.Equal("medium", instance.ReasoningEffort); + } + + [Fact] + public void RoundtripJson() + { + // Test that FromJson -> ToJson -> FromJson produces equivalent data + string jsonData = """ +{ + "sessionId": "sess_abc123", + "schemaVersion": "1", + "producer": "prompty-agent", + "runtime": "typescript", + "promptyVersion": "2.0.0", + "startTime": "2026-06-09T20:00:00Z", + "selectedModel": "gpt-4o-mini", + "reasoningEffort": "medium" +} +"""; + + var original = SessionStartPayload.FromJson(jsonData); + Assert.NotNull(original); + + var json = original.ToJson(); + Assert.False(string.IsNullOrEmpty(json)); + + var reloaded = SessionStartPayload.FromJson(json); + Assert.NotNull(reloaded); + Assert.Equal("sess_abc123", reloaded.SessionId); + Assert.Equal("1", reloaded.SchemaVersion); + Assert.Equal("prompty-agent", reloaded.Producer); + Assert.Equal("typescript", reloaded.Runtime); + Assert.Equal("2.0.0", reloaded.PromptyVersion); + Assert.Equal("2026-06-09T20:00:00Z", reloaded.StartTime); + Assert.Equal("gpt-4o-mini", reloaded.SelectedModel); + Assert.Equal("medium", reloaded.ReasoningEffort); + } + + [Fact] + public void RoundtripYaml() + { + // Test that FromYaml -> ToYaml -> FromYaml produces equivalent data + string yamlData = """ +sessionId: sess_abc123 +schemaVersion: "1" +producer: prompty-agent +runtime: typescript +promptyVersion: 2.0.0 +startTime: "2026-06-09T20:00:00Z" +selectedModel: gpt-4o-mini +reasoningEffort: medium + +"""; + + var original = SessionStartPayload.FromYaml(yamlData); + Assert.NotNull(original); + + var yaml = original.ToYaml(); + Assert.False(string.IsNullOrEmpty(yaml)); + + var reloaded = SessionStartPayload.FromYaml(yaml); + Assert.NotNull(reloaded); + Assert.Equal("sess_abc123", reloaded.SessionId); + Assert.Equal("1", reloaded.SchemaVersion); + Assert.Equal("prompty-agent", reloaded.Producer); + Assert.Equal("typescript", reloaded.Runtime); + Assert.Equal("2.0.0", reloaded.PromptyVersion); + Assert.Equal("2026-06-09T20:00:00Z", reloaded.StartTime); + Assert.Equal("gpt-4o-mini", reloaded.SelectedModel); + Assert.Equal("medium", reloaded.ReasoningEffort); + } + + [Fact] + public void ToJsonProducesValidJson() + { + string jsonData = """ +{ + "sessionId": "sess_abc123", + "schemaVersion": "1", + "producer": "prompty-agent", + "runtime": "typescript", + "promptyVersion": "2.0.0", + "startTime": "2026-06-09T20:00:00Z", + "selectedModel": "gpt-4o-mini", + "reasoningEffort": "medium" +} +"""; + + var instance = SessionStartPayload.FromJson(jsonData); + var json = instance.ToJson(); + + // Verify it's valid JSON by parsing it + var parsed = System.Text.Json.JsonDocument.Parse(json); + Assert.NotNull(parsed); + } + + [Fact] + public void ToYamlProducesValidYaml() + { + string yamlData = """ +sessionId: sess_abc123 +schemaVersion: "1" +producer: prompty-agent +runtime: typescript +promptyVersion: 2.0.0 +startTime: "2026-06-09T20:00:00Z" +selectedModel: gpt-4o-mini +reasoningEffort: medium + +"""; + + var instance = SessionStartPayload.FromYaml(yamlData); + var yaml = instance.ToYaml(); + + // Verify it's valid YAML by parsing it + var deserializer = new YamlDotNet.Serialization.DeserializerBuilder().Build(); + var parsed = deserializer.Deserialize(yaml); + Assert.NotNull(parsed); + } +} diff --git a/runtime/csharp/Prompty.Core.Tests/Model/events/SessionSummaryConversionTests.cs b/runtime/csharp/Prompty.Core.Tests/Model/events/SessionSummaryConversionTests.cs new file mode 100644 index 00000000..211e92e4 --- /dev/null +++ b/runtime/csharp/Prompty.Core.Tests/Model/events/SessionSummaryConversionTests.cs @@ -0,0 +1,153 @@ +// +using Xunit; + +#pragma warning disable IDE0130 +namespace Prompty.Core; +#pragma warning restore IDE0130 + + +public class SessionSummaryConversionTests +{ + [Fact] + public void LoadYamlInput() + { + string yamlData = """ +sessionId: sess_abc123 +status: success +turns: 5 +checkpoints: 2 +durationMs: 12500 + +"""; + + var instance = SessionSummary.FromYaml(yamlData); + + Assert.NotNull(instance); + Assert.Equal("sess_abc123", instance.SessionId); + Assert.Equal(SessionSummaryStatus.Success, instance.Status); + Assert.Equal(5, instance.Turns); + Assert.Equal(2, instance.Checkpoints); + Assert.Equal(12500, instance.DurationMs); + } + + [Fact] + public void LoadJsonInput() + { + string jsonData = """ +{ + "sessionId": "sess_abc123", + "status": "success", + "turns": 5, + "checkpoints": 2, + "durationMs": 12500 +} +"""; + + var instance = SessionSummary.FromJson(jsonData); + Assert.NotNull(instance); + Assert.Equal("sess_abc123", instance.SessionId); + Assert.Equal(SessionSummaryStatus.Success, instance.Status); + Assert.Equal(5, instance.Turns); + Assert.Equal(2, instance.Checkpoints); + Assert.Equal(12500, instance.DurationMs); + } + + [Fact] + public void RoundtripJson() + { + // Test that FromJson -> ToJson -> FromJson produces equivalent data + string jsonData = """ +{ + "sessionId": "sess_abc123", + "status": "success", + "turns": 5, + "checkpoints": 2, + "durationMs": 12500 +} +"""; + + var original = SessionSummary.FromJson(jsonData); + Assert.NotNull(original); + + var json = original.ToJson(); + Assert.False(string.IsNullOrEmpty(json)); + + var reloaded = SessionSummary.FromJson(json); + Assert.NotNull(reloaded); + Assert.Equal("sess_abc123", reloaded.SessionId); + Assert.Equal(SessionSummaryStatus.Success, reloaded.Status); + Assert.Equal(5, reloaded.Turns); + Assert.Equal(2, reloaded.Checkpoints); + Assert.Equal(12500, reloaded.DurationMs); + } + + [Fact] + public void RoundtripYaml() + { + // Test that FromYaml -> ToYaml -> FromYaml produces equivalent data + string yamlData = """ +sessionId: sess_abc123 +status: success +turns: 5 +checkpoints: 2 +durationMs: 12500 + +"""; + + var original = SessionSummary.FromYaml(yamlData); + Assert.NotNull(original); + + var yaml = original.ToYaml(); + Assert.False(string.IsNullOrEmpty(yaml)); + + var reloaded = SessionSummary.FromYaml(yaml); + Assert.NotNull(reloaded); + Assert.Equal("sess_abc123", reloaded.SessionId); + Assert.Equal(SessionSummaryStatus.Success, reloaded.Status); + Assert.Equal(5, reloaded.Turns); + Assert.Equal(2, reloaded.Checkpoints); + Assert.Equal(12500, reloaded.DurationMs); + } + + [Fact] + public void ToJsonProducesValidJson() + { + string jsonData = """ +{ + "sessionId": "sess_abc123", + "status": "success", + "turns": 5, + "checkpoints": 2, + "durationMs": 12500 +} +"""; + + var instance = SessionSummary.FromJson(jsonData); + var json = instance.ToJson(); + + // Verify it's valid JSON by parsing it + var parsed = System.Text.Json.JsonDocument.Parse(json); + Assert.NotNull(parsed); + } + + [Fact] + public void ToYamlProducesValidYaml() + { + string yamlData = """ +sessionId: sess_abc123 +status: success +turns: 5 +checkpoints: 2 +durationMs: 12500 + +"""; + + var instance = SessionSummary.FromYaml(yamlData); + var yaml = instance.ToYaml(); + + // Verify it's valid YAML by parsing it + var deserializer = new YamlDotNet.Serialization.DeserializerBuilder().Build(); + var parsed = deserializer.Deserialize(yaml); + Assert.NotNull(parsed); + } +} diff --git a/runtime/csharp/Prompty.Core.Tests/Model/events/SessionTraceConversionTests.cs b/runtime/csharp/Prompty.Core.Tests/Model/events/SessionTraceConversionTests.cs new file mode 100644 index 00000000..fea885d8 --- /dev/null +++ b/runtime/csharp/Prompty.Core.Tests/Model/events/SessionTraceConversionTests.cs @@ -0,0 +1,143 @@ +// +using Xunit; + +#pragma warning disable IDE0130 +namespace Prompty.Core; +#pragma warning restore IDE0130 + + +public class SessionTraceConversionTests +{ + [Fact] + public void LoadYamlInput() + { + string yamlData = """ +version: "1" +runtime: typescript +promptyVersion: 2.0.0 +sessionId: sess_abc123 + +"""; + + var instance = SessionTrace.FromYaml(yamlData); + + Assert.NotNull(instance); + Assert.Equal("1", instance.Version); + Assert.Equal("typescript", instance.Runtime); + Assert.Equal("2.0.0", instance.PromptyVersion); + Assert.Equal("sess_abc123", instance.SessionId); + } + + [Fact] + public void LoadJsonInput() + { + string jsonData = """ +{ + "version": "1", + "runtime": "typescript", + "promptyVersion": "2.0.0", + "sessionId": "sess_abc123" +} +"""; + + var instance = SessionTrace.FromJson(jsonData); + Assert.NotNull(instance); + Assert.Equal("1", instance.Version); + Assert.Equal("typescript", instance.Runtime); + Assert.Equal("2.0.0", instance.PromptyVersion); + Assert.Equal("sess_abc123", instance.SessionId); + } + + [Fact] + public void RoundtripJson() + { + // Test that FromJson -> ToJson -> FromJson produces equivalent data + string jsonData = """ +{ + "version": "1", + "runtime": "typescript", + "promptyVersion": "2.0.0", + "sessionId": "sess_abc123" +} +"""; + + var original = SessionTrace.FromJson(jsonData); + Assert.NotNull(original); + + var json = original.ToJson(); + Assert.False(string.IsNullOrEmpty(json)); + + var reloaded = SessionTrace.FromJson(json); + Assert.NotNull(reloaded); + Assert.Equal("1", reloaded.Version); + Assert.Equal("typescript", reloaded.Runtime); + Assert.Equal("2.0.0", reloaded.PromptyVersion); + Assert.Equal("sess_abc123", reloaded.SessionId); + } + + [Fact] + public void RoundtripYaml() + { + // Test that FromYaml -> ToYaml -> FromYaml produces equivalent data + string yamlData = """ +version: "1" +runtime: typescript +promptyVersion: 2.0.0 +sessionId: sess_abc123 + +"""; + + var original = SessionTrace.FromYaml(yamlData); + Assert.NotNull(original); + + var yaml = original.ToYaml(); + Assert.False(string.IsNullOrEmpty(yaml)); + + var reloaded = SessionTrace.FromYaml(yaml); + Assert.NotNull(reloaded); + Assert.Equal("1", reloaded.Version); + Assert.Equal("typescript", reloaded.Runtime); + Assert.Equal("2.0.0", reloaded.PromptyVersion); + Assert.Equal("sess_abc123", reloaded.SessionId); + } + + [Fact] + public void ToJsonProducesValidJson() + { + string jsonData = """ +{ + "version": "1", + "runtime": "typescript", + "promptyVersion": "2.0.0", + "sessionId": "sess_abc123" +} +"""; + + var instance = SessionTrace.FromJson(jsonData); + var json = instance.ToJson(); + + // Verify it's valid JSON by parsing it + var parsed = System.Text.Json.JsonDocument.Parse(json); + Assert.NotNull(parsed); + } + + [Fact] + public void ToYamlProducesValidYaml() + { + string yamlData = """ +version: "1" +runtime: typescript +promptyVersion: 2.0.0 +sessionId: sess_abc123 + +"""; + + var instance = SessionTrace.FromYaml(yamlData); + var yaml = instance.ToYaml(); + + // Verify it's valid YAML by parsing it + var deserializer = new YamlDotNet.Serialization.DeserializerBuilder().Build(); + var parsed = deserializer.Deserialize(yaml); + Assert.NotNull(parsed); + } +} diff --git a/runtime/csharp/Prompty.Core.Tests/Model/events/SessionWarningPayloadConversionTests.cs b/runtime/csharp/Prompty.Core.Tests/Model/events/SessionWarningPayloadConversionTests.cs new file mode 100644 index 00000000..8cdfdcb2 --- /dev/null +++ b/runtime/csharp/Prompty.Core.Tests/Model/events/SessionWarningPayloadConversionTests.cs @@ -0,0 +1,123 @@ +// +using Xunit; + +#pragma warning disable IDE0130 +namespace Prompty.Core; +#pragma warning restore IDE0130 + + +public class SessionWarningPayloadConversionTests +{ + [Fact] + public void LoadYamlInput() + { + string yamlData = """ +warningType: remote +message: Remote session disabled + +"""; + + var instance = SessionWarningPayload.FromYaml(yamlData); + + Assert.NotNull(instance); + Assert.Equal("remote", instance.WarningType); + Assert.Equal("Remote session disabled", instance.Message); + } + + [Fact] + public void LoadJsonInput() + { + string jsonData = """ +{ + "warningType": "remote", + "message": "Remote session disabled" +} +"""; + + var instance = SessionWarningPayload.FromJson(jsonData); + Assert.NotNull(instance); + Assert.Equal("remote", instance.WarningType); + Assert.Equal("Remote session disabled", instance.Message); + } + + [Fact] + public void RoundtripJson() + { + // Test that FromJson -> ToJson -> FromJson produces equivalent data + string jsonData = """ +{ + "warningType": "remote", + "message": "Remote session disabled" +} +"""; + + var original = SessionWarningPayload.FromJson(jsonData); + Assert.NotNull(original); + + var json = original.ToJson(); + Assert.False(string.IsNullOrEmpty(json)); + + var reloaded = SessionWarningPayload.FromJson(json); + Assert.NotNull(reloaded); + Assert.Equal("remote", reloaded.WarningType); + Assert.Equal("Remote session disabled", reloaded.Message); + } + + [Fact] + public void RoundtripYaml() + { + // Test that FromYaml -> ToYaml -> FromYaml produces equivalent data + string yamlData = """ +warningType: remote +message: Remote session disabled + +"""; + + var original = SessionWarningPayload.FromYaml(yamlData); + Assert.NotNull(original); + + var yaml = original.ToYaml(); + Assert.False(string.IsNullOrEmpty(yaml)); + + var reloaded = SessionWarningPayload.FromYaml(yaml); + Assert.NotNull(reloaded); + Assert.Equal("remote", reloaded.WarningType); + Assert.Equal("Remote session disabled", reloaded.Message); + } + + [Fact] + public void ToJsonProducesValidJson() + { + string jsonData = """ +{ + "warningType": "remote", + "message": "Remote session disabled" +} +"""; + + var instance = SessionWarningPayload.FromJson(jsonData); + var json = instance.ToJson(); + + // Verify it's valid JSON by parsing it + var parsed = System.Text.Json.JsonDocument.Parse(json); + Assert.NotNull(parsed); + } + + [Fact] + public void ToYamlProducesValidYaml() + { + string yamlData = """ +warningType: remote +message: Remote session disabled + +"""; + + var instance = SessionWarningPayload.FromYaml(yamlData); + var yaml = instance.ToYaml(); + + // Verify it's valid YAML by parsing it + var deserializer = new YamlDotNet.Serialization.DeserializerBuilder().Build(); + var parsed = deserializer.Deserialize(yaml); + Assert.NotNull(parsed); + } +} diff --git a/runtime/csharp/Prompty.Core.Tests/Model/events/StatusEventPayloadConversionTests.cs b/runtime/csharp/Prompty.Core.Tests/Model/events/StatusEventPayloadConversionTests.cs index 1b5ac43e..ec617bb1 100644 --- a/runtime/csharp/Prompty.Core.Tests/Model/events/StatusEventPayloadConversionTests.cs +++ b/runtime/csharp/Prompty.Core.Tests/Model/events/StatusEventPayloadConversionTests.cs @@ -1,3 +1,4 @@ +// using Xunit; #pragma warning disable IDE0130 diff --git a/runtime/csharp/Prompty.Core.Tests/Model/events/StreamChunkConversionTests.cs b/runtime/csharp/Prompty.Core.Tests/Model/events/StreamChunkConversionTests.cs index 16733bb8..2f68fa7d 100644 --- a/runtime/csharp/Prompty.Core.Tests/Model/events/StreamChunkConversionTests.cs +++ b/runtime/csharp/Prompty.Core.Tests/Model/events/StreamChunkConversionTests.cs @@ -1,3 +1,4 @@ +// using Xunit; #pragma warning disable IDE0130 diff --git a/runtime/csharp/Prompty.Core.Tests/Model/events/TextChunkConversionTests.cs b/runtime/csharp/Prompty.Core.Tests/Model/events/TextChunkConversionTests.cs index 97c129ad..fb7a2b63 100644 --- a/runtime/csharp/Prompty.Core.Tests/Model/events/TextChunkConversionTests.cs +++ b/runtime/csharp/Prompty.Core.Tests/Model/events/TextChunkConversionTests.cs @@ -1,3 +1,4 @@ +// using Xunit; #pragma warning disable IDE0130 diff --git a/runtime/csharp/Prompty.Core.Tests/Model/events/ThinkingChunkConversionTests.cs b/runtime/csharp/Prompty.Core.Tests/Model/events/ThinkingChunkConversionTests.cs index 3d2495a8..5b7977e4 100644 --- a/runtime/csharp/Prompty.Core.Tests/Model/events/ThinkingChunkConversionTests.cs +++ b/runtime/csharp/Prompty.Core.Tests/Model/events/ThinkingChunkConversionTests.cs @@ -1,3 +1,4 @@ +// using Xunit; #pragma warning disable IDE0130 diff --git a/runtime/csharp/Prompty.Core.Tests/Model/events/ThinkingEventPayloadConversionTests.cs b/runtime/csharp/Prompty.Core.Tests/Model/events/ThinkingEventPayloadConversionTests.cs index 3731ba7d..2916d38b 100644 --- a/runtime/csharp/Prompty.Core.Tests/Model/events/ThinkingEventPayloadConversionTests.cs +++ b/runtime/csharp/Prompty.Core.Tests/Model/events/ThinkingEventPayloadConversionTests.cs @@ -1,3 +1,4 @@ +// using Xunit; #pragma warning disable IDE0130 diff --git a/runtime/csharp/Prompty.Core.Tests/Model/events/TokenEventPayloadConversionTests.cs b/runtime/csharp/Prompty.Core.Tests/Model/events/TokenEventPayloadConversionTests.cs index cff968d0..5f8f3e1b 100644 --- a/runtime/csharp/Prompty.Core.Tests/Model/events/TokenEventPayloadConversionTests.cs +++ b/runtime/csharp/Prompty.Core.Tests/Model/events/TokenEventPayloadConversionTests.cs @@ -1,3 +1,4 @@ +// using Xunit; #pragma warning disable IDE0130 diff --git a/runtime/csharp/Prompty.Core.Tests/Model/events/ToolCallCompletePayloadConversionTests.cs b/runtime/csharp/Prompty.Core.Tests/Model/events/ToolCallCompletePayloadConversionTests.cs new file mode 100644 index 00000000..256ab85a --- /dev/null +++ b/runtime/csharp/Prompty.Core.Tests/Model/events/ToolCallCompletePayloadConversionTests.cs @@ -0,0 +1,153 @@ +// +using Xunit; + +#pragma warning disable IDE0130 +namespace Prompty.Core; +#pragma warning restore IDE0130 + + +public class ToolCallCompletePayloadConversionTests +{ + [Fact] + public void LoadYamlInput() + { + string yamlData = """ +id: call_abc123 +name: get_weather +success: true +durationMs: 42 +errorKind: timeout + +"""; + + var instance = ToolCallCompletePayload.FromYaml(yamlData); + + Assert.NotNull(instance); + Assert.Equal("call_abc123", instance.Id); + Assert.Equal("get_weather", instance.Name); + Assert.True(instance.Success); + Assert.Equal(42, instance.DurationMs); + Assert.Equal("timeout", instance.ErrorKind); + } + + [Fact] + public void LoadJsonInput() + { + string jsonData = """ +{ + "id": "call_abc123", + "name": "get_weather", + "success": true, + "durationMs": 42, + "errorKind": "timeout" +} +"""; + + var instance = ToolCallCompletePayload.FromJson(jsonData); + Assert.NotNull(instance); + Assert.Equal("call_abc123", instance.Id); + Assert.Equal("get_weather", instance.Name); + Assert.True(instance.Success); + Assert.Equal(42, instance.DurationMs); + Assert.Equal("timeout", instance.ErrorKind); + } + + [Fact] + public void RoundtripJson() + { + // Test that FromJson -> ToJson -> FromJson produces equivalent data + string jsonData = """ +{ + "id": "call_abc123", + "name": "get_weather", + "success": true, + "durationMs": 42, + "errorKind": "timeout" +} +"""; + + var original = ToolCallCompletePayload.FromJson(jsonData); + Assert.NotNull(original); + + var json = original.ToJson(); + Assert.False(string.IsNullOrEmpty(json)); + + var reloaded = ToolCallCompletePayload.FromJson(json); + Assert.NotNull(reloaded); + Assert.Equal("call_abc123", reloaded.Id); + Assert.Equal("get_weather", reloaded.Name); + Assert.True(reloaded.Success); + Assert.Equal(42, reloaded.DurationMs); + Assert.Equal("timeout", reloaded.ErrorKind); + } + + [Fact] + public void RoundtripYaml() + { + // Test that FromYaml -> ToYaml -> FromYaml produces equivalent data + string yamlData = """ +id: call_abc123 +name: get_weather +success: true +durationMs: 42 +errorKind: timeout + +"""; + + var original = ToolCallCompletePayload.FromYaml(yamlData); + Assert.NotNull(original); + + var yaml = original.ToYaml(); + Assert.False(string.IsNullOrEmpty(yaml)); + + var reloaded = ToolCallCompletePayload.FromYaml(yaml); + Assert.NotNull(reloaded); + Assert.Equal("call_abc123", reloaded.Id); + Assert.Equal("get_weather", reloaded.Name); + Assert.True(reloaded.Success); + Assert.Equal(42, reloaded.DurationMs); + Assert.Equal("timeout", reloaded.ErrorKind); + } + + [Fact] + public void ToJsonProducesValidJson() + { + string jsonData = """ +{ + "id": "call_abc123", + "name": "get_weather", + "success": true, + "durationMs": 42, + "errorKind": "timeout" +} +"""; + + var instance = ToolCallCompletePayload.FromJson(jsonData); + var json = instance.ToJson(); + + // Verify it's valid JSON by parsing it + var parsed = System.Text.Json.JsonDocument.Parse(json); + Assert.NotNull(parsed); + } + + [Fact] + public void ToYamlProducesValidYaml() + { + string yamlData = """ +id: call_abc123 +name: get_weather +success: true +durationMs: 42 +errorKind: timeout + +"""; + + var instance = ToolCallCompletePayload.FromYaml(yamlData); + var yaml = instance.ToYaml(); + + // Verify it's valid YAML by parsing it + var deserializer = new YamlDotNet.Serialization.DeserializerBuilder().Build(); + var parsed = deserializer.Deserialize(yaml); + Assert.NotNull(parsed); + } +} diff --git a/runtime/csharp/Prompty.Core.Tests/Model/events/ToolCallStartPayloadConversionTests.cs b/runtime/csharp/Prompty.Core.Tests/Model/events/ToolCallStartPayloadConversionTests.cs index 7c907643..b8d098a6 100644 --- a/runtime/csharp/Prompty.Core.Tests/Model/events/ToolCallStartPayloadConversionTests.cs +++ b/runtime/csharp/Prompty.Core.Tests/Model/events/ToolCallStartPayloadConversionTests.cs @@ -1,3 +1,4 @@ +// using Xunit; #pragma warning disable IDE0130 @@ -11,6 +12,7 @@ public class ToolCallStartPayloadConversionTests public void LoadYamlInput() { string yamlData = """ +id: call_abc123 name: get_weather arguments: "{\"city\": \"Paris\"}" @@ -19,6 +21,7 @@ public void LoadYamlInput() var instance = ToolCallStartPayload.FromYaml(yamlData); Assert.NotNull(instance); + Assert.Equal("call_abc123", instance.Id); Assert.Equal("get_weather", instance.Name); Assert.Equal(@"{""city"": ""Paris""}".Replace("\r\n", "\n"), instance.Arguments); } @@ -28,6 +31,7 @@ public void LoadJsonInput() { string jsonData = """ { + "id": "call_abc123", "name": "get_weather", "arguments": "{\"city\": \"Paris\"}" } @@ -35,6 +39,7 @@ public void LoadJsonInput() var instance = ToolCallStartPayload.FromJson(jsonData); Assert.NotNull(instance); + Assert.Equal("call_abc123", instance.Id); Assert.Equal("get_weather", instance.Name); Assert.Equal(@"{""city"": ""Paris""}".Replace("\r\n", "\n"), instance.Arguments); } @@ -45,6 +50,7 @@ public void RoundtripJson() // Test that FromJson -> ToJson -> FromJson produces equivalent data string jsonData = """ { + "id": "call_abc123", "name": "get_weather", "arguments": "{\"city\": \"Paris\"}" } @@ -58,6 +64,7 @@ public void RoundtripJson() var reloaded = ToolCallStartPayload.FromJson(json); Assert.NotNull(reloaded); + Assert.Equal("call_abc123", reloaded.Id); Assert.Equal("get_weather", reloaded.Name); Assert.Equal(@"{""city"": ""Paris""}".Replace("\r\n", "\n"), reloaded.Arguments); } @@ -67,6 +74,7 @@ public void RoundtripYaml() { // Test that FromYaml -> ToYaml -> FromYaml produces equivalent data string yamlData = """ +id: call_abc123 name: get_weather arguments: "{\"city\": \"Paris\"}" @@ -80,6 +88,7 @@ public void RoundtripYaml() var reloaded = ToolCallStartPayload.FromYaml(yaml); Assert.NotNull(reloaded); + Assert.Equal("call_abc123", reloaded.Id); Assert.Equal("get_weather", reloaded.Name); Assert.Equal(@"{""city"": ""Paris""}".Replace("\r\n", "\n"), reloaded.Arguments); } @@ -89,6 +98,7 @@ public void ToJsonProducesValidJson() { string jsonData = """ { + "id": "call_abc123", "name": "get_weather", "arguments": "{\"city\": \"Paris\"}" } @@ -106,6 +116,7 @@ public void ToJsonProducesValidJson() public void ToYamlProducesValidYaml() { string yamlData = """ +id: call_abc123 name: get_weather arguments: "{\"city\": \"Paris\"}" diff --git a/runtime/csharp/Prompty.Core.Tests/Model/events/ToolChunkConversionTests.cs b/runtime/csharp/Prompty.Core.Tests/Model/events/ToolChunkConversionTests.cs index ef3b3c8c..41861996 100644 --- a/runtime/csharp/Prompty.Core.Tests/Model/events/ToolChunkConversionTests.cs +++ b/runtime/csharp/Prompty.Core.Tests/Model/events/ToolChunkConversionTests.cs @@ -1,3 +1,4 @@ +// using Xunit; #pragma warning disable IDE0130 diff --git a/runtime/csharp/Prompty.Core.Tests/Model/events/ToolExecutionCompletePayloadConversionTests.cs b/runtime/csharp/Prompty.Core.Tests/Model/events/ToolExecutionCompletePayloadConversionTests.cs new file mode 100644 index 00000000..8cd34fd9 --- /dev/null +++ b/runtime/csharp/Prompty.Core.Tests/Model/events/ToolExecutionCompletePayloadConversionTests.cs @@ -0,0 +1,173 @@ +// +using Xunit; + +#pragma warning disable IDE0130 +namespace Prompty.Core; +#pragma warning restore IDE0130 + + +public class ToolExecutionCompletePayloadConversionTests +{ + [Fact] + public void LoadYamlInput() + { + string yamlData = """ +requestId: exec_abc123 +toolCallId: call_abc123 +toolName: powershell +success: true +exitCode: 0 +durationMs: 250 +errorKind: timeout + +"""; + + var instance = ToolExecutionCompletePayload.FromYaml(yamlData); + + Assert.NotNull(instance); + Assert.Equal("exec_abc123", instance.RequestId); + Assert.Equal("call_abc123", instance.ToolCallId); + Assert.Equal("powershell", instance.ToolName); + Assert.True(instance.Success); + Assert.Equal(0, instance.ExitCode); + Assert.Equal(250, instance.DurationMs); + Assert.Equal("timeout", instance.ErrorKind); + } + + [Fact] + public void LoadJsonInput() + { + string jsonData = """ +{ + "requestId": "exec_abc123", + "toolCallId": "call_abc123", + "toolName": "powershell", + "success": true, + "exitCode": 0, + "durationMs": 250, + "errorKind": "timeout" +} +"""; + + var instance = ToolExecutionCompletePayload.FromJson(jsonData); + Assert.NotNull(instance); + Assert.Equal("exec_abc123", instance.RequestId); + Assert.Equal("call_abc123", instance.ToolCallId); + Assert.Equal("powershell", instance.ToolName); + Assert.True(instance.Success); + Assert.Equal(0, instance.ExitCode); + Assert.Equal(250, instance.DurationMs); + Assert.Equal("timeout", instance.ErrorKind); + } + + [Fact] + public void RoundtripJson() + { + // Test that FromJson -> ToJson -> FromJson produces equivalent data + string jsonData = """ +{ + "requestId": "exec_abc123", + "toolCallId": "call_abc123", + "toolName": "powershell", + "success": true, + "exitCode": 0, + "durationMs": 250, + "errorKind": "timeout" +} +"""; + + var original = ToolExecutionCompletePayload.FromJson(jsonData); + Assert.NotNull(original); + + var json = original.ToJson(); + Assert.False(string.IsNullOrEmpty(json)); + + var reloaded = ToolExecutionCompletePayload.FromJson(json); + Assert.NotNull(reloaded); + Assert.Equal("exec_abc123", reloaded.RequestId); + Assert.Equal("call_abc123", reloaded.ToolCallId); + Assert.Equal("powershell", reloaded.ToolName); + Assert.True(reloaded.Success); + Assert.Equal(0, reloaded.ExitCode); + Assert.Equal(250, reloaded.DurationMs); + Assert.Equal("timeout", reloaded.ErrorKind); + } + + [Fact] + public void RoundtripYaml() + { + // Test that FromYaml -> ToYaml -> FromYaml produces equivalent data + string yamlData = """ +requestId: exec_abc123 +toolCallId: call_abc123 +toolName: powershell +success: true +exitCode: 0 +durationMs: 250 +errorKind: timeout + +"""; + + var original = ToolExecutionCompletePayload.FromYaml(yamlData); + Assert.NotNull(original); + + var yaml = original.ToYaml(); + Assert.False(string.IsNullOrEmpty(yaml)); + + var reloaded = ToolExecutionCompletePayload.FromYaml(yaml); + Assert.NotNull(reloaded); + Assert.Equal("exec_abc123", reloaded.RequestId); + Assert.Equal("call_abc123", reloaded.ToolCallId); + Assert.Equal("powershell", reloaded.ToolName); + Assert.True(reloaded.Success); + Assert.Equal(0, reloaded.ExitCode); + Assert.Equal(250, reloaded.DurationMs); + Assert.Equal("timeout", reloaded.ErrorKind); + } + + [Fact] + public void ToJsonProducesValidJson() + { + string jsonData = """ +{ + "requestId": "exec_abc123", + "toolCallId": "call_abc123", + "toolName": "powershell", + "success": true, + "exitCode": 0, + "durationMs": 250, + "errorKind": "timeout" +} +"""; + + var instance = ToolExecutionCompletePayload.FromJson(jsonData); + var json = instance.ToJson(); + + // Verify it's valid JSON by parsing it + var parsed = System.Text.Json.JsonDocument.Parse(json); + Assert.NotNull(parsed); + } + + [Fact] + public void ToYamlProducesValidYaml() + { + string yamlData = """ +requestId: exec_abc123 +toolCallId: call_abc123 +toolName: powershell +success: true +exitCode: 0 +durationMs: 250 +errorKind: timeout + +"""; + + var instance = ToolExecutionCompletePayload.FromYaml(yamlData); + var yaml = instance.ToYaml(); + + // Verify it's valid YAML by parsing it + var deserializer = new YamlDotNet.Serialization.DeserializerBuilder().Build(); + var parsed = deserializer.Deserialize(yaml); + Assert.NotNull(parsed); + } +} diff --git a/runtime/csharp/Prompty.Core.Tests/Model/events/ToolExecutionStartPayloadConversionTests.cs b/runtime/csharp/Prompty.Core.Tests/Model/events/ToolExecutionStartPayloadConversionTests.cs new file mode 100644 index 00000000..64a6adfb --- /dev/null +++ b/runtime/csharp/Prompty.Core.Tests/Model/events/ToolExecutionStartPayloadConversionTests.cs @@ -0,0 +1,143 @@ +// +using Xunit; + +#pragma warning disable IDE0130 +namespace Prompty.Core; +#pragma warning restore IDE0130 + + +public class ToolExecutionStartPayloadConversionTests +{ + [Fact] + public void LoadYamlInput() + { + string yamlData = """ +requestId: exec_abc123 +toolCallId: call_abc123 +toolName: powershell +workingDirectory: /workspace/project + +"""; + + var instance = ToolExecutionStartPayload.FromYaml(yamlData); + + Assert.NotNull(instance); + Assert.Equal("exec_abc123", instance.RequestId); + Assert.Equal("call_abc123", instance.ToolCallId); + Assert.Equal("powershell", instance.ToolName); + Assert.Equal("/workspace/project", instance.WorkingDirectory); + } + + [Fact] + public void LoadJsonInput() + { + string jsonData = """ +{ + "requestId": "exec_abc123", + "toolCallId": "call_abc123", + "toolName": "powershell", + "workingDirectory": "/workspace/project" +} +"""; + + var instance = ToolExecutionStartPayload.FromJson(jsonData); + Assert.NotNull(instance); + Assert.Equal("exec_abc123", instance.RequestId); + Assert.Equal("call_abc123", instance.ToolCallId); + Assert.Equal("powershell", instance.ToolName); + Assert.Equal("/workspace/project", instance.WorkingDirectory); + } + + [Fact] + public void RoundtripJson() + { + // Test that FromJson -> ToJson -> FromJson produces equivalent data + string jsonData = """ +{ + "requestId": "exec_abc123", + "toolCallId": "call_abc123", + "toolName": "powershell", + "workingDirectory": "/workspace/project" +} +"""; + + var original = ToolExecutionStartPayload.FromJson(jsonData); + Assert.NotNull(original); + + var json = original.ToJson(); + Assert.False(string.IsNullOrEmpty(json)); + + var reloaded = ToolExecutionStartPayload.FromJson(json); + Assert.NotNull(reloaded); + Assert.Equal("exec_abc123", reloaded.RequestId); + Assert.Equal("call_abc123", reloaded.ToolCallId); + Assert.Equal("powershell", reloaded.ToolName); + Assert.Equal("/workspace/project", reloaded.WorkingDirectory); + } + + [Fact] + public void RoundtripYaml() + { + // Test that FromYaml -> ToYaml -> FromYaml produces equivalent data + string yamlData = """ +requestId: exec_abc123 +toolCallId: call_abc123 +toolName: powershell +workingDirectory: /workspace/project + +"""; + + var original = ToolExecutionStartPayload.FromYaml(yamlData); + Assert.NotNull(original); + + var yaml = original.ToYaml(); + Assert.False(string.IsNullOrEmpty(yaml)); + + var reloaded = ToolExecutionStartPayload.FromYaml(yaml); + Assert.NotNull(reloaded); + Assert.Equal("exec_abc123", reloaded.RequestId); + Assert.Equal("call_abc123", reloaded.ToolCallId); + Assert.Equal("powershell", reloaded.ToolName); + Assert.Equal("/workspace/project", reloaded.WorkingDirectory); + } + + [Fact] + public void ToJsonProducesValidJson() + { + string jsonData = """ +{ + "requestId": "exec_abc123", + "toolCallId": "call_abc123", + "toolName": "powershell", + "workingDirectory": "/workspace/project" +} +"""; + + var instance = ToolExecutionStartPayload.FromJson(jsonData); + var json = instance.ToJson(); + + // Verify it's valid JSON by parsing it + var parsed = System.Text.Json.JsonDocument.Parse(json); + Assert.NotNull(parsed); + } + + [Fact] + public void ToYamlProducesValidYaml() + { + string yamlData = """ +requestId: exec_abc123 +toolCallId: call_abc123 +toolName: powershell +workingDirectory: /workspace/project + +"""; + + var instance = ToolExecutionStartPayload.FromYaml(yamlData); + var yaml = instance.ToYaml(); + + // Verify it's valid YAML by parsing it + var deserializer = new YamlDotNet.Serialization.DeserializerBuilder().Build(); + var parsed = deserializer.Deserialize(yaml); + Assert.NotNull(parsed); + } +} diff --git a/runtime/csharp/Prompty.Core.Tests/Model/events/ToolResultPayloadConversionTests.cs b/runtime/csharp/Prompty.Core.Tests/Model/events/ToolResultPayloadConversionTests.cs index adf10d5e..d0307262 100644 --- a/runtime/csharp/Prompty.Core.Tests/Model/events/ToolResultPayloadConversionTests.cs +++ b/runtime/csharp/Prompty.Core.Tests/Model/events/ToolResultPayloadConversionTests.cs @@ -1,3 +1,4 @@ +// using Xunit; #pragma warning disable IDE0130 diff --git a/runtime/csharp/Prompty.Core.Tests/Model/events/TrajectoryEventConversionTests.cs b/runtime/csharp/Prompty.Core.Tests/Model/events/TrajectoryEventConversionTests.cs new file mode 100644 index 00000000..7c5bf570 --- /dev/null +++ b/runtime/csharp/Prompty.Core.Tests/Model/events/TrajectoryEventConversionTests.cs @@ -0,0 +1,173 @@ +// +using Xunit; + +#pragma warning disable IDE0130 +namespace Prompty.Core; +#pragma warning restore IDE0130 + + +public class TrajectoryEventConversionTests +{ + [Fact] + public void LoadYamlInput() + { + string yamlData = """ +id: traj_abc123 +sessionId: sess_abc123 +turnId: turn_001 +toolCallId: call_abc123 +turnIndex: 4 +eventType: command +createdAt: "2026-06-09T20:00:00Z" + +"""; + + var instance = TrajectoryEvent.FromYaml(yamlData); + + Assert.NotNull(instance); + Assert.Equal("traj_abc123", instance.Id); + Assert.Equal("sess_abc123", instance.SessionId); + Assert.Equal("turn_001", instance.TurnId); + Assert.Equal("call_abc123", instance.ToolCallId); + Assert.Equal(4, instance.TurnIndex); + Assert.Equal("command", instance.EventType); + Assert.Equal("2026-06-09T20:00:00Z", instance.CreatedAt); + } + + [Fact] + public void LoadJsonInput() + { + string jsonData = """ +{ + "id": "traj_abc123", + "sessionId": "sess_abc123", + "turnId": "turn_001", + "toolCallId": "call_abc123", + "turnIndex": 4, + "eventType": "command", + "createdAt": "2026-06-09T20:00:00Z" +} +"""; + + var instance = TrajectoryEvent.FromJson(jsonData); + Assert.NotNull(instance); + Assert.Equal("traj_abc123", instance.Id); + Assert.Equal("sess_abc123", instance.SessionId); + Assert.Equal("turn_001", instance.TurnId); + Assert.Equal("call_abc123", instance.ToolCallId); + Assert.Equal(4, instance.TurnIndex); + Assert.Equal("command", instance.EventType); + Assert.Equal("2026-06-09T20:00:00Z", instance.CreatedAt); + } + + [Fact] + public void RoundtripJson() + { + // Test that FromJson -> ToJson -> FromJson produces equivalent data + string jsonData = """ +{ + "id": "traj_abc123", + "sessionId": "sess_abc123", + "turnId": "turn_001", + "toolCallId": "call_abc123", + "turnIndex": 4, + "eventType": "command", + "createdAt": "2026-06-09T20:00:00Z" +} +"""; + + var original = TrajectoryEvent.FromJson(jsonData); + Assert.NotNull(original); + + var json = original.ToJson(); + Assert.False(string.IsNullOrEmpty(json)); + + var reloaded = TrajectoryEvent.FromJson(json); + Assert.NotNull(reloaded); + Assert.Equal("traj_abc123", reloaded.Id); + Assert.Equal("sess_abc123", reloaded.SessionId); + Assert.Equal("turn_001", reloaded.TurnId); + Assert.Equal("call_abc123", reloaded.ToolCallId); + Assert.Equal(4, reloaded.TurnIndex); + Assert.Equal("command", reloaded.EventType); + Assert.Equal("2026-06-09T20:00:00Z", reloaded.CreatedAt); + } + + [Fact] + public void RoundtripYaml() + { + // Test that FromYaml -> ToYaml -> FromYaml produces equivalent data + string yamlData = """ +id: traj_abc123 +sessionId: sess_abc123 +turnId: turn_001 +toolCallId: call_abc123 +turnIndex: 4 +eventType: command +createdAt: "2026-06-09T20:00:00Z" + +"""; + + var original = TrajectoryEvent.FromYaml(yamlData); + Assert.NotNull(original); + + var yaml = original.ToYaml(); + Assert.False(string.IsNullOrEmpty(yaml)); + + var reloaded = TrajectoryEvent.FromYaml(yaml); + Assert.NotNull(reloaded); + Assert.Equal("traj_abc123", reloaded.Id); + Assert.Equal("sess_abc123", reloaded.SessionId); + Assert.Equal("turn_001", reloaded.TurnId); + Assert.Equal("call_abc123", reloaded.ToolCallId); + Assert.Equal(4, reloaded.TurnIndex); + Assert.Equal("command", reloaded.EventType); + Assert.Equal("2026-06-09T20:00:00Z", reloaded.CreatedAt); + } + + [Fact] + public void ToJsonProducesValidJson() + { + string jsonData = """ +{ + "id": "traj_abc123", + "sessionId": "sess_abc123", + "turnId": "turn_001", + "toolCallId": "call_abc123", + "turnIndex": 4, + "eventType": "command", + "createdAt": "2026-06-09T20:00:00Z" +} +"""; + + var instance = TrajectoryEvent.FromJson(jsonData); + var json = instance.ToJson(); + + // Verify it's valid JSON by parsing it + var parsed = System.Text.Json.JsonDocument.Parse(json); + Assert.NotNull(parsed); + } + + [Fact] + public void ToYamlProducesValidYaml() + { + string yamlData = """ +id: traj_abc123 +sessionId: sess_abc123 +turnId: turn_001 +toolCallId: call_abc123 +turnIndex: 4 +eventType: command +createdAt: "2026-06-09T20:00:00Z" + +"""; + + var instance = TrajectoryEvent.FromYaml(yamlData); + var yaml = instance.ToYaml(); + + // Verify it's valid YAML by parsing it + var deserializer = new YamlDotNet.Serialization.DeserializerBuilder().Build(); + var parsed = deserializer.Deserialize(yaml); + Assert.NotNull(parsed); + } +} diff --git a/runtime/csharp/Prompty.Core.Tests/Model/events/TurnEndPayloadConversionTests.cs b/runtime/csharp/Prompty.Core.Tests/Model/events/TurnEndPayloadConversionTests.cs new file mode 100644 index 00000000..ec38407d --- /dev/null +++ b/runtime/csharp/Prompty.Core.Tests/Model/events/TurnEndPayloadConversionTests.cs @@ -0,0 +1,123 @@ +// +using Xunit; + +#pragma warning disable IDE0130 +namespace Prompty.Core; +#pragma warning restore IDE0130 + + +public class TurnEndPayloadConversionTests +{ + [Fact] + public void LoadYamlInput() + { + string yamlData = """ +iterations: 2 +durationMs: 1500 + +"""; + + var instance = TurnEndPayload.FromYaml(yamlData); + + Assert.NotNull(instance); + Assert.Equal(2, instance.Iterations); + Assert.Equal(1500, instance.DurationMs); + } + + [Fact] + public void LoadJsonInput() + { + string jsonData = """ +{ + "iterations": 2, + "durationMs": 1500 +} +"""; + + var instance = TurnEndPayload.FromJson(jsonData); + Assert.NotNull(instance); + Assert.Equal(2, instance.Iterations); + Assert.Equal(1500, instance.DurationMs); + } + + [Fact] + public void RoundtripJson() + { + // Test that FromJson -> ToJson -> FromJson produces equivalent data + string jsonData = """ +{ + "iterations": 2, + "durationMs": 1500 +} +"""; + + var original = TurnEndPayload.FromJson(jsonData); + Assert.NotNull(original); + + var json = original.ToJson(); + Assert.False(string.IsNullOrEmpty(json)); + + var reloaded = TurnEndPayload.FromJson(json); + Assert.NotNull(reloaded); + Assert.Equal(2, reloaded.Iterations); + Assert.Equal(1500, reloaded.DurationMs); + } + + [Fact] + public void RoundtripYaml() + { + // Test that FromYaml -> ToYaml -> FromYaml produces equivalent data + string yamlData = """ +iterations: 2 +durationMs: 1500 + +"""; + + var original = TurnEndPayload.FromYaml(yamlData); + Assert.NotNull(original); + + var yaml = original.ToYaml(); + Assert.False(string.IsNullOrEmpty(yaml)); + + var reloaded = TurnEndPayload.FromYaml(yaml); + Assert.NotNull(reloaded); + Assert.Equal(2, reloaded.Iterations); + Assert.Equal(1500, reloaded.DurationMs); + } + + [Fact] + public void ToJsonProducesValidJson() + { + string jsonData = """ +{ + "iterations": 2, + "durationMs": 1500 +} +"""; + + var instance = TurnEndPayload.FromJson(jsonData); + var json = instance.ToJson(); + + // Verify it's valid JSON by parsing it + var parsed = System.Text.Json.JsonDocument.Parse(json); + Assert.NotNull(parsed); + } + + [Fact] + public void ToYamlProducesValidYaml() + { + string yamlData = """ +iterations: 2 +durationMs: 1500 + +"""; + + var instance = TurnEndPayload.FromYaml(yamlData); + var yaml = instance.ToYaml(); + + // Verify it's valid YAML by parsing it + var deserializer = new YamlDotNet.Serialization.DeserializerBuilder().Build(); + var parsed = deserializer.Deserialize(yaml); + Assert.NotNull(parsed); + } +} diff --git a/runtime/csharp/Prompty.Core.Tests/Model/events/TurnEventConversionTests.cs b/runtime/csharp/Prompty.Core.Tests/Model/events/TurnEventConversionTests.cs new file mode 100644 index 00000000..9aabec4e --- /dev/null +++ b/runtime/csharp/Prompty.Core.Tests/Model/events/TurnEventConversionTests.cs @@ -0,0 +1,163 @@ +// +using Xunit; + +#pragma warning disable IDE0130 +namespace Prompty.Core; +#pragma warning restore IDE0130 + + +public class TurnEventConversionTests +{ + [Fact] + public void LoadYamlInput() + { + string yamlData = """ +id: evt_abc123 +timestamp: "2026-06-09T20:00:00Z" +turnId: turn_001 +iteration: 0 +parentId: evt_parent +spanId: span_tool_001 + +"""; + + var instance = TurnEvent.FromYaml(yamlData); + + Assert.NotNull(instance); + Assert.Equal("evt_abc123", instance.Id); + Assert.Equal("2026-06-09T20:00:00Z", instance.Timestamp); + Assert.Equal("turn_001", instance.TurnId); + Assert.Equal(0, instance.Iteration); + Assert.Equal("evt_parent", instance.ParentId); + Assert.Equal("span_tool_001", instance.SpanId); + } + + [Fact] + public void LoadJsonInput() + { + string jsonData = """ +{ + "id": "evt_abc123", + "timestamp": "2026-06-09T20:00:00Z", + "turnId": "turn_001", + "iteration": 0, + "parentId": "evt_parent", + "spanId": "span_tool_001" +} +"""; + + var instance = TurnEvent.FromJson(jsonData); + Assert.NotNull(instance); + Assert.Equal("evt_abc123", instance.Id); + Assert.Equal("2026-06-09T20:00:00Z", instance.Timestamp); + Assert.Equal("turn_001", instance.TurnId); + Assert.Equal(0, instance.Iteration); + Assert.Equal("evt_parent", instance.ParentId); + Assert.Equal("span_tool_001", instance.SpanId); + } + + [Fact] + public void RoundtripJson() + { + // Test that FromJson -> ToJson -> FromJson produces equivalent data + string jsonData = """ +{ + "id": "evt_abc123", + "timestamp": "2026-06-09T20:00:00Z", + "turnId": "turn_001", + "iteration": 0, + "parentId": "evt_parent", + "spanId": "span_tool_001" +} +"""; + + var original = TurnEvent.FromJson(jsonData); + Assert.NotNull(original); + + var json = original.ToJson(); + Assert.False(string.IsNullOrEmpty(json)); + + var reloaded = TurnEvent.FromJson(json); + Assert.NotNull(reloaded); + Assert.Equal("evt_abc123", reloaded.Id); + Assert.Equal("2026-06-09T20:00:00Z", reloaded.Timestamp); + Assert.Equal("turn_001", reloaded.TurnId); + Assert.Equal(0, reloaded.Iteration); + Assert.Equal("evt_parent", reloaded.ParentId); + Assert.Equal("span_tool_001", reloaded.SpanId); + } + + [Fact] + public void RoundtripYaml() + { + // Test that FromYaml -> ToYaml -> FromYaml produces equivalent data + string yamlData = """ +id: evt_abc123 +timestamp: "2026-06-09T20:00:00Z" +turnId: turn_001 +iteration: 0 +parentId: evt_parent +spanId: span_tool_001 + +"""; + + var original = TurnEvent.FromYaml(yamlData); + Assert.NotNull(original); + + var yaml = original.ToYaml(); + Assert.False(string.IsNullOrEmpty(yaml)); + + var reloaded = TurnEvent.FromYaml(yaml); + Assert.NotNull(reloaded); + Assert.Equal("evt_abc123", reloaded.Id); + Assert.Equal("2026-06-09T20:00:00Z", reloaded.Timestamp); + Assert.Equal("turn_001", reloaded.TurnId); + Assert.Equal(0, reloaded.Iteration); + Assert.Equal("evt_parent", reloaded.ParentId); + Assert.Equal("span_tool_001", reloaded.SpanId); + } + + [Fact] + public void ToJsonProducesValidJson() + { + string jsonData = """ +{ + "id": "evt_abc123", + "timestamp": "2026-06-09T20:00:00Z", + "turnId": "turn_001", + "iteration": 0, + "parentId": "evt_parent", + "spanId": "span_tool_001" +} +"""; + + var instance = TurnEvent.FromJson(jsonData); + var json = instance.ToJson(); + + // Verify it's valid JSON by parsing it + var parsed = System.Text.Json.JsonDocument.Parse(json); + Assert.NotNull(parsed); + } + + [Fact] + public void ToYamlProducesValidYaml() + { + string yamlData = """ +id: evt_abc123 +timestamp: "2026-06-09T20:00:00Z" +turnId: turn_001 +iteration: 0 +parentId: evt_parent +spanId: span_tool_001 + +"""; + + var instance = TurnEvent.FromYaml(yamlData); + var yaml = instance.ToYaml(); + + // Verify it's valid YAML by parsing it + var deserializer = new YamlDotNet.Serialization.DeserializerBuilder().Build(); + var parsed = deserializer.Deserialize(yaml); + Assert.NotNull(parsed); + } +} diff --git a/runtime/csharp/Prompty.Core.Tests/Model/events/TurnStartPayloadConversionTests.cs b/runtime/csharp/Prompty.Core.Tests/Model/events/TurnStartPayloadConversionTests.cs new file mode 100644 index 00000000..e832830d --- /dev/null +++ b/runtime/csharp/Prompty.Core.Tests/Model/events/TurnStartPayloadConversionTests.cs @@ -0,0 +1,123 @@ +// +using Xunit; + +#pragma warning disable IDE0130 +namespace Prompty.Core; +#pragma warning restore IDE0130 + + +public class TurnStartPayloadConversionTests +{ + [Fact] + public void LoadYamlInput() + { + string yamlData = """ +agent: weather-agent +maxIterations: 10 + +"""; + + var instance = TurnStartPayload.FromYaml(yamlData); + + Assert.NotNull(instance); + Assert.Equal("weather-agent", instance.Agent); + Assert.Equal(10, instance.MaxIterations); + } + + [Fact] + public void LoadJsonInput() + { + string jsonData = """ +{ + "agent": "weather-agent", + "maxIterations": 10 +} +"""; + + var instance = TurnStartPayload.FromJson(jsonData); + Assert.NotNull(instance); + Assert.Equal("weather-agent", instance.Agent); + Assert.Equal(10, instance.MaxIterations); + } + + [Fact] + public void RoundtripJson() + { + // Test that FromJson -> ToJson -> FromJson produces equivalent data + string jsonData = """ +{ + "agent": "weather-agent", + "maxIterations": 10 +} +"""; + + var original = TurnStartPayload.FromJson(jsonData); + Assert.NotNull(original); + + var json = original.ToJson(); + Assert.False(string.IsNullOrEmpty(json)); + + var reloaded = TurnStartPayload.FromJson(json); + Assert.NotNull(reloaded); + Assert.Equal("weather-agent", reloaded.Agent); + Assert.Equal(10, reloaded.MaxIterations); + } + + [Fact] + public void RoundtripYaml() + { + // Test that FromYaml -> ToYaml -> FromYaml produces equivalent data + string yamlData = """ +agent: weather-agent +maxIterations: 10 + +"""; + + var original = TurnStartPayload.FromYaml(yamlData); + Assert.NotNull(original); + + var yaml = original.ToYaml(); + Assert.False(string.IsNullOrEmpty(yaml)); + + var reloaded = TurnStartPayload.FromYaml(yaml); + Assert.NotNull(reloaded); + Assert.Equal("weather-agent", reloaded.Agent); + Assert.Equal(10, reloaded.MaxIterations); + } + + [Fact] + public void ToJsonProducesValidJson() + { + string jsonData = """ +{ + "agent": "weather-agent", + "maxIterations": 10 +} +"""; + + var instance = TurnStartPayload.FromJson(jsonData); + var json = instance.ToJson(); + + // Verify it's valid JSON by parsing it + var parsed = System.Text.Json.JsonDocument.Parse(json); + Assert.NotNull(parsed); + } + + [Fact] + public void ToYamlProducesValidYaml() + { + string yamlData = """ +agent: weather-agent +maxIterations: 10 + +"""; + + var instance = TurnStartPayload.FromYaml(yamlData); + var yaml = instance.ToYaml(); + + // Verify it's valid YAML by parsing it + var deserializer = new YamlDotNet.Serialization.DeserializerBuilder().Build(); + var parsed = deserializer.Deserialize(yaml); + Assert.NotNull(parsed); + } +} diff --git a/runtime/csharp/Prompty.Core.Tests/Model/events/TurnSummaryConversionTests.cs b/runtime/csharp/Prompty.Core.Tests/Model/events/TurnSummaryConversionTests.cs new file mode 100644 index 00000000..829809e6 --- /dev/null +++ b/runtime/csharp/Prompty.Core.Tests/Model/events/TurnSummaryConversionTests.cs @@ -0,0 +1,173 @@ +// +using Xunit; + +#pragma warning disable IDE0130 +namespace Prompty.Core; +#pragma warning restore IDE0130 + + +public class TurnSummaryConversionTests +{ + [Fact] + public void LoadYamlInput() + { + string yamlData = """ +turnId: turn_001 +status: success +iterations: 2 +llmCalls: 3 +toolCalls: 2 +retries: 1 +durationMs: 2500 + +"""; + + var instance = TurnSummary.FromYaml(yamlData); + + Assert.NotNull(instance); + Assert.Equal("turn_001", instance.TurnId); + Assert.Equal("success", instance.Status); + Assert.Equal(2, instance.Iterations); + Assert.Equal(3, instance.LlmCalls); + Assert.Equal(2, instance.ToolCalls); + Assert.Equal(1, instance.Retries); + Assert.Equal(2500, instance.DurationMs); + } + + [Fact] + public void LoadJsonInput() + { + string jsonData = """ +{ + "turnId": "turn_001", + "status": "success", + "iterations": 2, + "llmCalls": 3, + "toolCalls": 2, + "retries": 1, + "durationMs": 2500 +} +"""; + + var instance = TurnSummary.FromJson(jsonData); + Assert.NotNull(instance); + Assert.Equal("turn_001", instance.TurnId); + Assert.Equal("success", instance.Status); + Assert.Equal(2, instance.Iterations); + Assert.Equal(3, instance.LlmCalls); + Assert.Equal(2, instance.ToolCalls); + Assert.Equal(1, instance.Retries); + Assert.Equal(2500, instance.DurationMs); + } + + [Fact] + public void RoundtripJson() + { + // Test that FromJson -> ToJson -> FromJson produces equivalent data + string jsonData = """ +{ + "turnId": "turn_001", + "status": "success", + "iterations": 2, + "llmCalls": 3, + "toolCalls": 2, + "retries": 1, + "durationMs": 2500 +} +"""; + + var original = TurnSummary.FromJson(jsonData); + Assert.NotNull(original); + + var json = original.ToJson(); + Assert.False(string.IsNullOrEmpty(json)); + + var reloaded = TurnSummary.FromJson(json); + Assert.NotNull(reloaded); + Assert.Equal("turn_001", reloaded.TurnId); + Assert.Equal("success", reloaded.Status); + Assert.Equal(2, reloaded.Iterations); + Assert.Equal(3, reloaded.LlmCalls); + Assert.Equal(2, reloaded.ToolCalls); + Assert.Equal(1, reloaded.Retries); + Assert.Equal(2500, reloaded.DurationMs); + } + + [Fact] + public void RoundtripYaml() + { + // Test that FromYaml -> ToYaml -> FromYaml produces equivalent data + string yamlData = """ +turnId: turn_001 +status: success +iterations: 2 +llmCalls: 3 +toolCalls: 2 +retries: 1 +durationMs: 2500 + +"""; + + var original = TurnSummary.FromYaml(yamlData); + Assert.NotNull(original); + + var yaml = original.ToYaml(); + Assert.False(string.IsNullOrEmpty(yaml)); + + var reloaded = TurnSummary.FromYaml(yaml); + Assert.NotNull(reloaded); + Assert.Equal("turn_001", reloaded.TurnId); + Assert.Equal("success", reloaded.Status); + Assert.Equal(2, reloaded.Iterations); + Assert.Equal(3, reloaded.LlmCalls); + Assert.Equal(2, reloaded.ToolCalls); + Assert.Equal(1, reloaded.Retries); + Assert.Equal(2500, reloaded.DurationMs); + } + + [Fact] + public void ToJsonProducesValidJson() + { + string jsonData = """ +{ + "turnId": "turn_001", + "status": "success", + "iterations": 2, + "llmCalls": 3, + "toolCalls": 2, + "retries": 1, + "durationMs": 2500 +} +"""; + + var instance = TurnSummary.FromJson(jsonData); + var json = instance.ToJson(); + + // Verify it's valid JSON by parsing it + var parsed = System.Text.Json.JsonDocument.Parse(json); + Assert.NotNull(parsed); + } + + [Fact] + public void ToYamlProducesValidYaml() + { + string yamlData = """ +turnId: turn_001 +status: success +iterations: 2 +llmCalls: 3 +toolCalls: 2 +retries: 1 +durationMs: 2500 + +"""; + + var instance = TurnSummary.FromYaml(yamlData); + var yaml = instance.ToYaml(); + + // Verify it's valid YAML by parsing it + var deserializer = new YamlDotNet.Serialization.DeserializerBuilder().Build(); + var parsed = deserializer.Deserialize(yaml); + Assert.NotNull(parsed); + } +} diff --git a/runtime/csharp/Prompty.Core.Tests/Model/events/TurnTraceConversionTests.cs b/runtime/csharp/Prompty.Core.Tests/Model/events/TurnTraceConversionTests.cs new file mode 100644 index 00000000..8494ef20 --- /dev/null +++ b/runtime/csharp/Prompty.Core.Tests/Model/events/TurnTraceConversionTests.cs @@ -0,0 +1,133 @@ +// +using Xunit; + +#pragma warning disable IDE0130 +namespace Prompty.Core; +#pragma warning restore IDE0130 + + +public class TurnTraceConversionTests +{ + [Fact] + public void LoadYamlInput() + { + string yamlData = """ +version: "1" +runtime: typescript +promptyVersion: 2.0.0 + +"""; + + var instance = TurnTrace.FromYaml(yamlData); + + Assert.NotNull(instance); + Assert.Equal("1", instance.Version); + Assert.Equal("typescript", instance.Runtime); + Assert.Equal("2.0.0", instance.PromptyVersion); + } + + [Fact] + public void LoadJsonInput() + { + string jsonData = """ +{ + "version": "1", + "runtime": "typescript", + "promptyVersion": "2.0.0" +} +"""; + + var instance = TurnTrace.FromJson(jsonData); + Assert.NotNull(instance); + Assert.Equal("1", instance.Version); + Assert.Equal("typescript", instance.Runtime); + Assert.Equal("2.0.0", instance.PromptyVersion); + } + + [Fact] + public void RoundtripJson() + { + // Test that FromJson -> ToJson -> FromJson produces equivalent data + string jsonData = """ +{ + "version": "1", + "runtime": "typescript", + "promptyVersion": "2.0.0" +} +"""; + + var original = TurnTrace.FromJson(jsonData); + Assert.NotNull(original); + + var json = original.ToJson(); + Assert.False(string.IsNullOrEmpty(json)); + + var reloaded = TurnTrace.FromJson(json); + Assert.NotNull(reloaded); + Assert.Equal("1", reloaded.Version); + Assert.Equal("typescript", reloaded.Runtime); + Assert.Equal("2.0.0", reloaded.PromptyVersion); + } + + [Fact] + public void RoundtripYaml() + { + // Test that FromYaml -> ToYaml -> FromYaml produces equivalent data + string yamlData = """ +version: "1" +runtime: typescript +promptyVersion: 2.0.0 + +"""; + + var original = TurnTrace.FromYaml(yamlData); + Assert.NotNull(original); + + var yaml = original.ToYaml(); + Assert.False(string.IsNullOrEmpty(yaml)); + + var reloaded = TurnTrace.FromYaml(yaml); + Assert.NotNull(reloaded); + Assert.Equal("1", reloaded.Version); + Assert.Equal("typescript", reloaded.Runtime); + Assert.Equal("2.0.0", reloaded.PromptyVersion); + } + + [Fact] + public void ToJsonProducesValidJson() + { + string jsonData = """ +{ + "version": "1", + "runtime": "typescript", + "promptyVersion": "2.0.0" +} +"""; + + var instance = TurnTrace.FromJson(jsonData); + var json = instance.ToJson(); + + // Verify it's valid JSON by parsing it + var parsed = System.Text.Json.JsonDocument.Parse(json); + Assert.NotNull(parsed); + } + + [Fact] + public void ToYamlProducesValidYaml() + { + string yamlData = """ +version: "1" +runtime: typescript +promptyVersion: 2.0.0 + +"""; + + var instance = TurnTrace.FromYaml(yamlData); + var yaml = instance.ToYaml(); + + // Verify it's valid YAML by parsing it + var deserializer = new YamlDotNet.Serialization.DeserializerBuilder().Build(); + var parsed = deserializer.Deserialize(yaml); + Assert.NotNull(parsed); + } +} diff --git a/runtime/csharp/Prompty.Core.Tests/Model/model/ModelConversionTests.cs b/runtime/csharp/Prompty.Core.Tests/Model/model/ModelConversionTests.cs index 8caf5761..ed993e2a 100644 --- a/runtime/csharp/Prompty.Core.Tests/Model/model/ModelConversionTests.cs +++ b/runtime/csharp/Prompty.Core.Tests/Model/model/ModelConversionTests.cs @@ -1,3 +1,4 @@ +// using Xunit; #pragma warning disable IDE0130 diff --git a/runtime/csharp/Prompty.Core.Tests/Model/model/ModelInfoConversionTests.cs b/runtime/csharp/Prompty.Core.Tests/Model/model/ModelInfoConversionTests.cs index 61f89034..3873758e 100644 --- a/runtime/csharp/Prompty.Core.Tests/Model/model/ModelInfoConversionTests.cs +++ b/runtime/csharp/Prompty.Core.Tests/Model/model/ModelInfoConversionTests.cs @@ -1,3 +1,4 @@ +// using Xunit; #pragma warning disable IDE0130 diff --git a/runtime/csharp/Prompty.Core.Tests/Model/model/ModelOptionsConversionTests.cs b/runtime/csharp/Prompty.Core.Tests/Model/model/ModelOptionsConversionTests.cs index dc497430..34cbea93 100644 --- a/runtime/csharp/Prompty.Core.Tests/Model/model/ModelOptionsConversionTests.cs +++ b/runtime/csharp/Prompty.Core.Tests/Model/model/ModelOptionsConversionTests.cs @@ -1,3 +1,4 @@ +// using Xunit; #pragma warning disable IDE0130 diff --git a/runtime/csharp/Prompty.Core.Tests/Model/model/TokenUsageConversionTests.cs b/runtime/csharp/Prompty.Core.Tests/Model/model/TokenUsageConversionTests.cs index 13605019..4e57f030 100644 --- a/runtime/csharp/Prompty.Core.Tests/Model/model/TokenUsageConversionTests.cs +++ b/runtime/csharp/Prompty.Core.Tests/Model/model/TokenUsageConversionTests.cs @@ -1,3 +1,4 @@ +// using Xunit; #pragma warning disable IDE0130 diff --git a/runtime/csharp/Prompty.Core.Tests/Model/pipeline/CompactionConfigConversionTests.cs b/runtime/csharp/Prompty.Core.Tests/Model/pipeline/CompactionConfigConversionTests.cs index 31c383c3..041b2ea4 100644 --- a/runtime/csharp/Prompty.Core.Tests/Model/pipeline/CompactionConfigConversionTests.cs +++ b/runtime/csharp/Prompty.Core.Tests/Model/pipeline/CompactionConfigConversionTests.cs @@ -1,3 +1,4 @@ +// using Xunit; #pragma warning disable IDE0130 diff --git a/runtime/csharp/Prompty.Core.Tests/Model/pipeline/ReplayJournalRecordConversionTests.cs b/runtime/csharp/Prompty.Core.Tests/Model/pipeline/ReplayJournalRecordConversionTests.cs new file mode 100644 index 00000000..85e4f0d6 --- /dev/null +++ b/runtime/csharp/Prompty.Core.Tests/Model/pipeline/ReplayJournalRecordConversionTests.cs @@ -0,0 +1,11 @@ +// +using Xunit; + +#pragma warning disable IDE0130 +namespace Prompty.Core; +#pragma warning restore IDE0130 + + +public class ReplayJournalRecordConversionTests +{ +} diff --git a/runtime/csharp/Prompty.Core.Tests/Model/pipeline/ReplayMismatchConversionTests.cs b/runtime/csharp/Prompty.Core.Tests/Model/pipeline/ReplayMismatchConversionTests.cs new file mode 100644 index 00000000..9c4929a5 --- /dev/null +++ b/runtime/csharp/Prompty.Core.Tests/Model/pipeline/ReplayMismatchConversionTests.cs @@ -0,0 +1,11 @@ +// +using Xunit; + +#pragma warning disable IDE0130 +namespace Prompty.Core; +#pragma warning restore IDE0130 + + +public class ReplayMismatchConversionTests +{ +} diff --git a/runtime/csharp/Prompty.Core.Tests/Model/pipeline/ReplayVerificationRequestConversionTests.cs b/runtime/csharp/Prompty.Core.Tests/Model/pipeline/ReplayVerificationRequestConversionTests.cs new file mode 100644 index 00000000..68aa243f --- /dev/null +++ b/runtime/csharp/Prompty.Core.Tests/Model/pipeline/ReplayVerificationRequestConversionTests.cs @@ -0,0 +1,11 @@ +// +using Xunit; + +#pragma warning disable IDE0130 +namespace Prompty.Core; +#pragma warning restore IDE0130 + + +public class ReplayVerificationRequestConversionTests +{ +} diff --git a/runtime/csharp/Prompty.Core.Tests/Model/pipeline/ReplayVerificationResultConversionTests.cs b/runtime/csharp/Prompty.Core.Tests/Model/pipeline/ReplayVerificationResultConversionTests.cs new file mode 100644 index 00000000..d6b8bc87 --- /dev/null +++ b/runtime/csharp/Prompty.Core.Tests/Model/pipeline/ReplayVerificationResultConversionTests.cs @@ -0,0 +1,11 @@ +// +using Xunit; + +#pragma warning disable IDE0130 +namespace Prompty.Core; +#pragma warning restore IDE0130 + + +public class ReplayVerificationResultConversionTests +{ +} diff --git a/runtime/csharp/Prompty.Core.Tests/Model/pipeline/RunTurnRequestConversionTests.cs b/runtime/csharp/Prompty.Core.Tests/Model/pipeline/RunTurnRequestConversionTests.cs new file mode 100644 index 00000000..b53e9507 --- /dev/null +++ b/runtime/csharp/Prompty.Core.Tests/Model/pipeline/RunTurnRequestConversionTests.cs @@ -0,0 +1,123 @@ +// +using Xunit; + +#pragma warning disable IDE0130 +namespace Prompty.Core; +#pragma warning restore IDE0130 + + +public class RunTurnRequestConversionTests +{ + [Fact] + public void LoadYamlInput() + { + string yamlData = """ +sessionId: sess_abc123 +turnId: turn_abc123 + +"""; + + var instance = RunTurnRequest.FromYaml(yamlData); + + Assert.NotNull(instance); + Assert.Equal("sess_abc123", instance.SessionId); + Assert.Equal("turn_abc123", instance.TurnId); + } + + [Fact] + public void LoadJsonInput() + { + string jsonData = """ +{ + "sessionId": "sess_abc123", + "turnId": "turn_abc123" +} +"""; + + var instance = RunTurnRequest.FromJson(jsonData); + Assert.NotNull(instance); + Assert.Equal("sess_abc123", instance.SessionId); + Assert.Equal("turn_abc123", instance.TurnId); + } + + [Fact] + public void RoundtripJson() + { + // Test that FromJson -> ToJson -> FromJson produces equivalent data + string jsonData = """ +{ + "sessionId": "sess_abc123", + "turnId": "turn_abc123" +} +"""; + + var original = RunTurnRequest.FromJson(jsonData); + Assert.NotNull(original); + + var json = original.ToJson(); + Assert.False(string.IsNullOrEmpty(json)); + + var reloaded = RunTurnRequest.FromJson(json); + Assert.NotNull(reloaded); + Assert.Equal("sess_abc123", reloaded.SessionId); + Assert.Equal("turn_abc123", reloaded.TurnId); + } + + [Fact] + public void RoundtripYaml() + { + // Test that FromYaml -> ToYaml -> FromYaml produces equivalent data + string yamlData = """ +sessionId: sess_abc123 +turnId: turn_abc123 + +"""; + + var original = RunTurnRequest.FromYaml(yamlData); + Assert.NotNull(original); + + var yaml = original.ToYaml(); + Assert.False(string.IsNullOrEmpty(yaml)); + + var reloaded = RunTurnRequest.FromYaml(yaml); + Assert.NotNull(reloaded); + Assert.Equal("sess_abc123", reloaded.SessionId); + Assert.Equal("turn_abc123", reloaded.TurnId); + } + + [Fact] + public void ToJsonProducesValidJson() + { + string jsonData = """ +{ + "sessionId": "sess_abc123", + "turnId": "turn_abc123" +} +"""; + + var instance = RunTurnRequest.FromJson(jsonData); + var json = instance.ToJson(); + + // Verify it's valid JSON by parsing it + var parsed = System.Text.Json.JsonDocument.Parse(json); + Assert.NotNull(parsed); + } + + [Fact] + public void ToYamlProducesValidYaml() + { + string yamlData = """ +sessionId: sess_abc123 +turnId: turn_abc123 + +"""; + + var instance = RunTurnRequest.FromYaml(yamlData); + var yaml = instance.ToYaml(); + + // Verify it's valid YAML by parsing it + var deserializer = new YamlDotNet.Serialization.DeserializerBuilder().Build(); + var parsed = deserializer.Deserialize(yaml); + Assert.NotNull(parsed); + } +} diff --git a/runtime/csharp/Prompty.Core.Tests/Model/pipeline/RunTurnResultConversionTests.cs b/runtime/csharp/Prompty.Core.Tests/Model/pipeline/RunTurnResultConversionTests.cs new file mode 100644 index 00000000..5884332f --- /dev/null +++ b/runtime/csharp/Prompty.Core.Tests/Model/pipeline/RunTurnResultConversionTests.cs @@ -0,0 +1,133 @@ +// +using Xunit; + +#pragma warning disable IDE0130 +namespace Prompty.Core; +#pragma warning restore IDE0130 + + +public class RunTurnResultConversionTests +{ + [Fact] + public void LoadYamlInput() + { + string yamlData = """ +sessionId: sess_abc123 +turnId: turn_abc123 +iterations: 1 + +"""; + + var instance = RunTurnResult.FromYaml(yamlData); + + Assert.NotNull(instance); + Assert.Equal("sess_abc123", instance.SessionId); + Assert.Equal("turn_abc123", instance.TurnId); + Assert.Equal(1, instance.Iterations); + } + + [Fact] + public void LoadJsonInput() + { + string jsonData = """ +{ + "sessionId": "sess_abc123", + "turnId": "turn_abc123", + "iterations": 1 +} +"""; + + var instance = RunTurnResult.FromJson(jsonData); + Assert.NotNull(instance); + Assert.Equal("sess_abc123", instance.SessionId); + Assert.Equal("turn_abc123", instance.TurnId); + Assert.Equal(1, instance.Iterations); + } + + [Fact] + public void RoundtripJson() + { + // Test that FromJson -> ToJson -> FromJson produces equivalent data + string jsonData = """ +{ + "sessionId": "sess_abc123", + "turnId": "turn_abc123", + "iterations": 1 +} +"""; + + var original = RunTurnResult.FromJson(jsonData); + Assert.NotNull(original); + + var json = original.ToJson(); + Assert.False(string.IsNullOrEmpty(json)); + + var reloaded = RunTurnResult.FromJson(json); + Assert.NotNull(reloaded); + Assert.Equal("sess_abc123", reloaded.SessionId); + Assert.Equal("turn_abc123", reloaded.TurnId); + Assert.Equal(1, reloaded.Iterations); + } + + [Fact] + public void RoundtripYaml() + { + // Test that FromYaml -> ToYaml -> FromYaml produces equivalent data + string yamlData = """ +sessionId: sess_abc123 +turnId: turn_abc123 +iterations: 1 + +"""; + + var original = RunTurnResult.FromYaml(yamlData); + Assert.NotNull(original); + + var yaml = original.ToYaml(); + Assert.False(string.IsNullOrEmpty(yaml)); + + var reloaded = RunTurnResult.FromYaml(yaml); + Assert.NotNull(reloaded); + Assert.Equal("sess_abc123", reloaded.SessionId); + Assert.Equal("turn_abc123", reloaded.TurnId); + Assert.Equal(1, reloaded.Iterations); + } + + [Fact] + public void ToJsonProducesValidJson() + { + string jsonData = """ +{ + "sessionId": "sess_abc123", + "turnId": "turn_abc123", + "iterations": 1 +} +"""; + + var instance = RunTurnResult.FromJson(jsonData); + var json = instance.ToJson(); + + // Verify it's valid JSON by parsing it + var parsed = System.Text.Json.JsonDocument.Parse(json); + Assert.NotNull(parsed); + } + + [Fact] + public void ToYamlProducesValidYaml() + { + string yamlData = """ +sessionId: sess_abc123 +turnId: turn_abc123 +iterations: 1 + +"""; + + var instance = RunTurnResult.FromYaml(yamlData); + var yaml = instance.ToYaml(); + + // Verify it's valid YAML by parsing it + var deserializer = new YamlDotNet.Serialization.DeserializerBuilder().Build(); + var parsed = deserializer.Deserialize(yaml); + Assert.NotNull(parsed); + } +} diff --git a/runtime/csharp/Prompty.Core.Tests/Model/pipeline/TurnModelRequestConversionTests.cs b/runtime/csharp/Prompty.Core.Tests/Model/pipeline/TurnModelRequestConversionTests.cs new file mode 100644 index 00000000..2b25d24a --- /dev/null +++ b/runtime/csharp/Prompty.Core.Tests/Model/pipeline/TurnModelRequestConversionTests.cs @@ -0,0 +1,133 @@ +// +using Xunit; + +#pragma warning disable IDE0130 +namespace Prompty.Core; +#pragma warning restore IDE0130 + + +public class TurnModelRequestConversionTests +{ + [Fact] + public void LoadYamlInput() + { + string yamlData = """ +sessionId: sess_abc123 +turnId: turn_abc123 +iteration: 0 + +"""; + + var instance = TurnModelRequest.FromYaml(yamlData); + + Assert.NotNull(instance); + Assert.Equal("sess_abc123", instance.SessionId); + Assert.Equal("turn_abc123", instance.TurnId); + Assert.Equal(0, instance.Iteration); + } + + [Fact] + public void LoadJsonInput() + { + string jsonData = """ +{ + "sessionId": "sess_abc123", + "turnId": "turn_abc123", + "iteration": 0 +} +"""; + + var instance = TurnModelRequest.FromJson(jsonData); + Assert.NotNull(instance); + Assert.Equal("sess_abc123", instance.SessionId); + Assert.Equal("turn_abc123", instance.TurnId); + Assert.Equal(0, instance.Iteration); + } + + [Fact] + public void RoundtripJson() + { + // Test that FromJson -> ToJson -> FromJson produces equivalent data + string jsonData = """ +{ + "sessionId": "sess_abc123", + "turnId": "turn_abc123", + "iteration": 0 +} +"""; + + var original = TurnModelRequest.FromJson(jsonData); + Assert.NotNull(original); + + var json = original.ToJson(); + Assert.False(string.IsNullOrEmpty(json)); + + var reloaded = TurnModelRequest.FromJson(json); + Assert.NotNull(reloaded); + Assert.Equal("sess_abc123", reloaded.SessionId); + Assert.Equal("turn_abc123", reloaded.TurnId); + Assert.Equal(0, reloaded.Iteration); + } + + [Fact] + public void RoundtripYaml() + { + // Test that FromYaml -> ToYaml -> FromYaml produces equivalent data + string yamlData = """ +sessionId: sess_abc123 +turnId: turn_abc123 +iteration: 0 + +"""; + + var original = TurnModelRequest.FromYaml(yamlData); + Assert.NotNull(original); + + var yaml = original.ToYaml(); + Assert.False(string.IsNullOrEmpty(yaml)); + + var reloaded = TurnModelRequest.FromYaml(yaml); + Assert.NotNull(reloaded); + Assert.Equal("sess_abc123", reloaded.SessionId); + Assert.Equal("turn_abc123", reloaded.TurnId); + Assert.Equal(0, reloaded.Iteration); + } + + [Fact] + public void ToJsonProducesValidJson() + { + string jsonData = """ +{ + "sessionId": "sess_abc123", + "turnId": "turn_abc123", + "iteration": 0 +} +"""; + + var instance = TurnModelRequest.FromJson(jsonData); + var json = instance.ToJson(); + + // Verify it's valid JSON by parsing it + var parsed = System.Text.Json.JsonDocument.Parse(json); + Assert.NotNull(parsed); + } + + [Fact] + public void ToYamlProducesValidYaml() + { + string yamlData = """ +sessionId: sess_abc123 +turnId: turn_abc123 +iteration: 0 + +"""; + + var instance = TurnModelRequest.FromYaml(yamlData); + var yaml = instance.ToYaml(); + + // Verify it's valid YAML by parsing it + var deserializer = new YamlDotNet.Serialization.DeserializerBuilder().Build(); + var parsed = deserializer.Deserialize(yaml); + Assert.NotNull(parsed); + } +} diff --git a/runtime/csharp/Prompty.Core.Tests/Model/pipeline/TurnModelResponseConversionTests.cs b/runtime/csharp/Prompty.Core.Tests/Model/pipeline/TurnModelResponseConversionTests.cs new file mode 100644 index 00000000..5c1c6a38 --- /dev/null +++ b/runtime/csharp/Prompty.Core.Tests/Model/pipeline/TurnModelResponseConversionTests.cs @@ -0,0 +1,11 @@ +// +using Xunit; + +#pragma warning disable IDE0130 +namespace Prompty.Core; +#pragma warning restore IDE0130 + + +public class TurnModelResponseConversionTests +{ +} diff --git a/runtime/csharp/Prompty.Core.Tests/Model/pipeline/TurnOptionsConversionTests.cs b/runtime/csharp/Prompty.Core.Tests/Model/pipeline/TurnOptionsConversionTests.cs index 901a5bc2..6df3c3d4 100644 --- a/runtime/csharp/Prompty.Core.Tests/Model/pipeline/TurnOptionsConversionTests.cs +++ b/runtime/csharp/Prompty.Core.Tests/Model/pipeline/TurnOptionsConversionTests.cs @@ -1,3 +1,4 @@ +// using Xunit; #pragma warning disable IDE0130 diff --git a/runtime/csharp/Prompty.Core.Tests/Model/streaming/StreamOptionsConversionTests.cs b/runtime/csharp/Prompty.Core.Tests/Model/streaming/StreamOptionsConversionTests.cs index ddf23e47..bc96f118 100644 --- a/runtime/csharp/Prompty.Core.Tests/Model/streaming/StreamOptionsConversionTests.cs +++ b/runtime/csharp/Prompty.Core.Tests/Model/streaming/StreamOptionsConversionTests.cs @@ -1,3 +1,4 @@ +// using Xunit; #pragma warning disable IDE0130 diff --git a/runtime/csharp/Prompty.Core.Tests/Model/template/FormatConfigConversionTests.cs b/runtime/csharp/Prompty.Core.Tests/Model/template/FormatConfigConversionTests.cs index b93f51bf..e2a2d0c4 100644 --- a/runtime/csharp/Prompty.Core.Tests/Model/template/FormatConfigConversionTests.cs +++ b/runtime/csharp/Prompty.Core.Tests/Model/template/FormatConfigConversionTests.cs @@ -1,3 +1,4 @@ +// using Xunit; #pragma warning disable IDE0130 diff --git a/runtime/csharp/Prompty.Core.Tests/Model/template/ParserConfigConversionTests.cs b/runtime/csharp/Prompty.Core.Tests/Model/template/ParserConfigConversionTests.cs index 1f2edafe..89322a25 100644 --- a/runtime/csharp/Prompty.Core.Tests/Model/template/ParserConfigConversionTests.cs +++ b/runtime/csharp/Prompty.Core.Tests/Model/template/ParserConfigConversionTests.cs @@ -1,3 +1,4 @@ +// using Xunit; #pragma warning disable IDE0130 diff --git a/runtime/csharp/Prompty.Core.Tests/Model/template/TemplateConversionTests.cs b/runtime/csharp/Prompty.Core.Tests/Model/template/TemplateConversionTests.cs index 695bbc8c..d2d8ac67 100644 --- a/runtime/csharp/Prompty.Core.Tests/Model/template/TemplateConversionTests.cs +++ b/runtime/csharp/Prompty.Core.Tests/Model/template/TemplateConversionTests.cs @@ -1,3 +1,4 @@ +// using Xunit; #pragma warning disable IDE0130 diff --git a/runtime/csharp/Prompty.Core.Tests/Model/tools/BindingConversionTests.cs b/runtime/csharp/Prompty.Core.Tests/Model/tools/BindingConversionTests.cs index 5ae77e47..225c3d16 100644 --- a/runtime/csharp/Prompty.Core.Tests/Model/tools/BindingConversionTests.cs +++ b/runtime/csharp/Prompty.Core.Tests/Model/tools/BindingConversionTests.cs @@ -1,3 +1,4 @@ +// using Xunit; #pragma warning disable IDE0130 diff --git a/runtime/csharp/Prompty.Core.Tests/Model/tools/CustomToolConversionTests.cs b/runtime/csharp/Prompty.Core.Tests/Model/tools/CustomToolConversionTests.cs index 9586ab5e..3c76ef2e 100644 --- a/runtime/csharp/Prompty.Core.Tests/Model/tools/CustomToolConversionTests.cs +++ b/runtime/csharp/Prompty.Core.Tests/Model/tools/CustomToolConversionTests.cs @@ -1,3 +1,4 @@ +// using Xunit; #pragma warning disable IDE0130 diff --git a/runtime/csharp/Prompty.Core.Tests/Model/tools/FunctionToolConversionTests.cs b/runtime/csharp/Prompty.Core.Tests/Model/tools/FunctionToolConversionTests.cs index 079b3c9c..a62d759a 100644 --- a/runtime/csharp/Prompty.Core.Tests/Model/tools/FunctionToolConversionTests.cs +++ b/runtime/csharp/Prompty.Core.Tests/Model/tools/FunctionToolConversionTests.cs @@ -1,3 +1,4 @@ +// using Xunit; #pragma warning disable IDE0130 diff --git a/runtime/csharp/Prompty.Core.Tests/Model/tools/McpApprovalModeConversionTests.cs b/runtime/csharp/Prompty.Core.Tests/Model/tools/McpApprovalModeConversionTests.cs index 0ccda745..98a09769 100644 --- a/runtime/csharp/Prompty.Core.Tests/Model/tools/McpApprovalModeConversionTests.cs +++ b/runtime/csharp/Prompty.Core.Tests/Model/tools/McpApprovalModeConversionTests.cs @@ -1,3 +1,4 @@ +// using Xunit; #pragma warning disable IDE0130 diff --git a/runtime/csharp/Prompty.Core.Tests/Model/tools/McpToolConversionTests.cs b/runtime/csharp/Prompty.Core.Tests/Model/tools/McpToolConversionTests.cs index 5fec1d09..fc10a3a5 100644 --- a/runtime/csharp/Prompty.Core.Tests/Model/tools/McpToolConversionTests.cs +++ b/runtime/csharp/Prompty.Core.Tests/Model/tools/McpToolConversionTests.cs @@ -1,3 +1,4 @@ +// using Xunit; #pragma warning disable IDE0130 diff --git a/runtime/csharp/Prompty.Core.Tests/Model/tools/OpenApiToolConversionTests.cs b/runtime/csharp/Prompty.Core.Tests/Model/tools/OpenApiToolConversionTests.cs index e1678895..b9df13c5 100644 --- a/runtime/csharp/Prompty.Core.Tests/Model/tools/OpenApiToolConversionTests.cs +++ b/runtime/csharp/Prompty.Core.Tests/Model/tools/OpenApiToolConversionTests.cs @@ -1,3 +1,4 @@ +// using Xunit; #pragma warning disable IDE0130 diff --git a/runtime/csharp/Prompty.Core.Tests/Model/tools/PromptyToolConversionTests.cs b/runtime/csharp/Prompty.Core.Tests/Model/tools/PromptyToolConversionTests.cs index 15745977..99f20b42 100644 --- a/runtime/csharp/Prompty.Core.Tests/Model/tools/PromptyToolConversionTests.cs +++ b/runtime/csharp/Prompty.Core.Tests/Model/tools/PromptyToolConversionTests.cs @@ -1,3 +1,4 @@ +// using Xunit; #pragma warning disable IDE0130 diff --git a/runtime/csharp/Prompty.Core.Tests/Model/tools/ToolContextConversionTests.cs b/runtime/csharp/Prompty.Core.Tests/Model/tools/ToolContextConversionTests.cs index beee477a..76a8d83d 100644 --- a/runtime/csharp/Prompty.Core.Tests/Model/tools/ToolContextConversionTests.cs +++ b/runtime/csharp/Prompty.Core.Tests/Model/tools/ToolContextConversionTests.cs @@ -1,3 +1,4 @@ +// using Xunit; #pragma warning disable IDE0130 diff --git a/runtime/csharp/Prompty.Core.Tests/Model/tools/ToolConversionTests.cs b/runtime/csharp/Prompty.Core.Tests/Model/tools/ToolConversionTests.cs index 3ffb95e5..b08fec57 100644 --- a/runtime/csharp/Prompty.Core.Tests/Model/tools/ToolConversionTests.cs +++ b/runtime/csharp/Prompty.Core.Tests/Model/tools/ToolConversionTests.cs @@ -1,3 +1,4 @@ +// using Xunit; #pragma warning disable IDE0130 diff --git a/runtime/csharp/Prompty.Core.Tests/Model/tools/ToolDispatchResultConversionTests.cs b/runtime/csharp/Prompty.Core.Tests/Model/tools/ToolDispatchResultConversionTests.cs index f442e5d8..387b7018 100644 --- a/runtime/csharp/Prompty.Core.Tests/Model/tools/ToolDispatchResultConversionTests.cs +++ b/runtime/csharp/Prompty.Core.Tests/Model/tools/ToolDispatchResultConversionTests.cs @@ -1,3 +1,4 @@ +// using Xunit; #pragma warning disable IDE0130 diff --git a/runtime/csharp/Prompty.Core.Tests/Model/tracing/TraceFileConversionTests.cs b/runtime/csharp/Prompty.Core.Tests/Model/tracing/TraceFileConversionTests.cs index 67ae4d98..66dac13a 100644 --- a/runtime/csharp/Prompty.Core.Tests/Model/tracing/TraceFileConversionTests.cs +++ b/runtime/csharp/Prompty.Core.Tests/Model/tracing/TraceFileConversionTests.cs @@ -1,3 +1,4 @@ +// using Xunit; #pragma warning disable IDE0130 diff --git a/runtime/csharp/Prompty.Core.Tests/Model/tracing/TraceSpanConversionTests.cs b/runtime/csharp/Prompty.Core.Tests/Model/tracing/TraceSpanConversionTests.cs index 23b18fca..249576ed 100644 --- a/runtime/csharp/Prompty.Core.Tests/Model/tracing/TraceSpanConversionTests.cs +++ b/runtime/csharp/Prompty.Core.Tests/Model/tracing/TraceSpanConversionTests.cs @@ -1,3 +1,4 @@ +// using Xunit; #pragma warning disable IDE0130 diff --git a/runtime/csharp/Prompty.Core.Tests/Model/tracing/TraceTimeConversionTests.cs b/runtime/csharp/Prompty.Core.Tests/Model/tracing/TraceTimeConversionTests.cs index 00cbd26e..b851d49a 100644 --- a/runtime/csharp/Prompty.Core.Tests/Model/tracing/TraceTimeConversionTests.cs +++ b/runtime/csharp/Prompty.Core.Tests/Model/tracing/TraceTimeConversionTests.cs @@ -1,3 +1,4 @@ +// using Xunit; #pragma warning disable IDE0130 diff --git a/runtime/csharp/Prompty.Core.Tests/Model/wire/AnthropicImageBlockConversionTests.cs b/runtime/csharp/Prompty.Core.Tests/Model/wire/AnthropicImageBlockConversionTests.cs index dee7235c..1784c108 100644 --- a/runtime/csharp/Prompty.Core.Tests/Model/wire/AnthropicImageBlockConversionTests.cs +++ b/runtime/csharp/Prompty.Core.Tests/Model/wire/AnthropicImageBlockConversionTests.cs @@ -1,3 +1,4 @@ +// using Xunit; #pragma warning disable IDE0130 diff --git a/runtime/csharp/Prompty.Core.Tests/Model/wire/AnthropicImageSourceConversionTests.cs b/runtime/csharp/Prompty.Core.Tests/Model/wire/AnthropicImageSourceConversionTests.cs index c5cedd30..4a223505 100644 --- a/runtime/csharp/Prompty.Core.Tests/Model/wire/AnthropicImageSourceConversionTests.cs +++ b/runtime/csharp/Prompty.Core.Tests/Model/wire/AnthropicImageSourceConversionTests.cs @@ -1,3 +1,4 @@ +// using Xunit; #pragma warning disable IDE0130 diff --git a/runtime/csharp/Prompty.Core.Tests/Model/wire/AnthropicMessagesRequestConversionTests.cs b/runtime/csharp/Prompty.Core.Tests/Model/wire/AnthropicMessagesRequestConversionTests.cs index a425a7d5..3a823573 100644 --- a/runtime/csharp/Prompty.Core.Tests/Model/wire/AnthropicMessagesRequestConversionTests.cs +++ b/runtime/csharp/Prompty.Core.Tests/Model/wire/AnthropicMessagesRequestConversionTests.cs @@ -1,3 +1,4 @@ +// using Xunit; #pragma warning disable IDE0130 diff --git a/runtime/csharp/Prompty.Core.Tests/Model/wire/AnthropicMessagesResponseConversionTests.cs b/runtime/csharp/Prompty.Core.Tests/Model/wire/AnthropicMessagesResponseConversionTests.cs index 47d6e93a..7b252b09 100644 --- a/runtime/csharp/Prompty.Core.Tests/Model/wire/AnthropicMessagesResponseConversionTests.cs +++ b/runtime/csharp/Prompty.Core.Tests/Model/wire/AnthropicMessagesResponseConversionTests.cs @@ -1,3 +1,4 @@ +// using Xunit; #pragma warning disable IDE0130 diff --git a/runtime/csharp/Prompty.Core.Tests/Model/wire/AnthropicTextBlockConversionTests.cs b/runtime/csharp/Prompty.Core.Tests/Model/wire/AnthropicTextBlockConversionTests.cs index e516e507..ca4336d4 100644 --- a/runtime/csharp/Prompty.Core.Tests/Model/wire/AnthropicTextBlockConversionTests.cs +++ b/runtime/csharp/Prompty.Core.Tests/Model/wire/AnthropicTextBlockConversionTests.cs @@ -1,3 +1,4 @@ +// using Xunit; #pragma warning disable IDE0130 diff --git a/runtime/csharp/Prompty.Core.Tests/Model/wire/AnthropicToolDefinitionConversionTests.cs b/runtime/csharp/Prompty.Core.Tests/Model/wire/AnthropicToolDefinitionConversionTests.cs index 9a1b9960..5851652f 100644 --- a/runtime/csharp/Prompty.Core.Tests/Model/wire/AnthropicToolDefinitionConversionTests.cs +++ b/runtime/csharp/Prompty.Core.Tests/Model/wire/AnthropicToolDefinitionConversionTests.cs @@ -1,3 +1,4 @@ +// using Xunit; #pragma warning disable IDE0130 diff --git a/runtime/csharp/Prompty.Core.Tests/Model/wire/AnthropicToolResultBlockConversionTests.cs b/runtime/csharp/Prompty.Core.Tests/Model/wire/AnthropicToolResultBlockConversionTests.cs index 8de7c2c9..c048595a 100644 --- a/runtime/csharp/Prompty.Core.Tests/Model/wire/AnthropicToolResultBlockConversionTests.cs +++ b/runtime/csharp/Prompty.Core.Tests/Model/wire/AnthropicToolResultBlockConversionTests.cs @@ -1,3 +1,4 @@ +// using Xunit; #pragma warning disable IDE0130 diff --git a/runtime/csharp/Prompty.Core.Tests/Model/wire/AnthropicToolUseBlockConversionTests.cs b/runtime/csharp/Prompty.Core.Tests/Model/wire/AnthropicToolUseBlockConversionTests.cs index 114774c5..9340190c 100644 --- a/runtime/csharp/Prompty.Core.Tests/Model/wire/AnthropicToolUseBlockConversionTests.cs +++ b/runtime/csharp/Prompty.Core.Tests/Model/wire/AnthropicToolUseBlockConversionTests.cs @@ -1,3 +1,4 @@ +// using Xunit; #pragma warning disable IDE0130 diff --git a/runtime/csharp/Prompty.Core.Tests/Model/wire/AnthropicUsageConversionTests.cs b/runtime/csharp/Prompty.Core.Tests/Model/wire/AnthropicUsageConversionTests.cs index 4c54a2ed..66bced5a 100644 --- a/runtime/csharp/Prompty.Core.Tests/Model/wire/AnthropicUsageConversionTests.cs +++ b/runtime/csharp/Prompty.Core.Tests/Model/wire/AnthropicUsageConversionTests.cs @@ -1,3 +1,4 @@ +// using Xunit; #pragma warning disable IDE0130 diff --git a/runtime/csharp/Prompty.Core.Tests/Model/wire/AnthropicWireMessageConversionTests.cs b/runtime/csharp/Prompty.Core.Tests/Model/wire/AnthropicWireMessageConversionTests.cs index 57806912..2999434b 100644 --- a/runtime/csharp/Prompty.Core.Tests/Model/wire/AnthropicWireMessageConversionTests.cs +++ b/runtime/csharp/Prompty.Core.Tests/Model/wire/AnthropicWireMessageConversionTests.cs @@ -1,3 +1,4 @@ +// using Xunit; #pragma warning disable IDE0130 diff --git a/runtime/csharp/Prompty.Core.Tests/ReplayVerifierTests.cs b/runtime/csharp/Prompty.Core.Tests/ReplayVerifierTests.cs new file mode 100644 index 00000000..51164725 --- /dev/null +++ b/runtime/csharp/Prompty.Core.Tests/ReplayVerifierTests.cs @@ -0,0 +1,62 @@ +// Copyright (c) Microsoft. All rights reserved. + +using Prompty.Core; + +namespace Prompty.Core.Tests; + +public class ReplayVerifierTests +{ + [Fact] + public void ReferenceReplayVerifier_PassesIdenticalRecords() + { + var record = new ReplayJournalRecord + { + Kind = ReplayRecordKind.Turn, + Type = "turn_end", + TurnId = "turn-1", + Iteration = 1, + Status = ReplayRecordStatus.Success + }; + + var result = new ReferenceReplayVerifier().Verify(new ReplayVerificationRequest + { + Expected = [record], + Actual = [record] + }); + + Assert.Equal(ReplayVerificationStatus.Passed, result.Status); + Assert.Empty(result.Mismatches!); + Assert.Equal(1, result.ExpectedCount); + Assert.Equal(1, result.ActualCount); + } + + [Fact] + public void ReferenceReplayVerifier_ReportsMismatches() + { + var result = new ReferenceReplayVerifier().Verify(new ReplayVerificationRequest + { + Expected = + [ + new ReplayJournalRecord + { + Kind = ReplayRecordKind.Summary, + SessionId = "session-1", + Status = ReplayRecordStatus.Success + } + ], + Actual = + [ + new ReplayJournalRecord + { + Kind = ReplayRecordKind.Summary, + SessionId = "session-1", + Status = ReplayRecordStatus.Error + } + ] + }); + + Assert.Equal(ReplayVerificationStatus.Failed, result.Status); + Assert.Equal(0, result.Mismatches![0].Index); + Assert.Equal("Replay record mismatch", result.Mismatches![0].Message); + } +} diff --git a/runtime/csharp/Prompty.Core.Tests/TurnRunnerTests.cs b/runtime/csharp/Prompty.Core.Tests/TurnRunnerTests.cs new file mode 100644 index 00000000..82d8ead4 --- /dev/null +++ b/runtime/csharp/Prompty.Core.Tests/TurnRunnerTests.cs @@ -0,0 +1,463 @@ +// Copyright (c) Microsoft. All rights reserved. + +using System.Text.Json; +using Prompty.Core; + +namespace Prompty.Core.Tests; + +public class TurnRunnerTests +{ + [Fact] + public async Task ReferenceTurnRunner_EmitsJournalsAndCheckpoints() + { + var directory = Directory.CreateTempSubdirectory("prompty-turn-"); + try + { + var journalPath = Path.Combine(directory.FullName, "trace.jsonl"); + var sink = new CollectingEventSink(); + var checkpointStore = new InMemoryCheckpointStore(); + var runner = new ReferenceTurnRunner( + sink, + new JsonlEventJournalWriter(journalPath), + checkpointStore, + new AllowAllPermissionResolver(), + new FunctionHostToolExecutor(new Dictionary()), + request => Task.FromResult(new TurnModelResponse + { + Output = new Dictionary { ["text"] = $"hello {request.Inputs["name"]}" }, + CheckpointState = new Dictionary { ["stable"] = true } + }), + FixedClock(), + FixedIds()); + + var result = await runner.RunAsync(new RunTurnRequest + { + SessionId = "session-1", + TurnId = "turn-1", + Inputs = new Dictionary { ["name"] = "Ada" }, + Options = new TurnOptions { MaxIterations = 3 } + }); + + Assert.Equal(RunTurnStatus.Success, result.Status); + Assert.Equal(1, result.Iterations); + Assert.Equal("hello Ada", Assert.IsType>(result.Output)["text"]); + Assert.Equal( + [TurnEventType.TurnStart, TurnEventType.LlmStart, TurnEventType.LlmComplete, TurnEventType.TurnEnd], + sink.TurnEvents.Select(turnEvent => turnEvent.Type).ToArray()); + Assert.Equal( + [SessionEventType.SessionStart, SessionEventType.CheckpointCreated, SessionEventType.SessionEnd], + sink.SessionEvents.Select(sessionEvent => sessionEvent.Type).ToArray()); + var checkpoint = await checkpointStore.LoadAsync("session-1", "turn-1-checkpoint-0"); + Assert.NotNull(checkpoint); + Assert.True((bool)checkpoint.State!["stable"]); + Assert.Equal( + ["session", "turn", "turn", "turn", "session", "turn", "session", "summary"], + JournalKinds(journalPath)); + } + finally + { + directory.Delete(recursive: true); + } + } + + [Fact] + public async Task ReferenceTurnRunner_ExecutesHostTools() + { + var directory = Directory.CreateTempSubdirectory("prompty-turn-tool-"); + try + { + var sink = new CollectingEventSink(); + var runner = new ReferenceTurnRunner( + sink, + new JsonlEventJournalWriter(Path.Combine(directory.FullName, "trace.jsonl")), + new InMemoryCheckpointStore(), + new AllowAllPermissionResolver(), + new FunctionHostToolExecutor(new Dictionary + { + ["add"] = (args, _) => Task.FromResult(Convert.ToInt32(args["a"]) + Convert.ToInt32(args["b"])) + }), + request => + { + if (request.Iteration == 0) + { + return Task.FromResult(new TurnModelResponse + { + ToolRequests = + [ + new HostToolRequest + { + RequestId = "exec-1", + ToolCallId = "call-1", + ToolName = "add", + Arguments = new Dictionary { ["a"] = 2, ["b"] = 3 } + } + ] + }); + } + + return Task.FromResult(new TurnModelResponse + { + Output = new Dictionary { ["toolResult"] = request.ToolResults[0].Result } + }); + }, + FixedClock(), + FixedIds()); + + var result = await runner.RunAsync(new RunTurnRequest { SessionId = "session-1", TurnId = "turn-1" }); + + Assert.Equal(5, Assert.IsType>(result.Output)["toolResult"]); + Assert.True(result.ToolResults[0].Success); + Assert.Equal( + [ + TurnEventType.TurnStart, + TurnEventType.LlmStart, + TurnEventType.LlmComplete, + TurnEventType.PermissionRequested, + TurnEventType.PermissionCompleted, + TurnEventType.ToolExecutionStart, + TurnEventType.ToolExecutionComplete, + TurnEventType.ToolResult, + TurnEventType.MessagesUpdated, + TurnEventType.LlmStart, + TurnEventType.LlmComplete, + TurnEventType.TurnEnd + ], + sink.TurnEvents.Select(turnEvent => turnEvent.Type).ToArray()); + } + finally + { + directory.Delete(recursive: true); + } + } + + [Fact] + public async Task ReferenceTurnRunner_DeniedPermissionSkipsExecution() + { + var directory = Directory.CreateTempSubdirectory("prompty-turn-deny-"); + try + { + var sink = new CollectingEventSink(); + var runner = new ReferenceTurnRunner( + sink, + new JsonlEventJournalWriter(Path.Combine(directory.FullName, "trace.jsonl")), + new InMemoryCheckpointStore(), + new DenyAllPermissionResolver(), + new FunctionHostToolExecutor(new Dictionary + { + ["shell"] = (_, _) => throw new InvalidOperationException("should not execute") + }), + request => + { + if (request.Iteration == 0) + { + return Task.FromResult(new TurnModelResponse + { + ToolRequests = [new HostToolRequest { RequestId = "exec-1", ToolName = "shell" }] + }); + } + + return Task.FromResult(new TurnModelResponse + { + Output = new Dictionary { ["denied"] = request.ToolResults[0].ErrorKind } + }); + }, + FixedClock(), + FixedIds()); + + var result = await runner.RunAsync(new RunTurnRequest { SessionId = "session-1", TurnId = "turn-1" }); + + Assert.Equal("permission_denied", Assert.IsType>(result.Output)["denied"]); + Assert.DoesNotContain(sink.TurnEvents, turnEvent => turnEvent.Type == TurnEventType.ToolExecutionStart); + } + finally + { + directory.Delete(recursive: true); + } + } + + [Fact] + public async Task ReferenceTurnRunner_HostToolFailureIsReplayable() + { + var directory = Directory.CreateTempSubdirectory("prompty-turn-fail-"); + try + { + var runner = new ReferenceTurnRunner( + new CollectingEventSink(), + new JsonlEventJournalWriter(Path.Combine(directory.FullName, "trace.jsonl")), + new InMemoryCheckpointStore(), + new AllowAllPermissionResolver(), + new FunctionHostToolExecutor(new Dictionary + { + ["fail"] = (_, _) => throw new InvalidOperationException("boom") + }), + request => + { + if (request.Iteration == 0) + { + return Task.FromResult(new TurnModelResponse + { + ToolRequests = [new HostToolRequest { RequestId = "exec-1", ToolName = "fail" }] + }); + } + + return Task.FromResult(new TurnModelResponse { Output = request.ToolResults[0].Save() }); + }, + FixedClock(), + FixedIds()); + + var result = await runner.RunAsync(new RunTurnRequest { SessionId = "session-1", TurnId = "turn-1" }); + + var output = Assert.IsType>(result.Output); + Assert.Equal(false, output["success"]); + Assert.Equal("exception", output["errorKind"]); + } + finally + { + directory.Delete(recursive: true); + } + } + + [Fact] + public async Task ReferenceTurnRunner_DeterministicJournal() + { + var directory = Directory.CreateTempSubdirectory("prompty-turn-deterministic-"); + try + { + async Task RunOnce(string path) + { + var runner = new ReferenceTurnRunner( + new CollectingEventSink(), + new JsonlEventJournalWriter(path), + new InMemoryCheckpointStore(), + new AllowAllPermissionResolver(), + new FunctionHostToolExecutor(new Dictionary()), + _ => Task.FromResult(new TurnModelResponse { Output = "done" }), + FixedClock(), + FixedIds()); + await runner.RunAsync(new RunTurnRequest { SessionId = "session-1", TurnId = "turn-1" }); + return File.ReadAllText(path); + } + + Assert.Equal( + await RunOnce(Path.Combine(directory.FullName, "first.jsonl")), + await RunOnce(Path.Combine(directory.FullName, "second.jsonl"))); + } + finally + { + directory.Delete(recursive: true); + } + } + + [Fact] + public async Task ReferenceTurnRunner_MatchesSharedGoldenReplayVectors() + { + var vectors = ReplayVectors.Load(); + + foreach (var scenario in vectors.Scenarios) + { + var directory = Directory.CreateTempSubdirectory($"prompty-replay-{scenario.Name}-"); + try + { + var journalPath = Path.Combine(directory.FullName, "trace.jsonl"); + var runner = new ReferenceTurnRunner( + new CollectingEventSink(), + new JsonlEventJournalWriter(journalPath), + new InMemoryCheckpointStore(), + scenario.Name == "permission_denied" ? new DenyAllPermissionResolver() : new AllowAllPermissionResolver(), + new FunctionHostToolExecutor(new Dictionary + { + ["add"] = (args, _) => Task.FromResult(Convert.ToInt32(args["a"]) + Convert.ToInt32(args["b"])), + ["fail"] = (_, _) => throw new InvalidOperationException("boom") + }), + ModelForScenario(scenario.Name), + () => vectors.Clock, + FixedIds()); + + await runner.RunAsync(new RunTurnRequest + { + SessionId = vectors.SessionId, + TurnId = vectors.TurnId, + Inputs = scenario.Inputs ?? new Dictionary(), + Options = new TurnOptions { MaxIterations = scenario.MaxIterations } + }); + + Assert.Equal(scenario.Expected, NormalizeJournal(journalPath)); + } + finally + { + directory.Delete(recursive: true); + } + } + } + + private static Func FixedClock() => () => "2026-06-28T00:00:00Z"; + + private static Func FixedIds() + { + var index = 0; + return prefix => $"{prefix}-{++index}"; + } + + private static string[] JournalKinds(string path) + { + return File.ReadAllLines(path) + .Select(line => JsonSerializer.Deserialize>(line)!) + .Select(record => record["kind"].GetString()!) + .ToArray(); + } + + private static Func> ModelForScenario(string name) + { + return request => + { + if (name == "no_tool") + { + return Task.FromResult(new TurnModelResponse + { + Output = new Dictionary { ["text"] = $"hello {request.Inputs["name"]}" }, + CheckpointState = new Dictionary { ["stable"] = true } + }); + } + + if (request.Iteration == 0) + { + return Task.FromResult(new TurnModelResponse + { + ToolRequests = + [ + new HostToolRequest + { + RequestId = "exec-1", + ToolCallId = "call-1", + ToolName = name == "tool_failure" ? "fail" : "add", + Arguments = new Dictionary { ["a"] = 2, ["b"] = 3 } + } + ] + }); + } + + return Task.FromResult(new TurnModelResponse + { + Output = new Dictionary + { + ["toolResult"] = request.ToolResults[0].Result, + ["errorKind"] = request.ToolResults[0].ErrorKind + } + }); + }; + } + + private static string[] NormalizeJournal(string path) + { + return File.ReadAllLines(path) + .Select(line => JsonSerializer.Deserialize(line)) + .Select(NormalizeRecord) + .ToArray(); + } + + private static string NormalizeRecord(JsonElement record) + { + if (record.GetProperty("kind").GetString() == "summary") + { + var summary = record.GetProperty("summary"); + return $"summary:{summary.GetProperty("sessionId").GetString()}:{NormalizeStatus(summary.GetProperty("status"))}:turns={summary.GetProperty("turns").GetInt32()}:checkpoints={summary.GetProperty("checkpoints").GetInt32()}"; + } + + var eventElement = record.GetProperty("event"); + var type = eventElement.GetProperty("type").GetString()!; + if (record.GetProperty("kind").GetString() == "session") + { + if (type == "session_end") + { + return $"session:{type}:{eventElement.GetProperty("sessionId").GetString()}:{eventElement.GetProperty("turnId").GetString()}:{NormalizeStatus(eventElement.GetProperty("payload").GetProperty("status"))}"; + } + + return $"session:{type}:{eventElement.GetProperty("sessionId").GetString()}:{eventElement.GetProperty("turnId").GetString()}"; + } + + var iteration = eventElement.GetProperty("iteration").GetInt32(); + var payload = eventElement.GetProperty("payload"); + return type switch + { + "permission_requested" => $"turn:{type}:{iteration}:{payload.GetProperty("requestId").GetString()}", + "permission_completed" => $"turn:{type}:{iteration}:{payload.GetProperty("approved").GetBoolean().ToString().ToLowerInvariant()}", + "tool_execution_start" => $"turn:{type}:{iteration}:{payload.GetProperty("toolName").GetString()}", + "tool_execution_complete" or "tool_result" => NormalizeToolResult(type, iteration, payload), + "error" => $"turn:{type}:{iteration}:{payload.GetProperty("errorKind").GetString()}", + "turn_end" => $"turn:{type}:{iteration}:{NormalizeStatus(payload.GetProperty("status"))}", + _ => $"turn:{type}:{iteration}" + }; + } + + private static string NormalizeToolResult(string type, int iteration, JsonElement payload) + { + var value = $"turn:{type}:{iteration}:{payload.GetProperty("toolName").GetString()}:{payload.GetProperty("success").GetBoolean().ToString().ToLowerInvariant()}"; + return payload.TryGetProperty("errorKind", out var errorKind) && errorKind.ValueKind != JsonValueKind.Null + ? $"{value}:{errorKind.GetString()}" + : value; + } + + private static string NormalizeStatus(JsonElement value) + { + return value.ValueKind == JsonValueKind.String + ? value.GetString()!.ToLowerInvariant() + : value.ToString().ToLowerInvariant(); + } + + private sealed record ReplayVectors( + int Version, + string Clock, + string SessionId, + string TurnId, + IReadOnlyList Scenarios) + { + public static ReplayVectors Load() + { + var path = Path.GetFullPath(Path.Combine(AppContext.BaseDirectory, "..", "..", "..", "..", "..", "..", "spec", "vectors", "harness", "replay_vectors.json")); + var document = JsonDocument.Parse(File.ReadAllText(path)); + var root = document.RootElement; + Assert.Equal(1, root.GetProperty("version").GetInt32()); + return new ReplayVectors( + root.GetProperty("version").GetInt32(), + root.GetProperty("clock").GetString()!, + root.GetProperty("sessionId").GetString()!, + root.GetProperty("turnId").GetString()!, + root.GetProperty("scenarios").EnumerateArray().Select(ReplayScenario.Load).ToList()); + } + } + + private sealed record ReplayScenario( + string Name, + Dictionary? Inputs, + int? MaxIterations, + string[] Expected) + { + public static ReplayScenario Load(JsonElement element) + { + return new ReplayScenario( + element.GetProperty("name").GetString()!, + element.TryGetProperty("inputs", out var inputs) ? ToDictionary(inputs) : null, + element.TryGetProperty("maxIterations", out var maxIterations) ? maxIterations.GetInt32() : null, + element.GetProperty("expected").EnumerateArray().Select(item => item.GetString()!).ToArray()); + } + } + + private static Dictionary ToDictionary(JsonElement element) + { + return element.EnumerateObject().ToDictionary(property => property.Name, property => ToObject(property.Value)!); + } + + private static object? ToObject(JsonElement element) + { + return element.ValueKind switch + { + JsonValueKind.String => element.GetString(), + JsonValueKind.Number => element.TryGetInt32(out var intValue) ? intValue : element.GetDouble(), + JsonValueKind.True => true, + JsonValueKind.False => false, + JsonValueKind.Object => ToDictionary(element), + JsonValueKind.Array => element.EnumerateArray().Select(ToObject).ToList(), + _ => null + }; + } +} diff --git a/runtime/csharp/Prompty.Core/AgentEvents.cs b/runtime/csharp/Prompty.Core/AgentEvents.cs index f2a272cd..b7172acd 100644 --- a/runtime/csharp/Prompty.Core/AgentEvents.cs +++ b/runtime/csharp/Prompty.Core/AgentEvents.cs @@ -5,9 +5,17 @@ namespace Prompty.Core; /// §13.1 Agent loop event types. public enum AgentEventType { + TurnStart, + TurnEnd, + LlmStart, + LlmComplete, + Retry, + PermissionRequested, + PermissionCompleted, Token, Thinking, ToolCallStart, + ToolCallComplete, ToolResult, Status, MessagesUpdated, @@ -34,7 +42,18 @@ public static void EmitEvent(EventCallback? callback, AgentEventType eventType, if (callback is null) return; try { - callback(eventType, data); + var eventData = new Dictionary(data) + { + ["turnEvent"] = new Dictionary + { + ["id"] = $"evt_{Guid.NewGuid():N}", + ["type"] = ToWireName(eventType), + ["timestamp"] = DateTimeOffset.UtcNow.ToString("O"), + ["iteration"] = data.TryGetValue("iteration", out var iteration) ? iteration : null, + ["payload"] = data, + }, + }; + callback(eventType, eventData); } catch (Exception ex) { @@ -42,4 +61,23 @@ public static void EmitEvent(EventCallback? callback, AgentEventType eventType, System.Diagnostics.Debug.WriteLine($"Event callback error for {eventType}: {ex.Message}"); } } + + private static string ToWireName(AgentEventType eventType) => eventType switch + { + AgentEventType.TurnStart => "turn_start", + AgentEventType.TurnEnd => "turn_end", + AgentEventType.LlmStart => "llm_start", + AgentEventType.LlmComplete => "llm_complete", + AgentEventType.Retry => "retry", + AgentEventType.PermissionRequested => "permission_requested", + AgentEventType.PermissionCompleted => "permission_completed", + AgentEventType.ToolCallStart => "tool_call_start", + AgentEventType.ToolCallComplete => "tool_call_complete", + AgentEventType.ToolResult => "tool_result", + AgentEventType.MessagesUpdated => "messages_updated", + AgentEventType.CompactionStart => "compaction_start", + AgentEventType.CompactionComplete => "compaction_complete", + AgentEventType.CompactionFailed => "compaction_failed", + _ => eventType.ToString().ToLowerInvariant(), + }; } diff --git a/runtime/csharp/Prompty.Core/HarnessAdapters.cs b/runtime/csharp/Prompty.Core/HarnessAdapters.cs new file mode 100644 index 00000000..ef034875 --- /dev/null +++ b/runtime/csharp/Prompty.Core/HarnessAdapters.cs @@ -0,0 +1,295 @@ +// Copyright (c) Microsoft. All rights reserved. + +using System.Diagnostics; +using System.Reflection; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace Prompty.Core; + +/// +/// Captures emitted turn and session events in memory. +/// +public sealed class CollectingEventSink : IEventSink +{ + private readonly object _lock = new(); + private readonly List _turnEvents = []; + private readonly List _sessionEvents = []; + + public List TurnEvents + { + get + { + lock (_lock) + { + return [.. _turnEvents]; + } + } + } + + public List SessionEvents + { + get + { + lock (_lock) + { + return [.. _sessionEvents]; + } + } + } + + public bool EmitTurn(TurnEvent turnEvent) + { + lock (_lock) + { + _turnEvents.Add(turnEvent); + } + return true; + } + + public bool EmitSession(SessionEvent sessionEvent) + { + lock (_lock) + { + _sessionEvents.Add(sessionEvent); + } + return true; + } +} + +/// +/// Appends replayable event journal records as newline-delimited JSON. +/// +public sealed class JsonlEventJournalWriter : IEventJournalWriter +{ + private readonly object _lock = new(); + private readonly string _path; + private bool _closed; + + public JsonlEventJournalWriter(string path) + { + _path = path; + var directory = Path.GetDirectoryName(path); + if (!string.IsNullOrEmpty(directory)) + { + Directory.CreateDirectory(directory); + } + } + + public bool AppendTurn(TurnEvent turnEvent) + { + return Write(new Dictionary + { + ["kind"] = "turn", + ["event"] = SaveEvent(turnEvent) + }); + } + + public bool AppendSession(SessionEvent sessionEvent) + { + return Write(new Dictionary + { + ["kind"] = "session", + ["event"] = SaveEvent(sessionEvent) + }); + } + + public bool Close(SessionSummary? summary) + { + if (summary is not null) + { + if (!Write(new Dictionary + { + ["kind"] = "summary", + ["summary"] = summary.Save() + })) + { + return false; + } + } + _closed = true; + return true; + } + + private bool Write(Dictionary record) + { + lock (_lock) + { + if (_closed) + { + return false; + } + File.AppendAllText(_path, JsonSerializer.Serialize(record) + "\n"); + return true; + } + } + + private static Dictionary SaveEvent(TurnEvent turnEvent) + { + var saved = turnEvent.Save(); + saved["type"] = JsonEnumName(turnEvent.Type); + return saved; + } + + private static Dictionary SaveEvent(SessionEvent sessionEvent) + { + var saved = sessionEvent.Save(); + saved["type"] = JsonEnumName(sessionEvent.Type); + return saved; + } + + private static string JsonEnumName(TEnum value) + where TEnum : struct, Enum + { + var member = typeof(TEnum).GetMember(value.ToString()).SingleOrDefault(); + return member?.GetCustomAttribute()?.Name ?? value.ToString().ToLowerInvariant(); + } +} + +/// +/// Stores checkpoints in memory by session and checkpoint identifier. +/// +public sealed class InMemoryCheckpointStore : ICheckpointStore +{ + private readonly object _lock = new(); + private readonly Dictionary<(string SessionId, string CheckpointId), Checkpoint> _checkpoints = []; + + public Task SaveAsync(Checkpoint checkpoint) + { + var key = RequireCheckpointKey(checkpoint); + lock (_lock) + { + _checkpoints[key] = checkpoint; + } + return Task.FromResult(checkpoint); + } + + public Task LoadAsync(string sessionId, string checkpointId) + { + Checkpoint? checkpoint; + lock (_lock) + { + _checkpoints.TryGetValue((sessionId, checkpointId), out checkpoint); + } + return Task.FromResult(checkpoint); + } + + public Task> ListCheckpointsAsync(string sessionId) + { + List checkpoints; + lock (_lock) + { + checkpoints = _checkpoints.Values.Where(checkpoint => checkpoint.SessionId == sessionId).ToList(); + } + return Task.FromResult(checkpoints); + } + + private static (string SessionId, string CheckpointId) RequireCheckpointKey(Checkpoint checkpoint) + { + if (string.IsNullOrEmpty(checkpoint.SessionId)) + { + throw new ArgumentException("Checkpoint SessionId is required", nameof(checkpoint)); + } + if (string.IsNullOrEmpty(checkpoint.Id)) + { + throw new ArgumentException("Checkpoint Id is required", nameof(checkpoint)); + } + return (checkpoint.SessionId, checkpoint.Id); + } +} + +/// +/// Resolves every permission request as approved. +/// +public sealed class AllowAllPermissionResolver : IPermissionResolver +{ + public Task RequestAsync(PermissionRequest request) + { + return Task.FromResult(new PermissionDecision + { + RequestId = request.RequestId, + ToolCallId = request.ToolCallId, + Permission = request.Permission, + Approved = true, + Reason = "allow_all" + }); + } +} + +/// +/// Resolves every permission request as denied. +/// +public sealed class DenyAllPermissionResolver : IPermissionResolver +{ + public Task RequestAsync(PermissionRequest request) + { + return Task.FromResult(new PermissionDecision + { + RequestId = request.RequestId, + ToolCallId = request.ToolCallId, + Permission = request.Permission, + Approved = false, + Reason = "deny_all" + }); + } +} + +public delegate Task HostToolHandler(IDictionary arguments, HostToolRequest request); + +/// +/// Dispatches host tool requests to registered local functions. +/// +public sealed class FunctionHostToolExecutor : IHostToolExecutor +{ + private readonly IReadOnlyDictionary _handlers; + + public FunctionHostToolExecutor(IReadOnlyDictionary handlers) + { + _handlers = handlers; + } + + public async Task ExecuteAsync(HostToolRequest request) + { + var stopwatch = Stopwatch.StartNew(); + if (!_handlers.TryGetValue(request.ToolName, out var handler)) + { + return new HostToolResult + { + RequestId = request.RequestId, + ToolCallId = request.ToolCallId, + ToolName = request.ToolName, + Success = false, + ErrorKind = "not_found", + Result = new Dictionary { ["message"] = $"No host tool registered for '{request.ToolName}'" }, + DurationMs = stopwatch.Elapsed.TotalMilliseconds + }; + } + + try + { + var result = await handler(request.Arguments ?? new Dictionary(), request); + return new HostToolResult + { + RequestId = request.RequestId, + ToolCallId = request.ToolCallId, + ToolName = request.ToolName, + Success = true, + Result = result, + DurationMs = stopwatch.Elapsed.TotalMilliseconds + }; + } + catch (Exception exception) + { + return new HostToolResult + { + RequestId = request.RequestId, + ToolCallId = request.ToolCallId, + ToolName = request.ToolName, + Success = false, + ErrorKind = "exception", + Result = new Dictionary { ["message"] = exception.Message }, + DurationMs = stopwatch.Elapsed.TotalMilliseconds + }; + } + } +} diff --git a/runtime/csharp/Prompty.Core/Model/Context.cs b/runtime/csharp/Prompty.Core/Model/Context.cs index c1eb3c26..6182b68e 100644 --- a/runtime/csharp/Prompty.Core/Model/Context.cs +++ b/runtime/csharp/Prompty.Core/Model/Context.cs @@ -1,3 +1,4 @@ +// // Copyright (c) Microsoft. All rights reserved. using System.Text.Json; using YamlDotNet.Serialization; diff --git a/runtime/csharp/Prompty.Core/Model/Utils.cs b/runtime/csharp/Prompty.Core/Model/Utils.cs index 484b34b9..5d2e7d07 100644 --- a/runtime/csharp/Prompty.Core/Model/Utils.cs +++ b/runtime/csharp/Prompty.Core/Model/Utils.cs @@ -1,3 +1,4 @@ +// // Copyright (c) Microsoft. All rights reserved. using System.Collections; using System.Reflection; diff --git a/runtime/csharp/Prompty.Core/Model/agent/GuardrailResult.cs b/runtime/csharp/Prompty.Core/Model/agent/GuardrailResult.cs index bd8b00c9..6b115776 100644 --- a/runtime/csharp/Prompty.Core/Model/agent/GuardrailResult.cs +++ b/runtime/csharp/Prompty.Core/Model/agent/GuardrailResult.cs @@ -1,3 +1,4 @@ +// // Copyright (c) Microsoft. All rights reserved. using System.Text.Json; using YamlDotNet.Serialization; @@ -6,13 +7,13 @@ namespace Prompty.Core; #pragma warning restore IDE0130 -/// -/// The result of a guardrail evaluation. Guardrails are safety checks that -/// -/// run at specific phases of the agent loop and can allow, deny, or rewrite -/// -/// content. -/// + /// + /// The result of a guardrail evaluation. Guardrails are safety checks that + /// + /// run at specific phases of the agent loop and can allow, deny, or rewrite + /// + /// content. + /// public partial class GuardrailResult { /// diff --git a/runtime/csharp/Prompty.Core/Model/agent/Prompty.cs b/runtime/csharp/Prompty.Core/Model/agent/Prompty.cs index e5e2a6d3..ff0f7910 100644 --- a/runtime/csharp/Prompty.Core/Model/agent/Prompty.cs +++ b/runtime/csharp/Prompty.Core/Model/agent/Prompty.cs @@ -1,3 +1,4 @@ +// // Copyright (c) Microsoft. All rights reserved. using System.Text.Json; using YamlDotNet.Serialization; @@ -6,17 +7,27 @@ namespace Prompty.Core; #pragma warning restore IDE0130 -/// -/// A Prompty is a markdown file format for LLM prompts. The frontmatter defines -/// -/// structured metadata including model configuration, input/output schemas, tools, -/// -/// and template settings. The markdown body becomes the instructions. -/// -/// This is the single root type for the Prompty schema — there is no abstract base -/// -/// class or kind discriminator. A .prompty file always produces a Prompty instance. -/// + /// + /// A Prompty is a markdown file format for LLM prompts. The frontmatter defines + /// + /// structured metadata including model configuration, input/output schemas, tools, + /// + /// and template settings. The markdown body becomes the instructions. + /// + /// This is the single root type for the Prompty schema — there is no abstract base + /// + /// class or kind discriminator. A .prompty file always produces a Prompty instance. + /// + /// Runtime loaders may resolve frontmatter references such as `${env:VAR}` and + /// + /// `${file:relative/path}`. File references must be treated as a host-controlled + /// + /// capability: by default they are scoped to the containing .prompty file's + /// + /// directory tree after canonicalization, and any additional allowed roots must + /// + /// be supplied by the host application's load options rather than frontmatter. + /// public partial class Prompty { /// diff --git a/runtime/csharp/Prompty.Core/Model/connection/AnonymousConnection.cs b/runtime/csharp/Prompty.Core/Model/connection/AnonymousConnection.cs index 67da5fec..8e3a42fe 100644 --- a/runtime/csharp/Prompty.Core/Model/connection/AnonymousConnection.cs +++ b/runtime/csharp/Prompty.Core/Model/connection/AnonymousConnection.cs @@ -1,3 +1,4 @@ +// // Copyright (c) Microsoft. All rights reserved. using System.Text.Json; using YamlDotNet.Serialization; @@ -6,8 +7,8 @@ namespace Prompty.Core; #pragma warning restore IDE0130 -/// -/// + /// + /// public partial class AnonymousConnection : Connection { /// diff --git a/runtime/csharp/Prompty.Core/Model/connection/ApiKeyConnection.cs b/runtime/csharp/Prompty.Core/Model/connection/ApiKeyConnection.cs index c103ea25..edd36572 100644 --- a/runtime/csharp/Prompty.Core/Model/connection/ApiKeyConnection.cs +++ b/runtime/csharp/Prompty.Core/Model/connection/ApiKeyConnection.cs @@ -1,3 +1,4 @@ +// // Copyright (c) Microsoft. All rights reserved. using System.Text.Json; using YamlDotNet.Serialization; @@ -6,9 +7,9 @@ namespace Prompty.Core; #pragma warning restore IDE0130 -/// -/// Connection configuration for AI services using API keys. -/// + /// + /// Connection configuration for AI services using API keys. + /// public partial class ApiKeyConnection : Connection { /// diff --git a/runtime/csharp/Prompty.Core/Model/connection/AuthenticationMode.cs b/runtime/csharp/Prompty.Core/Model/connection/AuthenticationMode.cs index d21331c1..0b1821ab 100644 --- a/runtime/csharp/Prompty.Core/Model/connection/AuthenticationMode.cs +++ b/runtime/csharp/Prompty.Core/Model/connection/AuthenticationMode.cs @@ -1,5 +1,6 @@ +// // -// Code generated by Prompty emitter; DO NOT EDIT. +// Code generated by Typra emitter; DO NOT EDIT. using System.Text.Json.Serialization; diff --git a/runtime/csharp/Prompty.Core/Model/connection/Connection.cs b/runtime/csharp/Prompty.Core/Model/connection/Connection.cs index 43206e90..5b538496 100644 --- a/runtime/csharp/Prompty.Core/Model/connection/Connection.cs +++ b/runtime/csharp/Prompty.Core/Model/connection/Connection.cs @@ -1,3 +1,4 @@ +// // Copyright (c) Microsoft. All rights reserved. using System.Text.Json; using YamlDotNet.Serialization; @@ -6,13 +7,13 @@ namespace Prompty.Core; #pragma warning restore IDE0130 -/// -/// Connection configuration for AI agents. -/// -/// `provider`, `kind`, and `endpoint` are required properties here, -/// -/// but this section can accept additional via options. -/// + /// + /// Connection configuration for AI agents. + /// + /// `provider`, `kind`, and `endpoint` are required properties here, + /// + /// but this section can accept additional via options. + /// public abstract partial class Connection { /// @@ -140,7 +141,7 @@ private static Connection LoadKind(Dictionary data, LoadContext if (obj.AuthenticationMode is not null) { - result["authenticationMode"] = obj.AuthenticationMode.ToString().ToLowerInvariant(); + result["authenticationMode"] = obj.AuthenticationMode.Value.ToString().ToLowerInvariant(); } diff --git a/runtime/csharp/Prompty.Core/Model/connection/FoundryConnection.cs b/runtime/csharp/Prompty.Core/Model/connection/FoundryConnection.cs index 5be78291..7e427c77 100644 --- a/runtime/csharp/Prompty.Core/Model/connection/FoundryConnection.cs +++ b/runtime/csharp/Prompty.Core/Model/connection/FoundryConnection.cs @@ -1,3 +1,4 @@ +// // Copyright (c) Microsoft. All rights reserved. using System.Text.Json; using YamlDotNet.Serialization; @@ -6,13 +7,13 @@ namespace Prompty.Core; #pragma warning restore IDE0130 -/// -/// Connection configuration for Microsoft Foundry projects. -/// -/// Provides project-scoped access to models, tools, and services -/// -/// via Entra ID (DefaultAzureCredential) authentication. -/// + /// + /// Connection configuration for Microsoft Foundry projects. + /// + /// Provides project-scoped access to models, tools, and services + /// + /// via Entra ID (DefaultAzureCredential) authentication. + /// public partial class FoundryConnection : Connection { /// diff --git a/runtime/csharp/Prompty.Core/Model/connection/OAuthConnection.cs b/runtime/csharp/Prompty.Core/Model/connection/OAuthConnection.cs index bf557755..cef46b2d 100644 --- a/runtime/csharp/Prompty.Core/Model/connection/OAuthConnection.cs +++ b/runtime/csharp/Prompty.Core/Model/connection/OAuthConnection.cs @@ -1,3 +1,4 @@ +// // Copyright (c) Microsoft. All rights reserved. using System.Text.Json; using YamlDotNet.Serialization; @@ -6,13 +7,13 @@ namespace Prompty.Core; #pragma warning restore IDE0130 -/// -/// Connection configuration using OAuth 2.0 client credentials. -/// -/// Useful for tools and services that require OAuth authentication, -/// -/// such as MCP servers, OpenAPI endpoints, or other REST APIs. -/// + /// + /// Connection configuration using OAuth 2.0 client credentials. + /// + /// Useful for tools and services that require OAuth authentication, + /// + /// such as MCP servers, OpenAPI endpoints, or other REST APIs. + /// public partial class OAuthConnection : Connection { /// diff --git a/runtime/csharp/Prompty.Core/Model/connection/ReferenceConnection.cs b/runtime/csharp/Prompty.Core/Model/connection/ReferenceConnection.cs index 6df6fe5a..861ef369 100644 --- a/runtime/csharp/Prompty.Core/Model/connection/ReferenceConnection.cs +++ b/runtime/csharp/Prompty.Core/Model/connection/ReferenceConnection.cs @@ -1,3 +1,4 @@ +// // Copyright (c) Microsoft. All rights reserved. using System.Text.Json; using YamlDotNet.Serialization; @@ -6,9 +7,9 @@ namespace Prompty.Core; #pragma warning restore IDE0130 -/// -/// Connection configuration for AI services using named connections. -/// + /// + /// Connection configuration for AI services using named connections. + /// public partial class ReferenceConnection : Connection { /// diff --git a/runtime/csharp/Prompty.Core/Model/connection/RemoteConnection.cs b/runtime/csharp/Prompty.Core/Model/connection/RemoteConnection.cs index d94c4763..89d7c71a 100644 --- a/runtime/csharp/Prompty.Core/Model/connection/RemoteConnection.cs +++ b/runtime/csharp/Prompty.Core/Model/connection/RemoteConnection.cs @@ -1,3 +1,4 @@ +// // Copyright (c) Microsoft. All rights reserved. using System.Text.Json; using YamlDotNet.Serialization; @@ -6,9 +7,9 @@ namespace Prompty.Core; #pragma warning restore IDE0130 -/// -/// Connection configuration for AI services using named connections. -/// + /// + /// Connection configuration for AI services using named connections. + /// public partial class RemoteConnection : Connection { /// diff --git a/runtime/csharp/Prompty.Core/Model/conversation/AudioPart.cs b/runtime/csharp/Prompty.Core/Model/conversation/AudioPart.cs index 8e31fdf6..79b69e4f 100644 --- a/runtime/csharp/Prompty.Core/Model/conversation/AudioPart.cs +++ b/runtime/csharp/Prompty.Core/Model/conversation/AudioPart.cs @@ -1,3 +1,4 @@ +// // Copyright (c) Microsoft. All rights reserved. using System.Text.Json; using YamlDotNet.Serialization; @@ -6,9 +7,9 @@ namespace Prompty.Core; #pragma warning restore IDE0130 -/// -/// An audio content part. The source may be a URL or base64-encoded data. -/// + /// + /// An audio content part. The source may be a URL or base64-encoded data. + /// public partial class AudioPart : ContentPart { /// diff --git a/runtime/csharp/Prompty.Core/Model/conversation/ContentPart.cs b/runtime/csharp/Prompty.Core/Model/conversation/ContentPart.cs index 8bf55070..04d7e6cc 100644 --- a/runtime/csharp/Prompty.Core/Model/conversation/ContentPart.cs +++ b/runtime/csharp/Prompty.Core/Model/conversation/ContentPart.cs @@ -1,3 +1,4 @@ +// // Copyright (c) Microsoft. All rights reserved. using System.Text.Json; using YamlDotNet.Serialization; @@ -6,11 +7,11 @@ namespace Prompty.Core; #pragma warning restore IDE0130 -/// -/// A part of a message's content. Content parts are discriminated on the `kind` -/// -/// field and represent the different modalities that can appear in a message. -/// + /// + /// A part of a message's content. Content parts are discriminated on the `kind` + /// + /// field and represent the different modalities that can appear in a message. + /// public abstract partial class ContentPart { /// diff --git a/runtime/csharp/Prompty.Core/Model/conversation/FilePart.cs b/runtime/csharp/Prompty.Core/Model/conversation/FilePart.cs index 14082700..ef9d3dad 100644 --- a/runtime/csharp/Prompty.Core/Model/conversation/FilePart.cs +++ b/runtime/csharp/Prompty.Core/Model/conversation/FilePart.cs @@ -1,3 +1,4 @@ +// // Copyright (c) Microsoft. All rights reserved. using System.Text.Json; using YamlDotNet.Serialization; @@ -6,9 +7,9 @@ namespace Prompty.Core; #pragma warning restore IDE0130 -/// -/// A file content part. The source may be a URL or base64-encoded data. -/// + /// + /// A file content part. The source may be a URL or base64-encoded data. + /// public partial class FilePart : ContentPart { /// diff --git a/runtime/csharp/Prompty.Core/Model/conversation/ImagePart.cs b/runtime/csharp/Prompty.Core/Model/conversation/ImagePart.cs index db67e64e..8d131e52 100644 --- a/runtime/csharp/Prompty.Core/Model/conversation/ImagePart.cs +++ b/runtime/csharp/Prompty.Core/Model/conversation/ImagePart.cs @@ -1,3 +1,4 @@ +// // Copyright (c) Microsoft. All rights reserved. using System.Text.Json; using YamlDotNet.Serialization; @@ -6,9 +7,9 @@ namespace Prompty.Core; #pragma warning restore IDE0130 -/// -/// An image content part. The source may be a URL or base64-encoded data. -/// + /// + /// An image content part. The source may be a URL or base64-encoded data. + /// public partial class ImagePart : ContentPart { /// diff --git a/runtime/csharp/Prompty.Core/Model/conversation/Message.cs b/runtime/csharp/Prompty.Core/Model/conversation/Message.cs index f13b76f8..683b594c 100644 --- a/runtime/csharp/Prompty.Core/Model/conversation/Message.cs +++ b/runtime/csharp/Prompty.Core/Model/conversation/Message.cs @@ -1,3 +1,4 @@ +// // Copyright (c) Microsoft. All rights reserved. using System.Text.Json; using YamlDotNet.Serialization; @@ -6,11 +7,11 @@ namespace Prompty.Core; #pragma warning restore IDE0130 -/// -/// A message in a conversation. Messages have a role and a list of content parts -/// -/// representing the different modalities of the message content. -/// + /// + /// A message in a conversation. Messages have a role and a list of content parts + /// + /// representing the different modalities of the message content. + /// public partial class Message : IMessageHelpers { /// diff --git a/runtime/csharp/Prompty.Core/Model/conversation/Role.cs b/runtime/csharp/Prompty.Core/Model/conversation/Role.cs index d215649e..9e6ca991 100644 --- a/runtime/csharp/Prompty.Core/Model/conversation/Role.cs +++ b/runtime/csharp/Prompty.Core/Model/conversation/Role.cs @@ -1,5 +1,6 @@ +// // -// Code generated by Prompty emitter; DO NOT EDIT. +// Code generated by Typra emitter; DO NOT EDIT. using System.Text.Json.Serialization; diff --git a/runtime/csharp/Prompty.Core/Model/conversation/TextPart.cs b/runtime/csharp/Prompty.Core/Model/conversation/TextPart.cs index ef0d1dc3..009ecc41 100644 --- a/runtime/csharp/Prompty.Core/Model/conversation/TextPart.cs +++ b/runtime/csharp/Prompty.Core/Model/conversation/TextPart.cs @@ -1,3 +1,4 @@ +// // Copyright (c) Microsoft. All rights reserved. using System.Text.Json; using YamlDotNet.Serialization; @@ -6,9 +7,9 @@ namespace Prompty.Core; #pragma warning restore IDE0130 -/// -/// A text content part. -/// + /// + /// A text content part. + /// public partial class TextPart : ContentPart { /// diff --git a/runtime/csharp/Prompty.Core/Model/conversation/ThreadMarker.cs b/runtime/csharp/Prompty.Core/Model/conversation/ThreadMarker.cs index 7cf74f85..6671442d 100644 --- a/runtime/csharp/Prompty.Core/Model/conversation/ThreadMarker.cs +++ b/runtime/csharp/Prompty.Core/Model/conversation/ThreadMarker.cs @@ -1,3 +1,4 @@ +// // Copyright (c) Microsoft. All rights reserved. using System.Text.Json; using YamlDotNet.Serialization; @@ -6,15 +7,15 @@ namespace Prompty.Core; #pragma warning restore IDE0130 -/// -/// Positional marker for conversation history insertion during template rendering. -/// -/// During `prepare()`, nonce strings in rendered text are replaced with -/// -/// ThreadMarker objects. The pipeline then replaces them with actual -/// -/// conversation messages from the inputs. -/// + /// + /// Positional marker for conversation history insertion during template rendering. + /// + /// During `prepare()`, nonce strings in rendered text are replaced with + /// + /// ThreadMarker objects. The pipeline then replaces them with actual + /// + /// conversation messages from the inputs. + /// public partial class ThreadMarker { /// diff --git a/runtime/csharp/Prompty.Core/Model/conversation/ToolCall.cs b/runtime/csharp/Prompty.Core/Model/conversation/ToolCall.cs index 4c68a00e..e63d86c9 100644 --- a/runtime/csharp/Prompty.Core/Model/conversation/ToolCall.cs +++ b/runtime/csharp/Prompty.Core/Model/conversation/ToolCall.cs @@ -1,3 +1,4 @@ +// // Copyright (c) Microsoft. All rights reserved. using System.Text.Json; using YamlDotNet.Serialization; @@ -6,11 +7,11 @@ namespace Prompty.Core; #pragma warning restore IDE0130 -/// -/// A tool call requested by the LLM. Contains the function name and serialized -/// -/// arguments that should be dispatched to the appropriate tool handler. -/// + /// + /// A tool call requested by the LLM. Contains the function name and serialized + /// + /// arguments that should be dispatched to the appropriate tool handler. + /// public partial class ToolCall { /// diff --git a/runtime/csharp/Prompty.Core/Model/conversation/ToolResult.cs b/runtime/csharp/Prompty.Core/Model/conversation/ToolResult.cs index 9f045d87..d64bc34a 100644 --- a/runtime/csharp/Prompty.Core/Model/conversation/ToolResult.cs +++ b/runtime/csharp/Prompty.Core/Model/conversation/ToolResult.cs @@ -1,3 +1,4 @@ +// // Copyright (c) Microsoft. All rights reserved. using System.Text.Json; using YamlDotNet.Serialization; @@ -6,15 +7,15 @@ namespace Prompty.Core; #pragma warning restore IDE0130 -/// -/// The result of a tool execution. Contains a list of content parts, enabling -/// -/// rich tool results (text, images, files, audio) rather than just strings. -/// -/// Implementations MUST support conversion from a plain string to a ToolResult -/// -/// containing a single TextPart for backward compatibility. -/// + /// + /// The result of a tool execution. Contains a list of content parts, enabling + /// + /// rich tool results (text, images, files, audio) rather than just strings. + /// + /// Implementations MUST support conversion from a plain string to a ToolResult + /// + /// containing a single TextPart for backward compatibility. + /// public partial class ToolResult : IToolResultHelpers { /// @@ -36,6 +37,26 @@ public ToolResult() /// public IList Parts { get; set; } = []; + /// + /// Semantic execution status for the tool result + /// + public ToolResultStatus? Status { get; set; } + + /// + /// Stable machine-readable error category when status is not success + /// + public string? ErrorKind { get; set; } + + /// + /// Human-readable error message when status is not success + /// + public string? ErrorMessage { get; set; } + + /// + /// Tool execution duration in milliseconds + /// + public double? DurationMs { get; set; } + #region Load Methods @@ -63,6 +84,26 @@ public static ToolResult Load(Dictionary data, LoadContext? con instance.Parts = LoadParts(partsValue, context); } + if (data.TryGetValue("status", out var statusValue) && statusValue is not null) + { + instance.Status = Enum.Parse(statusValue?.ToString()!, true); + } + + if (data.TryGetValue("errorKind", out var errorKindValue) && errorKindValue is not null) + { + instance.ErrorKind = errorKindValue?.ToString()!; + } + + if (data.TryGetValue("errorMessage", out var errorMessageValue) && errorMessageValue is not null) + { + instance.ErrorMessage = errorMessageValue?.ToString()!; + } + + if (data.TryGetValue("durationMs", out var durationMsValue) && durationMsValue is not null) + { + instance.DurationMs = Convert.ToDouble(durationMsValue); + } + if (context is not null) { instance = context.ProcessOutput(instance); @@ -149,6 +190,30 @@ public static IList LoadParts(object data, LoadContext? context) result["parts"] = SaveParts(obj.Parts, context); + if (obj.Status is not null) + { + result["status"] = obj.Status.Value.ToString().ToLowerInvariant(); + } + + + if (obj.ErrorKind is not null) + { + result["errorKind"] = obj.ErrorKind; + } + + + if (obj.ErrorMessage is not null) + { + result["errorMessage"] = obj.ErrorMessage; + } + + + if (obj.DurationMs is not null) + { + result["durationMs"] = obj.DurationMs; + } + + if (context is not null) { result = context.ProcessDict(result); diff --git a/runtime/csharp/Prompty.Core/Model/conversation/ToolResultStatus.cs b/runtime/csharp/Prompty.Core/Model/conversation/ToolResultStatus.cs new file mode 100644 index 00000000..3230cfd7 --- /dev/null +++ b/runtime/csharp/Prompty.Core/Model/conversation/ToolResultStatus.cs @@ -0,0 +1,24 @@ +// +// +// Code generated by Typra emitter; DO NOT EDIT. + +using System.Text.Json.Serialization; + +namespace Prompty.Core; + +[JsonConverter(typeof(JsonStringEnumConverter))] +public enum ToolResultStatus +{ + [JsonPropertyName("success")] + Success, + + [JsonPropertyName("error")] + Error, + + [JsonPropertyName("cancelled")] + Cancelled, + + [JsonPropertyName("timeout")] + Timeout, + +} diff --git a/runtime/csharp/Prompty.Core/Model/core/ArrayProperty.cs b/runtime/csharp/Prompty.Core/Model/core/ArrayProperty.cs index 24d31f62..b1b3caeb 100644 --- a/runtime/csharp/Prompty.Core/Model/core/ArrayProperty.cs +++ b/runtime/csharp/Prompty.Core/Model/core/ArrayProperty.cs @@ -1,3 +1,4 @@ +// // Copyright (c) Microsoft. All rights reserved. using System.Text.Json; using YamlDotNet.Serialization; @@ -6,11 +7,11 @@ namespace Prompty.Core; #pragma warning restore IDE0130 -/// -/// Represents an array property. -/// -/// This extends the base Property model to represent an array of items. -/// + /// + /// Represents an array property. + /// + /// This extends the base Property model to represent an array of items. + /// public partial class ArrayProperty : Property { /// diff --git a/runtime/csharp/Prompty.Core/Model/core/FileNotFoundError.cs b/runtime/csharp/Prompty.Core/Model/core/FileNotFoundError.cs index 42e4f636..083dc759 100644 --- a/runtime/csharp/Prompty.Core/Model/core/FileNotFoundError.cs +++ b/runtime/csharp/Prompty.Core/Model/core/FileNotFoundError.cs @@ -1,3 +1,4 @@ +// // Copyright (c) Microsoft. All rights reserved. using System.Text.Json; using YamlDotNet.Serialization; @@ -6,11 +7,11 @@ namespace Prompty.Core; #pragma warning restore IDE0130 -/// -/// Raised when a referenced file cannot be found. This applies to both -/// -/// .prompty files and ${file:path} references in frontmatter. -/// + /// + /// Raised when a referenced file cannot be found. This applies to both + /// + /// .prompty files and ${file:path} references in frontmatter. + /// public partial class FileNotFoundError { /// diff --git a/runtime/csharp/Prompty.Core/Model/core/InvokerError.cs b/runtime/csharp/Prompty.Core/Model/core/InvokerError.cs index 840c4b65..4cb68a7c 100644 --- a/runtime/csharp/Prompty.Core/Model/core/InvokerError.cs +++ b/runtime/csharp/Prompty.Core/Model/core/InvokerError.cs @@ -1,3 +1,4 @@ +// // Copyright (c) Microsoft. All rights reserved. using System.Text.Json; using YamlDotNet.Serialization; @@ -6,13 +7,13 @@ namespace Prompty.Core; #pragma warning restore IDE0130 -/// -/// Raised when no invoker implementation is registered for a given component -/// -/// and key. For example, if no renderer is registered for the key "jinja2", -/// -/// an InvokerError is raised. -/// + /// + /// Raised when no invoker implementation is registered for a given component + /// + /// and key. For example, if no renderer is registered for the key "jinja2", + /// + /// an InvokerError is raised. + /// public partial class InvokerError { /// diff --git a/runtime/csharp/Prompty.Core/Model/core/ObjectProperty.cs b/runtime/csharp/Prompty.Core/Model/core/ObjectProperty.cs index 1ef8f5cf..097174f5 100644 --- a/runtime/csharp/Prompty.Core/Model/core/ObjectProperty.cs +++ b/runtime/csharp/Prompty.Core/Model/core/ObjectProperty.cs @@ -1,3 +1,4 @@ +// // Copyright (c) Microsoft. All rights reserved. using System.Text.Json; using YamlDotNet.Serialization; @@ -6,11 +7,11 @@ namespace Prompty.Core; #pragma warning restore IDE0130 -/// -/// Represents an object property. -/// -/// This extends the base Property model to represent a structured object. -/// + /// + /// Represents an object property. + /// + /// This extends the base Property model to represent a structured object. + /// public partial class ObjectProperty : Property { /// diff --git a/runtime/csharp/Prompty.Core/Model/core/Property.cs b/runtime/csharp/Prompty.Core/Model/core/Property.cs index 38ac93a6..41d15002 100644 --- a/runtime/csharp/Prompty.Core/Model/core/Property.cs +++ b/runtime/csharp/Prompty.Core/Model/core/Property.cs @@ -1,3 +1,4 @@ +// // Copyright (c) Microsoft. All rights reserved. using System.Text.Json; using YamlDotNet.Serialization; @@ -6,17 +7,17 @@ namespace Prompty.Core; #pragma warning restore IDE0130 -/// -/// Represents a single property. -/// -/// - This model defines the structure of properties that can be used in prompts, -/// -/// including their type, description, whether they are required, and other attributes. -/// -/// - It allows for the definition of dynamic inputs that can be filled with data -/// -/// and processed to generate prompts for AI models. -/// + /// + /// Represents a single property. + /// + /// - This model defines the structure of properties that can be used in prompts, + /// + /// including their type, description, whether they are required, and other attributes. + /// + /// - It allows for the definition of dynamic inputs that can be filled with data + /// + /// and processed to generate prompts for AI models. + /// public partial class Property { /// diff --git a/runtime/csharp/Prompty.Core/Model/core/ValidationError.cs b/runtime/csharp/Prompty.Core/Model/core/ValidationError.cs index b2a9192a..aaab609d 100644 --- a/runtime/csharp/Prompty.Core/Model/core/ValidationError.cs +++ b/runtime/csharp/Prompty.Core/Model/core/ValidationError.cs @@ -1,3 +1,4 @@ +// // Copyright (c) Microsoft. All rights reserved. using System.Text.Json; using YamlDotNet.Serialization; @@ -6,11 +7,11 @@ namespace Prompty.Core; #pragma warning restore IDE0130 -/// -/// Raised when input validation fails. Each ValidationError describes a -/// -/// single property that did not satisfy its constraint. -/// + /// + /// Raised when input validation fails. Each ValidationError describes a + /// + /// single property that did not satisfy its constraint. + /// public partial class ValidationError { /// diff --git a/runtime/csharp/Prompty.Core/Model/core/ValidationResult.cs b/runtime/csharp/Prompty.Core/Model/core/ValidationResult.cs index 3ac6d938..9694a60c 100644 --- a/runtime/csharp/Prompty.Core/Model/core/ValidationResult.cs +++ b/runtime/csharp/Prompty.Core/Model/core/ValidationResult.cs @@ -1,3 +1,4 @@ +// // Copyright (c) Microsoft. All rights reserved. using System.Text.Json; using YamlDotNet.Serialization; @@ -6,13 +7,13 @@ namespace Prompty.Core; #pragma warning restore IDE0130 -/// -/// The result of validating inputs against a Prompty's inputs. -/// -/// Returned by `validate_inputs` (§12.2) to indicate whether all -/// -/// required inputs are present and satisfy their constraints. -/// + /// + /// The result of validating inputs against a Prompty's inputs. + /// + /// Returned by `validate_inputs` (§12.2) to indicate whether all + /// + /// required inputs are present and satisfy their constraints. + /// public partial class ValidationResult { /// diff --git a/runtime/csharp/Prompty.Core/Model/events/Checkpoint.cs b/runtime/csharp/Prompty.Core/Model/events/Checkpoint.cs new file mode 100644 index 00000000..858754c8 --- /dev/null +++ b/runtime/csharp/Prompty.Core/Model/events/Checkpoint.cs @@ -0,0 +1,316 @@ +// +// Copyright (c) Microsoft. All rights reserved. +using System.Text.Json; +using YamlDotNet.Serialization; + +#pragma warning disable IDE0130 +namespace Prompty.Core; +#pragma warning restore IDE0130 + + /// + /// A persisted handoff point for a harness session. + /// +public partial class Checkpoint +{ + /// + /// The shorthand property name for this type, if any. + /// + public static string? ShorthandProperty => null; + + /// + /// Initializes a new instance of . + /// +#pragma warning disable CS8618 + public Checkpoint() + { + } +#pragma warning restore CS8618 + + /// + /// Stable checkpoint identifier + /// + public string? Id { get; set; } + + /// + /// Stable session identifier + /// + public string? SessionId { get; set; } + + /// + /// Associated turn identifier, when the checkpoint was created inside a turn + /// + public string? TurnId { get; set; } + + /// + /// Monotonic checkpoint number within the session + /// + public int? CheckpointNumber { get; set; } + + /// + /// Short checkpoint title + /// + public string Title { get; set; } = string.Empty; + + /// + /// Short human-readable overview + /// + public string? Overview { get; set; } + + /// + /// Portable checkpoint state needed to resume or hand off the session + /// + public IDictionary? State { get; set; } + + /// + /// Optional host-authored summary or handoff note + /// + public string? Summary { get; set; } + + /// + /// Host-defined checkpoint metadata + /// + public IDictionary? Metadata { get; set; } + + /// + /// ISO 8601 UTC timestamp when the checkpoint was created + /// + public string? CreatedAt { get; set; } + + /// + /// Redaction state for sensitive checkpoint fields + /// + public RedactionMetadata? Redaction { get; set; } + + + + #region Load Methods + + /// + /// Load a Checkpoint instance from a dictionary. + /// + /// The dictionary containing the data. + /// Optional context with pre/post processing callbacks. + /// The loaded Checkpoint instance. + public static Checkpoint Load(Dictionary data, LoadContext? context = null) + { + if (context is not null) + { + data = context.ProcessInput(data); + } + + + // Create new instance + var instance = new Checkpoint(); + + + if (data.TryGetValue("id", out var idValue) && idValue is not null) + { + instance.Id = idValue?.ToString()!; + } + + if (data.TryGetValue("sessionId", out var sessionIdValue) && sessionIdValue is not null) + { + instance.SessionId = sessionIdValue?.ToString()!; + } + + if (data.TryGetValue("turnId", out var turnIdValue) && turnIdValue is not null) + { + instance.TurnId = turnIdValue?.ToString()!; + } + + if (data.TryGetValue("checkpointNumber", out var checkpointNumberValue) && checkpointNumberValue is not null) + { + instance.CheckpointNumber = Convert.ToInt32(checkpointNumberValue); + } + + if (data.TryGetValue("title", out var titleValue) && titleValue is not null) + { + instance.Title = titleValue?.ToString()!; + } + + if (data.TryGetValue("overview", out var overviewValue) && overviewValue is not null) + { + instance.Overview = overviewValue?.ToString()!; + } + + if (data.TryGetValue("state", out var stateValue) && stateValue is not null) + { + instance.State = stateValue.GetDictionary()!; + } + + if (data.TryGetValue("summary", out var summaryValue) && summaryValue is not null) + { + instance.Summary = summaryValue?.ToString()!; + } + + if (data.TryGetValue("metadata", out var metadataValue) && metadataValue is not null) + { + instance.Metadata = metadataValue.GetDictionary()!; + } + + if (data.TryGetValue("createdAt", out var createdAtValue) && createdAtValue is not null) + { + instance.CreatedAt = createdAtValue?.ToString()!; + } + + if (data.TryGetValue("redaction", out var redactionValue) && redactionValue is not null) + { + instance.Redaction = RedactionMetadata.Load(redactionValue.GetDictionary(RedactionMetadata.ShorthandProperty), context); + } + + if (context is not null) + { + instance = context.ProcessOutput(instance); + } + return instance; + } + + + #endregion + + #region Save Methods + + /// + /// Save the Checkpoint instance to a dictionary. + /// + /// Optional context with pre/post processing callbacks. + /// The dictionary representation of this instance. + public Dictionary Save(SaveContext? context = null) + { + var obj = this; + if (context is not null) + { + obj = context.ProcessObject(obj); + } + + + var result = new Dictionary(); + + + if (obj.Id is not null) + { + result["id"] = obj.Id; + } + + + if (obj.SessionId is not null) + { + result["sessionId"] = obj.SessionId; + } + + + if (obj.TurnId is not null) + { + result["turnId"] = obj.TurnId; + } + + + if (obj.CheckpointNumber is not null) + { + result["checkpointNumber"] = obj.CheckpointNumber; + } + + + result["title"] = obj.Title; + + + if (obj.Overview is not null) + { + result["overview"] = obj.Overview; + } + + + if (obj.State is not null) + { + result["state"] = obj.State; + } + + + if (obj.Summary is not null) + { + result["summary"] = obj.Summary; + } + + + if (obj.Metadata is not null) + { + result["metadata"] = obj.Metadata; + } + + + if (obj.CreatedAt is not null) + { + result["createdAt"] = obj.CreatedAt; + } + + + if (obj.Redaction is not null) + { + result["redaction"] = obj.Redaction?.Save(context); + } + + + if (context is not null) + { + result = context.ProcessDict(result); + } + + return result; + } + + + /// + /// Convert the Checkpoint instance to a YAML string. + /// + /// Optional context with pre/post processing callbacks. + /// The YAML string representation of this instance. + public string ToYaml(SaveContext? context = null) + { + context ??= new SaveContext(); + return context.ToYaml(Save(context)); + } + + /// + /// Convert the Checkpoint instance to a JSON string. + /// + /// Optional context with pre/post processing callbacks. + /// Whether to indent the output. Defaults to true. + /// The JSON string representation of this instance. + public string ToJson(SaveContext? context = null, bool indent = true) + { + context ??= new SaveContext(); + return context.ToJson(Save(context), indent); + } + + /// + /// Load a Checkpoint instance from a JSON string. + /// + /// The JSON string to parse. + /// Optional context with pre/post processing callbacks. + /// The loaded Checkpoint instance. + public static Checkpoint FromJson(string json, LoadContext? context = null) + { + using var doc = JsonDocument.Parse(json); + Dictionary dict; + dict = JsonSerializer.Deserialize>(json, JsonUtils.Options) + ?? throw new ArgumentException("Failed to parse JSON as dictionary"); + + return Load(dict, context); + } + + /// + /// Load a Checkpoint instance from a YAML string. + /// + /// The YAML string to parse. + /// Optional context with pre/post processing callbacks. + /// The loaded Checkpoint instance. + public static Checkpoint FromYaml(string yaml, LoadContext? context = null) + { + var dict = YamlUtils.Deserializer.Deserialize>(yaml) + ?? throw new ArgumentException("Failed to parse YAML as dictionary"); + + return Load(dict, context); + } + + #endregion +} diff --git a/runtime/csharp/Prompty.Core/Model/events/CompactionCompletePayload.cs b/runtime/csharp/Prompty.Core/Model/events/CompactionCompletePayload.cs index ab81e80c..58226e29 100644 --- a/runtime/csharp/Prompty.Core/Model/events/CompactionCompletePayload.cs +++ b/runtime/csharp/Prompty.Core/Model/events/CompactionCompletePayload.cs @@ -1,3 +1,4 @@ +// // Copyright (c) Microsoft. All rights reserved. using System.Text.Json; using YamlDotNet.Serialization; @@ -6,9 +7,9 @@ namespace Prompty.Core; #pragma warning restore IDE0130 -/// -/// Payload for "compaction_complete" events — context compaction finished. -/// + /// + /// Payload for "compaction_complete" events — context compaction finished. + /// public partial class CompactionCompletePayload { /// @@ -35,6 +36,11 @@ public CompactionCompletePayload() /// public int Remaining { get; set; } + /// + /// Length of the generated summary, when a summarization strategy is used + /// + public int? SummaryLength { get; set; } + #region Load Methods @@ -67,6 +73,11 @@ public static CompactionCompletePayload Load(Dictionary data, L instance.Remaining = Convert.ToInt32(remainingValue); } + if (data.TryGetValue("summaryLength", out var summaryLengthValue) && summaryLengthValue is not null) + { + instance.SummaryLength = Convert.ToInt32(summaryLengthValue); + } + if (context is not null) { instance = context.ProcessOutput(instance); @@ -102,6 +113,12 @@ public static CompactionCompletePayload Load(Dictionary data, L result["remaining"] = obj.Remaining; + if (obj.SummaryLength is not null) + { + result["summaryLength"] = obj.SummaryLength; + } + + if (context is not null) { result = context.ProcessDict(result); diff --git a/runtime/csharp/Prompty.Core/Model/events/CompactionFailedPayload.cs b/runtime/csharp/Prompty.Core/Model/events/CompactionFailedPayload.cs index b6f8693c..7afec04b 100644 --- a/runtime/csharp/Prompty.Core/Model/events/CompactionFailedPayload.cs +++ b/runtime/csharp/Prompty.Core/Model/events/CompactionFailedPayload.cs @@ -1,3 +1,4 @@ +// // Copyright (c) Microsoft. All rights reserved. using System.Text.Json; using YamlDotNet.Serialization; @@ -6,9 +7,9 @@ namespace Prompty.Core; #pragma warning restore IDE0130 -/// -/// Payload for "compaction_failed" events — compaction could not be completed. -/// + /// + /// Payload for "compaction_failed" events — compaction could not be completed. + /// public partial class CompactionFailedPayload { /// diff --git a/runtime/csharp/Prompty.Core/Model/events/CompactionStartPayload.cs b/runtime/csharp/Prompty.Core/Model/events/CompactionStartPayload.cs new file mode 100644 index 00000000..97631ba5 --- /dev/null +++ b/runtime/csharp/Prompty.Core/Model/events/CompactionStartPayload.cs @@ -0,0 +1,156 @@ +// +// Copyright (c) Microsoft. All rights reserved. +using System.Text.Json; +using YamlDotNet.Serialization; + +#pragma warning disable IDE0130 +namespace Prompty.Core; +#pragma warning restore IDE0130 + + /// + /// Payload for "compaction_start" events — context compaction is beginning. + /// +public partial class CompactionStartPayload +{ + /// + /// The shorthand property name for this type, if any. + /// + public static string? ShorthandProperty => null; + + /// + /// Initializes a new instance of . + /// +#pragma warning disable CS8618 + public CompactionStartPayload() + { + } +#pragma warning restore CS8618 + + /// + /// Number of messages selected for compaction + /// + public int DroppedCount { get; set; } + + + + #region Load Methods + + /// + /// Load a CompactionStartPayload instance from a dictionary. + /// + /// The dictionary containing the data. + /// Optional context with pre/post processing callbacks. + /// The loaded CompactionStartPayload instance. + public static CompactionStartPayload Load(Dictionary data, LoadContext? context = null) + { + if (context is not null) + { + data = context.ProcessInput(data); + } + + + // Create new instance + var instance = new CompactionStartPayload(); + + + if (data.TryGetValue("droppedCount", out var droppedCountValue) && droppedCountValue is not null) + { + instance.DroppedCount = Convert.ToInt32(droppedCountValue); + } + + if (context is not null) + { + instance = context.ProcessOutput(instance); + } + return instance; + } + + + #endregion + + #region Save Methods + + /// + /// Save the CompactionStartPayload instance to a dictionary. + /// + /// Optional context with pre/post processing callbacks. + /// The dictionary representation of this instance. + public Dictionary Save(SaveContext? context = null) + { + var obj = this; + if (context is not null) + { + obj = context.ProcessObject(obj); + } + + + var result = new Dictionary(); + + + result["droppedCount"] = obj.DroppedCount; + + + if (context is not null) + { + result = context.ProcessDict(result); + } + + return result; + } + + + /// + /// Convert the CompactionStartPayload instance to a YAML string. + /// + /// Optional context with pre/post processing callbacks. + /// The YAML string representation of this instance. + public string ToYaml(SaveContext? context = null) + { + context ??= new SaveContext(); + return context.ToYaml(Save(context)); + } + + /// + /// Convert the CompactionStartPayload instance to a JSON string. + /// + /// Optional context with pre/post processing callbacks. + /// Whether to indent the output. Defaults to true. + /// The JSON string representation of this instance. + public string ToJson(SaveContext? context = null, bool indent = true) + { + context ??= new SaveContext(); + return context.ToJson(Save(context), indent); + } + + /// + /// Load a CompactionStartPayload instance from a JSON string. + /// + /// The JSON string to parse. + /// Optional context with pre/post processing callbacks. + /// The loaded CompactionStartPayload instance. + public static CompactionStartPayload FromJson(string json, LoadContext? context = null) + { + using var doc = JsonDocument.Parse(json); + Dictionary dict; + dict = JsonSerializer.Deserialize>(json, JsonUtils.Options) + ?? throw new ArgumentException("Failed to parse JSON as dictionary"); + + return Load(dict, context); + } + + /// + /// Load a CompactionStartPayload instance from a YAML string. + /// + /// The YAML string to parse. + /// Optional context with pre/post processing callbacks. + /// The loaded CompactionStartPayload instance. + public static CompactionStartPayload FromYaml(string yaml, LoadContext? context = null) + { + var dict = YamlUtils.Deserializer.Deserialize>(yaml) + ?? throw new ArgumentException("Failed to parse YAML as dictionary"); + + return Load(dict, context); + } + + #endregion +} diff --git a/runtime/csharp/Prompty.Core/Model/events/DoneEventPayload.cs b/runtime/csharp/Prompty.Core/Model/events/DoneEventPayload.cs index 3845e84b..3640a80c 100644 --- a/runtime/csharp/Prompty.Core/Model/events/DoneEventPayload.cs +++ b/runtime/csharp/Prompty.Core/Model/events/DoneEventPayload.cs @@ -1,3 +1,4 @@ +// // Copyright (c) Microsoft. All rights reserved. using System.Text.Json; using YamlDotNet.Serialization; @@ -6,9 +7,9 @@ namespace Prompty.Core; #pragma warning restore IDE0130 -/// -/// Payload for "done" events — the agent loop completed successfully. -/// + /// + /// Payload for "done" events — the agent loop completed successfully. + /// public partial class DoneEventPayload { /// @@ -26,9 +27,9 @@ public DoneEventPayload() #pragma warning restore CS8618 /// - /// The final text response from the LLM + /// The final response from the LLM after processing /// - public string Response { get; set; } = string.Empty; + public object Response { get; set; } = new object(); /// /// The final conversation state including all messages @@ -59,7 +60,7 @@ public static DoneEventPayload Load(Dictionary data, LoadContex if (data.TryGetValue("response", out var responseValue) && responseValue is not null) { - instance.Response = responseValue?.ToString()!; + instance.Response = responseValue; } if (data.TryGetValue("messages", out var messagesValue) && messagesValue is not null) diff --git a/runtime/csharp/Prompty.Core/Model/events/ErrorChunk.cs b/runtime/csharp/Prompty.Core/Model/events/ErrorChunk.cs index 430616bf..2982e77f 100644 --- a/runtime/csharp/Prompty.Core/Model/events/ErrorChunk.cs +++ b/runtime/csharp/Prompty.Core/Model/events/ErrorChunk.cs @@ -1,3 +1,4 @@ +// // Copyright (c) Microsoft. All rights reserved. using System.Text.Json; using YamlDotNet.Serialization; @@ -6,9 +7,9 @@ namespace Prompty.Core; #pragma warning restore IDE0130 -/// -/// An error chunk from the LLM response stream. -/// + /// + /// An error chunk from the LLM response stream. + /// public partial class ErrorChunk : StreamChunk { /// diff --git a/runtime/csharp/Prompty.Core/Model/events/ErrorEventPayload.cs b/runtime/csharp/Prompty.Core/Model/events/ErrorEventPayload.cs index 8513e878..72210e05 100644 --- a/runtime/csharp/Prompty.Core/Model/events/ErrorEventPayload.cs +++ b/runtime/csharp/Prompty.Core/Model/events/ErrorEventPayload.cs @@ -1,3 +1,4 @@ +// // Copyright (c) Microsoft. All rights reserved. using System.Text.Json; using YamlDotNet.Serialization; @@ -6,9 +7,9 @@ namespace Prompty.Core; #pragma warning restore IDE0130 -/// -/// Payload for "error" events — an error occurred during the loop. -/// + /// + /// Payload for "error" events — an error occurred during the loop. + /// public partial class ErrorEventPayload { /// @@ -30,6 +31,16 @@ public ErrorEventPayload() /// public string Message { get; set; } = string.Empty; + /// + /// Stable machine-readable error category + /// + public string? ErrorKind { get; set; } + + /// + /// Operation or phase where the error occurred + /// + public string? Phase { get; set; } + #region Load Methods @@ -57,6 +68,16 @@ public static ErrorEventPayload Load(Dictionary data, LoadConte instance.Message = messageValue?.ToString()!; } + if (data.TryGetValue("errorKind", out var errorKindValue) && errorKindValue is not null) + { + instance.ErrorKind = errorKindValue?.ToString()!; + } + + if (data.TryGetValue("phase", out var phaseValue) && phaseValue is not null) + { + instance.Phase = phaseValue?.ToString()!; + } + if (context is not null) { instance = context.ProcessOutput(instance); @@ -89,6 +110,18 @@ public static ErrorEventPayload Load(Dictionary data, LoadConte result["message"] = obj.Message; + if (obj.ErrorKind is not null) + { + result["errorKind"] = obj.ErrorKind; + } + + + if (obj.Phase is not null) + { + result["phase"] = obj.Phase; + } + + if (context is not null) { result = context.ProcessDict(result); diff --git a/runtime/csharp/Prompty.Core/Model/events/HarnessContext.cs b/runtime/csharp/Prompty.Core/Model/events/HarnessContext.cs new file mode 100644 index 00000000..e406aa2d --- /dev/null +++ b/runtime/csharp/Prompty.Core/Model/events/HarnessContext.cs @@ -0,0 +1,195 @@ +// +// Copyright (c) Microsoft. All rights reserved. +using System.Text.Json; +using YamlDotNet.Serialization; + +#pragma warning disable IDE0130 +namespace Prompty.Core; +#pragma warning restore IDE0130 + + /// + /// Execution context associated with a harness session. Host-specific + /// + /// environments can store detailed profiles in metadata without making the core + /// + /// contract depend on one source-control provider or workspace shape. + /// +public partial class HarnessContext +{ + /// + /// The shorthand property name for this type, if any. + /// + public static string? ShorthandProperty => null; + + /// + /// Initializes a new instance of . + /// +#pragma warning disable CS8618 + public HarnessContext() + { + } +#pragma warning restore CS8618 + + /// + /// Current working directory for the harness + /// + public string? Cwd { get; set; } + + /// + /// Git repository root, when known + /// + public string? GitRoot { get; set; } + + /// + /// Host-defined context metadata, such as source-control or sandbox details + /// + public IDictionary? Metadata { get; set; } + + + + #region Load Methods + + /// + /// Load a HarnessContext instance from a dictionary. + /// + /// The dictionary containing the data. + /// Optional context with pre/post processing callbacks. + /// The loaded HarnessContext instance. + public static HarnessContext Load(Dictionary data, LoadContext? context = null) + { + if (context is not null) + { + data = context.ProcessInput(data); + } + + + // Create new instance + var instance = new HarnessContext(); + + + if (data.TryGetValue("cwd", out var cwdValue) && cwdValue is not null) + { + instance.Cwd = cwdValue?.ToString()!; + } + + if (data.TryGetValue("gitRoot", out var gitRootValue) && gitRootValue is not null) + { + instance.GitRoot = gitRootValue?.ToString()!; + } + + if (data.TryGetValue("metadata", out var metadataValue) && metadataValue is not null) + { + instance.Metadata = metadataValue.GetDictionary()!; + } + + if (context is not null) + { + instance = context.ProcessOutput(instance); + } + return instance; + } + + + #endregion + + #region Save Methods + + /// + /// Save the HarnessContext instance to a dictionary. + /// + /// Optional context with pre/post processing callbacks. + /// The dictionary representation of this instance. + public Dictionary Save(SaveContext? context = null) + { + var obj = this; + if (context is not null) + { + obj = context.ProcessObject(obj); + } + + + var result = new Dictionary(); + + + if (obj.Cwd is not null) + { + result["cwd"] = obj.Cwd; + } + + + if (obj.GitRoot is not null) + { + result["gitRoot"] = obj.GitRoot; + } + + + if (obj.Metadata is not null) + { + result["metadata"] = obj.Metadata; + } + + + if (context is not null) + { + result = context.ProcessDict(result); + } + + return result; + } + + + /// + /// Convert the HarnessContext instance to a YAML string. + /// + /// Optional context with pre/post processing callbacks. + /// The YAML string representation of this instance. + public string ToYaml(SaveContext? context = null) + { + context ??= new SaveContext(); + return context.ToYaml(Save(context)); + } + + /// + /// Convert the HarnessContext instance to a JSON string. + /// + /// Optional context with pre/post processing callbacks. + /// Whether to indent the output. Defaults to true. + /// The JSON string representation of this instance. + public string ToJson(SaveContext? context = null, bool indent = true) + { + context ??= new SaveContext(); + return context.ToJson(Save(context), indent); + } + + /// + /// Load a HarnessContext instance from a JSON string. + /// + /// The JSON string to parse. + /// Optional context with pre/post processing callbacks. + /// The loaded HarnessContext instance. + public static HarnessContext FromJson(string json, LoadContext? context = null) + { + using var doc = JsonDocument.Parse(json); + Dictionary dict; + dict = JsonSerializer.Deserialize>(json, JsonUtils.Options) + ?? throw new ArgumentException("Failed to parse JSON as dictionary"); + + return Load(dict, context); + } + + /// + /// Load a HarnessContext instance from a YAML string. + /// + /// The YAML string to parse. + /// Optional context with pre/post processing callbacks. + /// The loaded HarnessContext instance. + public static HarnessContext FromYaml(string yaml, LoadContext? context = null) + { + var dict = YamlUtils.Deserializer.Deserialize>(yaml) + ?? throw new ArgumentException("Failed to parse YAML as dictionary"); + + return Load(dict, context); + } + + #endregion +} diff --git a/runtime/csharp/Prompty.Core/Model/events/HookEndPayload.cs b/runtime/csharp/Prompty.Core/Model/events/HookEndPayload.cs new file mode 100644 index 00000000..e67f0025 --- /dev/null +++ b/runtime/csharp/Prompty.Core/Model/events/HookEndPayload.cs @@ -0,0 +1,262 @@ +// +// Copyright (c) Microsoft. All rights reserved. +using System.Text.Json; +using YamlDotNet.Serialization; + +#pragma warning disable IDE0130 +namespace Prompty.Core; +#pragma warning restore IDE0130 + + /// + /// Payload for "hook_end" events — a host lifecycle hook finished. + /// +public partial class HookEndPayload +{ + /// + /// The shorthand property name for this type, if any. + /// + public static string? ShorthandProperty => null; + + /// + /// Initializes a new instance of . + /// +#pragma warning disable CS8618 + public HookEndPayload() + { + } +#pragma warning restore CS8618 + + /// + /// Stable hook invocation identifier + /// + public string HookInvocationId { get; set; } = string.Empty; + + /// + /// Host-defined hook type + /// + public string HookType { get; set; } = string.Empty; + + /// + /// Whether the hook is scoped to a turn or the outer session + /// + public HookEndScope? Scope { get; set; } + + /// + /// Whether the hook completed successfully + /// + public bool Success { get; set; } = false; + + /// + /// Hook output after host-side sanitization + /// + public IDictionary? Output { get; set; } + + /// + /// Hook execution duration in milliseconds + /// + public double? DurationMs { get; set; } + + /// + /// Human-readable error when success is false + /// + public string? Error { get; set; } + + /// + /// Redaction state for sensitive hook output fields + /// + public RedactionMetadata? Redaction { get; set; } + + + + #region Load Methods + + /// + /// Load a HookEndPayload instance from a dictionary. + /// + /// The dictionary containing the data. + /// Optional context with pre/post processing callbacks. + /// The loaded HookEndPayload instance. + public static HookEndPayload Load(Dictionary data, LoadContext? context = null) + { + if (context is not null) + { + data = context.ProcessInput(data); + } + + + // Create new instance + var instance = new HookEndPayload(); + + + if (data.TryGetValue("hookInvocationId", out var hookInvocationIdValue) && hookInvocationIdValue is not null) + { + instance.HookInvocationId = hookInvocationIdValue?.ToString()!; + } + + if (data.TryGetValue("hookType", out var hookTypeValue) && hookTypeValue is not null) + { + instance.HookType = hookTypeValue?.ToString()!; + } + + if (data.TryGetValue("scope", out var scopeValue) && scopeValue is not null) + { + instance.Scope = Enum.Parse(scopeValue?.ToString()!, true); + } + + if (data.TryGetValue("success", out var successValue) && successValue is not null) + { + instance.Success = Convert.ToBoolean(successValue); + } + + if (data.TryGetValue("output", out var outputValue) && outputValue is not null) + { + instance.Output = outputValue.GetDictionary()!; + } + + if (data.TryGetValue("durationMs", out var durationMsValue) && durationMsValue is not null) + { + instance.DurationMs = Convert.ToDouble(durationMsValue); + } + + if (data.TryGetValue("error", out var errorValue) && errorValue is not null) + { + instance.Error = errorValue?.ToString()!; + } + + if (data.TryGetValue("redaction", out var redactionValue) && redactionValue is not null) + { + instance.Redaction = RedactionMetadata.Load(redactionValue.GetDictionary(RedactionMetadata.ShorthandProperty), context); + } + + if (context is not null) + { + instance = context.ProcessOutput(instance); + } + return instance; + } + + + #endregion + + #region Save Methods + + /// + /// Save the HookEndPayload instance to a dictionary. + /// + /// Optional context with pre/post processing callbacks. + /// The dictionary representation of this instance. + public Dictionary Save(SaveContext? context = null) + { + var obj = this; + if (context is not null) + { + obj = context.ProcessObject(obj); + } + + + var result = new Dictionary(); + + + result["hookInvocationId"] = obj.HookInvocationId; + + + result["hookType"] = obj.HookType; + + + if (obj.Scope is not null) + { + result["scope"] = obj.Scope.Value.ToString().ToLowerInvariant(); + } + + + result["success"] = obj.Success; + + + if (obj.Output is not null) + { + result["output"] = obj.Output; + } + + + if (obj.DurationMs is not null) + { + result["durationMs"] = obj.DurationMs; + } + + + if (obj.Error is not null) + { + result["error"] = obj.Error; + } + + + if (obj.Redaction is not null) + { + result["redaction"] = obj.Redaction?.Save(context); + } + + + if (context is not null) + { + result = context.ProcessDict(result); + } + + return result; + } + + + /// + /// Convert the HookEndPayload instance to a YAML string. + /// + /// Optional context with pre/post processing callbacks. + /// The YAML string representation of this instance. + public string ToYaml(SaveContext? context = null) + { + context ??= new SaveContext(); + return context.ToYaml(Save(context)); + } + + /// + /// Convert the HookEndPayload instance to a JSON string. + /// + /// Optional context with pre/post processing callbacks. + /// Whether to indent the output. Defaults to true. + /// The JSON string representation of this instance. + public string ToJson(SaveContext? context = null, bool indent = true) + { + context ??= new SaveContext(); + return context.ToJson(Save(context), indent); + } + + /// + /// Load a HookEndPayload instance from a JSON string. + /// + /// The JSON string to parse. + /// Optional context with pre/post processing callbacks. + /// The loaded HookEndPayload instance. + public static HookEndPayload FromJson(string json, LoadContext? context = null) + { + using var doc = JsonDocument.Parse(json); + Dictionary dict; + dict = JsonSerializer.Deserialize>(json, JsonUtils.Options) + ?? throw new ArgumentException("Failed to parse JSON as dictionary"); + + return Load(dict, context); + } + + /// + /// Load a HookEndPayload instance from a YAML string. + /// + /// The YAML string to parse. + /// Optional context with pre/post processing callbacks. + /// The loaded HookEndPayload instance. + public static HookEndPayload FromYaml(string yaml, LoadContext? context = null) + { + var dict = YamlUtils.Deserializer.Deserialize>(yaml) + ?? throw new ArgumentException("Failed to parse YAML as dictionary"); + + return Load(dict, context); + } + + #endregion +} diff --git a/runtime/csharp/Prompty.Core/Model/events/HookEndScope.cs b/runtime/csharp/Prompty.Core/Model/events/HookEndScope.cs new file mode 100644 index 00000000..d307eca7 --- /dev/null +++ b/runtime/csharp/Prompty.Core/Model/events/HookEndScope.cs @@ -0,0 +1,18 @@ +// +// +// Code generated by Typra emitter; DO NOT EDIT. + +using System.Text.Json.Serialization; + +namespace Prompty.Core; + +[JsonConverter(typeof(JsonStringEnumConverter))] +public enum HookEndScope +{ + [JsonPropertyName("turn")] + Turn, + + [JsonPropertyName("session")] + Session, + +} diff --git a/runtime/csharp/Prompty.Core/Model/events/HookStartPayload.cs b/runtime/csharp/Prompty.Core/Model/events/HookStartPayload.cs new file mode 100644 index 00000000..f3986367 --- /dev/null +++ b/runtime/csharp/Prompty.Core/Model/events/HookStartPayload.cs @@ -0,0 +1,217 @@ +// +// Copyright (c) Microsoft. All rights reserved. +using System.Text.Json; +using YamlDotNet.Serialization; + +#pragma warning disable IDE0130 +namespace Prompty.Core; +#pragma warning restore IDE0130 + + /// + /// Payload for "hook_start" events — a host lifecycle hook is beginning. + /// +public partial class HookStartPayload +{ + /// + /// The shorthand property name for this type, if any. + /// + public static string? ShorthandProperty => null; + + /// + /// Initializes a new instance of . + /// +#pragma warning disable CS8618 + public HookStartPayload() + { + } +#pragma warning restore CS8618 + + /// + /// Stable hook invocation identifier + /// + public string HookInvocationId { get; set; } = string.Empty; + + /// + /// Host-defined hook type + /// + public string HookType { get; set; } = string.Empty; + + /// + /// Whether the hook is scoped to a turn or the outer session + /// + public HookStartScope? Scope { get; set; } + + /// + /// Hook input after host-side sanitization + /// + public IDictionary? Input { get; set; } + + /// + /// Redaction state for sensitive hook input fields + /// + public RedactionMetadata? Redaction { get; set; } + + + + #region Load Methods + + /// + /// Load a HookStartPayload instance from a dictionary. + /// + /// The dictionary containing the data. + /// Optional context with pre/post processing callbacks. + /// The loaded HookStartPayload instance. + public static HookStartPayload Load(Dictionary data, LoadContext? context = null) + { + if (context is not null) + { + data = context.ProcessInput(data); + } + + + // Create new instance + var instance = new HookStartPayload(); + + + if (data.TryGetValue("hookInvocationId", out var hookInvocationIdValue) && hookInvocationIdValue is not null) + { + instance.HookInvocationId = hookInvocationIdValue?.ToString()!; + } + + if (data.TryGetValue("hookType", out var hookTypeValue) && hookTypeValue is not null) + { + instance.HookType = hookTypeValue?.ToString()!; + } + + if (data.TryGetValue("scope", out var scopeValue) && scopeValue is not null) + { + instance.Scope = Enum.Parse(scopeValue?.ToString()!, true); + } + + if (data.TryGetValue("input", out var inputValue) && inputValue is not null) + { + instance.Input = inputValue.GetDictionary()!; + } + + if (data.TryGetValue("redaction", out var redactionValue) && redactionValue is not null) + { + instance.Redaction = RedactionMetadata.Load(redactionValue.GetDictionary(RedactionMetadata.ShorthandProperty), context); + } + + if (context is not null) + { + instance = context.ProcessOutput(instance); + } + return instance; + } + + + #endregion + + #region Save Methods + + /// + /// Save the HookStartPayload instance to a dictionary. + /// + /// Optional context with pre/post processing callbacks. + /// The dictionary representation of this instance. + public Dictionary Save(SaveContext? context = null) + { + var obj = this; + if (context is not null) + { + obj = context.ProcessObject(obj); + } + + + var result = new Dictionary(); + + + result["hookInvocationId"] = obj.HookInvocationId; + + + result["hookType"] = obj.HookType; + + + if (obj.Scope is not null) + { + result["scope"] = obj.Scope.Value.ToString().ToLowerInvariant(); + } + + + if (obj.Input is not null) + { + result["input"] = obj.Input; + } + + + if (obj.Redaction is not null) + { + result["redaction"] = obj.Redaction?.Save(context); + } + + + if (context is not null) + { + result = context.ProcessDict(result); + } + + return result; + } + + + /// + /// Convert the HookStartPayload instance to a YAML string. + /// + /// Optional context with pre/post processing callbacks. + /// The YAML string representation of this instance. + public string ToYaml(SaveContext? context = null) + { + context ??= new SaveContext(); + return context.ToYaml(Save(context)); + } + + /// + /// Convert the HookStartPayload instance to a JSON string. + /// + /// Optional context with pre/post processing callbacks. + /// Whether to indent the output. Defaults to true. + /// The JSON string representation of this instance. + public string ToJson(SaveContext? context = null, bool indent = true) + { + context ??= new SaveContext(); + return context.ToJson(Save(context), indent); + } + + /// + /// Load a HookStartPayload instance from a JSON string. + /// + /// The JSON string to parse. + /// Optional context with pre/post processing callbacks. + /// The loaded HookStartPayload instance. + public static HookStartPayload FromJson(string json, LoadContext? context = null) + { + using var doc = JsonDocument.Parse(json); + Dictionary dict; + dict = JsonSerializer.Deserialize>(json, JsonUtils.Options) + ?? throw new ArgumentException("Failed to parse JSON as dictionary"); + + return Load(dict, context); + } + + /// + /// Load a HookStartPayload instance from a YAML string. + /// + /// The YAML string to parse. + /// Optional context with pre/post processing callbacks. + /// The loaded HookStartPayload instance. + public static HookStartPayload FromYaml(string yaml, LoadContext? context = null) + { + var dict = YamlUtils.Deserializer.Deserialize>(yaml) + ?? throw new ArgumentException("Failed to parse YAML as dictionary"); + + return Load(dict, context); + } + + #endregion +} diff --git a/runtime/csharp/Prompty.Core/Model/events/HookStartScope.cs b/runtime/csharp/Prompty.Core/Model/events/HookStartScope.cs new file mode 100644 index 00000000..3731bad3 --- /dev/null +++ b/runtime/csharp/Prompty.Core/Model/events/HookStartScope.cs @@ -0,0 +1,18 @@ +// +// +// Code generated by Typra emitter; DO NOT EDIT. + +using System.Text.Json.Serialization; + +namespace Prompty.Core; + +[JsonConverter(typeof(JsonStringEnumConverter))] +public enum HookStartScope +{ + [JsonPropertyName("turn")] + Turn, + + [JsonPropertyName("session")] + Session, + +} diff --git a/runtime/csharp/Prompty.Core/Model/events/HostToolRequest.cs b/runtime/csharp/Prompty.Core/Model/events/HostToolRequest.cs new file mode 100644 index 00000000..c487f609 --- /dev/null +++ b/runtime/csharp/Prompty.Core/Model/events/HostToolRequest.cs @@ -0,0 +1,220 @@ +// +// Copyright (c) Microsoft. All rights reserved. +using System.Text.Json; +using YamlDotNet.Serialization; + +#pragma warning disable IDE0130 +namespace Prompty.Core; +#pragma warning restore IDE0130 + + /// + /// Request passed to a host tool executor after policy and permission checks. + /// +public partial class HostToolRequest +{ + /// + /// The shorthand property name for this type, if any. + /// + public static string? ShorthandProperty => null; + + /// + /// Initializes a new instance of . + /// +#pragma warning disable CS8618 + public HostToolRequest() + { + } +#pragma warning restore CS8618 + + /// + /// Stable host execution request identifier + /// + public string? RequestId { get; set; } + + /// + /// Associated model tool call identifier, when available + /// + public string? ToolCallId { get; set; } + + /// + /// Name of the host tool being executed + /// + public string ToolName { get; set; } = string.Empty; + + /// + /// Tool arguments after host-side sanitization + /// + public IDictionary? Arguments { get; set; } + + /// + /// Working directory or execution scope for the tool + /// + public string? WorkingDirectory { get; set; } + + + + #region Load Methods + + /// + /// Load a HostToolRequest instance from a dictionary. + /// + /// The dictionary containing the data. + /// Optional context with pre/post processing callbacks. + /// The loaded HostToolRequest instance. + public static HostToolRequest Load(Dictionary data, LoadContext? context = null) + { + if (context is not null) + { + data = context.ProcessInput(data); + } + + + // Create new instance + var instance = new HostToolRequest(); + + + if (data.TryGetValue("requestId", out var requestIdValue) && requestIdValue is not null) + { + instance.RequestId = requestIdValue?.ToString()!; + } + + if (data.TryGetValue("toolCallId", out var toolCallIdValue) && toolCallIdValue is not null) + { + instance.ToolCallId = toolCallIdValue?.ToString()!; + } + + if (data.TryGetValue("toolName", out var toolNameValue) && toolNameValue is not null) + { + instance.ToolName = toolNameValue?.ToString()!; + } + + if (data.TryGetValue("arguments", out var argumentsValue) && argumentsValue is not null) + { + instance.Arguments = argumentsValue.GetDictionary()!; + } + + if (data.TryGetValue("workingDirectory", out var workingDirectoryValue) && workingDirectoryValue is not null) + { + instance.WorkingDirectory = workingDirectoryValue?.ToString()!; + } + + if (context is not null) + { + instance = context.ProcessOutput(instance); + } + return instance; + } + + + #endregion + + #region Save Methods + + /// + /// Save the HostToolRequest instance to a dictionary. + /// + /// Optional context with pre/post processing callbacks. + /// The dictionary representation of this instance. + public Dictionary Save(SaveContext? context = null) + { + var obj = this; + if (context is not null) + { + obj = context.ProcessObject(obj); + } + + + var result = new Dictionary(); + + + if (obj.RequestId is not null) + { + result["requestId"] = obj.RequestId; + } + + + if (obj.ToolCallId is not null) + { + result["toolCallId"] = obj.ToolCallId; + } + + + result["toolName"] = obj.ToolName; + + + if (obj.Arguments is not null) + { + result["arguments"] = obj.Arguments; + } + + + if (obj.WorkingDirectory is not null) + { + result["workingDirectory"] = obj.WorkingDirectory; + } + + + if (context is not null) + { + result = context.ProcessDict(result); + } + + return result; + } + + + /// + /// Convert the HostToolRequest instance to a YAML string. + /// + /// Optional context with pre/post processing callbacks. + /// The YAML string representation of this instance. + public string ToYaml(SaveContext? context = null) + { + context ??= new SaveContext(); + return context.ToYaml(Save(context)); + } + + /// + /// Convert the HostToolRequest instance to a JSON string. + /// + /// Optional context with pre/post processing callbacks. + /// Whether to indent the output. Defaults to true. + /// The JSON string representation of this instance. + public string ToJson(SaveContext? context = null, bool indent = true) + { + context ??= new SaveContext(); + return context.ToJson(Save(context), indent); + } + + /// + /// Load a HostToolRequest instance from a JSON string. + /// + /// The JSON string to parse. + /// Optional context with pre/post processing callbacks. + /// The loaded HostToolRequest instance. + public static HostToolRequest FromJson(string json, LoadContext? context = null) + { + using var doc = JsonDocument.Parse(json); + Dictionary dict; + dict = JsonSerializer.Deserialize>(json, JsonUtils.Options) + ?? throw new ArgumentException("Failed to parse JSON as dictionary"); + + return Load(dict, context); + } + + /// + /// Load a HostToolRequest instance from a YAML string. + /// + /// The YAML string to parse. + /// Optional context with pre/post processing callbacks. + /// The loaded HostToolRequest instance. + public static HostToolRequest FromYaml(string yaml, LoadContext? context = null) + { + var dict = YamlUtils.Deserializer.Deserialize>(yaml) + ?? throw new ArgumentException("Failed to parse YAML as dictionary"); + + return Load(dict, context); + } + + #endregion +} diff --git a/runtime/csharp/Prompty.Core/Model/events/HostToolResult.cs b/runtime/csharp/Prompty.Core/Model/events/HostToolResult.cs new file mode 100644 index 00000000..d909c5e9 --- /dev/null +++ b/runtime/csharp/Prompty.Core/Model/events/HostToolResult.cs @@ -0,0 +1,281 @@ +// +// Copyright (c) Microsoft. All rights reserved. +using System.Text.Json; +using YamlDotNet.Serialization; + +#pragma warning disable IDE0130 +namespace Prompty.Core; +#pragma warning restore IDE0130 + + /// + /// Result returned by a host tool executor. + /// +public partial class HostToolResult +{ + /// + /// The shorthand property name for this type, if any. + /// + public static string? ShorthandProperty => null; + + /// + /// Initializes a new instance of . + /// +#pragma warning disable CS8618 + public HostToolResult() + { + } +#pragma warning restore CS8618 + + /// + /// Stable host execution request identifier + /// + public string? RequestId { get; set; } + + /// + /// Associated model tool call identifier, when available + /// + public string? ToolCallId { get; set; } + + /// + /// Name of the host tool that executed + /// + public string ToolName { get; set; } = string.Empty; + + /// + /// Whether the host execution completed successfully + /// + public bool Success { get; set; } = false; + + /// + /// Host-normalized execution result + /// + public object? Result { get; set; } + + /// + /// Process or host exit code, when applicable + /// + public int? ExitCode { get; set; } + + /// + /// Tool execution duration in milliseconds + /// + public double? DurationMs { get; set; } + + /// + /// Machine-readable error category when success is false + /// + public string? ErrorKind { get; set; } + + /// + /// Host-specific telemetry for the execution + /// + public IDictionary? Telemetry { get; set; } + + + + #region Load Methods + + /// + /// Load a HostToolResult instance from a dictionary. + /// + /// The dictionary containing the data. + /// Optional context with pre/post processing callbacks. + /// The loaded HostToolResult instance. + public static HostToolResult Load(Dictionary data, LoadContext? context = null) + { + if (context is not null) + { + data = context.ProcessInput(data); + } + + + // Create new instance + var instance = new HostToolResult(); + + + if (data.TryGetValue("requestId", out var requestIdValue) && requestIdValue is not null) + { + instance.RequestId = requestIdValue?.ToString()!; + } + + if (data.TryGetValue("toolCallId", out var toolCallIdValue) && toolCallIdValue is not null) + { + instance.ToolCallId = toolCallIdValue?.ToString()!; + } + + if (data.TryGetValue("toolName", out var toolNameValue) && toolNameValue is not null) + { + instance.ToolName = toolNameValue?.ToString()!; + } + + if (data.TryGetValue("success", out var successValue) && successValue is not null) + { + instance.Success = Convert.ToBoolean(successValue); + } + + if (data.TryGetValue("result", out var resultValue) && resultValue is not null) + { + instance.Result = resultValue; + } + + if (data.TryGetValue("exitCode", out var exitCodeValue) && exitCodeValue is not null) + { + instance.ExitCode = Convert.ToInt32(exitCodeValue); + } + + if (data.TryGetValue("durationMs", out var durationMsValue) && durationMsValue is not null) + { + instance.DurationMs = Convert.ToDouble(durationMsValue); + } + + if (data.TryGetValue("errorKind", out var errorKindValue) && errorKindValue is not null) + { + instance.ErrorKind = errorKindValue?.ToString()!; + } + + if (data.TryGetValue("telemetry", out var telemetryValue) && telemetryValue is not null) + { + instance.Telemetry = telemetryValue.GetDictionary()!; + } + + if (context is not null) + { + instance = context.ProcessOutput(instance); + } + return instance; + } + + + #endregion + + #region Save Methods + + /// + /// Save the HostToolResult instance to a dictionary. + /// + /// Optional context with pre/post processing callbacks. + /// The dictionary representation of this instance. + public Dictionary Save(SaveContext? context = null) + { + var obj = this; + if (context is not null) + { + obj = context.ProcessObject(obj); + } + + + var result = new Dictionary(); + + + if (obj.RequestId is not null) + { + result["requestId"] = obj.RequestId; + } + + + if (obj.ToolCallId is not null) + { + result["toolCallId"] = obj.ToolCallId; + } + + + result["toolName"] = obj.ToolName; + + + result["success"] = obj.Success; + + + if (obj.Result is not null) + { + result["result"] = obj.Result; + } + + + if (obj.ExitCode is not null) + { + result["exitCode"] = obj.ExitCode; + } + + + if (obj.DurationMs is not null) + { + result["durationMs"] = obj.DurationMs; + } + + + if (obj.ErrorKind is not null) + { + result["errorKind"] = obj.ErrorKind; + } + + + if (obj.Telemetry is not null) + { + result["telemetry"] = obj.Telemetry; + } + + + if (context is not null) + { + result = context.ProcessDict(result); + } + + return result; + } + + + /// + /// Convert the HostToolResult instance to a YAML string. + /// + /// Optional context with pre/post processing callbacks. + /// The YAML string representation of this instance. + public string ToYaml(SaveContext? context = null) + { + context ??= new SaveContext(); + return context.ToYaml(Save(context)); + } + + /// + /// Convert the HostToolResult instance to a JSON string. + /// + /// Optional context with pre/post processing callbacks. + /// Whether to indent the output. Defaults to true. + /// The JSON string representation of this instance. + public string ToJson(SaveContext? context = null, bool indent = true) + { + context ??= new SaveContext(); + return context.ToJson(Save(context), indent); + } + + /// + /// Load a HostToolResult instance from a JSON string. + /// + /// The JSON string to parse. + /// Optional context with pre/post processing callbacks. + /// The loaded HostToolResult instance. + public static HostToolResult FromJson(string json, LoadContext? context = null) + { + using var doc = JsonDocument.Parse(json); + Dictionary dict; + dict = JsonSerializer.Deserialize>(json, JsonUtils.Options) + ?? throw new ArgumentException("Failed to parse JSON as dictionary"); + + return Load(dict, context); + } + + /// + /// Load a HostToolResult instance from a YAML string. + /// + /// The YAML string to parse. + /// Optional context with pre/post processing callbacks. + /// The loaded HostToolResult instance. + public static HostToolResult FromYaml(string yaml, LoadContext? context = null) + { + var dict = YamlUtils.Deserializer.Deserialize>(yaml) + ?? throw new ArgumentException("Failed to parse YAML as dictionary"); + + return Load(dict, context); + } + + #endregion +} diff --git a/runtime/csharp/Prompty.Core/Model/events/LlmCompletePayload.cs b/runtime/csharp/Prompty.Core/Model/events/LlmCompletePayload.cs new file mode 100644 index 00000000..7465de8d --- /dev/null +++ b/runtime/csharp/Prompty.Core/Model/events/LlmCompletePayload.cs @@ -0,0 +1,207 @@ +// +// Copyright (c) Microsoft. All rights reserved. +using System.Text.Json; +using YamlDotNet.Serialization; + +#pragma warning disable IDE0130 +namespace Prompty.Core; +#pragma warning restore IDE0130 + + /// + /// Payload for "llm_complete" events — an LLM request completed. + /// +public partial class LlmCompletePayload +{ + /// + /// The shorthand property name for this type, if any. + /// + public static string? ShorthandProperty => null; + + /// + /// Initializes a new instance of . + /// +#pragma warning disable CS8618 + public LlmCompletePayload() + { + } +#pragma warning restore CS8618 + + /// + /// Provider request identifier, when supplied by the SDK/API + /// + public string? RequestId { get; set; } + + /// + /// Service request identifier, when supplied by the SDK/API + /// + public string? ServiceRequestId { get; set; } + + /// + /// Token usage reported by the provider + /// + public TokenUsage? Usage { get; set; } + + /// + /// LLM call duration in milliseconds + /// + public double? DurationMs { get; set; } + + + + #region Load Methods + + /// + /// Load a LlmCompletePayload instance from a dictionary. + /// + /// The dictionary containing the data. + /// Optional context with pre/post processing callbacks. + /// The loaded LlmCompletePayload instance. + public static LlmCompletePayload Load(Dictionary data, LoadContext? context = null) + { + if (context is not null) + { + data = context.ProcessInput(data); + } + + + // Create new instance + var instance = new LlmCompletePayload(); + + + if (data.TryGetValue("requestId", out var requestIdValue) && requestIdValue is not null) + { + instance.RequestId = requestIdValue?.ToString()!; + } + + if (data.TryGetValue("serviceRequestId", out var serviceRequestIdValue) && serviceRequestIdValue is not null) + { + instance.ServiceRequestId = serviceRequestIdValue?.ToString()!; + } + + if (data.TryGetValue("usage", out var usageValue) && usageValue is not null) + { + instance.Usage = TokenUsage.Load(usageValue.GetDictionary(TokenUsage.ShorthandProperty), context); + } + + if (data.TryGetValue("durationMs", out var durationMsValue) && durationMsValue is not null) + { + instance.DurationMs = Convert.ToDouble(durationMsValue); + } + + if (context is not null) + { + instance = context.ProcessOutput(instance); + } + return instance; + } + + + #endregion + + #region Save Methods + + /// + /// Save the LlmCompletePayload instance to a dictionary. + /// + /// Optional context with pre/post processing callbacks. + /// The dictionary representation of this instance. + public Dictionary Save(SaveContext? context = null) + { + var obj = this; + if (context is not null) + { + obj = context.ProcessObject(obj); + } + + + var result = new Dictionary(); + + + if (obj.RequestId is not null) + { + result["requestId"] = obj.RequestId; + } + + + if (obj.ServiceRequestId is not null) + { + result["serviceRequestId"] = obj.ServiceRequestId; + } + + + if (obj.Usage is not null) + { + result["usage"] = obj.Usage?.Save(context); + } + + + if (obj.DurationMs is not null) + { + result["durationMs"] = obj.DurationMs; + } + + + if (context is not null) + { + result = context.ProcessDict(result); + } + + return result; + } + + + /// + /// Convert the LlmCompletePayload instance to a YAML string. + /// + /// Optional context with pre/post processing callbacks. + /// The YAML string representation of this instance. + public string ToYaml(SaveContext? context = null) + { + context ??= new SaveContext(); + return context.ToYaml(Save(context)); + } + + /// + /// Convert the LlmCompletePayload instance to a JSON string. + /// + /// Optional context with pre/post processing callbacks. + /// Whether to indent the output. Defaults to true. + /// The JSON string representation of this instance. + public string ToJson(SaveContext? context = null, bool indent = true) + { + context ??= new SaveContext(); + return context.ToJson(Save(context), indent); + } + + /// + /// Load a LlmCompletePayload instance from a JSON string. + /// + /// The JSON string to parse. + /// Optional context with pre/post processing callbacks. + /// The loaded LlmCompletePayload instance. + public static LlmCompletePayload FromJson(string json, LoadContext? context = null) + { + using var doc = JsonDocument.Parse(json); + Dictionary dict; + dict = JsonSerializer.Deserialize>(json, JsonUtils.Options) + ?? throw new ArgumentException("Failed to parse JSON as dictionary"); + + return Load(dict, context); + } + + /// + /// Load a LlmCompletePayload instance from a YAML string. + /// + /// The YAML string to parse. + /// Optional context with pre/post processing callbacks. + /// The loaded LlmCompletePayload instance. + public static LlmCompletePayload FromYaml(string yaml, LoadContext? context = null) + { + var dict = YamlUtils.Deserializer.Deserialize>(yaml) + ?? throw new ArgumentException("Failed to parse YAML as dictionary"); + + return Load(dict, context); + } + + #endregion +} diff --git a/runtime/csharp/Prompty.Core/Model/events/LlmStartPayload.cs b/runtime/csharp/Prompty.Core/Model/events/LlmStartPayload.cs new file mode 100644 index 00000000..7fb7b206 --- /dev/null +++ b/runtime/csharp/Prompty.Core/Model/events/LlmStartPayload.cs @@ -0,0 +1,207 @@ +// +// Copyright (c) Microsoft. All rights reserved. +using System.Text.Json; +using YamlDotNet.Serialization; + +#pragma warning disable IDE0130 +namespace Prompty.Core; +#pragma warning restore IDE0130 + + /// + /// Payload for "llm_start" events — an LLM request is about to be sent. + /// +public partial class LlmStartPayload +{ + /// + /// The shorthand property name for this type, if any. + /// + public static string? ShorthandProperty => null; + + /// + /// Initializes a new instance of . + /// +#pragma warning disable CS8618 + public LlmStartPayload() + { + } +#pragma warning restore CS8618 + + /// + /// Provider identifier used for the request + /// + public string? Provider { get; set; } + + /// + /// Model or deployment identifier used for the request + /// + public string? ModelId { get; set; } + + /// + /// Number of messages sent to the provider + /// + public int? MessageCount { get; set; } + + /// + /// Retry attempt number, zero for the initial attempt + /// + public int? Attempt { get; set; } + + + + #region Load Methods + + /// + /// Load a LlmStartPayload instance from a dictionary. + /// + /// The dictionary containing the data. + /// Optional context with pre/post processing callbacks. + /// The loaded LlmStartPayload instance. + public static LlmStartPayload Load(Dictionary data, LoadContext? context = null) + { + if (context is not null) + { + data = context.ProcessInput(data); + } + + + // Create new instance + var instance = new LlmStartPayload(); + + + if (data.TryGetValue("provider", out var providerValue) && providerValue is not null) + { + instance.Provider = providerValue?.ToString()!; + } + + if (data.TryGetValue("modelId", out var modelIdValue) && modelIdValue is not null) + { + instance.ModelId = modelIdValue?.ToString()!; + } + + if (data.TryGetValue("messageCount", out var messageCountValue) && messageCountValue is not null) + { + instance.MessageCount = Convert.ToInt32(messageCountValue); + } + + if (data.TryGetValue("attempt", out var attemptValue) && attemptValue is not null) + { + instance.Attempt = Convert.ToInt32(attemptValue); + } + + if (context is not null) + { + instance = context.ProcessOutput(instance); + } + return instance; + } + + + #endregion + + #region Save Methods + + /// + /// Save the LlmStartPayload instance to a dictionary. + /// + /// Optional context with pre/post processing callbacks. + /// The dictionary representation of this instance. + public Dictionary Save(SaveContext? context = null) + { + var obj = this; + if (context is not null) + { + obj = context.ProcessObject(obj); + } + + + var result = new Dictionary(); + + + if (obj.Provider is not null) + { + result["provider"] = obj.Provider; + } + + + if (obj.ModelId is not null) + { + result["modelId"] = obj.ModelId; + } + + + if (obj.MessageCount is not null) + { + result["messageCount"] = obj.MessageCount; + } + + + if (obj.Attempt is not null) + { + result["attempt"] = obj.Attempt; + } + + + if (context is not null) + { + result = context.ProcessDict(result); + } + + return result; + } + + + /// + /// Convert the LlmStartPayload instance to a YAML string. + /// + /// Optional context with pre/post processing callbacks. + /// The YAML string representation of this instance. + public string ToYaml(SaveContext? context = null) + { + context ??= new SaveContext(); + return context.ToYaml(Save(context)); + } + + /// + /// Convert the LlmStartPayload instance to a JSON string. + /// + /// Optional context with pre/post processing callbacks. + /// Whether to indent the output. Defaults to true. + /// The JSON string representation of this instance. + public string ToJson(SaveContext? context = null, bool indent = true) + { + context ??= new SaveContext(); + return context.ToJson(Save(context), indent); + } + + /// + /// Load a LlmStartPayload instance from a JSON string. + /// + /// The JSON string to parse. + /// Optional context with pre/post processing callbacks. + /// The loaded LlmStartPayload instance. + public static LlmStartPayload FromJson(string json, LoadContext? context = null) + { + using var doc = JsonDocument.Parse(json); + Dictionary dict; + dict = JsonSerializer.Deserialize>(json, JsonUtils.Options) + ?? throw new ArgumentException("Failed to parse JSON as dictionary"); + + return Load(dict, context); + } + + /// + /// Load a LlmStartPayload instance from a YAML string. + /// + /// The YAML string to parse. + /// Optional context with pre/post processing callbacks. + /// The loaded LlmStartPayload instance. + public static LlmStartPayload FromYaml(string yaml, LoadContext? context = null) + { + var dict = YamlUtils.Deserializer.Deserialize>(yaml) + ?? throw new ArgumentException("Failed to parse YAML as dictionary"); + + return Load(dict, context); + } + + #endregion +} diff --git a/runtime/csharp/Prompty.Core/Model/events/MessagesUpdatedPayload.cs b/runtime/csharp/Prompty.Core/Model/events/MessagesUpdatedPayload.cs index cfddbe1a..a1266b6c 100644 --- a/runtime/csharp/Prompty.Core/Model/events/MessagesUpdatedPayload.cs +++ b/runtime/csharp/Prompty.Core/Model/events/MessagesUpdatedPayload.cs @@ -1,3 +1,4 @@ +// // Copyright (c) Microsoft. All rights reserved. using System.Text.Json; using YamlDotNet.Serialization; @@ -6,9 +7,9 @@ namespace Prompty.Core; #pragma warning restore IDE0130 -/// -/// Payload for "messages_updated" events — the conversation state has changed. -/// + /// + /// Payload for "messages_updated" events — the conversation state has changed. + /// public partial class MessagesUpdatedPayload { /// @@ -28,7 +29,22 @@ public MessagesUpdatedPayload() /// /// The current full message list after the update /// - public IList Messages { get; set; } = []; + public IList? Messages { get; set; } + + /// + /// Why the message list changed + /// + public string? Reason { get; set; } + + /// + /// Messages appended by this update, when available + /// + public IList? Appended { get; set; } + + /// + /// Number of messages removed by this update, when available + /// + public int? Removed { get; set; } @@ -57,6 +73,21 @@ public static MessagesUpdatedPayload Load(Dictionary data, Load instance.Messages = LoadMessages(messagesValue, context); } + if (data.TryGetValue("reason", out var reasonValue) && reasonValue is not null) + { + instance.Reason = reasonValue?.ToString()!; + } + + if (data.TryGetValue("appended", out var appendedValue) && appendedValue is not null) + { + instance.Appended = LoadAppended(appendedValue, context); + } + + if (data.TryGetValue("removed", out var removedValue) && removedValue is not null) + { + instance.Removed = Convert.ToInt32(removedValue); + } + if (context is not null) { instance = context.ProcessOutput(instance); @@ -119,6 +150,60 @@ public static IList LoadMessages(object data, LoadContext? context) } + /// + /// Load a list of Message from a dictionary or list. + /// + public static IList LoadAppended(object data, LoadContext? context) + { + var result = new List(); + + if (data is Dictionary dict) + { + // Convert named dictionary to list + foreach (var kvp in dict) + { + if (kvp.Value is IEnumerable) + { + throw new ArgumentException( + $"Invalid 'appended' format: key '{kvp.Key}' has an array value. " + + $"'appended' must be a flat list of objects or a name-keyed dict — " + + "not a nested {" + kvp.Key + ": [...]} structure."); + } + var itemDict = kvp.Value.GetDictionary(); + if (itemDict.Count > 0) + { + // Value is an object, add name to it + itemDict["name"] = kvp.Key; + result.Add(Message.Load(itemDict, context)); + } + else + { + // Value is a scalar, use it as the primary property + var newDict = new Dictionary + { + ["name"] = kvp.Key, + ["role"] = kvp.Value + }; + result.Add(Message.Load(newDict, context)); + } + } + } + else if (data is IEnumerable list) + { + foreach (var item in list) + { + var itemDict = item.GetDictionary(Message.ShorthandProperty); + if (itemDict.Count > 0) + { + result.Add(Message.Load(itemDict, context)); + } + } + } + + return result; + } + + #endregion #region Save Methods @@ -140,7 +225,28 @@ public static IList LoadMessages(object data, LoadContext? context) var result = new Dictionary(); - result["messages"] = SaveMessages(obj.Messages, context); + if (obj.Messages is not null) + { + result["messages"] = SaveMessages(obj.Messages, context); + } + + + if (obj.Reason is not null) + { + result["reason"] = obj.Reason; + } + + + if (obj.Appended is not null) + { + result["appended"] = SaveAppended(obj.Appended, context); + } + + + if (obj.Removed is not null) + { + result["removed"] = obj.Removed; + } if (context is not null) @@ -165,6 +271,19 @@ public static object SaveMessages(IList items, SaveContext? context) } + /// + /// Save a list of Message to object or array format. + /// + public static object SaveAppended(IList items, SaveContext? context) + { + context ??= new SaveContext(); + + // This collection type does not have a 'name' property, only array format is supported + return items.Select(item => item.Save(context)).ToList(); + + } + + /// /// Convert the MessagesUpdatedPayload instance to a YAML string. /// diff --git a/runtime/csharp/Prompty.Core/Model/events/PermissionCompletedPayload.cs b/runtime/csharp/Prompty.Core/Model/events/PermissionCompletedPayload.cs new file mode 100644 index 00000000..3793084e --- /dev/null +++ b/runtime/csharp/Prompty.Core/Model/events/PermissionCompletedPayload.cs @@ -0,0 +1,249 @@ +// +// Copyright (c) Microsoft. All rights reserved. +using System.Text.Json; +using YamlDotNet.Serialization; + +#pragma warning disable IDE0130 +namespace Prompty.Core; +#pragma warning restore IDE0130 + + /// + /// Payload for permission completion events — an approval decision was made. + /// +public partial class PermissionCompletedPayload +{ + /// + /// The shorthand property name for this type, if any. + /// + public static string? ShorthandProperty => null; + + /// + /// Initializes a new instance of . + /// +#pragma warning disable CS8618 + public PermissionCompletedPayload() + { + } +#pragma warning restore CS8618 + + /// + /// Stable permission request identifier + /// + public string? RequestId { get; set; } + + /// + /// Associated tool call identifier, when the permission gated a tool call + /// + public string? ToolCallId { get; set; } + + /// + /// Permission/action name that was decided + /// + public string Permission { get; set; } = string.Empty; + + /// + /// Whether the requested permission was approved + /// + public bool Approved { get; set; } = false; + + /// + /// Decision reason, if available + /// + public string? Reason { get; set; } + + /// + /// Host-specific decision result, such as a durable approval token or denial details + /// + public IDictionary? Result { get; set; } + + /// + /// Redaction state for sensitive decision fields + /// + public RedactionMetadata? Redaction { get; set; } + + + + #region Load Methods + + /// + /// Load a PermissionCompletedPayload instance from a dictionary. + /// + /// The dictionary containing the data. + /// Optional context with pre/post processing callbacks. + /// The loaded PermissionCompletedPayload instance. + public static PermissionCompletedPayload Load(Dictionary data, LoadContext? context = null) + { + if (context is not null) + { + data = context.ProcessInput(data); + } + + + // Create new instance + var instance = new PermissionCompletedPayload(); + + + if (data.TryGetValue("requestId", out var requestIdValue) && requestIdValue is not null) + { + instance.RequestId = requestIdValue?.ToString()!; + } + + if (data.TryGetValue("toolCallId", out var toolCallIdValue) && toolCallIdValue is not null) + { + instance.ToolCallId = toolCallIdValue?.ToString()!; + } + + if (data.TryGetValue("permission", out var permissionValue) && permissionValue is not null) + { + instance.Permission = permissionValue?.ToString()!; + } + + if (data.TryGetValue("approved", out var approvedValue) && approvedValue is not null) + { + instance.Approved = Convert.ToBoolean(approvedValue); + } + + if (data.TryGetValue("reason", out var reasonValue) && reasonValue is not null) + { + instance.Reason = reasonValue?.ToString()!; + } + + if (data.TryGetValue("result", out var resultValue) && resultValue is not null) + { + instance.Result = resultValue.GetDictionary()!; + } + + if (data.TryGetValue("redaction", out var redactionValue) && redactionValue is not null) + { + instance.Redaction = RedactionMetadata.Load(redactionValue.GetDictionary(RedactionMetadata.ShorthandProperty), context); + } + + if (context is not null) + { + instance = context.ProcessOutput(instance); + } + return instance; + } + + + #endregion + + #region Save Methods + + /// + /// Save the PermissionCompletedPayload instance to a dictionary. + /// + /// Optional context with pre/post processing callbacks. + /// The dictionary representation of this instance. + public Dictionary Save(SaveContext? context = null) + { + var obj = this; + if (context is not null) + { + obj = context.ProcessObject(obj); + } + + + var result = new Dictionary(); + + + if (obj.RequestId is not null) + { + result["requestId"] = obj.RequestId; + } + + + if (obj.ToolCallId is not null) + { + result["toolCallId"] = obj.ToolCallId; + } + + + result["permission"] = obj.Permission; + + + result["approved"] = obj.Approved; + + + if (obj.Reason is not null) + { + result["reason"] = obj.Reason; + } + + + if (obj.Result is not null) + { + result["result"] = obj.Result; + } + + + if (obj.Redaction is not null) + { + result["redaction"] = obj.Redaction?.Save(context); + } + + + if (context is not null) + { + result = context.ProcessDict(result); + } + + return result; + } + + + /// + /// Convert the PermissionCompletedPayload instance to a YAML string. + /// + /// Optional context with pre/post processing callbacks. + /// The YAML string representation of this instance. + public string ToYaml(SaveContext? context = null) + { + context ??= new SaveContext(); + return context.ToYaml(Save(context)); + } + + /// + /// Convert the PermissionCompletedPayload instance to a JSON string. + /// + /// Optional context with pre/post processing callbacks. + /// Whether to indent the output. Defaults to true. + /// The JSON string representation of this instance. + public string ToJson(SaveContext? context = null, bool indent = true) + { + context ??= new SaveContext(); + return context.ToJson(Save(context), indent); + } + + /// + /// Load a PermissionCompletedPayload instance from a JSON string. + /// + /// The JSON string to parse. + /// Optional context with pre/post processing callbacks. + /// The loaded PermissionCompletedPayload instance. + public static PermissionCompletedPayload FromJson(string json, LoadContext? context = null) + { + using var doc = JsonDocument.Parse(json); + Dictionary dict; + dict = JsonSerializer.Deserialize>(json, JsonUtils.Options) + ?? throw new ArgumentException("Failed to parse JSON as dictionary"); + + return Load(dict, context); + } + + /// + /// Load a PermissionCompletedPayload instance from a YAML string. + /// + /// The YAML string to parse. + /// Optional context with pre/post processing callbacks. + /// The loaded PermissionCompletedPayload instance. + public static PermissionCompletedPayload FromYaml(string yaml, LoadContext? context = null) + { + var dict = YamlUtils.Deserializer.Deserialize>(yaml) + ?? throw new ArgumentException("Failed to parse YAML as dictionary"); + + return Load(dict, context); + } + + #endregion +} diff --git a/runtime/csharp/Prompty.Core/Model/events/PermissionDecision.cs b/runtime/csharp/Prompty.Core/Model/events/PermissionDecision.cs new file mode 100644 index 00000000..74b6d83b --- /dev/null +++ b/runtime/csharp/Prompty.Core/Model/events/PermissionDecision.cs @@ -0,0 +1,233 @@ +// +// Copyright (c) Microsoft. All rights reserved. +using System.Text.Json; +using YamlDotNet.Serialization; + +#pragma warning disable IDE0130 +namespace Prompty.Core; +#pragma warning restore IDE0130 + + /// + /// Decision returned by a permission resolver. + /// +public partial class PermissionDecision +{ + /// + /// The shorthand property name for this type, if any. + /// + public static string? ShorthandProperty => null; + + /// + /// Initializes a new instance of . + /// +#pragma warning disable CS8618 + public PermissionDecision() + { + } +#pragma warning restore CS8618 + + /// + /// Stable permission request identifier + /// + public string? RequestId { get; set; } + + /// + /// Associated tool call identifier, when the permission gated a tool call + /// + public string? ToolCallId { get; set; } + + /// + /// Permission/action name that was decided + /// + public string Permission { get; set; } = string.Empty; + + /// + /// Whether the requested permission was approved + /// + public bool Approved { get; set; } = false; + + /// + /// Decision reason, if available + /// + public string? Reason { get; set; } + + /// + /// Host-specific decision result, such as a durable approval token or denial details + /// + public IDictionary? Result { get; set; } + + + + #region Load Methods + + /// + /// Load a PermissionDecision instance from a dictionary. + /// + /// The dictionary containing the data. + /// Optional context with pre/post processing callbacks. + /// The loaded PermissionDecision instance. + public static PermissionDecision Load(Dictionary data, LoadContext? context = null) + { + if (context is not null) + { + data = context.ProcessInput(data); + } + + + // Create new instance + var instance = new PermissionDecision(); + + + if (data.TryGetValue("requestId", out var requestIdValue) && requestIdValue is not null) + { + instance.RequestId = requestIdValue?.ToString()!; + } + + if (data.TryGetValue("toolCallId", out var toolCallIdValue) && toolCallIdValue is not null) + { + instance.ToolCallId = toolCallIdValue?.ToString()!; + } + + if (data.TryGetValue("permission", out var permissionValue) && permissionValue is not null) + { + instance.Permission = permissionValue?.ToString()!; + } + + if (data.TryGetValue("approved", out var approvedValue) && approvedValue is not null) + { + instance.Approved = Convert.ToBoolean(approvedValue); + } + + if (data.TryGetValue("reason", out var reasonValue) && reasonValue is not null) + { + instance.Reason = reasonValue?.ToString()!; + } + + if (data.TryGetValue("result", out var resultValue) && resultValue is not null) + { + instance.Result = resultValue.GetDictionary()!; + } + + if (context is not null) + { + instance = context.ProcessOutput(instance); + } + return instance; + } + + + #endregion + + #region Save Methods + + /// + /// Save the PermissionDecision instance to a dictionary. + /// + /// Optional context with pre/post processing callbacks. + /// The dictionary representation of this instance. + public Dictionary Save(SaveContext? context = null) + { + var obj = this; + if (context is not null) + { + obj = context.ProcessObject(obj); + } + + + var result = new Dictionary(); + + + if (obj.RequestId is not null) + { + result["requestId"] = obj.RequestId; + } + + + if (obj.ToolCallId is not null) + { + result["toolCallId"] = obj.ToolCallId; + } + + + result["permission"] = obj.Permission; + + + result["approved"] = obj.Approved; + + + if (obj.Reason is not null) + { + result["reason"] = obj.Reason; + } + + + if (obj.Result is not null) + { + result["result"] = obj.Result; + } + + + if (context is not null) + { + result = context.ProcessDict(result); + } + + return result; + } + + + /// + /// Convert the PermissionDecision instance to a YAML string. + /// + /// Optional context with pre/post processing callbacks. + /// The YAML string representation of this instance. + public string ToYaml(SaveContext? context = null) + { + context ??= new SaveContext(); + return context.ToYaml(Save(context)); + } + + /// + /// Convert the PermissionDecision instance to a JSON string. + /// + /// Optional context with pre/post processing callbacks. + /// Whether to indent the output. Defaults to true. + /// The JSON string representation of this instance. + public string ToJson(SaveContext? context = null, bool indent = true) + { + context ??= new SaveContext(); + return context.ToJson(Save(context), indent); + } + + /// + /// Load a PermissionDecision instance from a JSON string. + /// + /// The JSON string to parse. + /// Optional context with pre/post processing callbacks. + /// The loaded PermissionDecision instance. + public static PermissionDecision FromJson(string json, LoadContext? context = null) + { + using var doc = JsonDocument.Parse(json); + Dictionary dict; + dict = JsonSerializer.Deserialize>(json, JsonUtils.Options) + ?? throw new ArgumentException("Failed to parse JSON as dictionary"); + + return Load(dict, context); + } + + /// + /// Load a PermissionDecision instance from a YAML string. + /// + /// The YAML string to parse. + /// Optional context with pre/post processing callbacks. + /// The loaded PermissionDecision instance. + public static PermissionDecision FromYaml(string yaml, LoadContext? context = null) + { + var dict = YamlUtils.Deserializer.Deserialize>(yaml) + ?? throw new ArgumentException("Failed to parse YAML as dictionary"); + + return Load(dict, context); + } + + #endregion +} diff --git a/runtime/csharp/Prompty.Core/Model/events/PermissionRequest.cs b/runtime/csharp/Prompty.Core/Model/events/PermissionRequest.cs new file mode 100644 index 00000000..6cf407a0 --- /dev/null +++ b/runtime/csharp/Prompty.Core/Model/events/PermissionRequest.cs @@ -0,0 +1,254 @@ +// +// Copyright (c) Microsoft. All rights reserved. +using System.Text.Json; +using YamlDotNet.Serialization; + +#pragma warning disable IDE0130 +namespace Prompty.Core; +#pragma warning restore IDE0130 + + /// + /// Request passed to a permission resolver. This is the live protocol shape; the + /// + /// event payloads above can include trace-only metadata such as redaction state. + /// +public partial class PermissionRequest +{ + /// + /// The shorthand property name for this type, if any. + /// + public static string? ShorthandProperty => null; + + /// + /// Initializes a new instance of . + /// +#pragma warning disable CS8618 + public PermissionRequest() + { + } +#pragma warning restore CS8618 + + /// + /// Stable permission request identifier + /// + public string? RequestId { get; set; } + + /// + /// Associated tool call identifier, when the permission gates a tool call + /// + public string? ToolCallId { get; set; } + + /// + /// Permission/action name being requested + /// + public string Permission { get; set; } = string.Empty; + + /// + /// Resource or tool the permission applies to + /// + public string? Target { get; set; } + + /// + /// Additional host-specific permission details + /// + public IDictionary? Details { get; set; } + + /// + /// Human-readable prompt or rationale that can be shown to an approval UI + /// + public string? PromptRequest { get; set; } + + /// + /// Policy metadata used to evaluate or explain the permission request + /// + public IDictionary? Policy { get; set; } + + + + #region Load Methods + + /// + /// Load a PermissionRequest instance from a dictionary. + /// + /// The dictionary containing the data. + /// Optional context with pre/post processing callbacks. + /// The loaded PermissionRequest instance. + public static PermissionRequest Load(Dictionary data, LoadContext? context = null) + { + if (context is not null) + { + data = context.ProcessInput(data); + } + + + // Create new instance + var instance = new PermissionRequest(); + + + if (data.TryGetValue("requestId", out var requestIdValue) && requestIdValue is not null) + { + instance.RequestId = requestIdValue?.ToString()!; + } + + if (data.TryGetValue("toolCallId", out var toolCallIdValue) && toolCallIdValue is not null) + { + instance.ToolCallId = toolCallIdValue?.ToString()!; + } + + if (data.TryGetValue("permission", out var permissionValue) && permissionValue is not null) + { + instance.Permission = permissionValue?.ToString()!; + } + + if (data.TryGetValue("target", out var targetValue) && targetValue is not null) + { + instance.Target = targetValue?.ToString()!; + } + + if (data.TryGetValue("details", out var detailsValue) && detailsValue is not null) + { + instance.Details = detailsValue.GetDictionary()!; + } + + if (data.TryGetValue("promptRequest", out var promptRequestValue) && promptRequestValue is not null) + { + instance.PromptRequest = promptRequestValue?.ToString()!; + } + + if (data.TryGetValue("policy", out var policyValue) && policyValue is not null) + { + instance.Policy = policyValue.GetDictionary()!; + } + + if (context is not null) + { + instance = context.ProcessOutput(instance); + } + return instance; + } + + + #endregion + + #region Save Methods + + /// + /// Save the PermissionRequest instance to a dictionary. + /// + /// Optional context with pre/post processing callbacks. + /// The dictionary representation of this instance. + public Dictionary Save(SaveContext? context = null) + { + var obj = this; + if (context is not null) + { + obj = context.ProcessObject(obj); + } + + + var result = new Dictionary(); + + + if (obj.RequestId is not null) + { + result["requestId"] = obj.RequestId; + } + + + if (obj.ToolCallId is not null) + { + result["toolCallId"] = obj.ToolCallId; + } + + + result["permission"] = obj.Permission; + + + if (obj.Target is not null) + { + result["target"] = obj.Target; + } + + + if (obj.Details is not null) + { + result["details"] = obj.Details; + } + + + if (obj.PromptRequest is not null) + { + result["promptRequest"] = obj.PromptRequest; + } + + + if (obj.Policy is not null) + { + result["policy"] = obj.Policy; + } + + + if (context is not null) + { + result = context.ProcessDict(result); + } + + return result; + } + + + /// + /// Convert the PermissionRequest instance to a YAML string. + /// + /// Optional context with pre/post processing callbacks. + /// The YAML string representation of this instance. + public string ToYaml(SaveContext? context = null) + { + context ??= new SaveContext(); + return context.ToYaml(Save(context)); + } + + /// + /// Convert the PermissionRequest instance to a JSON string. + /// + /// Optional context with pre/post processing callbacks. + /// Whether to indent the output. Defaults to true. + /// The JSON string representation of this instance. + public string ToJson(SaveContext? context = null, bool indent = true) + { + context ??= new SaveContext(); + return context.ToJson(Save(context), indent); + } + + /// + /// Load a PermissionRequest instance from a JSON string. + /// + /// The JSON string to parse. + /// Optional context with pre/post processing callbacks. + /// The loaded PermissionRequest instance. + public static PermissionRequest FromJson(string json, LoadContext? context = null) + { + using var doc = JsonDocument.Parse(json); + Dictionary dict; + dict = JsonSerializer.Deserialize>(json, JsonUtils.Options) + ?? throw new ArgumentException("Failed to parse JSON as dictionary"); + + return Load(dict, context); + } + + /// + /// Load a PermissionRequest instance from a YAML string. + /// + /// The YAML string to parse. + /// Optional context with pre/post processing callbacks. + /// The loaded PermissionRequest instance. + public static PermissionRequest FromYaml(string yaml, LoadContext? context = null) + { + var dict = YamlUtils.Deserializer.Deserialize>(yaml) + ?? throw new ArgumentException("Failed to parse YAML as dictionary"); + + return Load(dict, context); + } + + #endregion +} diff --git a/runtime/csharp/Prompty.Core/Model/events/PermissionRequestedPayload.cs b/runtime/csharp/Prompty.Core/Model/events/PermissionRequestedPayload.cs new file mode 100644 index 00000000..55b927cc --- /dev/null +++ b/runtime/csharp/Prompty.Core/Model/events/PermissionRequestedPayload.cs @@ -0,0 +1,268 @@ +// +// Copyright (c) Microsoft. All rights reserved. +using System.Text.Json; +using YamlDotNet.Serialization; + +#pragma warning disable IDE0130 +namespace Prompty.Core; +#pragma warning restore IDE0130 + + /// + /// Payload for permission request events — a host is asked to approve an action. + /// +public partial class PermissionRequestedPayload +{ + /// + /// The shorthand property name for this type, if any. + /// + public static string? ShorthandProperty => null; + + /// + /// Initializes a new instance of . + /// +#pragma warning disable CS8618 + public PermissionRequestedPayload() + { + } +#pragma warning restore CS8618 + + /// + /// Stable permission request identifier + /// + public string? RequestId { get; set; } + + /// + /// Associated tool call identifier, when the permission gates a tool call + /// + public string? ToolCallId { get; set; } + + /// + /// Permission/action name being requested + /// + public string Permission { get; set; } = string.Empty; + + /// + /// Resource or tool the permission applies to + /// + public string? Target { get; set; } + + /// + /// Additional host-specific permission details + /// + public IDictionary? Details { get; set; } + + /// + /// Human-readable prompt or rationale that can be shown to an approval UI + /// + public string? PromptRequest { get; set; } + + /// + /// Policy metadata used to evaluate or explain the permission request + /// + public IDictionary? Policy { get; set; } + + /// + /// Redaction state for sensitive request fields + /// + public RedactionMetadata? Redaction { get; set; } + + + + #region Load Methods + + /// + /// Load a PermissionRequestedPayload instance from a dictionary. + /// + /// The dictionary containing the data. + /// Optional context with pre/post processing callbacks. + /// The loaded PermissionRequestedPayload instance. + public static PermissionRequestedPayload Load(Dictionary data, LoadContext? context = null) + { + if (context is not null) + { + data = context.ProcessInput(data); + } + + + // Create new instance + var instance = new PermissionRequestedPayload(); + + + if (data.TryGetValue("requestId", out var requestIdValue) && requestIdValue is not null) + { + instance.RequestId = requestIdValue?.ToString()!; + } + + if (data.TryGetValue("toolCallId", out var toolCallIdValue) && toolCallIdValue is not null) + { + instance.ToolCallId = toolCallIdValue?.ToString()!; + } + + if (data.TryGetValue("permission", out var permissionValue) && permissionValue is not null) + { + instance.Permission = permissionValue?.ToString()!; + } + + if (data.TryGetValue("target", out var targetValue) && targetValue is not null) + { + instance.Target = targetValue?.ToString()!; + } + + if (data.TryGetValue("details", out var detailsValue) && detailsValue is not null) + { + instance.Details = detailsValue.GetDictionary()!; + } + + if (data.TryGetValue("promptRequest", out var promptRequestValue) && promptRequestValue is not null) + { + instance.PromptRequest = promptRequestValue?.ToString()!; + } + + if (data.TryGetValue("policy", out var policyValue) && policyValue is not null) + { + instance.Policy = policyValue.GetDictionary()!; + } + + if (data.TryGetValue("redaction", out var redactionValue) && redactionValue is not null) + { + instance.Redaction = RedactionMetadata.Load(redactionValue.GetDictionary(RedactionMetadata.ShorthandProperty), context); + } + + if (context is not null) + { + instance = context.ProcessOutput(instance); + } + return instance; + } + + + #endregion + + #region Save Methods + + /// + /// Save the PermissionRequestedPayload instance to a dictionary. + /// + /// Optional context with pre/post processing callbacks. + /// The dictionary representation of this instance. + public Dictionary Save(SaveContext? context = null) + { + var obj = this; + if (context is not null) + { + obj = context.ProcessObject(obj); + } + + + var result = new Dictionary(); + + + if (obj.RequestId is not null) + { + result["requestId"] = obj.RequestId; + } + + + if (obj.ToolCallId is not null) + { + result["toolCallId"] = obj.ToolCallId; + } + + + result["permission"] = obj.Permission; + + + if (obj.Target is not null) + { + result["target"] = obj.Target; + } + + + if (obj.Details is not null) + { + result["details"] = obj.Details; + } + + + if (obj.PromptRequest is not null) + { + result["promptRequest"] = obj.PromptRequest; + } + + + if (obj.Policy is not null) + { + result["policy"] = obj.Policy; + } + + + if (obj.Redaction is not null) + { + result["redaction"] = obj.Redaction?.Save(context); + } + + + if (context is not null) + { + result = context.ProcessDict(result); + } + + return result; + } + + + /// + /// Convert the PermissionRequestedPayload instance to a YAML string. + /// + /// Optional context with pre/post processing callbacks. + /// The YAML string representation of this instance. + public string ToYaml(SaveContext? context = null) + { + context ??= new SaveContext(); + return context.ToYaml(Save(context)); + } + + /// + /// Convert the PermissionRequestedPayload instance to a JSON string. + /// + /// Optional context with pre/post processing callbacks. + /// Whether to indent the output. Defaults to true. + /// The JSON string representation of this instance. + public string ToJson(SaveContext? context = null, bool indent = true) + { + context ??= new SaveContext(); + return context.ToJson(Save(context), indent); + } + + /// + /// Load a PermissionRequestedPayload instance from a JSON string. + /// + /// The JSON string to parse. + /// Optional context with pre/post processing callbacks. + /// The loaded PermissionRequestedPayload instance. + public static PermissionRequestedPayload FromJson(string json, LoadContext? context = null) + { + using var doc = JsonDocument.Parse(json); + Dictionary dict; + dict = JsonSerializer.Deserialize>(json, JsonUtils.Options) + ?? throw new ArgumentException("Failed to parse JSON as dictionary"); + + return Load(dict, context); + } + + /// + /// Load a PermissionRequestedPayload instance from a YAML string. + /// + /// The YAML string to parse. + /// Optional context with pre/post processing callbacks. + /// The loaded PermissionRequestedPayload instance. + public static PermissionRequestedPayload FromYaml(string yaml, LoadContext? context = null) + { + var dict = YamlUtils.Deserializer.Deserialize>(yaml) + ?? throw new ArgumentException("Failed to parse YAML as dictionary"); + + return Load(dict, context); + } + + #endregion +} diff --git a/runtime/csharp/Prompty.Core/Model/events/RedactedField.cs b/runtime/csharp/Prompty.Core/Model/events/RedactedField.cs new file mode 100644 index 00000000..cc338e23 --- /dev/null +++ b/runtime/csharp/Prompty.Core/Model/events/RedactedField.cs @@ -0,0 +1,185 @@ +// +// Copyright (c) Microsoft. All rights reserved. +using System.Text.Json; +using YamlDotNet.Serialization; + +#pragma warning disable IDE0130 +namespace Prompty.Core; +#pragma warning restore IDE0130 + + /// + /// Redaction handling for one JSON-shaped field path. + /// +public partial class RedactedField +{ + /// + /// The shorthand property name for this type, if any. + /// + public static string? ShorthandProperty => null; + + /// + /// Initializes a new instance of . + /// +#pragma warning disable CS8618 + public RedactedField() + { + } +#pragma warning restore CS8618 + + /// + /// JSONPath-like field path, relative to the containing payload + /// + public string Path { get; set; } = string.Empty; + + /// + /// How the field was represented + /// + public RedactionMode Mode { get; set; } = RedactionMode.None; + + /// + /// Human-readable reason or policy that caused this handling + /// + public string? Reason { get; set; } + + + + #region Load Methods + + /// + /// Load a RedactedField instance from a dictionary. + /// + /// The dictionary containing the data. + /// Optional context with pre/post processing callbacks. + /// The loaded RedactedField instance. + public static RedactedField Load(Dictionary data, LoadContext? context = null) + { + if (context is not null) + { + data = context.ProcessInput(data); + } + + + // Create new instance + var instance = new RedactedField(); + + + if (data.TryGetValue("path", out var pathValue) && pathValue is not null) + { + instance.Path = pathValue?.ToString()!; + } + + if (data.TryGetValue("mode", out var modeValue) && modeValue is not null) + { + instance.Mode = Enum.Parse(modeValue?.ToString()!, true); + } + + if (data.TryGetValue("reason", out var reasonValue) && reasonValue is not null) + { + instance.Reason = reasonValue?.ToString()!; + } + + if (context is not null) + { + instance = context.ProcessOutput(instance); + } + return instance; + } + + + #endregion + + #region Save Methods + + /// + /// Save the RedactedField instance to a dictionary. + /// + /// Optional context with pre/post processing callbacks. + /// The dictionary representation of this instance. + public Dictionary Save(SaveContext? context = null) + { + var obj = this; + if (context is not null) + { + obj = context.ProcessObject(obj); + } + + + var result = new Dictionary(); + + + result["path"] = obj.Path; + + + result["mode"] = obj.Mode.ToString().ToLowerInvariant(); + + + if (obj.Reason is not null) + { + result["reason"] = obj.Reason; + } + + + if (context is not null) + { + result = context.ProcessDict(result); + } + + return result; + } + + + /// + /// Convert the RedactedField instance to a YAML string. + /// + /// Optional context with pre/post processing callbacks. + /// The YAML string representation of this instance. + public string ToYaml(SaveContext? context = null) + { + context ??= new SaveContext(); + return context.ToYaml(Save(context)); + } + + /// + /// Convert the RedactedField instance to a JSON string. + /// + /// Optional context with pre/post processing callbacks. + /// Whether to indent the output. Defaults to true. + /// The JSON string representation of this instance. + public string ToJson(SaveContext? context = null, bool indent = true) + { + context ??= new SaveContext(); + return context.ToJson(Save(context), indent); + } + + /// + /// Load a RedactedField instance from a JSON string. + /// + /// The JSON string to parse. + /// Optional context with pre/post processing callbacks. + /// The loaded RedactedField instance. + public static RedactedField FromJson(string json, LoadContext? context = null) + { + using var doc = JsonDocument.Parse(json); + Dictionary dict; + dict = JsonSerializer.Deserialize>(json, JsonUtils.Options) + ?? throw new ArgumentException("Failed to parse JSON as dictionary"); + + return Load(dict, context); + } + + /// + /// Load a RedactedField instance from a YAML string. + /// + /// The YAML string to parse. + /// Optional context with pre/post processing callbacks. + /// The loaded RedactedField instance. + public static RedactedField FromYaml(string yaml, LoadContext? context = null) + { + var dict = YamlUtils.Deserializer.Deserialize>(yaml) + ?? throw new ArgumentException("Failed to parse YAML as dictionary"); + + return Load(dict, context); + } + + #endregion +} diff --git a/runtime/csharp/Prompty.Core/Model/events/RedactionMetadata.cs b/runtime/csharp/Prompty.Core/Model/events/RedactionMetadata.cs new file mode 100644 index 00000000..c43813a3 --- /dev/null +++ b/runtime/csharp/Prompty.Core/Model/events/RedactionMetadata.cs @@ -0,0 +1,258 @@ +// +// Copyright (c) Microsoft. All rights reserved. +using System.Text.Json; +using YamlDotNet.Serialization; + +#pragma warning disable IDE0130 +namespace Prompty.Core; +#pragma warning restore IDE0130 + + /// + /// Metadata describing whether and how a payload was sanitized. + /// +public partial class RedactionMetadata +{ + /// + /// The shorthand property name for this type, if any. + /// + public static string? ShorthandProperty => null; + + /// + /// Initializes a new instance of . + /// +#pragma warning disable CS8618 + public RedactionMetadata() + { + } +#pragma warning restore CS8618 + + /// + /// Whether the payload has been sanitized for persistence or external display + /// + public bool? Sanitized { get; set; } + + /// + /// Field-level redaction details + /// + public IList? Fields { get; set; } + + /// + /// Host policy or sanitizer version that produced this metadata + /// + public string? Policy { get; set; } + + + + #region Load Methods + + /// + /// Load a RedactionMetadata instance from a dictionary. + /// + /// The dictionary containing the data. + /// Optional context with pre/post processing callbacks. + /// The loaded RedactionMetadata instance. + public static RedactionMetadata Load(Dictionary data, LoadContext? context = null) + { + if (context is not null) + { + data = context.ProcessInput(data); + } + + + // Create new instance + var instance = new RedactionMetadata(); + + + if (data.TryGetValue("sanitized", out var sanitizedValue) && sanitizedValue is not null) + { + instance.Sanitized = Convert.ToBoolean(sanitizedValue); + } + + if (data.TryGetValue("fields", out var fieldsValue) && fieldsValue is not null) + { + instance.Fields = LoadFields(fieldsValue, context); + } + + if (data.TryGetValue("policy", out var policyValue) && policyValue is not null) + { + instance.Policy = policyValue?.ToString()!; + } + + if (context is not null) + { + instance = context.ProcessOutput(instance); + } + return instance; + } + + + /// + /// Load a list of RedactedField from a dictionary or list. + /// + public static IList LoadFields(object data, LoadContext? context) + { + var result = new List(); + + if (data is Dictionary dict) + { + // Convert named dictionary to list + foreach (var kvp in dict) + { + if (kvp.Value is IEnumerable) + { + throw new ArgumentException( + $"Invalid 'fields' format: key '{kvp.Key}' has an array value. " + + $"'fields' must be a flat list of objects or a name-keyed dict — " + + "not a nested {" + kvp.Key + ": [...]} structure."); + } + var itemDict = kvp.Value.GetDictionary(); + if (itemDict.Count > 0) + { + // Value is an object, add name to it + itemDict["name"] = kvp.Key; + result.Add(RedactedField.Load(itemDict, context)); + } + else + { + // Value is a scalar, use it as the primary property + var newDict = new Dictionary + { + ["name"] = kvp.Key, + ["path"] = kvp.Value + }; + result.Add(RedactedField.Load(newDict, context)); + } + } + } + else if (data is IEnumerable list) + { + foreach (var item in list) + { + var itemDict = item.GetDictionary(RedactedField.ShorthandProperty); + if (itemDict.Count > 0) + { + result.Add(RedactedField.Load(itemDict, context)); + } + } + } + + return result; + } + + + #endregion + + #region Save Methods + + /// + /// Save the RedactionMetadata instance to a dictionary. + /// + /// Optional context with pre/post processing callbacks. + /// The dictionary representation of this instance. + public Dictionary Save(SaveContext? context = null) + { + var obj = this; + if (context is not null) + { + obj = context.ProcessObject(obj); + } + + + var result = new Dictionary(); + + + if (obj.Sanitized is not null) + { + result["sanitized"] = obj.Sanitized; + } + + + if (obj.Fields is not null) + { + result["fields"] = SaveFields(obj.Fields, context); + } + + + if (obj.Policy is not null) + { + result["policy"] = obj.Policy; + } + + + if (context is not null) + { + result = context.ProcessDict(result); + } + + return result; + } + + + /// + /// Save a list of RedactedField to object or array format. + /// + public static object SaveFields(IList items, SaveContext? context) + { + context ??= new SaveContext(); + + // This collection type does not have a 'name' property, only array format is supported + return items.Select(item => item.Save(context)).ToList(); + + } + + + /// + /// Convert the RedactionMetadata instance to a YAML string. + /// + /// Optional context with pre/post processing callbacks. + /// The YAML string representation of this instance. + public string ToYaml(SaveContext? context = null) + { + context ??= new SaveContext(); + return context.ToYaml(Save(context)); + } + + /// + /// Convert the RedactionMetadata instance to a JSON string. + /// + /// Optional context with pre/post processing callbacks. + /// Whether to indent the output. Defaults to true. + /// The JSON string representation of this instance. + public string ToJson(SaveContext? context = null, bool indent = true) + { + context ??= new SaveContext(); + return context.ToJson(Save(context), indent); + } + + /// + /// Load a RedactionMetadata instance from a JSON string. + /// + /// The JSON string to parse. + /// Optional context with pre/post processing callbacks. + /// The loaded RedactionMetadata instance. + public static RedactionMetadata FromJson(string json, LoadContext? context = null) + { + using var doc = JsonDocument.Parse(json); + Dictionary dict; + dict = JsonSerializer.Deserialize>(json, JsonUtils.Options) + ?? throw new ArgumentException("Failed to parse JSON as dictionary"); + + return Load(dict, context); + } + + /// + /// Load a RedactionMetadata instance from a YAML string. + /// + /// The YAML string to parse. + /// Optional context with pre/post processing callbacks. + /// The loaded RedactionMetadata instance. + public static RedactionMetadata FromYaml(string yaml, LoadContext? context = null) + { + var dict = YamlUtils.Deserializer.Deserialize>(yaml) + ?? throw new ArgumentException("Failed to parse YAML as dictionary"); + + return Load(dict, context); + } + + #endregion +} diff --git a/runtime/csharp/Prompty.Core/Model/events/RedactionMode.cs b/runtime/csharp/Prompty.Core/Model/events/RedactionMode.cs new file mode 100644 index 00000000..7aa9455b --- /dev/null +++ b/runtime/csharp/Prompty.Core/Model/events/RedactionMode.cs @@ -0,0 +1,27 @@ +// +// +// Code generated by Typra emitter; DO NOT EDIT. + +using System.Text.Json.Serialization; + +namespace Prompty.Core; + +[JsonConverter(typeof(JsonStringEnumConverter))] +public enum RedactionMode +{ + [JsonPropertyName("none")] + None, + + [JsonPropertyName("redacted")] + Redacted, + + [JsonPropertyName("hashed")] + Hashed, + + [JsonPropertyName("summary")] + Summary, + + [JsonPropertyName("reference")] + Reference, + +} diff --git a/runtime/csharp/Prompty.Core/Model/events/RetryPayload.cs b/runtime/csharp/Prompty.Core/Model/events/RetryPayload.cs new file mode 100644 index 00000000..bbdc575e --- /dev/null +++ b/runtime/csharp/Prompty.Core/Model/events/RetryPayload.cs @@ -0,0 +1,217 @@ +// +// Copyright (c) Microsoft. All rights reserved. +using System.Text.Json; +using YamlDotNet.Serialization; + +#pragma warning disable IDE0130 +namespace Prompty.Core; +#pragma warning restore IDE0130 + + /// + /// Payload for "retry" events — a transient operation will be retried. + /// +public partial class RetryPayload +{ + /// + /// The shorthand property name for this type, if any. + /// + public static string? ShorthandProperty => null; + + /// + /// Initializes a new instance of . + /// +#pragma warning disable CS8618 + public RetryPayload() + { + } +#pragma warning restore CS8618 + + /// + /// Operation being retried + /// + public string Operation { get; set; } = string.Empty; + + /// + /// Attempt number about to run + /// + public int Attempt { get; set; } + + /// + /// Maximum configured attempts + /// + public int? MaxAttempts { get; set; } + + /// + /// Backoff delay before the next attempt in milliseconds + /// + public double? DelayMs { get; set; } + + /// + /// Reason for the retry + /// + public string? Reason { get; set; } + + + + #region Load Methods + + /// + /// Load a RetryPayload instance from a dictionary. + /// + /// The dictionary containing the data. + /// Optional context with pre/post processing callbacks. + /// The loaded RetryPayload instance. + public static RetryPayload Load(Dictionary data, LoadContext? context = null) + { + if (context is not null) + { + data = context.ProcessInput(data); + } + + + // Create new instance + var instance = new RetryPayload(); + + + if (data.TryGetValue("operation", out var operationValue) && operationValue is not null) + { + instance.Operation = operationValue?.ToString()!; + } + + if (data.TryGetValue("attempt", out var attemptValue) && attemptValue is not null) + { + instance.Attempt = Convert.ToInt32(attemptValue); + } + + if (data.TryGetValue("maxAttempts", out var maxAttemptsValue) && maxAttemptsValue is not null) + { + instance.MaxAttempts = Convert.ToInt32(maxAttemptsValue); + } + + if (data.TryGetValue("delayMs", out var delayMsValue) && delayMsValue is not null) + { + instance.DelayMs = Convert.ToDouble(delayMsValue); + } + + if (data.TryGetValue("reason", out var reasonValue) && reasonValue is not null) + { + instance.Reason = reasonValue?.ToString()!; + } + + if (context is not null) + { + instance = context.ProcessOutput(instance); + } + return instance; + } + + + #endregion + + #region Save Methods + + /// + /// Save the RetryPayload instance to a dictionary. + /// + /// Optional context with pre/post processing callbacks. + /// The dictionary representation of this instance. + public Dictionary Save(SaveContext? context = null) + { + var obj = this; + if (context is not null) + { + obj = context.ProcessObject(obj); + } + + + var result = new Dictionary(); + + + result["operation"] = obj.Operation; + + + result["attempt"] = obj.Attempt; + + + if (obj.MaxAttempts is not null) + { + result["maxAttempts"] = obj.MaxAttempts; + } + + + if (obj.DelayMs is not null) + { + result["delayMs"] = obj.DelayMs; + } + + + if (obj.Reason is not null) + { + result["reason"] = obj.Reason; + } + + + if (context is not null) + { + result = context.ProcessDict(result); + } + + return result; + } + + + /// + /// Convert the RetryPayload instance to a YAML string. + /// + /// Optional context with pre/post processing callbacks. + /// The YAML string representation of this instance. + public string ToYaml(SaveContext? context = null) + { + context ??= new SaveContext(); + return context.ToYaml(Save(context)); + } + + /// + /// Convert the RetryPayload instance to a JSON string. + /// + /// Optional context with pre/post processing callbacks. + /// Whether to indent the output. Defaults to true. + /// The JSON string representation of this instance. + public string ToJson(SaveContext? context = null, bool indent = true) + { + context ??= new SaveContext(); + return context.ToJson(Save(context), indent); + } + + /// + /// Load a RetryPayload instance from a JSON string. + /// + /// The JSON string to parse. + /// Optional context with pre/post processing callbacks. + /// The loaded RetryPayload instance. + public static RetryPayload FromJson(string json, LoadContext? context = null) + { + using var doc = JsonDocument.Parse(json); + Dictionary dict; + dict = JsonSerializer.Deserialize>(json, JsonUtils.Options) + ?? throw new ArgumentException("Failed to parse JSON as dictionary"); + + return Load(dict, context); + } + + /// + /// Load a RetryPayload instance from a YAML string. + /// + /// The YAML string to parse. + /// Optional context with pre/post processing callbacks. + /// The loaded RetryPayload instance. + public static RetryPayload FromYaml(string yaml, LoadContext? context = null) + { + var dict = YamlUtils.Deserializer.Deserialize>(yaml) + ?? throw new ArgumentException("Failed to parse YAML as dictionary"); + + return Load(dict, context); + } + + #endregion +} diff --git a/runtime/csharp/Prompty.Core/Model/events/SessionEndPayload.cs b/runtime/csharp/Prompty.Core/Model/events/SessionEndPayload.cs new file mode 100644 index 00000000..4c80ef08 --- /dev/null +++ b/runtime/csharp/Prompty.Core/Model/events/SessionEndPayload.cs @@ -0,0 +1,207 @@ +// +// Copyright (c) Microsoft. All rights reserved. +using System.Text.Json; +using YamlDotNet.Serialization; + +#pragma warning disable IDE0130 +namespace Prompty.Core; +#pragma warning restore IDE0130 + + /// + /// Payload for "session_end" events. + /// +public partial class SessionEndPayload +{ + /// + /// The shorthand property name for this type, if any. + /// + public static string? ShorthandProperty => null; + + /// + /// Initializes a new instance of . + /// +#pragma warning disable CS8618 + public SessionEndPayload() + { + } +#pragma warning restore CS8618 + + /// + /// Stable session identifier + /// + public string? SessionId { get; set; } + + /// + /// Final session status + /// + public SessionEndStatus? Status { get; set; } + + /// + /// Host-specific reason the session ended + /// + public string? Reason { get; set; } + + /// + /// Total elapsed session duration in milliseconds + /// + public double? DurationMs { get; set; } + + + + #region Load Methods + + /// + /// Load a SessionEndPayload instance from a dictionary. + /// + /// The dictionary containing the data. + /// Optional context with pre/post processing callbacks. + /// The loaded SessionEndPayload instance. + public static SessionEndPayload Load(Dictionary data, LoadContext? context = null) + { + if (context is not null) + { + data = context.ProcessInput(data); + } + + + // Create new instance + var instance = new SessionEndPayload(); + + + if (data.TryGetValue("sessionId", out var sessionIdValue) && sessionIdValue is not null) + { + instance.SessionId = sessionIdValue?.ToString()!; + } + + if (data.TryGetValue("status", out var statusValue) && statusValue is not null) + { + instance.Status = Enum.Parse(statusValue?.ToString()!, true); + } + + if (data.TryGetValue("reason", out var reasonValue) && reasonValue is not null) + { + instance.Reason = reasonValue?.ToString()!; + } + + if (data.TryGetValue("durationMs", out var durationMsValue) && durationMsValue is not null) + { + instance.DurationMs = Convert.ToDouble(durationMsValue); + } + + if (context is not null) + { + instance = context.ProcessOutput(instance); + } + return instance; + } + + + #endregion + + #region Save Methods + + /// + /// Save the SessionEndPayload instance to a dictionary. + /// + /// Optional context with pre/post processing callbacks. + /// The dictionary representation of this instance. + public Dictionary Save(SaveContext? context = null) + { + var obj = this; + if (context is not null) + { + obj = context.ProcessObject(obj); + } + + + var result = new Dictionary(); + + + if (obj.SessionId is not null) + { + result["sessionId"] = obj.SessionId; + } + + + if (obj.Status is not null) + { + result["status"] = obj.Status.Value.ToString().ToLowerInvariant(); + } + + + if (obj.Reason is not null) + { + result["reason"] = obj.Reason; + } + + + if (obj.DurationMs is not null) + { + result["durationMs"] = obj.DurationMs; + } + + + if (context is not null) + { + result = context.ProcessDict(result); + } + + return result; + } + + + /// + /// Convert the SessionEndPayload instance to a YAML string. + /// + /// Optional context with pre/post processing callbacks. + /// The YAML string representation of this instance. + public string ToYaml(SaveContext? context = null) + { + context ??= new SaveContext(); + return context.ToYaml(Save(context)); + } + + /// + /// Convert the SessionEndPayload instance to a JSON string. + /// + /// Optional context with pre/post processing callbacks. + /// Whether to indent the output. Defaults to true. + /// The JSON string representation of this instance. + public string ToJson(SaveContext? context = null, bool indent = true) + { + context ??= new SaveContext(); + return context.ToJson(Save(context), indent); + } + + /// + /// Load a SessionEndPayload instance from a JSON string. + /// + /// The JSON string to parse. + /// Optional context with pre/post processing callbacks. + /// The loaded SessionEndPayload instance. + public static SessionEndPayload FromJson(string json, LoadContext? context = null) + { + using var doc = JsonDocument.Parse(json); + Dictionary dict; + dict = JsonSerializer.Deserialize>(json, JsonUtils.Options) + ?? throw new ArgumentException("Failed to parse JSON as dictionary"); + + return Load(dict, context); + } + + /// + /// Load a SessionEndPayload instance from a YAML string. + /// + /// The YAML string to parse. + /// Optional context with pre/post processing callbacks. + /// The loaded SessionEndPayload instance. + public static SessionEndPayload FromYaml(string yaml, LoadContext? context = null) + { + var dict = YamlUtils.Deserializer.Deserialize>(yaml) + ?? throw new ArgumentException("Failed to parse YAML as dictionary"); + + return Load(dict, context); + } + + #endregion +} diff --git a/runtime/csharp/Prompty.Core/Model/events/SessionEndStatus.cs b/runtime/csharp/Prompty.Core/Model/events/SessionEndStatus.cs new file mode 100644 index 00000000..01ddca6c --- /dev/null +++ b/runtime/csharp/Prompty.Core/Model/events/SessionEndStatus.cs @@ -0,0 +1,24 @@ +// +// +// Code generated by Typra emitter; DO NOT EDIT. + +using System.Text.Json.Serialization; + +namespace Prompty.Core; + +[JsonConverter(typeof(JsonStringEnumConverter))] +public enum SessionEndStatus +{ + [JsonPropertyName("success")] + Success, + + [JsonPropertyName("error")] + Error, + + [JsonPropertyName("cancelled")] + Cancelled, + + [JsonPropertyName("interrupted")] + Interrupted, + +} diff --git a/runtime/csharp/Prompty.Core/Model/events/SessionEvent.cs b/runtime/csharp/Prompty.Core/Model/events/SessionEvent.cs new file mode 100644 index 00000000..3847c7bd --- /dev/null +++ b/runtime/csharp/Prompty.Core/Model/events/SessionEvent.cs @@ -0,0 +1,275 @@ +// +// Copyright (c) Microsoft. All rights reserved. +using System.Text.Json; +using YamlDotNet.Serialization; + +#pragma warning disable IDE0130 +namespace Prompty.Core; +#pragma warning restore IDE0130 + + /// + /// A canonical event envelope emitted by an outer harness session. + /// +public partial class SessionEvent +{ + /// + /// The shorthand property name for this type, if any. + /// + public static string? ShorthandProperty => null; + + /// + /// Initializes a new instance of . + /// +#pragma warning disable CS8618 + public SessionEvent() + { + } +#pragma warning restore CS8618 + + /// + /// Unique identifier for this event + /// + public string Id { get; set; } = string.Empty; + + /// + /// Event type discriminator + /// + public SessionEventType Type { get; set; } = SessionEventType.SessionStart; + + /// + /// ISO 8601 UTC timestamp when the event was emitted + /// + public string Timestamp { get; set; } = string.Empty; + + /// + /// Stable identifier for the outer session + /// + public string? SessionId { get; set; } + + /// + /// Associated turn identifier, when this session event is linked to a turn + /// + public string? TurnId { get; set; } + + /// + /// Parent event or span identifier for reconstructing event hierarchy + /// + public string? ParentId { get; set; } + + /// + /// Trace span identifier associated with this event + /// + public string? SpanId { get; set; } + + /// + /// Event-specific payload. Use the typed payload model matching 'type'. + /// + public IDictionary Payload { get; set; } = new Dictionary(); + + /// + /// Redaction state for sensitive payload fields + /// + public RedactionMetadata? Redaction { get; set; } + + + + #region Load Methods + + /// + /// Load a SessionEvent instance from a dictionary. + /// + /// The dictionary containing the data. + /// Optional context with pre/post processing callbacks. + /// The loaded SessionEvent instance. + public static SessionEvent Load(Dictionary data, LoadContext? context = null) + { + if (context is not null) + { + data = context.ProcessInput(data); + } + + + // Create new instance + var instance = new SessionEvent(); + + + if (data.TryGetValue("id", out var idValue) && idValue is not null) + { + instance.Id = idValue?.ToString()!; + } + + if (data.TryGetValue("type", out var typeValue) && typeValue is not null) + { + instance.Type = Enum.Parse(typeValue?.ToString()!, true); + } + + if (data.TryGetValue("timestamp", out var timestampValue) && timestampValue is not null) + { + instance.Timestamp = timestampValue?.ToString()!; + } + + if (data.TryGetValue("sessionId", out var sessionIdValue) && sessionIdValue is not null) + { + instance.SessionId = sessionIdValue?.ToString()!; + } + + if (data.TryGetValue("turnId", out var turnIdValue) && turnIdValue is not null) + { + instance.TurnId = turnIdValue?.ToString()!; + } + + if (data.TryGetValue("parentId", out var parentIdValue) && parentIdValue is not null) + { + instance.ParentId = parentIdValue?.ToString()!; + } + + if (data.TryGetValue("spanId", out var spanIdValue) && spanIdValue is not null) + { + instance.SpanId = spanIdValue?.ToString()!; + } + + if (data.TryGetValue("payload", out var payloadValue) && payloadValue is not null) + { + instance.Payload = payloadValue.GetDictionary()!; + } + + if (data.TryGetValue("redaction", out var redactionValue) && redactionValue is not null) + { + instance.Redaction = RedactionMetadata.Load(redactionValue.GetDictionary(RedactionMetadata.ShorthandProperty), context); + } + + if (context is not null) + { + instance = context.ProcessOutput(instance); + } + return instance; + } + + + #endregion + + #region Save Methods + + /// + /// Save the SessionEvent instance to a dictionary. + /// + /// Optional context with pre/post processing callbacks. + /// The dictionary representation of this instance. + public Dictionary Save(SaveContext? context = null) + { + var obj = this; + if (context is not null) + { + obj = context.ProcessObject(obj); + } + + + var result = new Dictionary(); + + + result["id"] = obj.Id; + + + result["type"] = obj.Type.ToString().ToLowerInvariant(); + + + result["timestamp"] = obj.Timestamp; + + + if (obj.SessionId is not null) + { + result["sessionId"] = obj.SessionId; + } + + + if (obj.TurnId is not null) + { + result["turnId"] = obj.TurnId; + } + + + if (obj.ParentId is not null) + { + result["parentId"] = obj.ParentId; + } + + + if (obj.SpanId is not null) + { + result["spanId"] = obj.SpanId; + } + + + result["payload"] = obj.Payload; + + + if (obj.Redaction is not null) + { + result["redaction"] = obj.Redaction?.Save(context); + } + + + if (context is not null) + { + result = context.ProcessDict(result); + } + + return result; + } + + + /// + /// Convert the SessionEvent instance to a YAML string. + /// + /// Optional context with pre/post processing callbacks. + /// The YAML string representation of this instance. + public string ToYaml(SaveContext? context = null) + { + context ??= new SaveContext(); + return context.ToYaml(Save(context)); + } + + /// + /// Convert the SessionEvent instance to a JSON string. + /// + /// Optional context with pre/post processing callbacks. + /// Whether to indent the output. Defaults to true. + /// The JSON string representation of this instance. + public string ToJson(SaveContext? context = null, bool indent = true) + { + context ??= new SaveContext(); + return context.ToJson(Save(context), indent); + } + + /// + /// Load a SessionEvent instance from a JSON string. + /// + /// The JSON string to parse. + /// Optional context with pre/post processing callbacks. + /// The loaded SessionEvent instance. + public static SessionEvent FromJson(string json, LoadContext? context = null) + { + using var doc = JsonDocument.Parse(json); + Dictionary dict; + dict = JsonSerializer.Deserialize>(json, JsonUtils.Options) + ?? throw new ArgumentException("Failed to parse JSON as dictionary"); + + return Load(dict, context); + } + + /// + /// Load a SessionEvent instance from a YAML string. + /// + /// The YAML string to parse. + /// Optional context with pre/post processing callbacks. + /// The loaded SessionEvent instance. + public static SessionEvent FromYaml(string yaml, LoadContext? context = null) + { + var dict = YamlUtils.Deserializer.Deserialize>(yaml) + ?? throw new ArgumentException("Failed to parse YAML as dictionary"); + + return Load(dict, context); + } + + #endregion +} diff --git a/runtime/csharp/Prompty.Core/Model/events/SessionEventType.cs b/runtime/csharp/Prompty.Core/Model/events/SessionEventType.cs new file mode 100644 index 00000000..41bf379d --- /dev/null +++ b/runtime/csharp/Prompty.Core/Model/events/SessionEventType.cs @@ -0,0 +1,33 @@ +// +// +// Code generated by Typra emitter; DO NOT EDIT. + +using System.Text.Json.Serialization; + +namespace Prompty.Core; + +[JsonConverter(typeof(JsonStringEnumConverter))] +public enum SessionEventType +{ + [JsonPropertyName("session_start")] + SessionStart, + + [JsonPropertyName("session_end")] + SessionEnd, + + [JsonPropertyName("session_warning")] + SessionWarning, + + [JsonPropertyName("session_hook_start")] + SessionHookStart, + + [JsonPropertyName("session_hook_end")] + SessionHookEnd, + + [JsonPropertyName("checkpoint_created")] + CheckpointCreated, + + [JsonPropertyName("trajectory_event")] + TrajectoryEvent, + +} diff --git a/runtime/csharp/Prompty.Core/Model/events/SessionFileRef.cs b/runtime/csharp/Prompty.Core/Model/events/SessionFileRef.cs new file mode 100644 index 00000000..0dcf68fe --- /dev/null +++ b/runtime/csharp/Prompty.Core/Model/events/SessionFileRef.cs @@ -0,0 +1,220 @@ +// +// Copyright (c) Microsoft. All rights reserved. +using System.Text.Json; +using YamlDotNet.Serialization; + +#pragma warning disable IDE0130 +namespace Prompty.Core; +#pragma warning restore IDE0130 + + /// + /// A file observed or touched by a harness session. + /// +public partial class SessionFileRef +{ + /// + /// The shorthand property name for this type, if any. + /// + public static string? ShorthandProperty => null; + + /// + /// Initializes a new instance of . + /// +#pragma warning disable CS8618 + public SessionFileRef() + { + } +#pragma warning restore CS8618 + + /// + /// Stable session identifier + /// + public string? SessionId { get; set; } + + /// + /// File path, relative to the harness workspace when possible + /// + public string Path { get; set; } = string.Empty; + + /// + /// Tool that first observed the file, when known + /// + public string? ToolName { get; set; } + + /// + /// Zero-based turn index where the file was first observed + /// + public int? TurnIndex { get; set; } + + /// + /// ISO 8601 UTC timestamp when the file was first observed + /// + public string? FirstSeenAt { get; set; } + + + + #region Load Methods + + /// + /// Load a SessionFileRef instance from a dictionary. + /// + /// The dictionary containing the data. + /// Optional context with pre/post processing callbacks. + /// The loaded SessionFileRef instance. + public static SessionFileRef Load(Dictionary data, LoadContext? context = null) + { + if (context is not null) + { + data = context.ProcessInput(data); + } + + + // Create new instance + var instance = new SessionFileRef(); + + + if (data.TryGetValue("sessionId", out var sessionIdValue) && sessionIdValue is not null) + { + instance.SessionId = sessionIdValue?.ToString()!; + } + + if (data.TryGetValue("path", out var pathValue) && pathValue is not null) + { + instance.Path = pathValue?.ToString()!; + } + + if (data.TryGetValue("toolName", out var toolNameValue) && toolNameValue is not null) + { + instance.ToolName = toolNameValue?.ToString()!; + } + + if (data.TryGetValue("turnIndex", out var turnIndexValue) && turnIndexValue is not null) + { + instance.TurnIndex = Convert.ToInt32(turnIndexValue); + } + + if (data.TryGetValue("firstSeenAt", out var firstSeenAtValue) && firstSeenAtValue is not null) + { + instance.FirstSeenAt = firstSeenAtValue?.ToString()!; + } + + if (context is not null) + { + instance = context.ProcessOutput(instance); + } + return instance; + } + + + #endregion + + #region Save Methods + + /// + /// Save the SessionFileRef instance to a dictionary. + /// + /// Optional context with pre/post processing callbacks. + /// The dictionary representation of this instance. + public Dictionary Save(SaveContext? context = null) + { + var obj = this; + if (context is not null) + { + obj = context.ProcessObject(obj); + } + + + var result = new Dictionary(); + + + if (obj.SessionId is not null) + { + result["sessionId"] = obj.SessionId; + } + + + result["path"] = obj.Path; + + + if (obj.ToolName is not null) + { + result["toolName"] = obj.ToolName; + } + + + if (obj.TurnIndex is not null) + { + result["turnIndex"] = obj.TurnIndex; + } + + + if (obj.FirstSeenAt is not null) + { + result["firstSeenAt"] = obj.FirstSeenAt; + } + + + if (context is not null) + { + result = context.ProcessDict(result); + } + + return result; + } + + + /// + /// Convert the SessionFileRef instance to a YAML string. + /// + /// Optional context with pre/post processing callbacks. + /// The YAML string representation of this instance. + public string ToYaml(SaveContext? context = null) + { + context ??= new SaveContext(); + return context.ToYaml(Save(context)); + } + + /// + /// Convert the SessionFileRef instance to a JSON string. + /// + /// Optional context with pre/post processing callbacks. + /// Whether to indent the output. Defaults to true. + /// The JSON string representation of this instance. + public string ToJson(SaveContext? context = null, bool indent = true) + { + context ??= new SaveContext(); + return context.ToJson(Save(context), indent); + } + + /// + /// Load a SessionFileRef instance from a JSON string. + /// + /// The JSON string to parse. + /// Optional context with pre/post processing callbacks. + /// The loaded SessionFileRef instance. + public static SessionFileRef FromJson(string json, LoadContext? context = null) + { + using var doc = JsonDocument.Parse(json); + Dictionary dict; + dict = JsonSerializer.Deserialize>(json, JsonUtils.Options) + ?? throw new ArgumentException("Failed to parse JSON as dictionary"); + + return Load(dict, context); + } + + /// + /// Load a SessionFileRef instance from a YAML string. + /// + /// The YAML string to parse. + /// Optional context with pre/post processing callbacks. + /// The loaded SessionFileRef instance. + public static SessionFileRef FromYaml(string yaml, LoadContext? context = null) + { + var dict = YamlUtils.Deserializer.Deserialize>(yaml) + ?? throw new ArgumentException("Failed to parse YAML as dictionary"); + + return Load(dict, context); + } + + #endregion +} diff --git a/runtime/csharp/Prompty.Core/Model/events/SessionRef.cs b/runtime/csharp/Prompty.Core/Model/events/SessionRef.cs new file mode 100644 index 00000000..8468fb5d --- /dev/null +++ b/runtime/csharp/Prompty.Core/Model/events/SessionRef.cs @@ -0,0 +1,217 @@ +// +// Copyright (c) Microsoft. All rights reserved. +using System.Text.Json; +using YamlDotNet.Serialization; + +#pragma warning disable IDE0130 +namespace Prompty.Core; +#pragma warning restore IDE0130 + + /// + /// A non-file reference observed by a harness session. + /// +public partial class SessionRef +{ + /// + /// The shorthand property name for this type, if any. + /// + public static string? ShorthandProperty => null; + + /// + /// Initializes a new instance of . + /// +#pragma warning disable CS8618 + public SessionRef() + { + } +#pragma warning restore CS8618 + + /// + /// Stable session identifier + /// + public string? SessionId { get; set; } + + /// + /// Reference category + /// + public string RefType { get; set; } = string.Empty; + + /// + /// Reference value + /// + public string RefValue { get; set; } = string.Empty; + + /// + /// Zero-based turn index where the reference was first observed + /// + public int? TurnIndex { get; set; } + + /// + /// ISO 8601 UTC timestamp when the reference was recorded + /// + public string? CreatedAt { get; set; } + + + + #region Load Methods + + /// + /// Load a SessionRef instance from a dictionary. + /// + /// The dictionary containing the data. + /// Optional context with pre/post processing callbacks. + /// The loaded SessionRef instance. + public static SessionRef Load(Dictionary data, LoadContext? context = null) + { + if (context is not null) + { + data = context.ProcessInput(data); + } + + + // Create new instance + var instance = new SessionRef(); + + + if (data.TryGetValue("sessionId", out var sessionIdValue) && sessionIdValue is not null) + { + instance.SessionId = sessionIdValue?.ToString()!; + } + + if (data.TryGetValue("refType", out var refTypeValue) && refTypeValue is not null) + { + instance.RefType = refTypeValue?.ToString()!; + } + + if (data.TryGetValue("refValue", out var refValueValue) && refValueValue is not null) + { + instance.RefValue = refValueValue?.ToString()!; + } + + if (data.TryGetValue("turnIndex", out var turnIndexValue) && turnIndexValue is not null) + { + instance.TurnIndex = Convert.ToInt32(turnIndexValue); + } + + if (data.TryGetValue("createdAt", out var createdAtValue) && createdAtValue is not null) + { + instance.CreatedAt = createdAtValue?.ToString()!; + } + + if (context is not null) + { + instance = context.ProcessOutput(instance); + } + return instance; + } + + + #endregion + + #region Save Methods + + /// + /// Save the SessionRef instance to a dictionary. + /// + /// Optional context with pre/post processing callbacks. + /// The dictionary representation of this instance. + public Dictionary Save(SaveContext? context = null) + { + var obj = this; + if (context is not null) + { + obj = context.ProcessObject(obj); + } + + + var result = new Dictionary(); + + + if (obj.SessionId is not null) + { + result["sessionId"] = obj.SessionId; + } + + + result["refType"] = obj.RefType; + + + result["refValue"] = obj.RefValue; + + + if (obj.TurnIndex is not null) + { + result["turnIndex"] = obj.TurnIndex; + } + + + if (obj.CreatedAt is not null) + { + result["createdAt"] = obj.CreatedAt; + } + + + if (context is not null) + { + result = context.ProcessDict(result); + } + + return result; + } + + + /// + /// Convert the SessionRef instance to a YAML string. + /// + /// Optional context with pre/post processing callbacks. + /// The YAML string representation of this instance. + public string ToYaml(SaveContext? context = null) + { + context ??= new SaveContext(); + return context.ToYaml(Save(context)); + } + + /// + /// Convert the SessionRef instance to a JSON string. + /// + /// Optional context with pre/post processing callbacks. + /// Whether to indent the output. Defaults to true. + /// The JSON string representation of this instance. + public string ToJson(SaveContext? context = null, bool indent = true) + { + context ??= new SaveContext(); + return context.ToJson(Save(context), indent); + } + + /// + /// Load a SessionRef instance from a JSON string. + /// + /// The JSON string to parse. + /// Optional context with pre/post processing callbacks. + /// The loaded SessionRef instance. + public static SessionRef FromJson(string json, LoadContext? context = null) + { + using var doc = JsonDocument.Parse(json); + Dictionary dict; + dict = JsonSerializer.Deserialize>(json, JsonUtils.Options) + ?? throw new ArgumentException("Failed to parse JSON as dictionary"); + + return Load(dict, context); + } + + /// + /// Load a SessionRef instance from a YAML string. + /// + /// The YAML string to parse. + /// Optional context with pre/post processing callbacks. + /// The loaded SessionRef instance. + public static SessionRef FromYaml(string yaml, LoadContext? context = null) + { + var dict = YamlUtils.Deserializer.Deserialize>(yaml) + ?? throw new ArgumentException("Failed to parse YAML as dictionary"); + + return Load(dict, context); + } + + #endregion +} diff --git a/runtime/csharp/Prompty.Core/Model/events/SessionStartPayload.cs b/runtime/csharp/Prompty.Core/Model/events/SessionStartPayload.cs new file mode 100644 index 00000000..c4270514 --- /dev/null +++ b/runtime/csharp/Prompty.Core/Model/events/SessionStartPayload.cs @@ -0,0 +1,284 @@ +// +// Copyright (c) Microsoft. All rights reserved. +using System.Text.Json; +using YamlDotNet.Serialization; + +#pragma warning disable IDE0130 +namespace Prompty.Core; +#pragma warning restore IDE0130 + + /// + /// Payload for "session_start" events. + /// +public partial class SessionStartPayload +{ + /// + /// The shorthand property name for this type, if any. + /// + public static string? ShorthandProperty => null; + + /// + /// Initializes a new instance of . + /// +#pragma warning disable CS8618 + public SessionStartPayload() + { + } +#pragma warning restore CS8618 + + /// + /// Stable session identifier + /// + public string SessionId { get; set; } = string.Empty; + + /// + /// Session event schema version + /// + public string? SchemaVersion { get; set; } + + /// + /// Producer that started the session + /// + public string? Producer { get; set; } + + /// + /// Runtime that produced the session + /// + public string? Runtime { get; set; } + + /// + /// Prompty library version + /// + public string? PromptyVersion { get; set; } + + /// + /// ISO 8601 UTC timestamp when the session started + /// + public string? StartTime { get; set; } + + /// + /// Selected model identifier, when known + /// + public string? SelectedModel { get; set; } + + /// + /// Selected reasoning effort or equivalent model setting, when known + /// + public string? ReasoningEffort { get; set; } + + /// + /// Repository and execution context + /// + public HarnessContext? Context { get; set; } + + + + #region Load Methods + + /// + /// Load a SessionStartPayload instance from a dictionary. + /// + /// The dictionary containing the data. + /// Optional context with pre/post processing callbacks. + /// The loaded SessionStartPayload instance. + public static SessionStartPayload Load(Dictionary data, LoadContext? context = null) + { + if (context is not null) + { + data = context.ProcessInput(data); + } + + + // Create new instance + var instance = new SessionStartPayload(); + + + if (data.TryGetValue("sessionId", out var sessionIdValue) && sessionIdValue is not null) + { + instance.SessionId = sessionIdValue?.ToString()!; + } + + if (data.TryGetValue("schemaVersion", out var schemaVersionValue) && schemaVersionValue is not null) + { + instance.SchemaVersion = schemaVersionValue?.ToString()!; + } + + if (data.TryGetValue("producer", out var producerValue) && producerValue is not null) + { + instance.Producer = producerValue?.ToString()!; + } + + if (data.TryGetValue("runtime", out var runtimeValue) && runtimeValue is not null) + { + instance.Runtime = runtimeValue?.ToString()!; + } + + if (data.TryGetValue("promptyVersion", out var promptyVersionValue) && promptyVersionValue is not null) + { + instance.PromptyVersion = promptyVersionValue?.ToString()!; + } + + if (data.TryGetValue("startTime", out var startTimeValue) && startTimeValue is not null) + { + instance.StartTime = startTimeValue?.ToString()!; + } + + if (data.TryGetValue("selectedModel", out var selectedModelValue) && selectedModelValue is not null) + { + instance.SelectedModel = selectedModelValue?.ToString()!; + } + + if (data.TryGetValue("reasoningEffort", out var reasoningEffortValue) && reasoningEffortValue is not null) + { + instance.ReasoningEffort = reasoningEffortValue?.ToString()!; + } + + if (data.TryGetValue("context", out var contextValue) && contextValue is not null) + { + instance.Context = HarnessContext.Load(contextValue.GetDictionary(HarnessContext.ShorthandProperty), context); + } + + if (context is not null) + { + instance = context.ProcessOutput(instance); + } + return instance; + } + + + #endregion + + #region Save Methods + + /// + /// Save the SessionStartPayload instance to a dictionary. + /// + /// Optional context with pre/post processing callbacks. + /// The dictionary representation of this instance. + public Dictionary Save(SaveContext? context = null) + { + var obj = this; + if (context is not null) + { + obj = context.ProcessObject(obj); + } + + + var result = new Dictionary(); + + + result["sessionId"] = obj.SessionId; + + + if (obj.SchemaVersion is not null) + { + result["schemaVersion"] = obj.SchemaVersion; + } + + + if (obj.Producer is not null) + { + result["producer"] = obj.Producer; + } + + + if (obj.Runtime is not null) + { + result["runtime"] = obj.Runtime; + } + + + if (obj.PromptyVersion is not null) + { + result["promptyVersion"] = obj.PromptyVersion; + } + + + if (obj.StartTime is not null) + { + result["startTime"] = obj.StartTime; + } + + + if (obj.SelectedModel is not null) + { + result["selectedModel"] = obj.SelectedModel; + } + + + if (obj.ReasoningEffort is not null) + { + result["reasoningEffort"] = obj.ReasoningEffort; + } + + + if (obj.Context is not null) + { + result["context"] = obj.Context?.Save(context); + } + + + if (context is not null) + { + result = context.ProcessDict(result); + } + + return result; + } + + + /// + /// Convert the SessionStartPayload instance to a YAML string. + /// + /// Optional context with pre/post processing callbacks. + /// The YAML string representation of this instance. + public string ToYaml(SaveContext? context = null) + { + context ??= new SaveContext(); + return context.ToYaml(Save(context)); + } + + /// + /// Convert the SessionStartPayload instance to a JSON string. + /// + /// Optional context with pre/post processing callbacks. + /// Whether to indent the output. Defaults to true. + /// The JSON string representation of this instance. + public string ToJson(SaveContext? context = null, bool indent = true) + { + context ??= new SaveContext(); + return context.ToJson(Save(context), indent); + } + + /// + /// Load a SessionStartPayload instance from a JSON string. + /// + /// The JSON string to parse. + /// Optional context with pre/post processing callbacks. + /// The loaded SessionStartPayload instance. + public static SessionStartPayload FromJson(string json, LoadContext? context = null) + { + using var doc = JsonDocument.Parse(json); + Dictionary dict; + dict = JsonSerializer.Deserialize>(json, JsonUtils.Options) + ?? throw new ArgumentException("Failed to parse JSON as dictionary"); + + return Load(dict, context); + } + + /// + /// Load a SessionStartPayload instance from a YAML string. + /// + /// The YAML string to parse. + /// Optional context with pre/post processing callbacks. + /// The loaded SessionStartPayload instance. + public static SessionStartPayload FromYaml(string yaml, LoadContext? context = null) + { + var dict = YamlUtils.Deserializer.Deserialize>(yaml) + ?? throw new ArgumentException("Failed to parse YAML as dictionary"); + + return Load(dict, context); + } + + #endregion +} diff --git a/runtime/csharp/Prompty.Core/Model/events/SessionSummary.cs b/runtime/csharp/Prompty.Core/Model/events/SessionSummary.cs new file mode 100644 index 00000000..b1189c1e --- /dev/null +++ b/runtime/csharp/Prompty.Core/Model/events/SessionSummary.cs @@ -0,0 +1,236 @@ +// +// Copyright (c) Microsoft. All rights reserved. +using System.Text.Json; +using YamlDotNet.Serialization; + +#pragma warning disable IDE0130 +namespace Prompty.Core; +#pragma warning restore IDE0130 + + /// + /// Summary statistics for a completed session trace. + /// +public partial class SessionSummary +{ + /// + /// The shorthand property name for this type, if any. + /// + public static string? ShorthandProperty => null; + + /// + /// Initializes a new instance of . + /// +#pragma warning disable CS8618 + public SessionSummary() + { + } +#pragma warning restore CS8618 + + /// + /// Stable session identifier + /// + public string SessionId { get; set; } = string.Empty; + + /// + /// Final session status + /// + public SessionSummaryStatus? Status { get; set; } + + /// + /// Number of user/assistant turns in the session + /// + public int? Turns { get; set; } + + /// + /// Number of checkpoints created + /// + public int? Checkpoints { get; set; } + + /// + /// Aggregated token usage for the session + /// + public TokenUsage? Usage { get; set; } + + /// + /// Total elapsed session duration in milliseconds + /// + public double? DurationMs { get; set; } + + + + #region Load Methods + + /// + /// Load a SessionSummary instance from a dictionary. + /// + /// The dictionary containing the data. + /// Optional context with pre/post processing callbacks. + /// The loaded SessionSummary instance. + public static SessionSummary Load(Dictionary data, LoadContext? context = null) + { + if (context is not null) + { + data = context.ProcessInput(data); + } + + + // Create new instance + var instance = new SessionSummary(); + + + if (data.TryGetValue("sessionId", out var sessionIdValue) && sessionIdValue is not null) + { + instance.SessionId = sessionIdValue?.ToString()!; + } + + if (data.TryGetValue("status", out var statusValue) && statusValue is not null) + { + instance.Status = Enum.Parse(statusValue?.ToString()!, true); + } + + if (data.TryGetValue("turns", out var turnsValue) && turnsValue is not null) + { + instance.Turns = Convert.ToInt32(turnsValue); + } + + if (data.TryGetValue("checkpoints", out var checkpointsValue) && checkpointsValue is not null) + { + instance.Checkpoints = Convert.ToInt32(checkpointsValue); + } + + if (data.TryGetValue("usage", out var usageValue) && usageValue is not null) + { + instance.Usage = TokenUsage.Load(usageValue.GetDictionary(TokenUsage.ShorthandProperty), context); + } + + if (data.TryGetValue("durationMs", out var durationMsValue) && durationMsValue is not null) + { + instance.DurationMs = Convert.ToDouble(durationMsValue); + } + + if (context is not null) + { + instance = context.ProcessOutput(instance); + } + return instance; + } + + + #endregion + + #region Save Methods + + /// + /// Save the SessionSummary instance to a dictionary. + /// + /// Optional context with pre/post processing callbacks. + /// The dictionary representation of this instance. + public Dictionary Save(SaveContext? context = null) + { + var obj = this; + if (context is not null) + { + obj = context.ProcessObject(obj); + } + + + var result = new Dictionary(); + + + result["sessionId"] = obj.SessionId; + + + if (obj.Status is not null) + { + result["status"] = obj.Status.Value.ToString().ToLowerInvariant(); + } + + + if (obj.Turns is not null) + { + result["turns"] = obj.Turns; + } + + + if (obj.Checkpoints is not null) + { + result["checkpoints"] = obj.Checkpoints; + } + + + if (obj.Usage is not null) + { + result["usage"] = obj.Usage?.Save(context); + } + + + if (obj.DurationMs is not null) + { + result["durationMs"] = obj.DurationMs; + } + + + if (context is not null) + { + result = context.ProcessDict(result); + } + + return result; + } + + + /// + /// Convert the SessionSummary instance to a YAML string. + /// + /// Optional context with pre/post processing callbacks. + /// The YAML string representation of this instance. + public string ToYaml(SaveContext? context = null) + { + context ??= new SaveContext(); + return context.ToYaml(Save(context)); + } + + /// + /// Convert the SessionSummary instance to a JSON string. + /// + /// Optional context with pre/post processing callbacks. + /// Whether to indent the output. Defaults to true. + /// The JSON string representation of this instance. + public string ToJson(SaveContext? context = null, bool indent = true) + { + context ??= new SaveContext(); + return context.ToJson(Save(context), indent); + } + + /// + /// Load a SessionSummary instance from a JSON string. + /// + /// The JSON string to parse. + /// Optional context with pre/post processing callbacks. + /// The loaded SessionSummary instance. + public static SessionSummary FromJson(string json, LoadContext? context = null) + { + using var doc = JsonDocument.Parse(json); + Dictionary dict; + dict = JsonSerializer.Deserialize>(json, JsonUtils.Options) + ?? throw new ArgumentException("Failed to parse JSON as dictionary"); + + return Load(dict, context); + } + + /// + /// Load a SessionSummary instance from a YAML string. + /// + /// The YAML string to parse. + /// Optional context with pre/post processing callbacks. + /// The loaded SessionSummary instance. + public static SessionSummary FromYaml(string yaml, LoadContext? context = null) + { + var dict = YamlUtils.Deserializer.Deserialize>(yaml) + ?? throw new ArgumentException("Failed to parse YAML as dictionary"); + + return Load(dict, context); + } + + #endregion +} diff --git a/runtime/csharp/Prompty.Core/Model/events/SessionSummaryStatus.cs b/runtime/csharp/Prompty.Core/Model/events/SessionSummaryStatus.cs new file mode 100644 index 00000000..9f34f219 --- /dev/null +++ b/runtime/csharp/Prompty.Core/Model/events/SessionSummaryStatus.cs @@ -0,0 +1,24 @@ +// +// +// Code generated by Typra emitter; DO NOT EDIT. + +using System.Text.Json.Serialization; + +namespace Prompty.Core; + +[JsonConverter(typeof(JsonStringEnumConverter))] +public enum SessionSummaryStatus +{ + [JsonPropertyName("success")] + Success, + + [JsonPropertyName("error")] + Error, + + [JsonPropertyName("cancelled")] + Cancelled, + + [JsonPropertyName("interrupted")] + Interrupted, + +} diff --git a/runtime/csharp/Prompty.Core/Model/events/SessionTrace.cs b/runtime/csharp/Prompty.Core/Model/events/SessionTrace.cs new file mode 100644 index 00000000..2f96a080 --- /dev/null +++ b/runtime/csharp/Prompty.Core/Model/events/SessionTrace.cs @@ -0,0 +1,715 @@ +// +// Copyright (c) Microsoft. All rights reserved. +using System.Text.Json; +using YamlDotNet.Serialization; + +#pragma warning disable IDE0130 +namespace Prompty.Core; +#pragma warning restore IDE0130 + + /// + /// Portable replay container for an outer harness session. + /// +public partial class SessionTrace +{ + /// + /// The shorthand property name for this type, if any. + /// + public static string? ShorthandProperty => null; + + /// + /// Initializes a new instance of . + /// +#pragma warning disable CS8618 + public SessionTrace() + { + } +#pragma warning restore CS8618 + + /// + /// Trace schema version + /// + public string Version { get; set; } = "1"; + + /// + /// Runtime name that produced the trace + /// + public string? Runtime { get; set; } + + /// + /// Prompty library version that produced the trace + /// + public string? PromptyVersion { get; set; } + + /// + /// Stable session identifier + /// + public string? SessionId { get; set; } + + /// + /// Recorded session events in emission order + /// + public IList Events { get; set; } = []; + + /// + /// Recorded turn traces associated with the session + /// + public IList? Turns { get; set; } + + /// + /// Checkpoints created during the session + /// + public IList? Checkpoints { get; set; } + + /// + /// Compact trajectory records associated with the session + /// + public IList? Trajectory { get; set; } + + /// + /// Files observed or touched during the session + /// + public IList? Files { get; set; } + + /// + /// Non-file references observed during the session + /// + public IList? Refs { get; set; } + + /// + /// Optional summary computed from the event stream + /// + public SessionSummary? Summary { get; set; } + + + + #region Load Methods + + /// + /// Load a SessionTrace instance from a dictionary. + /// + /// The dictionary containing the data. + /// Optional context with pre/post processing callbacks. + /// The loaded SessionTrace instance. + public static SessionTrace Load(Dictionary data, LoadContext? context = null) + { + if (context is not null) + { + data = context.ProcessInput(data); + } + + + // Create new instance + var instance = new SessionTrace(); + + + if (data.TryGetValue("version", out var versionValue) && versionValue is not null) + { + instance.Version = versionValue?.ToString()!; + } + + if (data.TryGetValue("runtime", out var runtimeValue) && runtimeValue is not null) + { + instance.Runtime = runtimeValue?.ToString()!; + } + + if (data.TryGetValue("promptyVersion", out var promptyVersionValue) && promptyVersionValue is not null) + { + instance.PromptyVersion = promptyVersionValue?.ToString()!; + } + + if (data.TryGetValue("sessionId", out var sessionIdValue) && sessionIdValue is not null) + { + instance.SessionId = sessionIdValue?.ToString()!; + } + + if (data.TryGetValue("events", out var eventsValue) && eventsValue is not null) + { + instance.Events = LoadEvents(eventsValue, context); + } + + if (data.TryGetValue("turns", out var turnsValue) && turnsValue is not null) + { + instance.Turns = LoadTurns(turnsValue, context); + } + + if (data.TryGetValue("checkpoints", out var checkpointsValue) && checkpointsValue is not null) + { + instance.Checkpoints = LoadCheckpoints(checkpointsValue, context); + } + + if (data.TryGetValue("trajectory", out var trajectoryValue) && trajectoryValue is not null) + { + instance.Trajectory = LoadTrajectory(trajectoryValue, context); + } + + if (data.TryGetValue("files", out var filesValue) && filesValue is not null) + { + instance.Files = LoadFiles(filesValue, context); + } + + if (data.TryGetValue("refs", out var refsValue) && refsValue is not null) + { + instance.Refs = LoadRefs(refsValue, context); + } + + if (data.TryGetValue("summary", out var summaryValue) && summaryValue is not null) + { + instance.Summary = SessionSummary.Load(summaryValue.GetDictionary(SessionSummary.ShorthandProperty), context); + } + + if (context is not null) + { + instance = context.ProcessOutput(instance); + } + return instance; + } + + + /// + /// Load a list of SessionEvent from a dictionary or list. + /// + public static IList LoadEvents(object data, LoadContext? context) + { + var result = new List(); + + if (data is Dictionary dict) + { + // Convert named dictionary to list + foreach (var kvp in dict) + { + if (kvp.Value is IEnumerable) + { + throw new ArgumentException( + $"Invalid 'events' format: key '{kvp.Key}' has an array value. " + + $"'events' must be a flat list of objects or a name-keyed dict — " + + "not a nested {" + kvp.Key + ": [...]} structure."); + } + var itemDict = kvp.Value.GetDictionary(); + if (itemDict.Count > 0) + { + // Value is an object, add name to it + itemDict["name"] = kvp.Key; + result.Add(SessionEvent.Load(itemDict, context)); + } + else + { + // Value is a scalar, use it as the primary property + var newDict = new Dictionary + { + ["name"] = kvp.Key, + ["id"] = kvp.Value + }; + result.Add(SessionEvent.Load(newDict, context)); + } + } + } + else if (data is IEnumerable list) + { + foreach (var item in list) + { + var itemDict = item.GetDictionary(SessionEvent.ShorthandProperty); + if (itemDict.Count > 0) + { + result.Add(SessionEvent.Load(itemDict, context)); + } + } + } + + return result; + } + + + /// + /// Load a list of TurnTrace from a dictionary or list. + /// + public static IList LoadTurns(object data, LoadContext? context) + { + var result = new List(); + + if (data is Dictionary dict) + { + // Convert named dictionary to list + foreach (var kvp in dict) + { + if (kvp.Value is IEnumerable) + { + throw new ArgumentException( + $"Invalid 'turns' format: key '{kvp.Key}' has an array value. " + + $"'turns' must be a flat list of objects or a name-keyed dict — " + + "not a nested {" + kvp.Key + ": [...]} structure."); + } + var itemDict = kvp.Value.GetDictionary(); + if (itemDict.Count > 0) + { + // Value is an object, add name to it + itemDict["name"] = kvp.Key; + result.Add(TurnTrace.Load(itemDict, context)); + } + else + { + // Value is a scalar, use it as the primary property + var newDict = new Dictionary + { + ["name"] = kvp.Key, + ["version"] = kvp.Value + }; + result.Add(TurnTrace.Load(newDict, context)); + } + } + } + else if (data is IEnumerable list) + { + foreach (var item in list) + { + var itemDict = item.GetDictionary(TurnTrace.ShorthandProperty); + if (itemDict.Count > 0) + { + result.Add(TurnTrace.Load(itemDict, context)); + } + } + } + + return result; + } + + + /// + /// Load a list of Checkpoint from a dictionary or list. + /// + public static IList LoadCheckpoints(object data, LoadContext? context) + { + var result = new List(); + + if (data is Dictionary dict) + { + // Convert named dictionary to list + foreach (var kvp in dict) + { + if (kvp.Value is IEnumerable) + { + throw new ArgumentException( + $"Invalid 'checkpoints' format: key '{kvp.Key}' has an array value. " + + $"'checkpoints' must be a flat list of objects or a name-keyed dict — " + + "not a nested {" + kvp.Key + ": [...]} structure."); + } + var itemDict = kvp.Value.GetDictionary(); + if (itemDict.Count > 0) + { + // Value is an object, add name to it + itemDict["name"] = kvp.Key; + result.Add(Checkpoint.Load(itemDict, context)); + } + else + { + // Value is a scalar, use it as the primary property + var newDict = new Dictionary + { + ["name"] = kvp.Key, + ["id"] = kvp.Value + }; + result.Add(Checkpoint.Load(newDict, context)); + } + } + } + else if (data is IEnumerable list) + { + foreach (var item in list) + { + var itemDict = item.GetDictionary(Checkpoint.ShorthandProperty); + if (itemDict.Count > 0) + { + result.Add(Checkpoint.Load(itemDict, context)); + } + } + } + + return result; + } + + + /// + /// Load a list of TrajectoryEvent from a dictionary or list. + /// + public static IList LoadTrajectory(object data, LoadContext? context) + { + var result = new List(); + + if (data is Dictionary dict) + { + // Convert named dictionary to list + foreach (var kvp in dict) + { + if (kvp.Value is IEnumerable) + { + throw new ArgumentException( + $"Invalid 'trajectory' format: key '{kvp.Key}' has an array value. " + + $"'trajectory' must be a flat list of objects or a name-keyed dict — " + + "not a nested {" + kvp.Key + ": [...]} structure."); + } + var itemDict = kvp.Value.GetDictionary(); + if (itemDict.Count > 0) + { + // Value is an object, add name to it + itemDict["name"] = kvp.Key; + result.Add(TrajectoryEvent.Load(itemDict, context)); + } + else + { + // Value is a scalar, use it as the primary property + var newDict = new Dictionary + { + ["name"] = kvp.Key, + ["id"] = kvp.Value + }; + result.Add(TrajectoryEvent.Load(newDict, context)); + } + } + } + else if (data is IEnumerable list) + { + foreach (var item in list) + { + var itemDict = item.GetDictionary(TrajectoryEvent.ShorthandProperty); + if (itemDict.Count > 0) + { + result.Add(TrajectoryEvent.Load(itemDict, context)); + } + } + } + + return result; + } + + + /// + /// Load a list of SessionFileRef from a dictionary or list. + /// + public static IList LoadFiles(object data, LoadContext? context) + { + var result = new List(); + + if (data is Dictionary dict) + { + // Convert named dictionary to list + foreach (var kvp in dict) + { + if (kvp.Value is IEnumerable) + { + throw new ArgumentException( + $"Invalid 'files' format: key '{kvp.Key}' has an array value. " + + $"'files' must be a flat list of objects or a name-keyed dict — " + + "not a nested {" + kvp.Key + ": [...]} structure."); + } + var itemDict = kvp.Value.GetDictionary(); + if (itemDict.Count > 0) + { + // Value is an object, add name to it + itemDict["name"] = kvp.Key; + result.Add(SessionFileRef.Load(itemDict, context)); + } + else + { + // Value is a scalar, use it as the primary property + var newDict = new Dictionary + { + ["name"] = kvp.Key, + ["sessionId"] = kvp.Value + }; + result.Add(SessionFileRef.Load(newDict, context)); + } + } + } + else if (data is IEnumerable list) + { + foreach (var item in list) + { + var itemDict = item.GetDictionary(SessionFileRef.ShorthandProperty); + if (itemDict.Count > 0) + { + result.Add(SessionFileRef.Load(itemDict, context)); + } + } + } + + return result; + } + + + /// + /// Load a list of SessionRef from a dictionary or list. + /// + public static IList LoadRefs(object data, LoadContext? context) + { + var result = new List(); + + if (data is Dictionary dict) + { + // Convert named dictionary to list + foreach (var kvp in dict) + { + if (kvp.Value is IEnumerable) + { + throw new ArgumentException( + $"Invalid 'refs' format: key '{kvp.Key}' has an array value. " + + $"'refs' must be a flat list of objects or a name-keyed dict — " + + "not a nested {" + kvp.Key + ": [...]} structure."); + } + var itemDict = kvp.Value.GetDictionary(); + if (itemDict.Count > 0) + { + // Value is an object, add name to it + itemDict["name"] = kvp.Key; + result.Add(SessionRef.Load(itemDict, context)); + } + else + { + // Value is a scalar, use it as the primary property + var newDict = new Dictionary + { + ["name"] = kvp.Key, + ["sessionId"] = kvp.Value + }; + result.Add(SessionRef.Load(newDict, context)); + } + } + } + else if (data is IEnumerable list) + { + foreach (var item in list) + { + var itemDict = item.GetDictionary(SessionRef.ShorthandProperty); + if (itemDict.Count > 0) + { + result.Add(SessionRef.Load(itemDict, context)); + } + } + } + + return result; + } + + + #endregion + + #region Save Methods + + /// + /// Save the SessionTrace instance to a dictionary. + /// + /// Optional context with pre/post processing callbacks. + /// The dictionary representation of this instance. + public Dictionary Save(SaveContext? context = null) + { + var obj = this; + if (context is not null) + { + obj = context.ProcessObject(obj); + } + + + var result = new Dictionary(); + + + result["version"] = obj.Version; + + + if (obj.Runtime is not null) + { + result["runtime"] = obj.Runtime; + } + + + if (obj.PromptyVersion is not null) + { + result["promptyVersion"] = obj.PromptyVersion; + } + + + if (obj.SessionId is not null) + { + result["sessionId"] = obj.SessionId; + } + + + result["events"] = SaveEvents(obj.Events, context); + + + if (obj.Turns is not null) + { + result["turns"] = SaveTurns(obj.Turns, context); + } + + + if (obj.Checkpoints is not null) + { + result["checkpoints"] = SaveCheckpoints(obj.Checkpoints, context); + } + + + if (obj.Trajectory is not null) + { + result["trajectory"] = SaveTrajectory(obj.Trajectory, context); + } + + + if (obj.Files is not null) + { + result["files"] = SaveFiles(obj.Files, context); + } + + + if (obj.Refs is not null) + { + result["refs"] = SaveRefs(obj.Refs, context); + } + + + if (obj.Summary is not null) + { + result["summary"] = obj.Summary?.Save(context); + } + + + if (context is not null) + { + result = context.ProcessDict(result); + } + + return result; + } + + + /// + /// Save a list of SessionEvent to object or array format. + /// + public static object SaveEvents(IList items, SaveContext? context) + { + context ??= new SaveContext(); + + // This collection type does not have a 'name' property, only array format is supported + return items.Select(item => item.Save(context)).ToList(); + + } + + + /// + /// Save a list of TurnTrace to object or array format. + /// + public static object SaveTurns(IList items, SaveContext? context) + { + context ??= new SaveContext(); + + // This collection type does not have a 'name' property, only array format is supported + return items.Select(item => item.Save(context)).ToList(); + + } + + + /// + /// Save a list of Checkpoint to object or array format. + /// + public static object SaveCheckpoints(IList items, SaveContext? context) + { + context ??= new SaveContext(); + + // This collection type does not have a 'name' property, only array format is supported + return items.Select(item => item.Save(context)).ToList(); + + } + + + /// + /// Save a list of TrajectoryEvent to object or array format. + /// + public static object SaveTrajectory(IList items, SaveContext? context) + { + context ??= new SaveContext(); + + // This collection type does not have a 'name' property, only array format is supported + return items.Select(item => item.Save(context)).ToList(); + + } + + + /// + /// Save a list of SessionFileRef to object or array format. + /// + public static object SaveFiles(IList items, SaveContext? context) + { + context ??= new SaveContext(); + + // This collection type does not have a 'name' property, only array format is supported + return items.Select(item => item.Save(context)).ToList(); + + } + + + /// + /// Save a list of SessionRef to object or array format. + /// + public static object SaveRefs(IList items, SaveContext? context) + { + context ??= new SaveContext(); + + // This collection type does not have a 'name' property, only array format is supported + return items.Select(item => item.Save(context)).ToList(); + + } + + + /// + /// Convert the SessionTrace instance to a YAML string. + /// + /// Optional context with pre/post processing callbacks. + /// The YAML string representation of this instance. + public string ToYaml(SaveContext? context = null) + { + context ??= new SaveContext(); + return context.ToYaml(Save(context)); + } + + /// + /// Convert the SessionTrace instance to a JSON string. + /// + /// Optional context with pre/post processing callbacks. + /// Whether to indent the output. Defaults to true. + /// The JSON string representation of this instance. + public string ToJson(SaveContext? context = null, bool indent = true) + { + context ??= new SaveContext(); + return context.ToJson(Save(context), indent); + } + + /// + /// Load a SessionTrace instance from a JSON string. + /// + /// The JSON string to parse. + /// Optional context with pre/post processing callbacks. + /// The loaded SessionTrace instance. + public static SessionTrace FromJson(string json, LoadContext? context = null) + { + using var doc = JsonDocument.Parse(json); + Dictionary dict; + dict = JsonSerializer.Deserialize>(json, JsonUtils.Options) + ?? throw new ArgumentException("Failed to parse JSON as dictionary"); + + return Load(dict, context); + } + + /// + /// Load a SessionTrace instance from a YAML string. + /// + /// The YAML string to parse. + /// Optional context with pre/post processing callbacks. + /// The loaded SessionTrace instance. + public static SessionTrace FromYaml(string yaml, LoadContext? context = null) + { + var dict = YamlUtils.Deserializer.Deserialize>(yaml) + ?? throw new ArgumentException("Failed to parse YAML as dictionary"); + + return Load(dict, context); + } + + #endregion +} diff --git a/runtime/csharp/Prompty.Core/Model/events/SessionWarningPayload.cs b/runtime/csharp/Prompty.Core/Model/events/SessionWarningPayload.cs new file mode 100644 index 00000000..55b498da --- /dev/null +++ b/runtime/csharp/Prompty.Core/Model/events/SessionWarningPayload.cs @@ -0,0 +1,185 @@ +// +// Copyright (c) Microsoft. All rights reserved. +using System.Text.Json; +using YamlDotNet.Serialization; + +#pragma warning disable IDE0130 +namespace Prompty.Core; +#pragma warning restore IDE0130 + + /// + /// Payload for "session_warning" events. + /// +public partial class SessionWarningPayload +{ + /// + /// The shorthand property name for this type, if any. + /// + public static string? ShorthandProperty => null; + + /// + /// Initializes a new instance of . + /// +#pragma warning disable CS8618 + public SessionWarningPayload() + { + } +#pragma warning restore CS8618 + + /// + /// Stable machine-readable warning category + /// + public string WarningType { get; set; } = string.Empty; + + /// + /// Human-readable warning message + /// + public string Message { get; set; } = string.Empty; + + /// + /// Additional host-specific warning details + /// + public IDictionary? Details { get; set; } + + + + #region Load Methods + + /// + /// Load a SessionWarningPayload instance from a dictionary. + /// + /// The dictionary containing the data. + /// Optional context with pre/post processing callbacks. + /// The loaded SessionWarningPayload instance. + public static SessionWarningPayload Load(Dictionary data, LoadContext? context = null) + { + if (context is not null) + { + data = context.ProcessInput(data); + } + + + // Create new instance + var instance = new SessionWarningPayload(); + + + if (data.TryGetValue("warningType", out var warningTypeValue) && warningTypeValue is not null) + { + instance.WarningType = warningTypeValue?.ToString()!; + } + + if (data.TryGetValue("message", out var messageValue) && messageValue is not null) + { + instance.Message = messageValue?.ToString()!; + } + + if (data.TryGetValue("details", out var detailsValue) && detailsValue is not null) + { + instance.Details = detailsValue.GetDictionary()!; + } + + if (context is not null) + { + instance = context.ProcessOutput(instance); + } + return instance; + } + + + #endregion + + #region Save Methods + + /// + /// Save the SessionWarningPayload instance to a dictionary. + /// + /// Optional context with pre/post processing callbacks. + /// The dictionary representation of this instance. + public Dictionary Save(SaveContext? context = null) + { + var obj = this; + if (context is not null) + { + obj = context.ProcessObject(obj); + } + + + var result = new Dictionary(); + + + result["warningType"] = obj.WarningType; + + + result["message"] = obj.Message; + + + if (obj.Details is not null) + { + result["details"] = obj.Details; + } + + + if (context is not null) + { + result = context.ProcessDict(result); + } + + return result; + } + + + /// + /// Convert the SessionWarningPayload instance to a YAML string. + /// + /// Optional context with pre/post processing callbacks. + /// The YAML string representation of this instance. + public string ToYaml(SaveContext? context = null) + { + context ??= new SaveContext(); + return context.ToYaml(Save(context)); + } + + /// + /// Convert the SessionWarningPayload instance to a JSON string. + /// + /// Optional context with pre/post processing callbacks. + /// Whether to indent the output. Defaults to true. + /// The JSON string representation of this instance. + public string ToJson(SaveContext? context = null, bool indent = true) + { + context ??= new SaveContext(); + return context.ToJson(Save(context), indent); + } + + /// + /// Load a SessionWarningPayload instance from a JSON string. + /// + /// The JSON string to parse. + /// Optional context with pre/post processing callbacks. + /// The loaded SessionWarningPayload instance. + public static SessionWarningPayload FromJson(string json, LoadContext? context = null) + { + using var doc = JsonDocument.Parse(json); + Dictionary dict; + dict = JsonSerializer.Deserialize>(json, JsonUtils.Options) + ?? throw new ArgumentException("Failed to parse JSON as dictionary"); + + return Load(dict, context); + } + + /// + /// Load a SessionWarningPayload instance from a YAML string. + /// + /// The YAML string to parse. + /// Optional context with pre/post processing callbacks. + /// The loaded SessionWarningPayload instance. + public static SessionWarningPayload FromYaml(string yaml, LoadContext? context = null) + { + var dict = YamlUtils.Deserializer.Deserialize>(yaml) + ?? throw new ArgumentException("Failed to parse YAML as dictionary"); + + return Load(dict, context); + } + + #endregion +} diff --git a/runtime/csharp/Prompty.Core/Model/events/StatusEventPayload.cs b/runtime/csharp/Prompty.Core/Model/events/StatusEventPayload.cs index a1372eaf..dee6783b 100644 --- a/runtime/csharp/Prompty.Core/Model/events/StatusEventPayload.cs +++ b/runtime/csharp/Prompty.Core/Model/events/StatusEventPayload.cs @@ -1,3 +1,4 @@ +// // Copyright (c) Microsoft. All rights reserved. using System.Text.Json; using YamlDotNet.Serialization; @@ -6,9 +7,9 @@ namespace Prompty.Core; #pragma warning restore IDE0130 -/// -/// Payload for "status" events — informational messages about loop progress. -/// + /// + /// Payload for "status" events — informational messages about loop progress. + /// public partial class StatusEventPayload { /// diff --git a/runtime/csharp/Prompty.Core/Model/events/StreamChunk.cs b/runtime/csharp/Prompty.Core/Model/events/StreamChunk.cs index 2fcc0b9d..ff32d3b5 100644 --- a/runtime/csharp/Prompty.Core/Model/events/StreamChunk.cs +++ b/runtime/csharp/Prompty.Core/Model/events/StreamChunk.cs @@ -1,3 +1,4 @@ +// // Copyright (c) Microsoft. All rights reserved. using System.Text.Json; using YamlDotNet.Serialization; @@ -6,11 +7,11 @@ namespace Prompty.Core; #pragma warning restore IDE0130 -/// -/// A chunk of data from a streaming LLM response. Stream chunks are -/// -/// discriminated on the `kind` field. -/// + /// + /// A chunk of data from a streaming LLM response. Stream chunks are + /// + /// discriminated on the `kind` field. + /// public abstract partial class StreamChunk { /// diff --git a/runtime/csharp/Prompty.Core/Model/events/TextChunk.cs b/runtime/csharp/Prompty.Core/Model/events/TextChunk.cs index 12cce412..4f233e92 100644 --- a/runtime/csharp/Prompty.Core/Model/events/TextChunk.cs +++ b/runtime/csharp/Prompty.Core/Model/events/TextChunk.cs @@ -1,3 +1,4 @@ +// // Copyright (c) Microsoft. All rights reserved. using System.Text.Json; using YamlDotNet.Serialization; @@ -6,9 +7,9 @@ namespace Prompty.Core; #pragma warning restore IDE0130 -/// -/// A text content chunk from the LLM response stream. -/// + /// + /// A text content chunk from the LLM response stream. + /// public partial class TextChunk : StreamChunk { /// diff --git a/runtime/csharp/Prompty.Core/Model/events/ThinkingChunk.cs b/runtime/csharp/Prompty.Core/Model/events/ThinkingChunk.cs index a75d4497..a9ea48dd 100644 --- a/runtime/csharp/Prompty.Core/Model/events/ThinkingChunk.cs +++ b/runtime/csharp/Prompty.Core/Model/events/ThinkingChunk.cs @@ -1,3 +1,4 @@ +// // Copyright (c) Microsoft. All rights reserved. using System.Text.Json; using YamlDotNet.Serialization; @@ -6,9 +7,9 @@ namespace Prompty.Core; #pragma warning restore IDE0130 -/// -/// A thinking/reasoning content chunk from the LLM response stream. -/// + /// + /// A thinking/reasoning content chunk from the LLM response stream. + /// public partial class ThinkingChunk : StreamChunk { /// diff --git a/runtime/csharp/Prompty.Core/Model/events/ThinkingEventPayload.cs b/runtime/csharp/Prompty.Core/Model/events/ThinkingEventPayload.cs index 15ec5cbc..67c71038 100644 --- a/runtime/csharp/Prompty.Core/Model/events/ThinkingEventPayload.cs +++ b/runtime/csharp/Prompty.Core/Model/events/ThinkingEventPayload.cs @@ -1,3 +1,4 @@ +// // Copyright (c) Microsoft. All rights reserved. using System.Text.Json; using YamlDotNet.Serialization; @@ -6,9 +7,9 @@ namespace Prompty.Core; #pragma warning restore IDE0130 -/// -/// Payload for "thinking" events — reasoning/chain-of-thought tokens. -/// + /// + /// Payload for "thinking" events — reasoning/chain-of-thought tokens. + /// public partial class ThinkingEventPayload { /// diff --git a/runtime/csharp/Prompty.Core/Model/events/TokenEventPayload.cs b/runtime/csharp/Prompty.Core/Model/events/TokenEventPayload.cs index 46d720e0..b8f1430c 100644 --- a/runtime/csharp/Prompty.Core/Model/events/TokenEventPayload.cs +++ b/runtime/csharp/Prompty.Core/Model/events/TokenEventPayload.cs @@ -1,3 +1,4 @@ +// // Copyright (c) Microsoft. All rights reserved. using System.Text.Json; using YamlDotNet.Serialization; @@ -6,9 +7,9 @@ namespace Prompty.Core; #pragma warning restore IDE0130 -/// -/// Payload for "token" events — a single text token streamed from the LLM. -/// + /// + /// Payload for "token" events — a single text token streamed from the LLM. + /// public partial class TokenEventPayload { /// diff --git a/runtime/csharp/Prompty.Core/Model/events/ToolCallCompletePayload.cs b/runtime/csharp/Prompty.Core/Model/events/ToolCallCompletePayload.cs new file mode 100644 index 00000000..3dad5288 --- /dev/null +++ b/runtime/csharp/Prompty.Core/Model/events/ToolCallCompletePayload.cs @@ -0,0 +1,233 @@ +// +// Copyright (c) Microsoft. All rights reserved. +using System.Text.Json; +using YamlDotNet.Serialization; + +#pragma warning disable IDE0130 +namespace Prompty.Core; +#pragma warning restore IDE0130 + + /// + /// Payload for "tool_call_complete" events — a tool dispatch finished. + /// +public partial class ToolCallCompletePayload +{ + /// + /// The shorthand property name for this type, if any. + /// + public static string? ShorthandProperty => null; + + /// + /// Initializes a new instance of . + /// +#pragma warning disable CS8618 + public ToolCallCompletePayload() + { + } +#pragma warning restore CS8618 + + /// + /// The unique identifier of the tool call + /// + public string? Id { get; set; } + + /// + /// The name of the tool that completed + /// + public string Name { get; set; } = string.Empty; + + /// + /// Whether the tool dispatch succeeded semantically + /// + public bool Success { get; set; } = false; + + /// + /// Normalized tool result + /// + public ToolResult? Result { get; set; } + + /// + /// Tool execution duration in milliseconds + /// + public double? DurationMs { get; set; } + + /// + /// Machine-readable error category when success is false + /// + public string? ErrorKind { get; set; } + + + + #region Load Methods + + /// + /// Load a ToolCallCompletePayload instance from a dictionary. + /// + /// The dictionary containing the data. + /// Optional context with pre/post processing callbacks. + /// The loaded ToolCallCompletePayload instance. + public static ToolCallCompletePayload Load(Dictionary data, LoadContext? context = null) + { + if (context is not null) + { + data = context.ProcessInput(data); + } + + + // Create new instance + var instance = new ToolCallCompletePayload(); + + + if (data.TryGetValue("id", out var idValue) && idValue is not null) + { + instance.Id = idValue?.ToString()!; + } + + if (data.TryGetValue("name", out var nameValue) && nameValue is not null) + { + instance.Name = nameValue?.ToString()!; + } + + if (data.TryGetValue("success", out var successValue) && successValue is not null) + { + instance.Success = Convert.ToBoolean(successValue); + } + + if (data.TryGetValue("result", out var resultValue) && resultValue is not null) + { + instance.Result = ToolResult.Load(resultValue.GetDictionary(ToolResult.ShorthandProperty), context); + } + + if (data.TryGetValue("durationMs", out var durationMsValue) && durationMsValue is not null) + { + instance.DurationMs = Convert.ToDouble(durationMsValue); + } + + if (data.TryGetValue("errorKind", out var errorKindValue) && errorKindValue is not null) + { + instance.ErrorKind = errorKindValue?.ToString()!; + } + + if (context is not null) + { + instance = context.ProcessOutput(instance); + } + return instance; + } + + + #endregion + + #region Save Methods + + /// + /// Save the ToolCallCompletePayload instance to a dictionary. + /// + /// Optional context with pre/post processing callbacks. + /// The dictionary representation of this instance. + public Dictionary Save(SaveContext? context = null) + { + var obj = this; + if (context is not null) + { + obj = context.ProcessObject(obj); + } + + + var result = new Dictionary(); + + + if (obj.Id is not null) + { + result["id"] = obj.Id; + } + + + result["name"] = obj.Name; + + + result["success"] = obj.Success; + + + if (obj.Result is not null) + { + result["result"] = obj.Result?.Save(context); + } + + + if (obj.DurationMs is not null) + { + result["durationMs"] = obj.DurationMs; + } + + + if (obj.ErrorKind is not null) + { + result["errorKind"] = obj.ErrorKind; + } + + + if (context is not null) + { + result = context.ProcessDict(result); + } + + return result; + } + + + /// + /// Convert the ToolCallCompletePayload instance to a YAML string. + /// + /// Optional context with pre/post processing callbacks. + /// The YAML string representation of this instance. + public string ToYaml(SaveContext? context = null) + { + context ??= new SaveContext(); + return context.ToYaml(Save(context)); + } + + /// + /// Convert the ToolCallCompletePayload instance to a JSON string. + /// + /// Optional context with pre/post processing callbacks. + /// Whether to indent the output. Defaults to true. + /// The JSON string representation of this instance. + public string ToJson(SaveContext? context = null, bool indent = true) + { + context ??= new SaveContext(); + return context.ToJson(Save(context), indent); + } + + /// + /// Load a ToolCallCompletePayload instance from a JSON string. + /// + /// The JSON string to parse. + /// Optional context with pre/post processing callbacks. + /// The loaded ToolCallCompletePayload instance. + public static ToolCallCompletePayload FromJson(string json, LoadContext? context = null) + { + using var doc = JsonDocument.Parse(json); + Dictionary dict; + dict = JsonSerializer.Deserialize>(json, JsonUtils.Options) + ?? throw new ArgumentException("Failed to parse JSON as dictionary"); + + return Load(dict, context); + } + + /// + /// Load a ToolCallCompletePayload instance from a YAML string. + /// + /// The YAML string to parse. + /// Optional context with pre/post processing callbacks. + /// The loaded ToolCallCompletePayload instance. + public static ToolCallCompletePayload FromYaml(string yaml, LoadContext? context = null) + { + var dict = YamlUtils.Deserializer.Deserialize>(yaml) + ?? throw new ArgumentException("Failed to parse YAML as dictionary"); + + return Load(dict, context); + } + + #endregion +} diff --git a/runtime/csharp/Prompty.Core/Model/events/ToolCallStartPayload.cs b/runtime/csharp/Prompty.Core/Model/events/ToolCallStartPayload.cs index 7527106d..6ec361ce 100644 --- a/runtime/csharp/Prompty.Core/Model/events/ToolCallStartPayload.cs +++ b/runtime/csharp/Prompty.Core/Model/events/ToolCallStartPayload.cs @@ -1,3 +1,4 @@ +// // Copyright (c) Microsoft. All rights reserved. using System.Text.Json; using YamlDotNet.Serialization; @@ -6,9 +7,9 @@ namespace Prompty.Core; #pragma warning restore IDE0130 -/// -/// Payload for "tool_call_start" events — the LLM has requested a tool call. -/// + /// + /// Payload for "tool_call_start" events — the LLM has requested a tool call. + /// public partial class ToolCallStartPayload { /// @@ -25,6 +26,11 @@ public ToolCallStartPayload() } #pragma warning restore CS8618 + /// + /// The unique identifier of the tool call + /// + public string? Id { get; set; } + /// /// The name of the tool being called /// @@ -57,6 +63,11 @@ public static ToolCallStartPayload Load(Dictionary data, LoadCo var instance = new ToolCallStartPayload(); + if (data.TryGetValue("id", out var idValue) && idValue is not null) + { + instance.Id = idValue?.ToString()!; + } + if (data.TryGetValue("name", out var nameValue) && nameValue is not null) { instance.Name = nameValue?.ToString()!; @@ -96,6 +107,12 @@ public static ToolCallStartPayload Load(Dictionary data, LoadCo var result = new Dictionary(); + if (obj.Id is not null) + { + result["id"] = obj.Id; + } + + result["name"] = obj.Name; diff --git a/runtime/csharp/Prompty.Core/Model/events/ToolChunk.cs b/runtime/csharp/Prompty.Core/Model/events/ToolChunk.cs index ec25e197..34e3f203 100644 --- a/runtime/csharp/Prompty.Core/Model/events/ToolChunk.cs +++ b/runtime/csharp/Prompty.Core/Model/events/ToolChunk.cs @@ -1,3 +1,4 @@ +// // Copyright (c) Microsoft. All rights reserved. using System.Text.Json; using YamlDotNet.Serialization; @@ -6,9 +7,9 @@ namespace Prompty.Core; #pragma warning restore IDE0130 -/// -/// A tool call chunk from the LLM response stream. -/// + /// + /// A tool call chunk from the LLM response stream. + /// public partial class ToolChunk : StreamChunk { /// diff --git a/runtime/csharp/Prompty.Core/Model/events/ToolExecutionCompletePayload.cs b/runtime/csharp/Prompty.Core/Model/events/ToolExecutionCompletePayload.cs new file mode 100644 index 00000000..74a7a9fd --- /dev/null +++ b/runtime/csharp/Prompty.Core/Model/events/ToolExecutionCompletePayload.cs @@ -0,0 +1,297 @@ +// +// Copyright (c) Microsoft. All rights reserved. +using System.Text.Json; +using YamlDotNet.Serialization; + +#pragma warning disable IDE0130 +namespace Prompty.Core; +#pragma warning restore IDE0130 + + /// + /// Payload for "tool_execution_complete" events — a concrete host tool execution finished. + /// +public partial class ToolExecutionCompletePayload +{ + /// + /// The shorthand property name for this type, if any. + /// + public static string? ShorthandProperty => null; + + /// + /// Initializes a new instance of . + /// +#pragma warning disable CS8618 + public ToolExecutionCompletePayload() + { + } +#pragma warning restore CS8618 + + /// + /// Stable host execution request identifier + /// + public string? RequestId { get; set; } + + /// + /// Associated model tool call identifier, when available + /// + public string? ToolCallId { get; set; } + + /// + /// Name of the host tool that executed + /// + public string ToolName { get; set; } = string.Empty; + + /// + /// Whether the host execution completed successfully + /// + public bool Success { get; set; } = false; + + /// + /// Host-normalized execution result + /// + public object? Result { get; set; } + + /// + /// Process or host exit code, when applicable + /// + public int? ExitCode { get; set; } + + /// + /// Tool execution duration in milliseconds + /// + public double? DurationMs { get; set; } + + /// + /// Machine-readable error category when success is false + /// + public string? ErrorKind { get; set; } + + /// + /// Host-specific telemetry for the execution + /// + public IDictionary? Telemetry { get; set; } + + /// + /// Redaction state for sensitive result fields + /// + public RedactionMetadata? Redaction { get; set; } + + + + #region Load Methods + + /// + /// Load a ToolExecutionCompletePayload instance from a dictionary. + /// + /// The dictionary containing the data. + /// Optional context with pre/post processing callbacks. + /// The loaded ToolExecutionCompletePayload instance. + public static ToolExecutionCompletePayload Load(Dictionary data, LoadContext? context = null) + { + if (context is not null) + { + data = context.ProcessInput(data); + } + + + // Create new instance + var instance = new ToolExecutionCompletePayload(); + + + if (data.TryGetValue("requestId", out var requestIdValue) && requestIdValue is not null) + { + instance.RequestId = requestIdValue?.ToString()!; + } + + if (data.TryGetValue("toolCallId", out var toolCallIdValue) && toolCallIdValue is not null) + { + instance.ToolCallId = toolCallIdValue?.ToString()!; + } + + if (data.TryGetValue("toolName", out var toolNameValue) && toolNameValue is not null) + { + instance.ToolName = toolNameValue?.ToString()!; + } + + if (data.TryGetValue("success", out var successValue) && successValue is not null) + { + instance.Success = Convert.ToBoolean(successValue); + } + + if (data.TryGetValue("result", out var resultValue) && resultValue is not null) + { + instance.Result = resultValue; + } + + if (data.TryGetValue("exitCode", out var exitCodeValue) && exitCodeValue is not null) + { + instance.ExitCode = Convert.ToInt32(exitCodeValue); + } + + if (data.TryGetValue("durationMs", out var durationMsValue) && durationMsValue is not null) + { + instance.DurationMs = Convert.ToDouble(durationMsValue); + } + + if (data.TryGetValue("errorKind", out var errorKindValue) && errorKindValue is not null) + { + instance.ErrorKind = errorKindValue?.ToString()!; + } + + if (data.TryGetValue("telemetry", out var telemetryValue) && telemetryValue is not null) + { + instance.Telemetry = telemetryValue.GetDictionary()!; + } + + if (data.TryGetValue("redaction", out var redactionValue) && redactionValue is not null) + { + instance.Redaction = RedactionMetadata.Load(redactionValue.GetDictionary(RedactionMetadata.ShorthandProperty), context); + } + + if (context is not null) + { + instance = context.ProcessOutput(instance); + } + return instance; + } + + + #endregion + + #region Save Methods + + /// + /// Save the ToolExecutionCompletePayload instance to a dictionary. + /// + /// Optional context with pre/post processing callbacks. + /// The dictionary representation of this instance. + public Dictionary Save(SaveContext? context = null) + { + var obj = this; + if (context is not null) + { + obj = context.ProcessObject(obj); + } + + + var result = new Dictionary(); + + + if (obj.RequestId is not null) + { + result["requestId"] = obj.RequestId; + } + + + if (obj.ToolCallId is not null) + { + result["toolCallId"] = obj.ToolCallId; + } + + + result["toolName"] = obj.ToolName; + + + result["success"] = obj.Success; + + + if (obj.Result is not null) + { + result["result"] = obj.Result; + } + + + if (obj.ExitCode is not null) + { + result["exitCode"] = obj.ExitCode; + } + + + if (obj.DurationMs is not null) + { + result["durationMs"] = obj.DurationMs; + } + + + if (obj.ErrorKind is not null) + { + result["errorKind"] = obj.ErrorKind; + } + + + if (obj.Telemetry is not null) + { + result["telemetry"] = obj.Telemetry; + } + + + if (obj.Redaction is not null) + { + result["redaction"] = obj.Redaction?.Save(context); + } + + + if (context is not null) + { + result = context.ProcessDict(result); + } + + return result; + } + + + /// + /// Convert the ToolExecutionCompletePayload instance to a YAML string. + /// + /// Optional context with pre/post processing callbacks. + /// The YAML string representation of this instance. + public string ToYaml(SaveContext? context = null) + { + context ??= new SaveContext(); + return context.ToYaml(Save(context)); + } + + /// + /// Convert the ToolExecutionCompletePayload instance to a JSON string. + /// + /// Optional context with pre/post processing callbacks. + /// Whether to indent the output. Defaults to true. + /// The JSON string representation of this instance. + public string ToJson(SaveContext? context = null, bool indent = true) + { + context ??= new SaveContext(); + return context.ToJson(Save(context), indent); + } + + /// + /// Load a ToolExecutionCompletePayload instance from a JSON string. + /// + /// The JSON string to parse. + /// Optional context with pre/post processing callbacks. + /// The loaded ToolExecutionCompletePayload instance. + public static ToolExecutionCompletePayload FromJson(string json, LoadContext? context = null) + { + using var doc = JsonDocument.Parse(json); + Dictionary dict; + dict = JsonSerializer.Deserialize>(json, JsonUtils.Options) + ?? throw new ArgumentException("Failed to parse JSON as dictionary"); + + return Load(dict, context); + } + + /// + /// Load a ToolExecutionCompletePayload instance from a YAML string. + /// + /// The YAML string to parse. + /// Optional context with pre/post processing callbacks. + /// The loaded ToolExecutionCompletePayload instance. + public static ToolExecutionCompletePayload FromYaml(string yaml, LoadContext? context = null) + { + var dict = YamlUtils.Deserializer.Deserialize>(yaml) + ?? throw new ArgumentException("Failed to parse YAML as dictionary"); + + return Load(dict, context); + } + + #endregion +} diff --git a/runtime/csharp/Prompty.Core/Model/events/ToolExecutionStartPayload.cs b/runtime/csharp/Prompty.Core/Model/events/ToolExecutionStartPayload.cs new file mode 100644 index 00000000..a7aade30 --- /dev/null +++ b/runtime/csharp/Prompty.Core/Model/events/ToolExecutionStartPayload.cs @@ -0,0 +1,240 @@ +// +// Copyright (c) Microsoft. All rights reserved. +using System.Text.Json; +using YamlDotNet.Serialization; + +#pragma warning disable IDE0130 +namespace Prompty.Core; +#pragma warning restore IDE0130 + + /// + /// Payload for "tool_execution_start" events — the host is about to execute a concrete tool request. + /// + /// This is distinct from "tool_call_start", which records the model requesting a tool. + /// + /// Tool execution events capture the harness-side action after policy and permission checks. + /// +public partial class ToolExecutionStartPayload +{ + /// + /// The shorthand property name for this type, if any. + /// + public static string? ShorthandProperty => null; + + /// + /// Initializes a new instance of . + /// +#pragma warning disable CS8618 + public ToolExecutionStartPayload() + { + } +#pragma warning restore CS8618 + + /// + /// Stable host execution request identifier + /// + public string? RequestId { get; set; } + + /// + /// Associated model tool call identifier, when available + /// + public string? ToolCallId { get; set; } + + /// + /// Name of the host tool being executed + /// + public string ToolName { get; set; } = string.Empty; + + /// + /// Tool arguments after host-side sanitization + /// + public IDictionary? Arguments { get; set; } + + /// + /// Working directory or execution scope for the tool + /// + public string? WorkingDirectory { get; set; } + + /// + /// Redaction state for sensitive argument fields + /// + public RedactionMetadata? Redaction { get; set; } + + + + #region Load Methods + + /// + /// Load a ToolExecutionStartPayload instance from a dictionary. + /// + /// The dictionary containing the data. + /// Optional context with pre/post processing callbacks. + /// The loaded ToolExecutionStartPayload instance. + public static ToolExecutionStartPayload Load(Dictionary data, LoadContext? context = null) + { + if (context is not null) + { + data = context.ProcessInput(data); + } + + + // Create new instance + var instance = new ToolExecutionStartPayload(); + + + if (data.TryGetValue("requestId", out var requestIdValue) && requestIdValue is not null) + { + instance.RequestId = requestIdValue?.ToString()!; + } + + if (data.TryGetValue("toolCallId", out var toolCallIdValue) && toolCallIdValue is not null) + { + instance.ToolCallId = toolCallIdValue?.ToString()!; + } + + if (data.TryGetValue("toolName", out var toolNameValue) && toolNameValue is not null) + { + instance.ToolName = toolNameValue?.ToString()!; + } + + if (data.TryGetValue("arguments", out var argumentsValue) && argumentsValue is not null) + { + instance.Arguments = argumentsValue.GetDictionary()!; + } + + if (data.TryGetValue("workingDirectory", out var workingDirectoryValue) && workingDirectoryValue is not null) + { + instance.WorkingDirectory = workingDirectoryValue?.ToString()!; + } + + if (data.TryGetValue("redaction", out var redactionValue) && redactionValue is not null) + { + instance.Redaction = RedactionMetadata.Load(redactionValue.GetDictionary(RedactionMetadata.ShorthandProperty), context); + } + + if (context is not null) + { + instance = context.ProcessOutput(instance); + } + return instance; + } + + + #endregion + + #region Save Methods + + /// + /// Save the ToolExecutionStartPayload instance to a dictionary. + /// + /// Optional context with pre/post processing callbacks. + /// The dictionary representation of this instance. + public Dictionary Save(SaveContext? context = null) + { + var obj = this; + if (context is not null) + { + obj = context.ProcessObject(obj); + } + + + var result = new Dictionary(); + + + if (obj.RequestId is not null) + { + result["requestId"] = obj.RequestId; + } + + + if (obj.ToolCallId is not null) + { + result["toolCallId"] = obj.ToolCallId; + } + + + result["toolName"] = obj.ToolName; + + + if (obj.Arguments is not null) + { + result["arguments"] = obj.Arguments; + } + + + if (obj.WorkingDirectory is not null) + { + result["workingDirectory"] = obj.WorkingDirectory; + } + + + if (obj.Redaction is not null) + { + result["redaction"] = obj.Redaction?.Save(context); + } + + + if (context is not null) + { + result = context.ProcessDict(result); + } + + return result; + } + + + /// + /// Convert the ToolExecutionStartPayload instance to a YAML string. + /// + /// Optional context with pre/post processing callbacks. + /// The YAML string representation of this instance. + public string ToYaml(SaveContext? context = null) + { + context ??= new SaveContext(); + return context.ToYaml(Save(context)); + } + + /// + /// Convert the ToolExecutionStartPayload instance to a JSON string. + /// + /// Optional context with pre/post processing callbacks. + /// Whether to indent the output. Defaults to true. + /// The JSON string representation of this instance. + public string ToJson(SaveContext? context = null, bool indent = true) + { + context ??= new SaveContext(); + return context.ToJson(Save(context), indent); + } + + /// + /// Load a ToolExecutionStartPayload instance from a JSON string. + /// + /// The JSON string to parse. + /// Optional context with pre/post processing callbacks. + /// The loaded ToolExecutionStartPayload instance. + public static ToolExecutionStartPayload FromJson(string json, LoadContext? context = null) + { + using var doc = JsonDocument.Parse(json); + Dictionary dict; + dict = JsonSerializer.Deserialize>(json, JsonUtils.Options) + ?? throw new ArgumentException("Failed to parse JSON as dictionary"); + + return Load(dict, context); + } + + /// + /// Load a ToolExecutionStartPayload instance from a YAML string. + /// + /// The YAML string to parse. + /// Optional context with pre/post processing callbacks. + /// The loaded ToolExecutionStartPayload instance. + public static ToolExecutionStartPayload FromYaml(string yaml, LoadContext? context = null) + { + var dict = YamlUtils.Deserializer.Deserialize>(yaml) + ?? throw new ArgumentException("Failed to parse YAML as dictionary"); + + return Load(dict, context); + } + + #endregion +} diff --git a/runtime/csharp/Prompty.Core/Model/events/ToolResultPayload.cs b/runtime/csharp/Prompty.Core/Model/events/ToolResultPayload.cs index 07e378be..b9c2bce2 100644 --- a/runtime/csharp/Prompty.Core/Model/events/ToolResultPayload.cs +++ b/runtime/csharp/Prompty.Core/Model/events/ToolResultPayload.cs @@ -1,3 +1,4 @@ +// // Copyright (c) Microsoft. All rights reserved. using System.Text.Json; using YamlDotNet.Serialization; @@ -6,9 +7,9 @@ namespace Prompty.Core; #pragma warning restore IDE0130 -/// -/// Payload for "tool_result" events — a tool has returned its result. -/// + /// + /// Payload for "tool_result" events — a tool has returned its result. + /// public partial class ToolResultPayload { /// diff --git a/runtime/csharp/Prompty.Core/Model/events/TrajectoryEvent.cs b/runtime/csharp/Prompty.Core/Model/events/TrajectoryEvent.cs new file mode 100644 index 00000000..b860c3fd --- /dev/null +++ b/runtime/csharp/Prompty.Core/Model/events/TrajectoryEvent.cs @@ -0,0 +1,284 @@ +// +// Copyright (c) Microsoft. All rights reserved. +using System.Text.Json; +using YamlDotNet.Serialization; + +#pragma warning disable IDE0130 +namespace Prompty.Core; +#pragma warning restore IDE0130 + + /// + /// A compact, replay-oriented record of one harness-side action or observation. + /// +public partial class TrajectoryEvent +{ + /// + /// The shorthand property name for this type, if any. + /// + public static string? ShorthandProperty => null; + + /// + /// Initializes a new instance of . + /// +#pragma warning disable CS8618 + public TrajectoryEvent() + { + } +#pragma warning restore CS8618 + + /// + /// Stable trajectory event identifier + /// + public string? Id { get; set; } + + /// + /// Stable session identifier + /// + public string? SessionId { get; set; } + + /// + /// Associated turn identifier, when available + /// + public string? TurnId { get; set; } + + /// + /// Associated tool call identifier, when available + /// + public string? ToolCallId { get; set; } + + /// + /// Zero-based turn index in the session + /// + public int? TurnIndex { get; set; } + + /// + /// Host-defined trajectory event category + /// + public string EventType { get; set; } = string.Empty; + + /// + /// Sanitized event data + /// + public IDictionary? Data { get; set; } + + /// + /// ISO 8601 UTC timestamp when the trajectory event was recorded + /// + public string? CreatedAt { get; set; } + + /// + /// Redaction state for sensitive trajectory fields + /// + public RedactionMetadata? Redaction { get; set; } + + + + #region Load Methods + + /// + /// Load a TrajectoryEvent instance from a dictionary. + /// + /// The dictionary containing the data. + /// Optional context with pre/post processing callbacks. + /// The loaded TrajectoryEvent instance. + public static TrajectoryEvent Load(Dictionary data, LoadContext? context = null) + { + if (context is not null) + { + data = context.ProcessInput(data); + } + + + // Create new instance + var instance = new TrajectoryEvent(); + + + if (data.TryGetValue("id", out var idValue) && idValue is not null) + { + instance.Id = idValue?.ToString()!; + } + + if (data.TryGetValue("sessionId", out var sessionIdValue) && sessionIdValue is not null) + { + instance.SessionId = sessionIdValue?.ToString()!; + } + + if (data.TryGetValue("turnId", out var turnIdValue) && turnIdValue is not null) + { + instance.TurnId = turnIdValue?.ToString()!; + } + + if (data.TryGetValue("toolCallId", out var toolCallIdValue) && toolCallIdValue is not null) + { + instance.ToolCallId = toolCallIdValue?.ToString()!; + } + + if (data.TryGetValue("turnIndex", out var turnIndexValue) && turnIndexValue is not null) + { + instance.TurnIndex = Convert.ToInt32(turnIndexValue); + } + + if (data.TryGetValue("eventType", out var eventTypeValue) && eventTypeValue is not null) + { + instance.EventType = eventTypeValue?.ToString()!; + } + + if (data.TryGetValue("data", out var dataValue) && dataValue is not null) + { + instance.Data = dataValue.GetDictionary()!; + } + + if (data.TryGetValue("createdAt", out var createdAtValue) && createdAtValue is not null) + { + instance.CreatedAt = createdAtValue?.ToString()!; + } + + if (data.TryGetValue("redaction", out var redactionValue) && redactionValue is not null) + { + instance.Redaction = RedactionMetadata.Load(redactionValue.GetDictionary(RedactionMetadata.ShorthandProperty), context); + } + + if (context is not null) + { + instance = context.ProcessOutput(instance); + } + return instance; + } + + + #endregion + + #region Save Methods + + /// + /// Save the TrajectoryEvent instance to a dictionary. + /// + /// Optional context with pre/post processing callbacks. + /// The dictionary representation of this instance. + public Dictionary Save(SaveContext? context = null) + { + var obj = this; + if (context is not null) + { + obj = context.ProcessObject(obj); + } + + + var result = new Dictionary(); + + + if (obj.Id is not null) + { + result["id"] = obj.Id; + } + + + if (obj.SessionId is not null) + { + result["sessionId"] = obj.SessionId; + } + + + if (obj.TurnId is not null) + { + result["turnId"] = obj.TurnId; + } + + + if (obj.ToolCallId is not null) + { + result["toolCallId"] = obj.ToolCallId; + } + + + if (obj.TurnIndex is not null) + { + result["turnIndex"] = obj.TurnIndex; + } + + + result["eventType"] = obj.EventType; + + + if (obj.Data is not null) + { + result["data"] = obj.Data; + } + + + if (obj.CreatedAt is not null) + { + result["createdAt"] = obj.CreatedAt; + } + + + if (obj.Redaction is not null) + { + result["redaction"] = obj.Redaction?.Save(context); + } + + + if (context is not null) + { + result = context.ProcessDict(result); + } + + return result; + } + + + /// + /// Convert the TrajectoryEvent instance to a YAML string. + /// + /// Optional context with pre/post processing callbacks. + /// The YAML string representation of this instance. + public string ToYaml(SaveContext? context = null) + { + context ??= new SaveContext(); + return context.ToYaml(Save(context)); + } + + /// + /// Convert the TrajectoryEvent instance to a JSON string. + /// + /// Optional context with pre/post processing callbacks. + /// Whether to indent the output. Defaults to true. + /// The JSON string representation of this instance. + public string ToJson(SaveContext? context = null, bool indent = true) + { + context ??= new SaveContext(); + return context.ToJson(Save(context), indent); + } + + /// + /// Load a TrajectoryEvent instance from a JSON string. + /// + /// The JSON string to parse. + /// Optional context with pre/post processing callbacks. + /// The loaded TrajectoryEvent instance. + public static TrajectoryEvent FromJson(string json, LoadContext? context = null) + { + using var doc = JsonDocument.Parse(json); + Dictionary dict; + dict = JsonSerializer.Deserialize>(json, JsonUtils.Options) + ?? throw new ArgumentException("Failed to parse JSON as dictionary"); + + return Load(dict, context); + } + + /// + /// Load a TrajectoryEvent instance from a YAML string. + /// + /// The YAML string to parse. + /// Optional context with pre/post processing callbacks. + /// The loaded TrajectoryEvent instance. + public static TrajectoryEvent FromYaml(string yaml, LoadContext? context = null) + { + var dict = YamlUtils.Deserializer.Deserialize>(yaml) + ?? throw new ArgumentException("Failed to parse YAML as dictionary"); + + return Load(dict, context); + } + + #endregion +} diff --git a/runtime/csharp/Prompty.Core/Model/events/TurnEndPayload.cs b/runtime/csharp/Prompty.Core/Model/events/TurnEndPayload.cs new file mode 100644 index 00000000..5bbcc983 --- /dev/null +++ b/runtime/csharp/Prompty.Core/Model/events/TurnEndPayload.cs @@ -0,0 +1,207 @@ +// +// Copyright (c) Microsoft. All rights reserved. +using System.Text.Json; +using YamlDotNet.Serialization; + +#pragma warning disable IDE0130 +namespace Prompty.Core; +#pragma warning restore IDE0130 + + /// + /// Payload for "turn_end" events — a turn has completed. + /// +public partial class TurnEndPayload +{ + /// + /// The shorthand property name for this type, if any. + /// + public static string? ShorthandProperty => null; + + /// + /// Initializes a new instance of . + /// +#pragma warning disable CS8618 + public TurnEndPayload() + { + } +#pragma warning restore CS8618 + + /// + /// Number of tool-call iterations performed + /// + public int? Iterations { get; set; } + + /// + /// Final semantic status of the turn + /// + public TurnStatus? Status { get; set; } + + /// + /// Final response after processing, if available + /// + public object? Response { get; set; } + + /// + /// Total elapsed turn duration in milliseconds + /// + public double? DurationMs { get; set; } + + + + #region Load Methods + + /// + /// Load a TurnEndPayload instance from a dictionary. + /// + /// The dictionary containing the data. + /// Optional context with pre/post processing callbacks. + /// The loaded TurnEndPayload instance. + public static TurnEndPayload Load(Dictionary data, LoadContext? context = null) + { + if (context is not null) + { + data = context.ProcessInput(data); + } + + + // Create new instance + var instance = new TurnEndPayload(); + + + if (data.TryGetValue("iterations", out var iterationsValue) && iterationsValue is not null) + { + instance.Iterations = Convert.ToInt32(iterationsValue); + } + + if (data.TryGetValue("status", out var statusValue) && statusValue is not null) + { + instance.Status = Enum.Parse(statusValue?.ToString()!, true); + } + + if (data.TryGetValue("response", out var responseValue) && responseValue is not null) + { + instance.Response = responseValue; + } + + if (data.TryGetValue("durationMs", out var durationMsValue) && durationMsValue is not null) + { + instance.DurationMs = Convert.ToDouble(durationMsValue); + } + + if (context is not null) + { + instance = context.ProcessOutput(instance); + } + return instance; + } + + + #endregion + + #region Save Methods + + /// + /// Save the TurnEndPayload instance to a dictionary. + /// + /// Optional context with pre/post processing callbacks. + /// The dictionary representation of this instance. + public Dictionary Save(SaveContext? context = null) + { + var obj = this; + if (context is not null) + { + obj = context.ProcessObject(obj); + } + + + var result = new Dictionary(); + + + if (obj.Iterations is not null) + { + result["iterations"] = obj.Iterations; + } + + + if (obj.Status is not null) + { + result["status"] = obj.Status.Value.ToString().ToLowerInvariant(); + } + + + if (obj.Response is not null) + { + result["response"] = obj.Response; + } + + + if (obj.DurationMs is not null) + { + result["durationMs"] = obj.DurationMs; + } + + + if (context is not null) + { + result = context.ProcessDict(result); + } + + return result; + } + + + /// + /// Convert the TurnEndPayload instance to a YAML string. + /// + /// Optional context with pre/post processing callbacks. + /// The YAML string representation of this instance. + public string ToYaml(SaveContext? context = null) + { + context ??= new SaveContext(); + return context.ToYaml(Save(context)); + } + + /// + /// Convert the TurnEndPayload instance to a JSON string. + /// + /// Optional context with pre/post processing callbacks. + /// Whether to indent the output. Defaults to true. + /// The JSON string representation of this instance. + public string ToJson(SaveContext? context = null, bool indent = true) + { + context ??= new SaveContext(); + return context.ToJson(Save(context), indent); + } + + /// + /// Load a TurnEndPayload instance from a JSON string. + /// + /// The JSON string to parse. + /// Optional context with pre/post processing callbacks. + /// The loaded TurnEndPayload instance. + public static TurnEndPayload FromJson(string json, LoadContext? context = null) + { + using var doc = JsonDocument.Parse(json); + Dictionary dict; + dict = JsonSerializer.Deserialize>(json, JsonUtils.Options) + ?? throw new ArgumentException("Failed to parse JSON as dictionary"); + + return Load(dict, context); + } + + /// + /// Load a TurnEndPayload instance from a YAML string. + /// + /// The YAML string to parse. + /// Optional context with pre/post processing callbacks. + /// The loaded TurnEndPayload instance. + public static TurnEndPayload FromYaml(string yaml, LoadContext? context = null) + { + var dict = YamlUtils.Deserializer.Deserialize>(yaml) + ?? throw new ArgumentException("Failed to parse YAML as dictionary"); + + return Load(dict, context); + } + + #endregion +} diff --git a/runtime/csharp/Prompty.Core/Model/events/TurnEvent.cs b/runtime/csharp/Prompty.Core/Model/events/TurnEvent.cs new file mode 100644 index 00000000..0d630858 --- /dev/null +++ b/runtime/csharp/Prompty.Core/Model/events/TurnEvent.cs @@ -0,0 +1,263 @@ +// +// Copyright (c) Microsoft. All rights reserved. +using System.Text.Json; +using YamlDotNet.Serialization; + +#pragma warning disable IDE0130 +namespace Prompty.Core; +#pragma warning restore IDE0130 + + /// + /// A canonical event envelope emitted by the turn harness. The payload is kept + /// + /// JSON-shaped so runtimes can load all events even when newer payload types are + /// + /// added; event-specific typed payload models below define the canonical shapes. + /// +public partial class TurnEvent +{ + /// + /// The shorthand property name for this type, if any. + /// + public static string? ShorthandProperty => null; + + /// + /// Initializes a new instance of . + /// +#pragma warning disable CS8618 + public TurnEvent() + { + } +#pragma warning restore CS8618 + + /// + /// Unique identifier for this event + /// + public string Id { get; set; } = string.Empty; + + /// + /// Event type discriminator + /// + public TurnEventType Type { get; set; } = TurnEventType.TurnStart; + + /// + /// ISO 8601 UTC timestamp when the event was emitted + /// + public string Timestamp { get; set; } = string.Empty; + + /// + /// Stable identifier for the outer turn + /// + public string? TurnId { get; set; } + + /// + /// Zero-based agent-loop iteration associated with the event + /// + public int? Iteration { get; set; } + + /// + /// Parent event or span identifier for reconstructing event hierarchy + /// + public string? ParentId { get; set; } + + /// + /// Trace span identifier associated with this event + /// + public string? SpanId { get; set; } + + /// + /// Event-specific payload. Use the typed payload model matching 'type'. + /// + public IDictionary Payload { get; set; } = new Dictionary(); + + + + #region Load Methods + + /// + /// Load a TurnEvent instance from a dictionary. + /// + /// The dictionary containing the data. + /// Optional context with pre/post processing callbacks. + /// The loaded TurnEvent instance. + public static TurnEvent Load(Dictionary data, LoadContext? context = null) + { + if (context is not null) + { + data = context.ProcessInput(data); + } + + + // Create new instance + var instance = new TurnEvent(); + + + if (data.TryGetValue("id", out var idValue) && idValue is not null) + { + instance.Id = idValue?.ToString()!; + } + + if (data.TryGetValue("type", out var typeValue) && typeValue is not null) + { + instance.Type = Enum.Parse(typeValue?.ToString()!, true); + } + + if (data.TryGetValue("timestamp", out var timestampValue) && timestampValue is not null) + { + instance.Timestamp = timestampValue?.ToString()!; + } + + if (data.TryGetValue("turnId", out var turnIdValue) && turnIdValue is not null) + { + instance.TurnId = turnIdValue?.ToString()!; + } + + if (data.TryGetValue("iteration", out var iterationValue) && iterationValue is not null) + { + instance.Iteration = Convert.ToInt32(iterationValue); + } + + if (data.TryGetValue("parentId", out var parentIdValue) && parentIdValue is not null) + { + instance.ParentId = parentIdValue?.ToString()!; + } + + if (data.TryGetValue("spanId", out var spanIdValue) && spanIdValue is not null) + { + instance.SpanId = spanIdValue?.ToString()!; + } + + if (data.TryGetValue("payload", out var payloadValue) && payloadValue is not null) + { + instance.Payload = payloadValue.GetDictionary()!; + } + + if (context is not null) + { + instance = context.ProcessOutput(instance); + } + return instance; + } + + + #endregion + + #region Save Methods + + /// + /// Save the TurnEvent instance to a dictionary. + /// + /// Optional context with pre/post processing callbacks. + /// The dictionary representation of this instance. + public Dictionary Save(SaveContext? context = null) + { + var obj = this; + if (context is not null) + { + obj = context.ProcessObject(obj); + } + + + var result = new Dictionary(); + + + result["id"] = obj.Id; + + + result["type"] = obj.Type.ToString().ToLowerInvariant(); + + + result["timestamp"] = obj.Timestamp; + + + if (obj.TurnId is not null) + { + result["turnId"] = obj.TurnId; + } + + + if (obj.Iteration is not null) + { + result["iteration"] = obj.Iteration; + } + + + if (obj.ParentId is not null) + { + result["parentId"] = obj.ParentId; + } + + + if (obj.SpanId is not null) + { + result["spanId"] = obj.SpanId; + } + + + result["payload"] = obj.Payload; + + + if (context is not null) + { + result = context.ProcessDict(result); + } + + return result; + } + + + /// + /// Convert the TurnEvent instance to a YAML string. + /// + /// Optional context with pre/post processing callbacks. + /// The YAML string representation of this instance. + public string ToYaml(SaveContext? context = null) + { + context ??= new SaveContext(); + return context.ToYaml(Save(context)); + } + + /// + /// Convert the TurnEvent instance to a JSON string. + /// + /// Optional context with pre/post processing callbacks. + /// Whether to indent the output. Defaults to true. + /// The JSON string representation of this instance. + public string ToJson(SaveContext? context = null, bool indent = true) + { + context ??= new SaveContext(); + return context.ToJson(Save(context), indent); + } + + /// + /// Load a TurnEvent instance from a JSON string. + /// + /// The JSON string to parse. + /// Optional context with pre/post processing callbacks. + /// The loaded TurnEvent instance. + public static TurnEvent FromJson(string json, LoadContext? context = null) + { + using var doc = JsonDocument.Parse(json); + Dictionary dict; + dict = JsonSerializer.Deserialize>(json, JsonUtils.Options) + ?? throw new ArgumentException("Failed to parse JSON as dictionary"); + + return Load(dict, context); + } + + /// + /// Load a TurnEvent instance from a YAML string. + /// + /// The YAML string to parse. + /// Optional context with pre/post processing callbacks. + /// The loaded TurnEvent instance. + public static TurnEvent FromYaml(string yaml, LoadContext? context = null) + { + var dict = YamlUtils.Deserializer.Deserialize>(yaml) + ?? throw new ArgumentException("Failed to parse YAML as dictionary"); + + return Load(dict, context); + } + + #endregion +} diff --git a/runtime/csharp/Prompty.Core/Model/events/TurnEventType.cs b/runtime/csharp/Prompty.Core/Model/events/TurnEventType.cs new file mode 100644 index 00000000..50cd0fce --- /dev/null +++ b/runtime/csharp/Prompty.Core/Model/events/TurnEventType.cs @@ -0,0 +1,84 @@ +// +// +// Code generated by Typra emitter; DO NOT EDIT. + +using System.Text.Json.Serialization; + +namespace Prompty.Core; + +[JsonConverter(typeof(JsonStringEnumConverter))] +public enum TurnEventType +{ + [JsonPropertyName("turn_start")] + TurnStart, + + [JsonPropertyName("turn_end")] + TurnEnd, + + [JsonPropertyName("llm_start")] + LlmStart, + + [JsonPropertyName("llm_complete")] + LlmComplete, + + [JsonPropertyName("retry")] + Retry, + + [JsonPropertyName("permission_requested")] + PermissionRequested, + + [JsonPropertyName("permission_completed")] + PermissionCompleted, + + [JsonPropertyName("token")] + Token, + + [JsonPropertyName("thinking")] + Thinking, + + [JsonPropertyName("tool_call_start")] + ToolCallStart, + + [JsonPropertyName("tool_call_complete")] + ToolCallComplete, + + [JsonPropertyName("tool_execution_start")] + ToolExecutionStart, + + [JsonPropertyName("tool_execution_complete")] + ToolExecutionComplete, + + [JsonPropertyName("tool_result")] + ToolResult, + + [JsonPropertyName("hook_start")] + HookStart, + + [JsonPropertyName("hook_end")] + HookEnd, + + [JsonPropertyName("status")] + Status, + + [JsonPropertyName("messages_updated")] + MessagesUpdated, + + [JsonPropertyName("done")] + Done, + + [JsonPropertyName("error")] + Error, + + [JsonPropertyName("cancelled")] + Cancelled, + + [JsonPropertyName("compaction_start")] + CompactionStart, + + [JsonPropertyName("compaction_complete")] + CompactionComplete, + + [JsonPropertyName("compaction_failed")] + CompactionFailed, + +} diff --git a/runtime/csharp/Prompty.Core/Model/events/TurnStartPayload.cs b/runtime/csharp/Prompty.Core/Model/events/TurnStartPayload.cs new file mode 100644 index 00000000..53390b3b --- /dev/null +++ b/runtime/csharp/Prompty.Core/Model/events/TurnStartPayload.cs @@ -0,0 +1,191 @@ +// +// Copyright (c) Microsoft. All rights reserved. +using System.Text.Json; +using YamlDotNet.Serialization; + +#pragma warning disable IDE0130 +namespace Prompty.Core; +#pragma warning restore IDE0130 + + /// + /// Payload for "turn_start" events — a turn is beginning. + /// +public partial class TurnStartPayload +{ + /// + /// The shorthand property name for this type, if any. + /// + public static string? ShorthandProperty => null; + + /// + /// Initializes a new instance of . + /// +#pragma warning disable CS8618 + public TurnStartPayload() + { + } +#pragma warning restore CS8618 + + /// + /// Name of the loaded prompt/agent, when available + /// + public string? Agent { get; set; } + + /// + /// Input values supplied to the turn after host-side sanitization + /// + public IDictionary? Inputs { get; set; } + + /// + /// Configured maximum tool-call iterations + /// + public int? MaxIterations { get; set; } + + + + #region Load Methods + + /// + /// Load a TurnStartPayload instance from a dictionary. + /// + /// The dictionary containing the data. + /// Optional context with pre/post processing callbacks. + /// The loaded TurnStartPayload instance. + public static TurnStartPayload Load(Dictionary data, LoadContext? context = null) + { + if (context is not null) + { + data = context.ProcessInput(data); + } + + + // Create new instance + var instance = new TurnStartPayload(); + + + if (data.TryGetValue("agent", out var agentValue) && agentValue is not null) + { + instance.Agent = agentValue?.ToString()!; + } + + if (data.TryGetValue("inputs", out var inputsValue) && inputsValue is not null) + { + instance.Inputs = inputsValue.GetDictionary()!; + } + + if (data.TryGetValue("maxIterations", out var maxIterationsValue) && maxIterationsValue is not null) + { + instance.MaxIterations = Convert.ToInt32(maxIterationsValue); + } + + if (context is not null) + { + instance = context.ProcessOutput(instance); + } + return instance; + } + + + #endregion + + #region Save Methods + + /// + /// Save the TurnStartPayload instance to a dictionary. + /// + /// Optional context with pre/post processing callbacks. + /// The dictionary representation of this instance. + public Dictionary Save(SaveContext? context = null) + { + var obj = this; + if (context is not null) + { + obj = context.ProcessObject(obj); + } + + + var result = new Dictionary(); + + + if (obj.Agent is not null) + { + result["agent"] = obj.Agent; + } + + + if (obj.Inputs is not null) + { + result["inputs"] = obj.Inputs; + } + + + if (obj.MaxIterations is not null) + { + result["maxIterations"] = obj.MaxIterations; + } + + + if (context is not null) + { + result = context.ProcessDict(result); + } + + return result; + } + + + /// + /// Convert the TurnStartPayload instance to a YAML string. + /// + /// Optional context with pre/post processing callbacks. + /// The YAML string representation of this instance. + public string ToYaml(SaveContext? context = null) + { + context ??= new SaveContext(); + return context.ToYaml(Save(context)); + } + + /// + /// Convert the TurnStartPayload instance to a JSON string. + /// + /// Optional context with pre/post processing callbacks. + /// Whether to indent the output. Defaults to true. + /// The JSON string representation of this instance. + public string ToJson(SaveContext? context = null, bool indent = true) + { + context ??= new SaveContext(); + return context.ToJson(Save(context), indent); + } + + /// + /// Load a TurnStartPayload instance from a JSON string. + /// + /// The JSON string to parse. + /// Optional context with pre/post processing callbacks. + /// The loaded TurnStartPayload instance. + public static TurnStartPayload FromJson(string json, LoadContext? context = null) + { + using var doc = JsonDocument.Parse(json); + Dictionary dict; + dict = JsonSerializer.Deserialize>(json, JsonUtils.Options) + ?? throw new ArgumentException("Failed to parse JSON as dictionary"); + + return Load(dict, context); + } + + /// + /// Load a TurnStartPayload instance from a YAML string. + /// + /// The YAML string to parse. + /// Optional context with pre/post processing callbacks. + /// The loaded TurnStartPayload instance. + public static TurnStartPayload FromYaml(string yaml, LoadContext? context = null) + { + var dict = YamlUtils.Deserializer.Deserialize>(yaml) + ?? throw new ArgumentException("Failed to parse YAML as dictionary"); + + return Load(dict, context); + } + + #endregion +} diff --git a/runtime/csharp/Prompty.Core/Model/events/TurnStatus.cs b/runtime/csharp/Prompty.Core/Model/events/TurnStatus.cs new file mode 100644 index 00000000..79cad0fd --- /dev/null +++ b/runtime/csharp/Prompty.Core/Model/events/TurnStatus.cs @@ -0,0 +1,21 @@ +// +// +// Code generated by Typra emitter; DO NOT EDIT. + +using System.Text.Json.Serialization; + +namespace Prompty.Core; + +[JsonConverter(typeof(JsonStringEnumConverter))] +public enum TurnStatus +{ + [JsonPropertyName("success")] + Success, + + [JsonPropertyName("error")] + Error, + + [JsonPropertyName("cancelled")] + Cancelled, + +} diff --git a/runtime/csharp/Prompty.Core/Model/events/TurnSummary.cs b/runtime/csharp/Prompty.Core/Model/events/TurnSummary.cs new file mode 100644 index 00000000..0b8732ca --- /dev/null +++ b/runtime/csharp/Prompty.Core/Model/events/TurnSummary.cs @@ -0,0 +1,262 @@ +// +// Copyright (c) Microsoft. All rights reserved. +using System.Text.Json; +using YamlDotNet.Serialization; + +#pragma warning disable IDE0130 +namespace Prompty.Core; +#pragma warning restore IDE0130 + + /// + /// Summary statistics for a completed turn trace. + /// +public partial class TurnSummary +{ + /// + /// The shorthand property name for this type, if any. + /// + public static string? ShorthandProperty => null; + + /// + /// Initializes a new instance of . + /// +#pragma warning disable CS8618 + public TurnSummary() + { + } +#pragma warning restore CS8618 + + /// + /// Stable identifier for the outer turn + /// + public string TurnId { get; set; } = string.Empty; + + /// + /// Final turn status: 'success', 'error', or 'cancelled' + /// + public string Status { get; set; } = string.Empty; + + /// + /// Number of agent-loop iterations + /// + public int Iterations { get; set; } + + /// + /// Number of LLM calls made during the turn + /// + public int? LlmCalls { get; set; } + + /// + /// Number of tool calls dispatched during the turn + /// + public int? ToolCalls { get; set; } + + /// + /// Number of retry events during the turn + /// + public int? Retries { get; set; } + + /// + /// Aggregated token usage for the turn + /// + public TokenUsage? Usage { get; set; } + + /// + /// Total elapsed turn duration in milliseconds + /// + public double? DurationMs { get; set; } + + + + #region Load Methods + + /// + /// Load a TurnSummary instance from a dictionary. + /// + /// The dictionary containing the data. + /// Optional context with pre/post processing callbacks. + /// The loaded TurnSummary instance. + public static TurnSummary Load(Dictionary data, LoadContext? context = null) + { + if (context is not null) + { + data = context.ProcessInput(data); + } + + + // Create new instance + var instance = new TurnSummary(); + + + if (data.TryGetValue("turnId", out var turnIdValue) && turnIdValue is not null) + { + instance.TurnId = turnIdValue?.ToString()!; + } + + if (data.TryGetValue("status", out var statusValue) && statusValue is not null) + { + instance.Status = statusValue?.ToString()!; + } + + if (data.TryGetValue("iterations", out var iterationsValue) && iterationsValue is not null) + { + instance.Iterations = Convert.ToInt32(iterationsValue); + } + + if (data.TryGetValue("llmCalls", out var llmCallsValue) && llmCallsValue is not null) + { + instance.LlmCalls = Convert.ToInt32(llmCallsValue); + } + + if (data.TryGetValue("toolCalls", out var toolCallsValue) && toolCallsValue is not null) + { + instance.ToolCalls = Convert.ToInt32(toolCallsValue); + } + + if (data.TryGetValue("retries", out var retriesValue) && retriesValue is not null) + { + instance.Retries = Convert.ToInt32(retriesValue); + } + + if (data.TryGetValue("usage", out var usageValue) && usageValue is not null) + { + instance.Usage = TokenUsage.Load(usageValue.GetDictionary(TokenUsage.ShorthandProperty), context); + } + + if (data.TryGetValue("durationMs", out var durationMsValue) && durationMsValue is not null) + { + instance.DurationMs = Convert.ToDouble(durationMsValue); + } + + if (context is not null) + { + instance = context.ProcessOutput(instance); + } + return instance; + } + + + #endregion + + #region Save Methods + + /// + /// Save the TurnSummary instance to a dictionary. + /// + /// Optional context with pre/post processing callbacks. + /// The dictionary representation of this instance. + public Dictionary Save(SaveContext? context = null) + { + var obj = this; + if (context is not null) + { + obj = context.ProcessObject(obj); + } + + + var result = new Dictionary(); + + + result["turnId"] = obj.TurnId; + + + result["status"] = obj.Status; + + + result["iterations"] = obj.Iterations; + + + if (obj.LlmCalls is not null) + { + result["llmCalls"] = obj.LlmCalls; + } + + + if (obj.ToolCalls is not null) + { + result["toolCalls"] = obj.ToolCalls; + } + + + if (obj.Retries is not null) + { + result["retries"] = obj.Retries; + } + + + if (obj.Usage is not null) + { + result["usage"] = obj.Usage?.Save(context); + } + + + if (obj.DurationMs is not null) + { + result["durationMs"] = obj.DurationMs; + } + + + if (context is not null) + { + result = context.ProcessDict(result); + } + + return result; + } + + + /// + /// Convert the TurnSummary instance to a YAML string. + /// + /// Optional context with pre/post processing callbacks. + /// The YAML string representation of this instance. + public string ToYaml(SaveContext? context = null) + { + context ??= new SaveContext(); + return context.ToYaml(Save(context)); + } + + /// + /// Convert the TurnSummary instance to a JSON string. + /// + /// Optional context with pre/post processing callbacks. + /// Whether to indent the output. Defaults to true. + /// The JSON string representation of this instance. + public string ToJson(SaveContext? context = null, bool indent = true) + { + context ??= new SaveContext(); + return context.ToJson(Save(context), indent); + } + + /// + /// Load a TurnSummary instance from a JSON string. + /// + /// The JSON string to parse. + /// Optional context with pre/post processing callbacks. + /// The loaded TurnSummary instance. + public static TurnSummary FromJson(string json, LoadContext? context = null) + { + using var doc = JsonDocument.Parse(json); + Dictionary dict; + dict = JsonSerializer.Deserialize>(json, JsonUtils.Options) + ?? throw new ArgumentException("Failed to parse JSON as dictionary"); + + return Load(dict, context); + } + + /// + /// Load a TurnSummary instance from a YAML string. + /// + /// The YAML string to parse. + /// Optional context with pre/post processing callbacks. + /// The loaded TurnSummary instance. + public static TurnSummary FromYaml(string yaml, LoadContext? context = null) + { + var dict = YamlUtils.Deserializer.Deserialize>(yaml) + ?? throw new ArgumentException("Failed to parse YAML as dictionary"); + + return Load(dict, context); + } + + #endregion +} diff --git a/runtime/csharp/Prompty.Core/Model/events/TurnTrace.cs b/runtime/csharp/Prompty.Core/Model/events/TurnTrace.cs new file mode 100644 index 00000000..4834e03e --- /dev/null +++ b/runtime/csharp/Prompty.Core/Model/events/TurnTrace.cs @@ -0,0 +1,284 @@ +// +// Copyright (c) Microsoft. All rights reserved. +using System.Text.Json; +using YamlDotNet.Serialization; + +#pragma warning disable IDE0130 +namespace Prompty.Core; +#pragma warning restore IDE0130 + + /// + /// Portable JSONL/replay container for a recorded turn harness run. + /// +public partial class TurnTrace +{ + /// + /// The shorthand property name for this type, if any. + /// + public static string? ShorthandProperty => null; + + /// + /// Initializes a new instance of . + /// +#pragma warning disable CS8618 + public TurnTrace() + { + } +#pragma warning restore CS8618 + + /// + /// Trace schema version + /// + public string Version { get; set; } = "1"; + + /// + /// Runtime name that produced the trace + /// + public string? Runtime { get; set; } + + /// + /// Prompty library version that produced the trace + /// + public string? PromptyVersion { get; set; } + + /// + /// Recorded turn events in emission order + /// + public IList Events { get; set; } = []; + + /// + /// Optional summary computed from the event stream + /// + public TurnSummary? Summary { get; set; } + + + + #region Load Methods + + /// + /// Load a TurnTrace instance from a dictionary. + /// + /// The dictionary containing the data. + /// Optional context with pre/post processing callbacks. + /// The loaded TurnTrace instance. + public static TurnTrace Load(Dictionary data, LoadContext? context = null) + { + if (context is not null) + { + data = context.ProcessInput(data); + } + + + // Create new instance + var instance = new TurnTrace(); + + + if (data.TryGetValue("version", out var versionValue) && versionValue is not null) + { + instance.Version = versionValue?.ToString()!; + } + + if (data.TryGetValue("runtime", out var runtimeValue) && runtimeValue is not null) + { + instance.Runtime = runtimeValue?.ToString()!; + } + + if (data.TryGetValue("promptyVersion", out var promptyVersionValue) && promptyVersionValue is not null) + { + instance.PromptyVersion = promptyVersionValue?.ToString()!; + } + + if (data.TryGetValue("events", out var eventsValue) && eventsValue is not null) + { + instance.Events = LoadEvents(eventsValue, context); + } + + if (data.TryGetValue("summary", out var summaryValue) && summaryValue is not null) + { + instance.Summary = TurnSummary.Load(summaryValue.GetDictionary(TurnSummary.ShorthandProperty), context); + } + + if (context is not null) + { + instance = context.ProcessOutput(instance); + } + return instance; + } + + + /// + /// Load a list of TurnEvent from a dictionary or list. + /// + public static IList LoadEvents(object data, LoadContext? context) + { + var result = new List(); + + if (data is Dictionary dict) + { + // Convert named dictionary to list + foreach (var kvp in dict) + { + if (kvp.Value is IEnumerable) + { + throw new ArgumentException( + $"Invalid 'events' format: key '{kvp.Key}' has an array value. " + + $"'events' must be a flat list of objects or a name-keyed dict — " + + "not a nested {" + kvp.Key + ": [...]} structure."); + } + var itemDict = kvp.Value.GetDictionary(); + if (itemDict.Count > 0) + { + // Value is an object, add name to it + itemDict["name"] = kvp.Key; + result.Add(TurnEvent.Load(itemDict, context)); + } + else + { + // Value is a scalar, use it as the primary property + var newDict = new Dictionary + { + ["name"] = kvp.Key, + ["id"] = kvp.Value + }; + result.Add(TurnEvent.Load(newDict, context)); + } + } + } + else if (data is IEnumerable list) + { + foreach (var item in list) + { + var itemDict = item.GetDictionary(TurnEvent.ShorthandProperty); + if (itemDict.Count > 0) + { + result.Add(TurnEvent.Load(itemDict, context)); + } + } + } + + return result; + } + + + #endregion + + #region Save Methods + + /// + /// Save the TurnTrace instance to a dictionary. + /// + /// Optional context with pre/post processing callbacks. + /// The dictionary representation of this instance. + public Dictionary Save(SaveContext? context = null) + { + var obj = this; + if (context is not null) + { + obj = context.ProcessObject(obj); + } + + + var result = new Dictionary(); + + + result["version"] = obj.Version; + + + if (obj.Runtime is not null) + { + result["runtime"] = obj.Runtime; + } + + + if (obj.PromptyVersion is not null) + { + result["promptyVersion"] = obj.PromptyVersion; + } + + + result["events"] = SaveEvents(obj.Events, context); + + + if (obj.Summary is not null) + { + result["summary"] = obj.Summary?.Save(context); + } + + + if (context is not null) + { + result = context.ProcessDict(result); + } + + return result; + } + + + /// + /// Save a list of TurnEvent to object or array format. + /// + public static object SaveEvents(IList items, SaveContext? context) + { + context ??= new SaveContext(); + + // This collection type does not have a 'name' property, only array format is supported + return items.Select(item => item.Save(context)).ToList(); + + } + + + /// + /// Convert the TurnTrace instance to a YAML string. + /// + /// Optional context with pre/post processing callbacks. + /// The YAML string representation of this instance. + public string ToYaml(SaveContext? context = null) + { + context ??= new SaveContext(); + return context.ToYaml(Save(context)); + } + + /// + /// Convert the TurnTrace instance to a JSON string. + /// + /// Optional context with pre/post processing callbacks. + /// Whether to indent the output. Defaults to true. + /// The JSON string representation of this instance. + public string ToJson(SaveContext? context = null, bool indent = true) + { + context ??= new SaveContext(); + return context.ToJson(Save(context), indent); + } + + /// + /// Load a TurnTrace instance from a JSON string. + /// + /// The JSON string to parse. + /// Optional context with pre/post processing callbacks. + /// The loaded TurnTrace instance. + public static TurnTrace FromJson(string json, LoadContext? context = null) + { + using var doc = JsonDocument.Parse(json); + Dictionary dict; + dict = JsonSerializer.Deserialize>(json, JsonUtils.Options) + ?? throw new ArgumentException("Failed to parse JSON as dictionary"); + + return Load(dict, context); + } + + /// + /// Load a TurnTrace instance from a YAML string. + /// + /// The YAML string to parse. + /// Optional context with pre/post processing callbacks. + /// The loaded TurnTrace instance. + public static TurnTrace FromYaml(string yaml, LoadContext? context = null) + { + var dict = YamlUtils.Deserializer.Deserialize>(yaml) + ?? throw new ArgumentException("Failed to parse YAML as dictionary"); + + return Load(dict, context); + } + + #endregion +} diff --git a/runtime/csharp/Prompty.Core/Model/model/Model.cs b/runtime/csharp/Prompty.Core/Model/model/Model.cs index 26ed0117..8b1dd4cf 100644 --- a/runtime/csharp/Prompty.Core/Model/model/Model.cs +++ b/runtime/csharp/Prompty.Core/Model/model/Model.cs @@ -1,3 +1,4 @@ +// // Copyright (c) Microsoft. All rights reserved. using System.Text.Json; using YamlDotNet.Serialization; @@ -6,13 +7,13 @@ namespace Prompty.Core; #pragma warning restore IDE0130 -/// -/// Model for defining the structure and behavior of AI agents. -/// -/// This model includes properties for specifying the model's provider, connection details, and various options. -/// -/// It allows for flexible configuration of AI models to suit different use cases and requirements. -/// + /// + /// Model for defining the structure and behavior of AI agents. + /// + /// This model includes properties for specifying the model's provider, connection details, and various options. + /// + /// It allows for flexible configuration of AI models to suit different use cases and requirements. + /// public partial class Model { /// diff --git a/runtime/csharp/Prompty.Core/Model/model/ModelInfo.cs b/runtime/csharp/Prompty.Core/Model/model/ModelInfo.cs index 14620536..efda9bb0 100644 --- a/runtime/csharp/Prompty.Core/Model/model/ModelInfo.cs +++ b/runtime/csharp/Prompty.Core/Model/model/ModelInfo.cs @@ -1,3 +1,4 @@ +// // Copyright (c) Microsoft. All rights reserved. using System.Text.Json; using YamlDotNet.Serialization; @@ -6,17 +7,17 @@ namespace Prompty.Core; #pragma warning restore IDE0130 -/// -/// Information about a model available from a provider. Used by provider-level -/// -/// model discovery to report which models are available and their capabilities. -/// -/// Not all providers return all fields — implementations SHOULD populate as -/// -/// many fields as the provider's API supports and MAY enrich sparse results -/// -/// from a built-in lookup table of known models. -/// + /// + /// Information about a model available from a provider. Used by provider-level + /// + /// model discovery to report which models are available and their capabilities. + /// + /// Not all providers return all fields — implementations SHOULD populate as + /// + /// many fields as the provider's API supports and MAY enrich sparse results + /// + /// from a built-in lookup table of known models. + /// public partial class ModelInfo { /// diff --git a/runtime/csharp/Prompty.Core/Model/model/ModelOptions.cs b/runtime/csharp/Prompty.Core/Model/model/ModelOptions.cs index a2a9e173..8842fb92 100644 --- a/runtime/csharp/Prompty.Core/Model/model/ModelOptions.cs +++ b/runtime/csharp/Prompty.Core/Model/model/ModelOptions.cs @@ -1,3 +1,4 @@ +// // Copyright (c) Microsoft. All rights reserved. using System.Text.Json; using YamlDotNet.Serialization; @@ -6,9 +7,9 @@ namespace Prompty.Core; #pragma warning restore IDE0130 -/// -/// Options for configuring the behavior of the AI model. -/// + /// + /// Options for configuring the behavior of the AI model. + /// public partial class ModelOptions { /// diff --git a/runtime/csharp/Prompty.Core/Model/model/TokenUsage.cs b/runtime/csharp/Prompty.Core/Model/model/TokenUsage.cs index ace9cff2..aec8a533 100644 --- a/runtime/csharp/Prompty.Core/Model/model/TokenUsage.cs +++ b/runtime/csharp/Prompty.Core/Model/model/TokenUsage.cs @@ -1,3 +1,4 @@ +// // Copyright (c) Microsoft. All rights reserved. using System.Text.Json; using YamlDotNet.Serialization; @@ -6,13 +7,13 @@ namespace Prompty.Core; #pragma warning restore IDE0130 -/// -/// Tracks token consumption for a single LLM call. Provider-specific field -/// -/// names (e.g., OpenAI's `prompt_tokens` vs Anthropic's `input_tokens`) -/// -/// are mapped via `knownAs` augments in the wire directory. -/// + /// + /// Tracks token consumption for a single LLM call. Provider-specific field + /// + /// names (e.g., OpenAI's `prompt_tokens` vs Anthropic's `input_tokens`) + /// + /// are mapped via `knownAs` augments in the wire directory. + /// public partial class TokenUsage { /// diff --git a/runtime/csharp/Prompty.Core/Model/pipeline/CheckpointStore.cs b/runtime/csharp/Prompty.Core/Model/pipeline/CheckpointStore.cs new file mode 100644 index 00000000..89a41b65 --- /dev/null +++ b/runtime/csharp/Prompty.Core/Model/pipeline/CheckpointStore.cs @@ -0,0 +1,25 @@ +// +// Copyright (c) Microsoft. All rights reserved. + +#pragma warning disable IDE0130 +namespace Prompty.Core; +#pragma warning restore IDE0130 + + /// + /// Stores and retrieves resumable session checkpoints. + /// + public interface ICheckpointStore + { + /// + /// Persist a session checkpoint and return the stored checkpoint + /// + Task SaveAsync(Checkpoint checkpoint); + /// + /// Load a checkpoint by session and checkpoint identifier + /// + Task LoadAsync(string sessionId, string checkpointId); + /// + /// List checkpoints for a session + /// + Task> ListCheckpointsAsync(string sessionId); + } diff --git a/runtime/csharp/Prompty.Core/Model/pipeline/CompactionConfig.cs b/runtime/csharp/Prompty.Core/Model/pipeline/CompactionConfig.cs index cd757c3e..438375d9 100644 --- a/runtime/csharp/Prompty.Core/Model/pipeline/CompactionConfig.cs +++ b/runtime/csharp/Prompty.Core/Model/pipeline/CompactionConfig.cs @@ -1,3 +1,4 @@ +// // Copyright (c) Microsoft. All rights reserved. using System.Text.Json; using YamlDotNet.Serialization; @@ -6,13 +7,13 @@ namespace Prompty.Core; #pragma warning restore IDE0130 -/// -/// Configuration for context window compaction. When the message history -/// -/// exceeds the context budget, the compaction strategy is applied to -/// -/// reduce the message list while preserving essential information. -/// + /// + /// Configuration for context window compaction. When the message history + /// + /// exceeds the context budget, the compaction strategy is applied to + /// + /// reduce the message list while preserving essential information. + /// public partial class CompactionConfig { /// diff --git a/runtime/csharp/Prompty.Core/Model/pipeline/EventJournalWriter.cs b/runtime/csharp/Prompty.Core/Model/pipeline/EventJournalWriter.cs new file mode 100644 index 00000000..7f4c5dd3 --- /dev/null +++ b/runtime/csharp/Prompty.Core/Model/pipeline/EventJournalWriter.cs @@ -0,0 +1,25 @@ +// +// Copyright (c) Microsoft. All rights reserved. + +#pragma warning disable IDE0130 +namespace Prompty.Core; +#pragma warning restore IDE0130 + + /// + /// Persists typed events to a durable replay journal. + /// + public interface IEventJournalWriter + { + /// + /// Append a turn event to a durable replay journal + /// + bool AppendTurn(TurnEvent turnEvent); + /// + /// Append a session event to a durable replay journal + /// + bool AppendSession(SessionEvent sessionEvent); + /// + /// Finalize the journal with an optional session summary + /// + bool Close(SessionSummary? summary); + } diff --git a/runtime/csharp/Prompty.Core/Model/pipeline/EventSink.cs b/runtime/csharp/Prompty.Core/Model/pipeline/EventSink.cs new file mode 100644 index 00000000..fb58472c --- /dev/null +++ b/runtime/csharp/Prompty.Core/Model/pipeline/EventSink.cs @@ -0,0 +1,21 @@ +// +// Copyright (c) Microsoft. All rights reserved. + +#pragma warning disable IDE0130 +namespace Prompty.Core; +#pragma warning restore IDE0130 + + /// + /// Receives typed turn and session events from a harness. + /// + public interface IEventSink + { + /// + /// Emit a typed turn event to a host sink + /// + bool EmitTurn(TurnEvent turnEvent); + /// + /// Emit a typed session event to a host sink + /// + bool EmitSession(SessionEvent sessionEvent); + } diff --git a/runtime/csharp/Prompty.Core/Model/pipeline/Executor.cs b/runtime/csharp/Prompty.Core/Model/pipeline/Executor.cs index 3ed3cd9b..b641b4f5 100644 --- a/runtime/csharp/Prompty.Core/Model/pipeline/Executor.cs +++ b/runtime/csharp/Prompty.Core/Model/pipeline/Executor.cs @@ -1,24 +1,25 @@ +// // Copyright (c) Microsoft. All rights reserved. #pragma warning disable IDE0130 namespace Prompty.Core; #pragma warning restore IDE0130 -/// -/// Calls an LLM provider with messages and returns the raw provider response. -/// -public interface IExecutor -{ /// - /// Call an LLM provider with messages and return the raw response + /// Calls an LLM provider with messages and returns the raw provider response. /// - Task ExecuteAsync(Prompty agent, List messages); - /// - /// Call an LLM provider and return a streaming response. Returns a language-specific async iterable/stream of raw chunks. Not all providers support streaming; the default implementation should signal lack of support. - /// - Task ExecuteStreamAsync(Prompty agent, List messages) => Task.FromResult(default); - /// - /// Format tool call results into messages for the next iteration - /// - List FormatToolMessages(object rawResponse, List toolCalls, List toolResults, string? textContent); -} + public interface IExecutor + { + /// + /// Call an LLM provider with messages and return the raw response + /// + Task ExecuteAsync(Prompty agent, List messages); + /// + /// Call an LLM provider and return a streaming response. Returns a language-specific async iterable/stream of raw chunks. Not all providers support streaming; the default implementation should signal lack of support. + /// + Task ExecuteStreamAsync(Prompty agent, List messages) => Task.FromResult(default!); + /// + /// Format tool call results into messages for the next iteration + /// + List FormatToolMessages(object rawResponse, List toolCalls, List toolResults, string? textContent); + } diff --git a/runtime/csharp/Prompty.Core/Model/pipeline/HostToolExecutor.cs b/runtime/csharp/Prompty.Core/Model/pipeline/HostToolExecutor.cs new file mode 100644 index 00000000..e51f2c34 --- /dev/null +++ b/runtime/csharp/Prompty.Core/Model/pipeline/HostToolExecutor.cs @@ -0,0 +1,17 @@ +// +// Copyright (c) Microsoft. All rights reserved. + +#pragma warning disable IDE0130 +namespace Prompty.Core; +#pragma warning restore IDE0130 + + /// + /// Executes host tools after policy and permission checks. + /// + public interface IHostToolExecutor + { + /// + /// Execute a concrete host tool request and return its completion payload + /// + Task ExecuteAsync(HostToolRequest request); + } diff --git a/runtime/csharp/Prompty.Core/Model/pipeline/Parser.cs b/runtime/csharp/Prompty.Core/Model/pipeline/Parser.cs index c70c01fb..46167af1 100644 --- a/runtime/csharp/Prompty.Core/Model/pipeline/Parser.cs +++ b/runtime/csharp/Prompty.Core/Model/pipeline/Parser.cs @@ -1,20 +1,21 @@ +// // Copyright (c) Microsoft. All rights reserved. #pragma warning disable IDE0130 namespace Prompty.Core; #pragma warning restore IDE0130 -/// -/// Parses rendered prompt text into an array of structured messages with role markers. -/// -public interface IParser -{ /// - /// Pre-process a template before rendering, returning modified template and context + /// Parses rendered prompt text into an array of structured messages with role markers. /// - object? PreRender(string template) => default; - /// - /// Parse rendered text into a structured message array - /// - Task> ParseAsync(Prompty agent, string rendered, Dictionary? context); -} + public interface IParser + { + /// + /// Pre-process a template before rendering, returning modified template and context + /// + object? PreRender(string template) => default!; + /// + /// Parse rendered text into a structured message array + /// + Task> ParseAsync(Prompty agent, string rendered, Dictionary? context); + } diff --git a/runtime/csharp/Prompty.Core/Model/pipeline/PermissionResolver.cs b/runtime/csharp/Prompty.Core/Model/pipeline/PermissionResolver.cs new file mode 100644 index 00000000..721178ff --- /dev/null +++ b/runtime/csharp/Prompty.Core/Model/pipeline/PermissionResolver.cs @@ -0,0 +1,17 @@ +// +// Copyright (c) Microsoft. All rights reserved. + +#pragma warning disable IDE0130 +namespace Prompty.Core; +#pragma warning restore IDE0130 + + /// + /// Resolves host permission requests for potentially sensitive actions. + /// + public interface IPermissionResolver + { + /// + /// Resolve a host permission request + /// + Task RequestAsync(PermissionRequest request); + } diff --git a/runtime/csharp/Prompty.Core/Model/pipeline/Processor.cs b/runtime/csharp/Prompty.Core/Model/pipeline/Processor.cs index c9d42f2d..8153b828 100644 --- a/runtime/csharp/Prompty.Core/Model/pipeline/Processor.cs +++ b/runtime/csharp/Prompty.Core/Model/pipeline/Processor.cs @@ -1,20 +1,21 @@ +// // Copyright (c) Microsoft. All rights reserved. #pragma warning disable IDE0130 namespace Prompty.Core; #pragma warning restore IDE0130 -/// -/// Extracts a clean, typed result from a raw LLM provider response. -/// -public interface IProcessor -{ /// - /// Extract a clean result from a raw LLM response + /// Extracts a clean, typed result from a raw LLM provider response. /// - Task ProcessAsync(Prompty agent, object response); - /// - /// Process a streaming response into a stream of StreamChunk items. Takes raw chunks from the executor and yields processed text, thinking, tool, or error chunks. Not all providers support streaming; the default implementation should signal lack of support. - /// - Task ProcessStreamAsync(object stream) => Task.FromResult(default); -} + public interface IProcessor + { + /// + /// Extract a clean result from a raw LLM response + /// + Task ProcessAsync(Prompty agent, object response); + /// + /// Process a streaming response into a stream of StreamChunk items. Takes raw chunks from the executor and yields processed text, thinking, tool, or error chunks. Not all providers support streaming; the default implementation should signal lack of support. + /// + Task ProcessStreamAsync(object stream) => Task.FromResult(default!); + } diff --git a/runtime/csharp/Prompty.Core/Model/pipeline/Renderer.cs b/runtime/csharp/Prompty.Core/Model/pipeline/Renderer.cs index 67682679..232d42c7 100644 --- a/runtime/csharp/Prompty.Core/Model/pipeline/Renderer.cs +++ b/runtime/csharp/Prompty.Core/Model/pipeline/Renderer.cs @@ -1,16 +1,17 @@ +// // Copyright (c) Microsoft. All rights reserved. #pragma warning disable IDE0130 namespace Prompty.Core; #pragma warning restore IDE0130 -/// -/// Renders a template string with input values to produce the final prompt text. -/// -public interface IRenderer -{ /// - /// Render the template string with input values + /// Renders a template string with input values to produce the final prompt text. /// - Task RenderAsync(Prompty agent, string template, Dictionary inputs); -} + public interface IRenderer + { + /// + /// Render the template string with input values + /// + Task RenderAsync(Prompty agent, string template, Dictionary inputs); + } diff --git a/runtime/csharp/Prompty.Core/Model/pipeline/ReplayJournalRecord.cs b/runtime/csharp/Prompty.Core/Model/pipeline/ReplayJournalRecord.cs new file mode 100644 index 00000000..1f3f0370 --- /dev/null +++ b/runtime/csharp/Prompty.Core/Model/pipeline/ReplayJournalRecord.cs @@ -0,0 +1,338 @@ +// +// Copyright (c) Microsoft. All rights reserved. +using System.Text.Json; +using YamlDotNet.Serialization; + +#pragma warning disable IDE0130 +namespace Prompty.Core; +#pragma warning restore IDE0130 + + /// + /// Stable, replay-comparable projection of a journal record. + /// + /// Runtime journal records may carry additional payload fields, durations, telemetry, + /// + /// or provider-specific data. Replay verification compares this normalized shape so + /// + /// deterministic orchestration semantics are mechanically shared across runtimes. + /// +public partial class ReplayJournalRecord +{ + /// + /// The shorthand property name for this type, if any. + /// + public static string? ShorthandProperty => null; + + /// + /// Initializes a new instance of . + /// +#pragma warning disable CS8618 + public ReplayJournalRecord() + { + } +#pragma warning restore CS8618 + + /// + /// Journal record kind + /// + public ReplayRecordKind Kind { get; set; } = ReplayRecordKind.Session; + + /// + /// Turn or session event type, when kind is not summary + /// + public string? Type { get; set; } + + /// + /// Stable harness session identifier + /// + public string? SessionId { get; set; } + + /// + /// Stable turn identifier within the session + /// + public string? TurnId { get; set; } + + /// + /// Zero-based model loop iteration for turn records + /// + public int? Iteration { get; set; } + + /// + /// Final semantic status for turn/session/summary records + /// + public ReplayRecordStatus? Status { get; set; } + + /// + /// Permission request identifier for permission request records + /// + public string? RequestId { get; set; } + + /// + /// Host tool name for tool execution/result records + /// + public string? ToolName { get; set; } + + /// + /// Whether a permission or host tool operation succeeded + /// + public bool? Success { get; set; } + + /// + /// Stable error discriminator for failed records + /// + public string? ErrorKind { get; set; } + + /// + /// Number of turns represented by a summary record + /// + public int? Turns { get; set; } + + /// + /// Number of checkpoints represented by a summary record + /// + public int? Checkpoints { get; set; } + + + + #region Load Methods + + /// + /// Load a ReplayJournalRecord instance from a dictionary. + /// + /// The dictionary containing the data. + /// Optional context with pre/post processing callbacks. + /// The loaded ReplayJournalRecord instance. + public static ReplayJournalRecord Load(Dictionary data, LoadContext? context = null) + { + if (context is not null) + { + data = context.ProcessInput(data); + } + + + // Create new instance + var instance = new ReplayJournalRecord(); + + + if (data.TryGetValue("kind", out var kindValue) && kindValue is not null) + { + instance.Kind = Enum.Parse(kindValue?.ToString()!, true); + } + + if (data.TryGetValue("type", out var typeValue) && typeValue is not null) + { + instance.Type = typeValue?.ToString()!; + } + + if (data.TryGetValue("sessionId", out var sessionIdValue) && sessionIdValue is not null) + { + instance.SessionId = sessionIdValue?.ToString()!; + } + + if (data.TryGetValue("turnId", out var turnIdValue) && turnIdValue is not null) + { + instance.TurnId = turnIdValue?.ToString()!; + } + + if (data.TryGetValue("iteration", out var iterationValue) && iterationValue is not null) + { + instance.Iteration = Convert.ToInt32(iterationValue); + } + + if (data.TryGetValue("status", out var statusValue) && statusValue is not null) + { + instance.Status = Enum.Parse(statusValue?.ToString()!, true); + } + + if (data.TryGetValue("requestId", out var requestIdValue) && requestIdValue is not null) + { + instance.RequestId = requestIdValue?.ToString()!; + } + + if (data.TryGetValue("toolName", out var toolNameValue) && toolNameValue is not null) + { + instance.ToolName = toolNameValue?.ToString()!; + } + + if (data.TryGetValue("success", out var successValue) && successValue is not null) + { + instance.Success = Convert.ToBoolean(successValue); + } + + if (data.TryGetValue("errorKind", out var errorKindValue) && errorKindValue is not null) + { + instance.ErrorKind = errorKindValue?.ToString()!; + } + + if (data.TryGetValue("turns", out var turnsValue) && turnsValue is not null) + { + instance.Turns = Convert.ToInt32(turnsValue); + } + + if (data.TryGetValue("checkpoints", out var checkpointsValue) && checkpointsValue is not null) + { + instance.Checkpoints = Convert.ToInt32(checkpointsValue); + } + + if (context is not null) + { + instance = context.ProcessOutput(instance); + } + return instance; + } + + + #endregion + + #region Save Methods + + /// + /// Save the ReplayJournalRecord instance to a dictionary. + /// + /// Optional context with pre/post processing callbacks. + /// The dictionary representation of this instance. + public Dictionary Save(SaveContext? context = null) + { + var obj = this; + if (context is not null) + { + obj = context.ProcessObject(obj); + } + + + var result = new Dictionary(); + + + result["kind"] = obj.Kind.ToString().ToLowerInvariant(); + + + if (obj.Type is not null) + { + result["type"] = obj.Type; + } + + + if (obj.SessionId is not null) + { + result["sessionId"] = obj.SessionId; + } + + + if (obj.TurnId is not null) + { + result["turnId"] = obj.TurnId; + } + + + if (obj.Iteration is not null) + { + result["iteration"] = obj.Iteration; + } + + + if (obj.Status is not null) + { + result["status"] = obj.Status.Value.ToString().ToLowerInvariant(); + } + + + if (obj.RequestId is not null) + { + result["requestId"] = obj.RequestId; + } + + + if (obj.ToolName is not null) + { + result["toolName"] = obj.ToolName; + } + + + if (obj.Success is not null) + { + result["success"] = obj.Success; + } + + + if (obj.ErrorKind is not null) + { + result["errorKind"] = obj.ErrorKind; + } + + + if (obj.Turns is not null) + { + result["turns"] = obj.Turns; + } + + + if (obj.Checkpoints is not null) + { + result["checkpoints"] = obj.Checkpoints; + } + + + if (context is not null) + { + result = context.ProcessDict(result); + } + + return result; + } + + + /// + /// Convert the ReplayJournalRecord instance to a YAML string. + /// + /// Optional context with pre/post processing callbacks. + /// The YAML string representation of this instance. + public string ToYaml(SaveContext? context = null) + { + context ??= new SaveContext(); + return context.ToYaml(Save(context)); + } + + /// + /// Convert the ReplayJournalRecord instance to a JSON string. + /// + /// Optional context with pre/post processing callbacks. + /// Whether to indent the output. Defaults to true. + /// The JSON string representation of this instance. + public string ToJson(SaveContext? context = null, bool indent = true) + { + context ??= new SaveContext(); + return context.ToJson(Save(context), indent); + } + + /// + /// Load a ReplayJournalRecord instance from a JSON string. + /// + /// The JSON string to parse. + /// Optional context with pre/post processing callbacks. + /// The loaded ReplayJournalRecord instance. + public static ReplayJournalRecord FromJson(string json, LoadContext? context = null) + { + using var doc = JsonDocument.Parse(json); + Dictionary dict; + dict = JsonSerializer.Deserialize>(json, JsonUtils.Options) + ?? throw new ArgumentException("Failed to parse JSON as dictionary"); + + return Load(dict, context); + } + + /// + /// Load a ReplayJournalRecord instance from a YAML string. + /// + /// The YAML string to parse. + /// Optional context with pre/post processing callbacks. + /// The loaded ReplayJournalRecord instance. + public static ReplayJournalRecord FromYaml(string yaml, LoadContext? context = null) + { + var dict = YamlUtils.Deserializer.Deserialize>(yaml) + ?? throw new ArgumentException("Failed to parse YAML as dictionary"); + + return Load(dict, context); + } + + #endregion +} diff --git a/runtime/csharp/Prompty.Core/Model/pipeline/ReplayMismatch.cs b/runtime/csharp/Prompty.Core/Model/pipeline/ReplayMismatch.cs new file mode 100644 index 00000000..8d5ac8b5 --- /dev/null +++ b/runtime/csharp/Prompty.Core/Model/pipeline/ReplayMismatch.cs @@ -0,0 +1,201 @@ +// +// Copyright (c) Microsoft. All rights reserved. +using System.Text.Json; +using YamlDotNet.Serialization; + +#pragma warning disable IDE0130 +namespace Prompty.Core; +#pragma warning restore IDE0130 + + /// + /// A single mismatch produced by replay verification. + /// +public partial class ReplayMismatch +{ + /// + /// The shorthand property name for this type, if any. + /// + public static string? ShorthandProperty => null; + + /// + /// Initializes a new instance of . + /// +#pragma warning disable CS8618 + public ReplayMismatch() + { + } +#pragma warning restore CS8618 + + /// + /// Zero-based record index where the mismatch was found + /// + public int Index { get; set; } + + /// + /// Expected record at this index, when present + /// + public ReplayJournalRecord? Expected { get; set; } + + /// + /// Actual record at this index, when present + /// + public ReplayJournalRecord? Actual { get; set; } + + /// + /// Human-readable mismatch explanation + /// + public string Message { get; set; } = string.Empty; + + + + #region Load Methods + + /// + /// Load a ReplayMismatch instance from a dictionary. + /// + /// The dictionary containing the data. + /// Optional context with pre/post processing callbacks. + /// The loaded ReplayMismatch instance. + public static ReplayMismatch Load(Dictionary data, LoadContext? context = null) + { + if (context is not null) + { + data = context.ProcessInput(data); + } + + + // Create new instance + var instance = new ReplayMismatch(); + + + if (data.TryGetValue("index", out var indexValue) && indexValue is not null) + { + instance.Index = Convert.ToInt32(indexValue); + } + + if (data.TryGetValue("expected", out var expectedValue) && expectedValue is not null) + { + instance.Expected = ReplayJournalRecord.Load(expectedValue.GetDictionary(ReplayJournalRecord.ShorthandProperty), context); + } + + if (data.TryGetValue("actual", out var actualValue) && actualValue is not null) + { + instance.Actual = ReplayJournalRecord.Load(actualValue.GetDictionary(ReplayJournalRecord.ShorthandProperty), context); + } + + if (data.TryGetValue("message", out var messageValue) && messageValue is not null) + { + instance.Message = messageValue?.ToString()!; + } + + if (context is not null) + { + instance = context.ProcessOutput(instance); + } + return instance; + } + + + #endregion + + #region Save Methods + + /// + /// Save the ReplayMismatch instance to a dictionary. + /// + /// Optional context with pre/post processing callbacks. + /// The dictionary representation of this instance. + public Dictionary Save(SaveContext? context = null) + { + var obj = this; + if (context is not null) + { + obj = context.ProcessObject(obj); + } + + + var result = new Dictionary(); + + + result["index"] = obj.Index; + + + if (obj.Expected is not null) + { + result["expected"] = obj.Expected?.Save(context); + } + + + if (obj.Actual is not null) + { + result["actual"] = obj.Actual?.Save(context); + } + + + result["message"] = obj.Message; + + + if (context is not null) + { + result = context.ProcessDict(result); + } + + return result; + } + + + /// + /// Convert the ReplayMismatch instance to a YAML string. + /// + /// Optional context with pre/post processing callbacks. + /// The YAML string representation of this instance. + public string ToYaml(SaveContext? context = null) + { + context ??= new SaveContext(); + return context.ToYaml(Save(context)); + } + + /// + /// Convert the ReplayMismatch instance to a JSON string. + /// + /// Optional context with pre/post processing callbacks. + /// Whether to indent the output. Defaults to true. + /// The JSON string representation of this instance. + public string ToJson(SaveContext? context = null, bool indent = true) + { + context ??= new SaveContext(); + return context.ToJson(Save(context), indent); + } + + /// + /// Load a ReplayMismatch instance from a JSON string. + /// + /// The JSON string to parse. + /// Optional context with pre/post processing callbacks. + /// The loaded ReplayMismatch instance. + public static ReplayMismatch FromJson(string json, LoadContext? context = null) + { + using var doc = JsonDocument.Parse(json); + Dictionary dict; + dict = JsonSerializer.Deserialize>(json, JsonUtils.Options) + ?? throw new ArgumentException("Failed to parse JSON as dictionary"); + + return Load(dict, context); + } + + /// + /// Load a ReplayMismatch instance from a YAML string. + /// + /// The YAML string to parse. + /// Optional context with pre/post processing callbacks. + /// The loaded ReplayMismatch instance. + public static ReplayMismatch FromYaml(string yaml, LoadContext? context = null) + { + var dict = YamlUtils.Deserializer.Deserialize>(yaml) + ?? throw new ArgumentException("Failed to parse YAML as dictionary"); + + return Load(dict, context); + } + + #endregion +} diff --git a/runtime/csharp/Prompty.Core/Model/pipeline/ReplayRecordKind.cs b/runtime/csharp/Prompty.Core/Model/pipeline/ReplayRecordKind.cs new file mode 100644 index 00000000..28b5551c --- /dev/null +++ b/runtime/csharp/Prompty.Core/Model/pipeline/ReplayRecordKind.cs @@ -0,0 +1,21 @@ +// +// +// Code generated by Typra emitter; DO NOT EDIT. + +using System.Text.Json.Serialization; + +namespace Prompty.Core; + +[JsonConverter(typeof(JsonStringEnumConverter))] +public enum ReplayRecordKind +{ + [JsonPropertyName("session")] + Session, + + [JsonPropertyName("turn")] + Turn, + + [JsonPropertyName("summary")] + Summary, + +} diff --git a/runtime/csharp/Prompty.Core/Model/pipeline/ReplayRecordStatus.cs b/runtime/csharp/Prompty.Core/Model/pipeline/ReplayRecordStatus.cs new file mode 100644 index 00000000..33e75c30 --- /dev/null +++ b/runtime/csharp/Prompty.Core/Model/pipeline/ReplayRecordStatus.cs @@ -0,0 +1,21 @@ +// +// +// Code generated by Typra emitter; DO NOT EDIT. + +using System.Text.Json.Serialization; + +namespace Prompty.Core; + +[JsonConverter(typeof(JsonStringEnumConverter))] +public enum ReplayRecordStatus +{ + [JsonPropertyName("success")] + Success, + + [JsonPropertyName("error")] + Error, + + [JsonPropertyName("cancelled")] + Cancelled, + +} diff --git a/runtime/csharp/Prompty.Core/Model/pipeline/ReplayVerificationRequest.cs b/runtime/csharp/Prompty.Core/Model/pipeline/ReplayVerificationRequest.cs new file mode 100644 index 00000000..3714f11e --- /dev/null +++ b/runtime/csharp/Prompty.Core/Model/pipeline/ReplayVerificationRequest.cs @@ -0,0 +1,303 @@ +// +// Copyright (c) Microsoft. All rights reserved. +using System.Text.Json; +using YamlDotNet.Serialization; + +#pragma warning disable IDE0130 +namespace Prompty.Core; +#pragma warning restore IDE0130 + + /// + /// Request accepted by a replay verifier implementation. + /// +public partial class ReplayVerificationRequest +{ + /// + /// The shorthand property name for this type, if any. + /// + public static string? ShorthandProperty => null; + + /// + /// Initializes a new instance of . + /// +#pragma warning disable CS8618 + public ReplayVerificationRequest() + { + } +#pragma warning restore CS8618 + + /// + /// Expected normalized replay records + /// + public IList Expected { get; set; } = []; + + /// + /// Actual normalized replay records + /// + public IList Actual { get; set; } = []; + + + + #region Load Methods + + /// + /// Load a ReplayVerificationRequest instance from a dictionary. + /// + /// The dictionary containing the data. + /// Optional context with pre/post processing callbacks. + /// The loaded ReplayVerificationRequest instance. + public static ReplayVerificationRequest Load(Dictionary data, LoadContext? context = null) + { + if (context is not null) + { + data = context.ProcessInput(data); + } + + + // Create new instance + var instance = new ReplayVerificationRequest(); + + + if (data.TryGetValue("expected", out var expectedValue) && expectedValue is not null) + { + instance.Expected = LoadExpected(expectedValue, context); + } + + if (data.TryGetValue("actual", out var actualValue) && actualValue is not null) + { + instance.Actual = LoadActual(actualValue, context); + } + + if (context is not null) + { + instance = context.ProcessOutput(instance); + } + return instance; + } + + + /// + /// Load a list of ReplayJournalRecord from a dictionary or list. + /// + public static IList LoadExpected(object data, LoadContext? context) + { + var result = new List(); + + if (data is Dictionary dict) + { + // Convert named dictionary to list + foreach (var kvp in dict) + { + if (kvp.Value is IEnumerable) + { + throw new ArgumentException( + $"Invalid 'expected' format: key '{kvp.Key}' has an array value. " + + $"'expected' must be a flat list of objects or a name-keyed dict — " + + "not a nested {" + kvp.Key + ": [...]} structure."); + } + var itemDict = kvp.Value.GetDictionary(); + if (itemDict.Count > 0) + { + // Value is an object, add name to it + itemDict["name"] = kvp.Key; + result.Add(ReplayJournalRecord.Load(itemDict, context)); + } + else + { + // Value is a scalar, use it as the primary property + var newDict = new Dictionary + { + ["name"] = kvp.Key, + ["kind"] = kvp.Value + }; + result.Add(ReplayJournalRecord.Load(newDict, context)); + } + } + } + else if (data is IEnumerable list) + { + foreach (var item in list) + { + var itemDict = item.GetDictionary(ReplayJournalRecord.ShorthandProperty); + if (itemDict.Count > 0) + { + result.Add(ReplayJournalRecord.Load(itemDict, context)); + } + } + } + + return result; + } + + + /// + /// Load a list of ReplayJournalRecord from a dictionary or list. + /// + public static IList LoadActual(object data, LoadContext? context) + { + var result = new List(); + + if (data is Dictionary dict) + { + // Convert named dictionary to list + foreach (var kvp in dict) + { + if (kvp.Value is IEnumerable) + { + throw new ArgumentException( + $"Invalid 'actual' format: key '{kvp.Key}' has an array value. " + + $"'actual' must be a flat list of objects or a name-keyed dict — " + + "not a nested {" + kvp.Key + ": [...]} structure."); + } + var itemDict = kvp.Value.GetDictionary(); + if (itemDict.Count > 0) + { + // Value is an object, add name to it + itemDict["name"] = kvp.Key; + result.Add(ReplayJournalRecord.Load(itemDict, context)); + } + else + { + // Value is a scalar, use it as the primary property + var newDict = new Dictionary + { + ["name"] = kvp.Key, + ["kind"] = kvp.Value + }; + result.Add(ReplayJournalRecord.Load(newDict, context)); + } + } + } + else if (data is IEnumerable list) + { + foreach (var item in list) + { + var itemDict = item.GetDictionary(ReplayJournalRecord.ShorthandProperty); + if (itemDict.Count > 0) + { + result.Add(ReplayJournalRecord.Load(itemDict, context)); + } + } + } + + return result; + } + + + #endregion + + #region Save Methods + + /// + /// Save the ReplayVerificationRequest instance to a dictionary. + /// + /// Optional context with pre/post processing callbacks. + /// The dictionary representation of this instance. + public Dictionary Save(SaveContext? context = null) + { + var obj = this; + if (context is not null) + { + obj = context.ProcessObject(obj); + } + + + var result = new Dictionary(); + + + result["expected"] = SaveExpected(obj.Expected, context); + + + result["actual"] = SaveActual(obj.Actual, context); + + + if (context is not null) + { + result = context.ProcessDict(result); + } + + return result; + } + + + /// + /// Save a list of ReplayJournalRecord to object or array format. + /// + public static object SaveExpected(IList items, SaveContext? context) + { + context ??= new SaveContext(); + + // This collection type does not have a 'name' property, only array format is supported + return items.Select(item => item.Save(context)).ToList(); + + } + + + /// + /// Save a list of ReplayJournalRecord to object or array format. + /// + public static object SaveActual(IList items, SaveContext? context) + { + context ??= new SaveContext(); + + // This collection type does not have a 'name' property, only array format is supported + return items.Select(item => item.Save(context)).ToList(); + + } + + + /// + /// Convert the ReplayVerificationRequest instance to a YAML string. + /// + /// Optional context with pre/post processing callbacks. + /// The YAML string representation of this instance. + public string ToYaml(SaveContext? context = null) + { + context ??= new SaveContext(); + return context.ToYaml(Save(context)); + } + + /// + /// Convert the ReplayVerificationRequest instance to a JSON string. + /// + /// Optional context with pre/post processing callbacks. + /// Whether to indent the output. Defaults to true. + /// The JSON string representation of this instance. + public string ToJson(SaveContext? context = null, bool indent = true) + { + context ??= new SaveContext(); + return context.ToJson(Save(context), indent); + } + + /// + /// Load a ReplayVerificationRequest instance from a JSON string. + /// + /// The JSON string to parse. + /// Optional context with pre/post processing callbacks. + /// The loaded ReplayVerificationRequest instance. + public static ReplayVerificationRequest FromJson(string json, LoadContext? context = null) + { + using var doc = JsonDocument.Parse(json); + Dictionary dict; + dict = JsonSerializer.Deserialize>(json, JsonUtils.Options) + ?? throw new ArgumentException("Failed to parse JSON as dictionary"); + + return Load(dict, context); + } + + /// + /// Load a ReplayVerificationRequest instance from a YAML string. + /// + /// The YAML string to parse. + /// Optional context with pre/post processing callbacks. + /// The loaded ReplayVerificationRequest instance. + public static ReplayVerificationRequest FromYaml(string yaml, LoadContext? context = null) + { + var dict = YamlUtils.Deserializer.Deserialize>(yaml) + ?? throw new ArgumentException("Failed to parse YAML as dictionary"); + + return Load(dict, context); + } + + #endregion +} diff --git a/runtime/csharp/Prompty.Core/Model/pipeline/ReplayVerificationResult.cs b/runtime/csharp/Prompty.Core/Model/pipeline/ReplayVerificationResult.cs new file mode 100644 index 00000000..2e60e796 --- /dev/null +++ b/runtime/csharp/Prompty.Core/Model/pipeline/ReplayVerificationResult.cs @@ -0,0 +1,265 @@ +// +// Copyright (c) Microsoft. All rights reserved. +using System.Text.Json; +using YamlDotNet.Serialization; + +#pragma warning disable IDE0130 +namespace Prompty.Core; +#pragma warning restore IDE0130 + + /// + /// Result returned by a replay verifier implementation. + /// +public partial class ReplayVerificationResult +{ + /// + /// The shorthand property name for this type, if any. + /// + public static string? ShorthandProperty => null; + + /// + /// Initializes a new instance of . + /// +#pragma warning disable CS8618 + public ReplayVerificationResult() + { + } +#pragma warning restore CS8618 + + /// + /// Replay verification status + /// + public ReplayVerificationStatus Status { get; set; } = ReplayVerificationStatus.Passed; + + /// + /// Record mismatches, empty when verification passed + /// + public IList? Mismatches { get; set; } + + /// + /// Number of expected records + /// + public int ExpectedCount { get; set; } + + /// + /// Number of actual records + /// + public int ActualCount { get; set; } + + + + #region Load Methods + + /// + /// Load a ReplayVerificationResult instance from a dictionary. + /// + /// The dictionary containing the data. + /// Optional context with pre/post processing callbacks. + /// The loaded ReplayVerificationResult instance. + public static ReplayVerificationResult Load(Dictionary data, LoadContext? context = null) + { + if (context is not null) + { + data = context.ProcessInput(data); + } + + + // Create new instance + var instance = new ReplayVerificationResult(); + + + if (data.TryGetValue("status", out var statusValue) && statusValue is not null) + { + instance.Status = Enum.Parse(statusValue?.ToString()!, true); + } + + if (data.TryGetValue("mismatches", out var mismatchesValue) && mismatchesValue is not null) + { + instance.Mismatches = LoadMismatches(mismatchesValue, context); + } + + if (data.TryGetValue("expectedCount", out var expectedCountValue) && expectedCountValue is not null) + { + instance.ExpectedCount = Convert.ToInt32(expectedCountValue); + } + + if (data.TryGetValue("actualCount", out var actualCountValue) && actualCountValue is not null) + { + instance.ActualCount = Convert.ToInt32(actualCountValue); + } + + if (context is not null) + { + instance = context.ProcessOutput(instance); + } + return instance; + } + + + /// + /// Load a list of ReplayMismatch from a dictionary or list. + /// + public static IList LoadMismatches(object data, LoadContext? context) + { + var result = new List(); + + if (data is Dictionary dict) + { + // Convert named dictionary to list + foreach (var kvp in dict) + { + if (kvp.Value is IEnumerable) + { + throw new ArgumentException( + $"Invalid 'mismatches' format: key '{kvp.Key}' has an array value. " + + $"'mismatches' must be a flat list of objects or a name-keyed dict — " + + "not a nested {" + kvp.Key + ": [...]} structure."); + } + var itemDict = kvp.Value.GetDictionary(); + if (itemDict.Count > 0) + { + // Value is an object, add name to it + itemDict["name"] = kvp.Key; + result.Add(ReplayMismatch.Load(itemDict, context)); + } + else + { + // Value is a scalar, use it as the primary property + var newDict = new Dictionary + { + ["name"] = kvp.Key, + ["index"] = kvp.Value + }; + result.Add(ReplayMismatch.Load(newDict, context)); + } + } + } + else if (data is IEnumerable list) + { + foreach (var item in list) + { + var itemDict = item.GetDictionary(ReplayMismatch.ShorthandProperty); + if (itemDict.Count > 0) + { + result.Add(ReplayMismatch.Load(itemDict, context)); + } + } + } + + return result; + } + + + #endregion + + #region Save Methods + + /// + /// Save the ReplayVerificationResult instance to a dictionary. + /// + /// Optional context with pre/post processing callbacks. + /// The dictionary representation of this instance. + public Dictionary Save(SaveContext? context = null) + { + var obj = this; + if (context is not null) + { + obj = context.ProcessObject(obj); + } + + + var result = new Dictionary(); + + + result["status"] = obj.Status.ToString().ToLowerInvariant(); + + + if (obj.Mismatches is not null) + { + result["mismatches"] = SaveMismatches(obj.Mismatches, context); + } + + + result["expectedCount"] = obj.ExpectedCount; + + + result["actualCount"] = obj.ActualCount; + + + if (context is not null) + { + result = context.ProcessDict(result); + } + + return result; + } + + + /// + /// Save a list of ReplayMismatch to object or array format. + /// + public static object SaveMismatches(IList items, SaveContext? context) + { + context ??= new SaveContext(); + + // This collection type does not have a 'name' property, only array format is supported + return items.Select(item => item.Save(context)).ToList(); + + } + + + /// + /// Convert the ReplayVerificationResult instance to a YAML string. + /// + /// Optional context with pre/post processing callbacks. + /// The YAML string representation of this instance. + public string ToYaml(SaveContext? context = null) + { + context ??= new SaveContext(); + return context.ToYaml(Save(context)); + } + + /// + /// Convert the ReplayVerificationResult instance to a JSON string. + /// + /// Optional context with pre/post processing callbacks. + /// Whether to indent the output. Defaults to true. + /// The JSON string representation of this instance. + public string ToJson(SaveContext? context = null, bool indent = true) + { + context ??= new SaveContext(); + return context.ToJson(Save(context), indent); + } + + /// + /// Load a ReplayVerificationResult instance from a JSON string. + /// + /// The JSON string to parse. + /// Optional context with pre/post processing callbacks. + /// The loaded ReplayVerificationResult instance. + public static ReplayVerificationResult FromJson(string json, LoadContext? context = null) + { + using var doc = JsonDocument.Parse(json); + Dictionary dict; + dict = JsonSerializer.Deserialize>(json, JsonUtils.Options) + ?? throw new ArgumentException("Failed to parse JSON as dictionary"); + + return Load(dict, context); + } + + /// + /// Load a ReplayVerificationResult instance from a YAML string. + /// + /// The YAML string to parse. + /// Optional context with pre/post processing callbacks. + /// The loaded ReplayVerificationResult instance. + public static ReplayVerificationResult FromYaml(string yaml, LoadContext? context = null) + { + var dict = YamlUtils.Deserializer.Deserialize>(yaml) + ?? throw new ArgumentException("Failed to parse YAML as dictionary"); + + return Load(dict, context); + } + + #endregion +} diff --git a/runtime/csharp/Prompty.Core/Model/pipeline/ReplayVerificationStatus.cs b/runtime/csharp/Prompty.Core/Model/pipeline/ReplayVerificationStatus.cs new file mode 100644 index 00000000..6146ce98 --- /dev/null +++ b/runtime/csharp/Prompty.Core/Model/pipeline/ReplayVerificationStatus.cs @@ -0,0 +1,18 @@ +// +// +// Code generated by Typra emitter; DO NOT EDIT. + +using System.Text.Json.Serialization; + +namespace Prompty.Core; + +[JsonConverter(typeof(JsonStringEnumConverter))] +public enum ReplayVerificationStatus +{ + [JsonPropertyName("passed")] + Passed, + + [JsonPropertyName("failed")] + Failed, + +} diff --git a/runtime/csharp/Prompty.Core/Model/pipeline/RunTurnRequest.cs b/runtime/csharp/Prompty.Core/Model/pipeline/RunTurnRequest.cs new file mode 100644 index 00000000..168b5c9b --- /dev/null +++ b/runtime/csharp/Prompty.Core/Model/pipeline/RunTurnRequest.cs @@ -0,0 +1,201 @@ +// +// Copyright (c) Microsoft. All rights reserved. +using System.Text.Json; +using YamlDotNet.Serialization; + +#pragma warning disable IDE0130 +namespace Prompty.Core; +#pragma warning restore IDE0130 + + /// + /// Request accepted by a reference turn runner implementation. + /// +public partial class RunTurnRequest +{ + /// + /// The shorthand property name for this type, if any. + /// + public static string? ShorthandProperty => null; + + /// + /// Initializes a new instance of . + /// +#pragma warning disable CS8618 + public RunTurnRequest() + { + } +#pragma warning restore CS8618 + + /// + /// Stable harness session identifier + /// + public string SessionId { get; set; } = string.Empty; + + /// + /// Stable turn identifier within the session + /// + public string TurnId { get; set; } = string.Empty; + + /// + /// Inputs supplied to the deterministic single-turn run + /// + public IDictionary? Inputs { get; set; } + + /// + /// Canonical turn execution options + /// + public TurnOptions? Options { get; set; } + + + + #region Load Methods + + /// + /// Load a RunTurnRequest instance from a dictionary. + /// + /// The dictionary containing the data. + /// Optional context with pre/post processing callbacks. + /// The loaded RunTurnRequest instance. + public static RunTurnRequest Load(Dictionary data, LoadContext? context = null) + { + if (context is not null) + { + data = context.ProcessInput(data); + } + + + // Create new instance + var instance = new RunTurnRequest(); + + + if (data.TryGetValue("sessionId", out var sessionIdValue) && sessionIdValue is not null) + { + instance.SessionId = sessionIdValue?.ToString()!; + } + + if (data.TryGetValue("turnId", out var turnIdValue) && turnIdValue is not null) + { + instance.TurnId = turnIdValue?.ToString()!; + } + + if (data.TryGetValue("inputs", out var inputsValue) && inputsValue is not null) + { + instance.Inputs = inputsValue.GetDictionary()!; + } + + if (data.TryGetValue("options", out var optionsValue) && optionsValue is not null) + { + instance.Options = TurnOptions.Load(optionsValue.GetDictionary(TurnOptions.ShorthandProperty), context); + } + + if (context is not null) + { + instance = context.ProcessOutput(instance); + } + return instance; + } + + + #endregion + + #region Save Methods + + /// + /// Save the RunTurnRequest instance to a dictionary. + /// + /// Optional context with pre/post processing callbacks. + /// The dictionary representation of this instance. + public Dictionary Save(SaveContext? context = null) + { + var obj = this; + if (context is not null) + { + obj = context.ProcessObject(obj); + } + + + var result = new Dictionary(); + + + result["sessionId"] = obj.SessionId; + + + result["turnId"] = obj.TurnId; + + + if (obj.Inputs is not null) + { + result["inputs"] = obj.Inputs; + } + + + if (obj.Options is not null) + { + result["options"] = obj.Options?.Save(context); + } + + + if (context is not null) + { + result = context.ProcessDict(result); + } + + return result; + } + + + /// + /// Convert the RunTurnRequest instance to a YAML string. + /// + /// Optional context with pre/post processing callbacks. + /// The YAML string representation of this instance. + public string ToYaml(SaveContext? context = null) + { + context ??= new SaveContext(); + return context.ToYaml(Save(context)); + } + + /// + /// Convert the RunTurnRequest instance to a JSON string. + /// + /// Optional context with pre/post processing callbacks. + /// Whether to indent the output. Defaults to true. + /// The JSON string representation of this instance. + public string ToJson(SaveContext? context = null, bool indent = true) + { + context ??= new SaveContext(); + return context.ToJson(Save(context), indent); + } + + /// + /// Load a RunTurnRequest instance from a JSON string. + /// + /// The JSON string to parse. + /// Optional context with pre/post processing callbacks. + /// The loaded RunTurnRequest instance. + public static RunTurnRequest FromJson(string json, LoadContext? context = null) + { + using var doc = JsonDocument.Parse(json); + Dictionary dict; + dict = JsonSerializer.Deserialize>(json, JsonUtils.Options) + ?? throw new ArgumentException("Failed to parse JSON as dictionary"); + + return Load(dict, context); + } + + /// + /// Load a RunTurnRequest instance from a YAML string. + /// + /// The YAML string to parse. + /// Optional context with pre/post processing callbacks. + /// The loaded RunTurnRequest instance. + public static RunTurnRequest FromYaml(string yaml, LoadContext? context = null) + { + var dict = YamlUtils.Deserializer.Deserialize>(yaml) + ?? throw new ArgumentException("Failed to parse YAML as dictionary"); + + return Load(dict, context); + } + + #endregion +} diff --git a/runtime/csharp/Prompty.Core/Model/pipeline/RunTurnResult.cs b/runtime/csharp/Prompty.Core/Model/pipeline/RunTurnResult.cs new file mode 100644 index 00000000..a537ef95 --- /dev/null +++ b/runtime/csharp/Prompty.Core/Model/pipeline/RunTurnResult.cs @@ -0,0 +1,377 @@ +// +// Copyright (c) Microsoft. All rights reserved. +using System.Text.Json; +using YamlDotNet.Serialization; + +#pragma warning disable IDE0130 +namespace Prompty.Core; +#pragma warning restore IDE0130 + + /// + /// Result returned by a reference turn runner implementation. + /// +public partial class RunTurnResult +{ + /// + /// The shorthand property name for this type, if any. + /// + public static string? ShorthandProperty => null; + + /// + /// Initializes a new instance of . + /// +#pragma warning disable CS8618 + public RunTurnResult() + { + } +#pragma warning restore CS8618 + + /// + /// Stable harness session identifier + /// + public string SessionId { get; set; } = string.Empty; + + /// + /// Stable turn identifier within the session + /// + public string TurnId { get; set; } = string.Empty; + + /// + /// Final semantic status for the deterministic turn + /// + public RunTurnStatus Status { get; set; } = RunTurnStatus.Success; + + /// + /// Provider-neutral final output returned by the injected model callback + /// + public object? Output { get; set; } + + /// + /// Number of model loop iterations executed + /// + public int Iterations { get; set; } + + /// + /// Host tool results produced during the turn + /// + public IList? ToolResults { get; set; } + + /// + /// Checkpoints created during the turn + /// + public IList? Checkpoints { get; set; } + + + + #region Load Methods + + /// + /// Load a RunTurnResult instance from a dictionary. + /// + /// The dictionary containing the data. + /// Optional context with pre/post processing callbacks. + /// The loaded RunTurnResult instance. + public static RunTurnResult Load(Dictionary data, LoadContext? context = null) + { + if (context is not null) + { + data = context.ProcessInput(data); + } + + + // Create new instance + var instance = new RunTurnResult(); + + + if (data.TryGetValue("sessionId", out var sessionIdValue) && sessionIdValue is not null) + { + instance.SessionId = sessionIdValue?.ToString()!; + } + + if (data.TryGetValue("turnId", out var turnIdValue) && turnIdValue is not null) + { + instance.TurnId = turnIdValue?.ToString()!; + } + + if (data.TryGetValue("status", out var statusValue) && statusValue is not null) + { + instance.Status = Enum.Parse(statusValue?.ToString()!, true); + } + + if (data.TryGetValue("output", out var outputValue) && outputValue is not null) + { + instance.Output = outputValue; + } + + if (data.TryGetValue("iterations", out var iterationsValue) && iterationsValue is not null) + { + instance.Iterations = Convert.ToInt32(iterationsValue); + } + + if (data.TryGetValue("toolResults", out var toolResultsValue) && toolResultsValue is not null) + { + instance.ToolResults = LoadToolResults(toolResultsValue, context); + } + + if (data.TryGetValue("checkpoints", out var checkpointsValue) && checkpointsValue is not null) + { + instance.Checkpoints = LoadCheckpoints(checkpointsValue, context); + } + + if (context is not null) + { + instance = context.ProcessOutput(instance); + } + return instance; + } + + + /// + /// Load a list of HostToolResult from a dictionary or list. + /// + public static IList LoadToolResults(object data, LoadContext? context) + { + var result = new List(); + + if (data is Dictionary dict) + { + // Convert named dictionary to list + foreach (var kvp in dict) + { + if (kvp.Value is IEnumerable) + { + throw new ArgumentException( + $"Invalid 'toolResults' format: key '{kvp.Key}' has an array value. " + + $"'toolResults' must be a flat list of objects or a name-keyed dict — " + + "not a nested {" + kvp.Key + ": [...]} structure."); + } + var itemDict = kvp.Value.GetDictionary(); + if (itemDict.Count > 0) + { + // Value is an object, add name to it + itemDict["name"] = kvp.Key; + result.Add(HostToolResult.Load(itemDict, context)); + } + else + { + // Value is a scalar, use it as the primary property + var newDict = new Dictionary + { + ["name"] = kvp.Key, + ["requestId"] = kvp.Value + }; + result.Add(HostToolResult.Load(newDict, context)); + } + } + } + else if (data is IEnumerable list) + { + foreach (var item in list) + { + var itemDict = item.GetDictionary(HostToolResult.ShorthandProperty); + if (itemDict.Count > 0) + { + result.Add(HostToolResult.Load(itemDict, context)); + } + } + } + + return result; + } + + + /// + /// Load a list of Checkpoint from a dictionary or list. + /// + public static IList LoadCheckpoints(object data, LoadContext? context) + { + var result = new List(); + + if (data is Dictionary dict) + { + // Convert named dictionary to list + foreach (var kvp in dict) + { + if (kvp.Value is IEnumerable) + { + throw new ArgumentException( + $"Invalid 'checkpoints' format: key '{kvp.Key}' has an array value. " + + $"'checkpoints' must be a flat list of objects or a name-keyed dict — " + + "not a nested {" + kvp.Key + ": [...]} structure."); + } + var itemDict = kvp.Value.GetDictionary(); + if (itemDict.Count > 0) + { + // Value is an object, add name to it + itemDict["name"] = kvp.Key; + result.Add(Checkpoint.Load(itemDict, context)); + } + else + { + // Value is a scalar, use it as the primary property + var newDict = new Dictionary + { + ["name"] = kvp.Key, + ["id"] = kvp.Value + }; + result.Add(Checkpoint.Load(newDict, context)); + } + } + } + else if (data is IEnumerable list) + { + foreach (var item in list) + { + var itemDict = item.GetDictionary(Checkpoint.ShorthandProperty); + if (itemDict.Count > 0) + { + result.Add(Checkpoint.Load(itemDict, context)); + } + } + } + + return result; + } + + + #endregion + + #region Save Methods + + /// + /// Save the RunTurnResult instance to a dictionary. + /// + /// Optional context with pre/post processing callbacks. + /// The dictionary representation of this instance. + public Dictionary Save(SaveContext? context = null) + { + var obj = this; + if (context is not null) + { + obj = context.ProcessObject(obj); + } + + + var result = new Dictionary(); + + + result["sessionId"] = obj.SessionId; + + + result["turnId"] = obj.TurnId; + + + result["status"] = obj.Status.ToString().ToLowerInvariant(); + + + if (obj.Output is not null) + { + result["output"] = obj.Output; + } + + + result["iterations"] = obj.Iterations; + + + if (obj.ToolResults is not null) + { + result["toolResults"] = SaveToolResults(obj.ToolResults, context); + } + + + if (obj.Checkpoints is not null) + { + result["checkpoints"] = SaveCheckpoints(obj.Checkpoints, context); + } + + + if (context is not null) + { + result = context.ProcessDict(result); + } + + return result; + } + + + /// + /// Save a list of HostToolResult to object or array format. + /// + public static object SaveToolResults(IList items, SaveContext? context) + { + context ??= new SaveContext(); + + // This collection type does not have a 'name' property, only array format is supported + return items.Select(item => item.Save(context)).ToList(); + + } + + + /// + /// Save a list of Checkpoint to object or array format. + /// + public static object SaveCheckpoints(IList items, SaveContext? context) + { + context ??= new SaveContext(); + + // This collection type does not have a 'name' property, only array format is supported + return items.Select(item => item.Save(context)).ToList(); + + } + + + /// + /// Convert the RunTurnResult instance to a YAML string. + /// + /// Optional context with pre/post processing callbacks. + /// The YAML string representation of this instance. + public string ToYaml(SaveContext? context = null) + { + context ??= new SaveContext(); + return context.ToYaml(Save(context)); + } + + /// + /// Convert the RunTurnResult instance to a JSON string. + /// + /// Optional context with pre/post processing callbacks. + /// Whether to indent the output. Defaults to true. + /// The JSON string representation of this instance. + public string ToJson(SaveContext? context = null, bool indent = true) + { + context ??= new SaveContext(); + return context.ToJson(Save(context), indent); + } + + /// + /// Load a RunTurnResult instance from a JSON string. + /// + /// The JSON string to parse. + /// Optional context with pre/post processing callbacks. + /// The loaded RunTurnResult instance. + public static RunTurnResult FromJson(string json, LoadContext? context = null) + { + using var doc = JsonDocument.Parse(json); + Dictionary dict; + dict = JsonSerializer.Deserialize>(json, JsonUtils.Options) + ?? throw new ArgumentException("Failed to parse JSON as dictionary"); + + return Load(dict, context); + } + + /// + /// Load a RunTurnResult instance from a YAML string. + /// + /// The YAML string to parse. + /// Optional context with pre/post processing callbacks. + /// The loaded RunTurnResult instance. + public static RunTurnResult FromYaml(string yaml, LoadContext? context = null) + { + var dict = YamlUtils.Deserializer.Deserialize>(yaml) + ?? throw new ArgumentException("Failed to parse YAML as dictionary"); + + return Load(dict, context); + } + + #endregion +} diff --git a/runtime/csharp/Prompty.Core/Model/pipeline/RunTurnStatus.cs b/runtime/csharp/Prompty.Core/Model/pipeline/RunTurnStatus.cs new file mode 100644 index 00000000..1cc3826c --- /dev/null +++ b/runtime/csharp/Prompty.Core/Model/pipeline/RunTurnStatus.cs @@ -0,0 +1,21 @@ +// +// +// Code generated by Typra emitter; DO NOT EDIT. + +using System.Text.Json.Serialization; + +namespace Prompty.Core; + +[JsonConverter(typeof(JsonStringEnumConverter))] +public enum RunTurnStatus +{ + [JsonPropertyName("success")] + Success, + + [JsonPropertyName("error")] + Error, + + [JsonPropertyName("cancelled")] + Cancelled, + +} diff --git a/runtime/csharp/Prompty.Core/Model/pipeline/TurnModelRequest.cs b/runtime/csharp/Prompty.Core/Model/pipeline/TurnModelRequest.cs new file mode 100644 index 00000000..31a4b223 --- /dev/null +++ b/runtime/csharp/Prompty.Core/Model/pipeline/TurnModelRequest.cs @@ -0,0 +1,301 @@ +// +// Copyright (c) Microsoft. All rights reserved. +using System.Text.Json; +using YamlDotNet.Serialization; + +#pragma warning disable IDE0130 +namespace Prompty.Core; +#pragma warning restore IDE0130 + + /// + /// Request passed by the reference turn runner to the injected model callback. + /// + /// The runner owns deterministic orchestration semantics; model/provider-specific + /// + /// execution stays behind this callback boundary. + /// +public partial class TurnModelRequest +{ + /// + /// The shorthand property name for this type, if any. + /// + public static string? ShorthandProperty => null; + + /// + /// Initializes a new instance of . + /// +#pragma warning disable CS8618 + public TurnModelRequest() + { + } +#pragma warning restore CS8618 + + /// + /// Stable harness session identifier + /// + public string SessionId { get; set; } = string.Empty; + + /// + /// Stable turn identifier within the session + /// + public string TurnId { get; set; } = string.Empty; + + /// + /// Zero-based model loop iteration + /// + public int Iteration { get; set; } + + /// + /// Inputs supplied to the deterministic single-turn run + /// + public IDictionary? Inputs { get; set; } + + /// + /// Canonical turn execution options + /// + public TurnOptions? Options { get; set; } + + /// + /// Host tool results produced by the previous iteration + /// + public IList? ToolResults { get; set; } + + + + #region Load Methods + + /// + /// Load a TurnModelRequest instance from a dictionary. + /// + /// The dictionary containing the data. + /// Optional context with pre/post processing callbacks. + /// The loaded TurnModelRequest instance. + public static TurnModelRequest Load(Dictionary data, LoadContext? context = null) + { + if (context is not null) + { + data = context.ProcessInput(data); + } + + + // Create new instance + var instance = new TurnModelRequest(); + + + if (data.TryGetValue("sessionId", out var sessionIdValue) && sessionIdValue is not null) + { + instance.SessionId = sessionIdValue?.ToString()!; + } + + if (data.TryGetValue("turnId", out var turnIdValue) && turnIdValue is not null) + { + instance.TurnId = turnIdValue?.ToString()!; + } + + if (data.TryGetValue("iteration", out var iterationValue) && iterationValue is not null) + { + instance.Iteration = Convert.ToInt32(iterationValue); + } + + if (data.TryGetValue("inputs", out var inputsValue) && inputsValue is not null) + { + instance.Inputs = inputsValue.GetDictionary()!; + } + + if (data.TryGetValue("options", out var optionsValue) && optionsValue is not null) + { + instance.Options = TurnOptions.Load(optionsValue.GetDictionary(TurnOptions.ShorthandProperty), context); + } + + if (data.TryGetValue("toolResults", out var toolResultsValue) && toolResultsValue is not null) + { + instance.ToolResults = LoadToolResults(toolResultsValue, context); + } + + if (context is not null) + { + instance = context.ProcessOutput(instance); + } + return instance; + } + + + /// + /// Load a list of HostToolResult from a dictionary or list. + /// + public static IList LoadToolResults(object data, LoadContext? context) + { + var result = new List(); + + if (data is Dictionary dict) + { + // Convert named dictionary to list + foreach (var kvp in dict) + { + if (kvp.Value is IEnumerable) + { + throw new ArgumentException( + $"Invalid 'toolResults' format: key '{kvp.Key}' has an array value. " + + $"'toolResults' must be a flat list of objects or a name-keyed dict — " + + "not a nested {" + kvp.Key + ": [...]} structure."); + } + var itemDict = kvp.Value.GetDictionary(); + if (itemDict.Count > 0) + { + // Value is an object, add name to it + itemDict["name"] = kvp.Key; + result.Add(HostToolResult.Load(itemDict, context)); + } + else + { + // Value is a scalar, use it as the primary property + var newDict = new Dictionary + { + ["name"] = kvp.Key, + ["requestId"] = kvp.Value + }; + result.Add(HostToolResult.Load(newDict, context)); + } + } + } + else if (data is IEnumerable list) + { + foreach (var item in list) + { + var itemDict = item.GetDictionary(HostToolResult.ShorthandProperty); + if (itemDict.Count > 0) + { + result.Add(HostToolResult.Load(itemDict, context)); + } + } + } + + return result; + } + + + #endregion + + #region Save Methods + + /// + /// Save the TurnModelRequest instance to a dictionary. + /// + /// Optional context with pre/post processing callbacks. + /// The dictionary representation of this instance. + public Dictionary Save(SaveContext? context = null) + { + var obj = this; + if (context is not null) + { + obj = context.ProcessObject(obj); + } + + + var result = new Dictionary(); + + + result["sessionId"] = obj.SessionId; + + + result["turnId"] = obj.TurnId; + + + result["iteration"] = obj.Iteration; + + + if (obj.Inputs is not null) + { + result["inputs"] = obj.Inputs; + } + + + if (obj.Options is not null) + { + result["options"] = obj.Options?.Save(context); + } + + + if (obj.ToolResults is not null) + { + result["toolResults"] = SaveToolResults(obj.ToolResults, context); + } + + + if (context is not null) + { + result = context.ProcessDict(result); + } + + return result; + } + + + /// + /// Save a list of HostToolResult to object or array format. + /// + public static object SaveToolResults(IList items, SaveContext? context) + { + context ??= new SaveContext(); + + // This collection type does not have a 'name' property, only array format is supported + return items.Select(item => item.Save(context)).ToList(); + + } + + + /// + /// Convert the TurnModelRequest instance to a YAML string. + /// + /// Optional context with pre/post processing callbacks. + /// The YAML string representation of this instance. + public string ToYaml(SaveContext? context = null) + { + context ??= new SaveContext(); + return context.ToYaml(Save(context)); + } + + /// + /// Convert the TurnModelRequest instance to a JSON string. + /// + /// Optional context with pre/post processing callbacks. + /// Whether to indent the output. Defaults to true. + /// The JSON string representation of this instance. + public string ToJson(SaveContext? context = null, bool indent = true) + { + context ??= new SaveContext(); + return context.ToJson(Save(context), indent); + } + + /// + /// Load a TurnModelRequest instance from a JSON string. + /// + /// The JSON string to parse. + /// Optional context with pre/post processing callbacks. + /// The loaded TurnModelRequest instance. + public static TurnModelRequest FromJson(string json, LoadContext? context = null) + { + using var doc = JsonDocument.Parse(json); + Dictionary dict; + dict = JsonSerializer.Deserialize>(json, JsonUtils.Options) + ?? throw new ArgumentException("Failed to parse JSON as dictionary"); + + return Load(dict, context); + } + + /// + /// Load a TurnModelRequest instance from a YAML string. + /// + /// The YAML string to parse. + /// Optional context with pre/post processing callbacks. + /// The loaded TurnModelRequest instance. + public static TurnModelRequest FromYaml(string yaml, LoadContext? context = null) + { + var dict = YamlUtils.Deserializer.Deserialize>(yaml) + ?? throw new ArgumentException("Failed to parse YAML as dictionary"); + + return Load(dict, context); + } + + #endregion +} diff --git a/runtime/csharp/Prompty.Core/Model/pipeline/TurnModelResponse.cs b/runtime/csharp/Prompty.Core/Model/pipeline/TurnModelResponse.cs new file mode 100644 index 00000000..2693462c --- /dev/null +++ b/runtime/csharp/Prompty.Core/Model/pipeline/TurnModelResponse.cs @@ -0,0 +1,258 @@ +// +// Copyright (c) Microsoft. All rights reserved. +using System.Text.Json; +using YamlDotNet.Serialization; + +#pragma warning disable IDE0130 +namespace Prompty.Core; +#pragma warning restore IDE0130 + + /// + /// Response returned by the injected model callback to the reference turn runner. + /// +public partial class TurnModelResponse +{ + /// + /// The shorthand property name for this type, if any. + /// + public static string? ShorthandProperty => null; + + /// + /// Initializes a new instance of . + /// +#pragma warning disable CS8618 + public TurnModelResponse() + { + } +#pragma warning restore CS8618 + + /// + /// Provider-neutral final model output for the turn when no more tools are requested + /// + public object? Output { get; set; } + + /// + /// Host tool execution requests emitted by the model callback + /// + public IList? ToolRequests { get; set; } + + /// + /// Additional deterministic state to merge into the iteration checkpoint + /// + public IDictionary? CheckpointState { get; set; } + + + + #region Load Methods + + /// + /// Load a TurnModelResponse instance from a dictionary. + /// + /// The dictionary containing the data. + /// Optional context with pre/post processing callbacks. + /// The loaded TurnModelResponse instance. + public static TurnModelResponse Load(Dictionary data, LoadContext? context = null) + { + if (context is not null) + { + data = context.ProcessInput(data); + } + + + // Create new instance + var instance = new TurnModelResponse(); + + + if (data.TryGetValue("output", out var outputValue) && outputValue is not null) + { + instance.Output = outputValue; + } + + if (data.TryGetValue("toolRequests", out var toolRequestsValue) && toolRequestsValue is not null) + { + instance.ToolRequests = LoadToolRequests(toolRequestsValue, context); + } + + if (data.TryGetValue("checkpointState", out var checkpointStateValue) && checkpointStateValue is not null) + { + instance.CheckpointState = checkpointStateValue.GetDictionary()!; + } + + if (context is not null) + { + instance = context.ProcessOutput(instance); + } + return instance; + } + + + /// + /// Load a list of HostToolRequest from a dictionary or list. + /// + public static IList LoadToolRequests(object data, LoadContext? context) + { + var result = new List(); + + if (data is Dictionary dict) + { + // Convert named dictionary to list + foreach (var kvp in dict) + { + if (kvp.Value is IEnumerable) + { + throw new ArgumentException( + $"Invalid 'toolRequests' format: key '{kvp.Key}' has an array value. " + + $"'toolRequests' must be a flat list of objects or a name-keyed dict — " + + "not a nested {" + kvp.Key + ": [...]} structure."); + } + var itemDict = kvp.Value.GetDictionary(); + if (itemDict.Count > 0) + { + // Value is an object, add name to it + itemDict["name"] = kvp.Key; + result.Add(HostToolRequest.Load(itemDict, context)); + } + else + { + // Value is a scalar, use it as the primary property + var newDict = new Dictionary + { + ["name"] = kvp.Key, + ["requestId"] = kvp.Value + }; + result.Add(HostToolRequest.Load(newDict, context)); + } + } + } + else if (data is IEnumerable list) + { + foreach (var item in list) + { + var itemDict = item.GetDictionary(HostToolRequest.ShorthandProperty); + if (itemDict.Count > 0) + { + result.Add(HostToolRequest.Load(itemDict, context)); + } + } + } + + return result; + } + + + #endregion + + #region Save Methods + + /// + /// Save the TurnModelResponse instance to a dictionary. + /// + /// Optional context with pre/post processing callbacks. + /// The dictionary representation of this instance. + public Dictionary Save(SaveContext? context = null) + { + var obj = this; + if (context is not null) + { + obj = context.ProcessObject(obj); + } + + + var result = new Dictionary(); + + + if (obj.Output is not null) + { + result["output"] = obj.Output; + } + + + if (obj.ToolRequests is not null) + { + result["toolRequests"] = SaveToolRequests(obj.ToolRequests, context); + } + + + if (obj.CheckpointState is not null) + { + result["checkpointState"] = obj.CheckpointState; + } + + + if (context is not null) + { + result = context.ProcessDict(result); + } + + return result; + } + + + /// + /// Save a list of HostToolRequest to object or array format. + /// + public static object SaveToolRequests(IList items, SaveContext? context) + { + context ??= new SaveContext(); + + // This collection type does not have a 'name' property, only array format is supported + return items.Select(item => item.Save(context)).ToList(); + + } + + + /// + /// Convert the TurnModelResponse instance to a YAML string. + /// + /// Optional context with pre/post processing callbacks. + /// The YAML string representation of this instance. + public string ToYaml(SaveContext? context = null) + { + context ??= new SaveContext(); + return context.ToYaml(Save(context)); + } + + /// + /// Convert the TurnModelResponse instance to a JSON string. + /// + /// Optional context with pre/post processing callbacks. + /// Whether to indent the output. Defaults to true. + /// The JSON string representation of this instance. + public string ToJson(SaveContext? context = null, bool indent = true) + { + context ??= new SaveContext(); + return context.ToJson(Save(context), indent); + } + + /// + /// Load a TurnModelResponse instance from a JSON string. + /// + /// The JSON string to parse. + /// Optional context with pre/post processing callbacks. + /// The loaded TurnModelResponse instance. + public static TurnModelResponse FromJson(string json, LoadContext? context = null) + { + using var doc = JsonDocument.Parse(json); + Dictionary dict; + dict = JsonSerializer.Deserialize>(json, JsonUtils.Options) + ?? throw new ArgumentException("Failed to parse JSON as dictionary"); + + return Load(dict, context); + } + + /// + /// Load a TurnModelResponse instance from a YAML string. + /// + /// The YAML string to parse. + /// Optional context with pre/post processing callbacks. + /// The loaded TurnModelResponse instance. + public static TurnModelResponse FromYaml(string yaml, LoadContext? context = null) + { + var dict = YamlUtils.Deserializer.Deserialize>(yaml) + ?? throw new ArgumentException("Failed to parse YAML as dictionary"); + + return Load(dict, context); + } + + #endregion +} diff --git a/runtime/csharp/Prompty.Core/Model/pipeline/TurnOptions.cs b/runtime/csharp/Prompty.Core/Model/pipeline/TurnOptions.cs index 99d25995..ae61154b 100644 --- a/runtime/csharp/Prompty.Core/Model/pipeline/TurnOptions.cs +++ b/runtime/csharp/Prompty.Core/Model/pipeline/TurnOptions.cs @@ -1,3 +1,4 @@ +// // Copyright (c) Microsoft. All rights reserved. using System.Text.Json; using YamlDotNet.Serialization; @@ -6,17 +7,17 @@ namespace Prompty.Core; #pragma warning restore IDE0130 -/// -/// Configuration for the agent loop's turn() function. Controls iteration -/// -/// limits, retry policy, context management, and execution behavior. -/// -/// Runtimes accept these as either a TurnOptions object or individual -/// -/// keyword/named parameters — the TypeSpec model defines the canonical -/// -/// field set. -/// + /// + /// Configuration for the agent loop's turn() function. Controls iteration + /// + /// limits, retry policy, context management, and execution behavior. + /// + /// Runtimes accept these as either a TurnOptions object or individual + /// + /// keyword/named parameters — the TypeSpec model defines the canonical + /// + /// field set. + /// public partial class TurnOptions { /// diff --git a/runtime/csharp/Prompty.Core/Model/streaming/StreamOptions.cs b/runtime/csharp/Prompty.Core/Model/streaming/StreamOptions.cs index 02fb105d..200c46e7 100644 --- a/runtime/csharp/Prompty.Core/Model/streaming/StreamOptions.cs +++ b/runtime/csharp/Prompty.Core/Model/streaming/StreamOptions.cs @@ -1,3 +1,4 @@ +// // Copyright (c) Microsoft. All rights reserved. using System.Text.Json; using YamlDotNet.Serialization; @@ -6,11 +7,11 @@ namespace Prompty.Core; #pragma warning restore IDE0130 -/// -/// Options controlling streaming behavior for LLM API calls. -/// -/// Passed alongside the model options when streaming is enabled. -/// + /// + /// Options controlling streaming behavior for LLM API calls. + /// + /// Passed alongside the model options when streaming is enabled. + /// public partial class StreamOptions { /// diff --git a/runtime/csharp/Prompty.Core/Model/template/FormatConfig.cs b/runtime/csharp/Prompty.Core/Model/template/FormatConfig.cs index 7eeab289..ee231d0a 100644 --- a/runtime/csharp/Prompty.Core/Model/template/FormatConfig.cs +++ b/runtime/csharp/Prompty.Core/Model/template/FormatConfig.cs @@ -1,3 +1,4 @@ +// // Copyright (c) Microsoft. All rights reserved. using System.Text.Json; using YamlDotNet.Serialization; @@ -6,9 +7,9 @@ namespace Prompty.Core; #pragma warning restore IDE0130 -/// -/// Template format definition -/// + /// + /// Template format definition + /// public partial class FormatConfig { /// diff --git a/runtime/csharp/Prompty.Core/Model/template/ParserConfig.cs b/runtime/csharp/Prompty.Core/Model/template/ParserConfig.cs index 04616f9d..5ffceb02 100644 --- a/runtime/csharp/Prompty.Core/Model/template/ParserConfig.cs +++ b/runtime/csharp/Prompty.Core/Model/template/ParserConfig.cs @@ -1,3 +1,4 @@ +// // Copyright (c) Microsoft. All rights reserved. using System.Text.Json; using YamlDotNet.Serialization; @@ -6,9 +7,9 @@ namespace Prompty.Core; #pragma warning restore IDE0130 -/// -/// Template parser definition -/// + /// + /// Template parser definition + /// public partial class ParserConfig { /// diff --git a/runtime/csharp/Prompty.Core/Model/template/Template.cs b/runtime/csharp/Prompty.Core/Model/template/Template.cs index 2484a19c..e35e379e 100644 --- a/runtime/csharp/Prompty.Core/Model/template/Template.cs +++ b/runtime/csharp/Prompty.Core/Model/template/Template.cs @@ -1,3 +1,4 @@ +// // Copyright (c) Microsoft. All rights reserved. using System.Text.Json; using YamlDotNet.Serialization; @@ -6,19 +7,19 @@ namespace Prompty.Core; #pragma warning restore IDE0130 -/// -/// Template model for defining prompt templates. -/// -/// This model specifies the rendering engine used for slot filling prompts, -/// -/// the parser used to process the rendered template into API-compatible format, -/// -/// and additional options for the template engine. -/// -/// It allows for the creation of reusable templates that can be filled with dynamic data -/// -/// and processed to generate prompts for AI models. -/// + /// + /// Template model for defining prompt templates. + /// + /// This model specifies the rendering engine used for slot filling prompts, + /// + /// the parser used to process the rendered template into API-compatible format, + /// + /// and additional options for the template engine. + /// + /// It allows for the creation of reusable templates that can be filled with dynamic data + /// + /// and processed to generate prompts for AI models. + /// public partial class Template { /// diff --git a/runtime/csharp/Prompty.Core/Model/tools/Binding.cs b/runtime/csharp/Prompty.Core/Model/tools/Binding.cs index 1990e8f3..65e896f4 100644 --- a/runtime/csharp/Prompty.Core/Model/tools/Binding.cs +++ b/runtime/csharp/Prompty.Core/Model/tools/Binding.cs @@ -1,3 +1,4 @@ +// // Copyright (c) Microsoft. All rights reserved. using System.Text.Json; using YamlDotNet.Serialization; @@ -6,9 +7,9 @@ namespace Prompty.Core; #pragma warning restore IDE0130 -/// -/// Represents a binding between an input property and a tool parameter. -/// + /// + /// Represents a binding between an input property and a tool parameter. + /// public partial class Binding { /// diff --git a/runtime/csharp/Prompty.Core/Model/tools/CustomTool.cs b/runtime/csharp/Prompty.Core/Model/tools/CustomTool.cs index f6cef50c..f6b0ff8f 100644 --- a/runtime/csharp/Prompty.Core/Model/tools/CustomTool.cs +++ b/runtime/csharp/Prompty.Core/Model/tools/CustomTool.cs @@ -1,3 +1,4 @@ +// // Copyright (c) Microsoft. All rights reserved. using System.Text.Json; using YamlDotNet.Serialization; @@ -6,17 +7,17 @@ namespace Prompty.Core; #pragma warning restore IDE0130 -/// -/// Represents a generic server tool that runs on a server -/// -/// This tool kind is designed for operations that require server-side execution -/// -/// It may include features such as authentication, data storage, and long-running processes -/// -/// This tool kind is ideal for tasks that involve complex computations or access to secure resources -/// -/// Server tools can be used to offload heavy processing from client applications -/// + /// + /// Represents a generic server tool that runs on a server + /// + /// This tool kind is designed for operations that require server-side execution + /// + /// It may include features such as authentication, data storage, and long-running processes + /// + /// This tool kind is ideal for tasks that involve complex computations or access to secure resources + /// + /// Server tools can be used to offload heavy processing from client applications + /// public partial class CustomTool : Tool { /// diff --git a/runtime/csharp/Prompty.Core/Model/tools/FunctionTool.cs b/runtime/csharp/Prompty.Core/Model/tools/FunctionTool.cs index 6a8bb165..9d2a5e69 100644 --- a/runtime/csharp/Prompty.Core/Model/tools/FunctionTool.cs +++ b/runtime/csharp/Prompty.Core/Model/tools/FunctionTool.cs @@ -1,3 +1,4 @@ +// // Copyright (c) Microsoft. All rights reserved. using System.Text.Json; using YamlDotNet.Serialization; @@ -6,9 +7,9 @@ namespace Prompty.Core; #pragma warning restore IDE0130 -/// -/// Represents a local function tool. -/// + /// + /// Represents a local function tool. + /// public partial class FunctionTool : Tool { /// diff --git a/runtime/csharp/Prompty.Core/Model/tools/McpApprovalMode.cs b/runtime/csharp/Prompty.Core/Model/tools/McpApprovalMode.cs index de02042f..bd69fd5c 100644 --- a/runtime/csharp/Prompty.Core/Model/tools/McpApprovalMode.cs +++ b/runtime/csharp/Prompty.Core/Model/tools/McpApprovalMode.cs @@ -1,3 +1,4 @@ +// // Copyright (c) Microsoft. All rights reserved. using System.Text.Json; using YamlDotNet.Serialization; @@ -6,13 +7,13 @@ namespace Prompty.Core; #pragma warning restore IDE0130 -/// -/// The approval mode for MCP server tools. -/// -/// When kind is "specify", use alwaysRequireApprovalTools and neverRequireApprovalTools -/// -/// to control per-tool approval. For "always" and "never", those fields are ignored. -/// + /// + /// The approval mode for MCP server tools. + /// + /// When kind is "specify", use alwaysRequireApprovalTools and neverRequireApprovalTools + /// + /// to control per-tool approval. For "always" and "never", those fields are ignored. + /// public partial class McpApprovalMode { /// diff --git a/runtime/csharp/Prompty.Core/Model/tools/McpApprovalModeKind.cs b/runtime/csharp/Prompty.Core/Model/tools/McpApprovalModeKind.cs index e1c2a5cc..fb978c8e 100644 --- a/runtime/csharp/Prompty.Core/Model/tools/McpApprovalModeKind.cs +++ b/runtime/csharp/Prompty.Core/Model/tools/McpApprovalModeKind.cs @@ -1,5 +1,6 @@ +// // -// Code generated by Prompty emitter; DO NOT EDIT. +// Code generated by Typra emitter; DO NOT EDIT. using System.Text.Json.Serialization; diff --git a/runtime/csharp/Prompty.Core/Model/tools/McpTool.cs b/runtime/csharp/Prompty.Core/Model/tools/McpTool.cs index 8ce8e077..b8e3cb6d 100644 --- a/runtime/csharp/Prompty.Core/Model/tools/McpTool.cs +++ b/runtime/csharp/Prompty.Core/Model/tools/McpTool.cs @@ -1,3 +1,4 @@ +// // Copyright (c) Microsoft. All rights reserved. using System.Text.Json; using YamlDotNet.Serialization; @@ -6,9 +7,9 @@ namespace Prompty.Core; #pragma warning restore IDE0130 -/// -/// The MCP Server tool. -/// + /// + /// The MCP Server tool. + /// public partial class McpTool : Tool { /// diff --git a/runtime/csharp/Prompty.Core/Model/tools/OpenApiTool.cs b/runtime/csharp/Prompty.Core/Model/tools/OpenApiTool.cs index b895d92b..6cc42578 100644 --- a/runtime/csharp/Prompty.Core/Model/tools/OpenApiTool.cs +++ b/runtime/csharp/Prompty.Core/Model/tools/OpenApiTool.cs @@ -1,3 +1,4 @@ +// // Copyright (c) Microsoft. All rights reserved. using System.Text.Json; using YamlDotNet.Serialization; @@ -6,8 +7,8 @@ namespace Prompty.Core; #pragma warning restore IDE0130 -/// -/// + /// + /// public partial class OpenApiTool : Tool { /// diff --git a/runtime/csharp/Prompty.Core/Model/tools/PromptyTool.cs b/runtime/csharp/Prompty.Core/Model/tools/PromptyTool.cs index b621e826..02dec593 100644 --- a/runtime/csharp/Prompty.Core/Model/tools/PromptyTool.cs +++ b/runtime/csharp/Prompty.Core/Model/tools/PromptyTool.cs @@ -1,3 +1,4 @@ +// // Copyright (c) Microsoft. All rights reserved. using System.Text.Json; using YamlDotNet.Serialization; @@ -6,13 +7,13 @@ namespace Prompty.Core; #pragma warning restore IDE0130 -/// -/// A tool that references another .prompty file to be invoked as a tool. -/// -/// The child prompty is executed as a single prompt invocation. Nested agent -/// -/// loops are intentionally not started from PromptyTool. -/// + /// + /// A tool that references another .prompty file to be invoked as a tool. + /// + /// The child prompty is executed as a single prompt invocation. Nested agent + /// + /// loops are intentionally not started from PromptyTool. + /// public partial class PromptyTool : Tool { /// diff --git a/runtime/csharp/Prompty.Core/Model/tools/Tool.cs b/runtime/csharp/Prompty.Core/Model/tools/Tool.cs index 3a19125c..f37e9ca0 100644 --- a/runtime/csharp/Prompty.Core/Model/tools/Tool.cs +++ b/runtime/csharp/Prompty.Core/Model/tools/Tool.cs @@ -1,3 +1,4 @@ +// // Copyright (c) Microsoft. All rights reserved. using System.Text.Json; using YamlDotNet.Serialization; @@ -6,9 +7,9 @@ namespace Prompty.Core; #pragma warning restore IDE0130 -/// -/// Represents a tool that can be used in prompts. -/// + /// + /// Represents a tool that can be used in prompts. + /// public abstract partial class Tool { /// diff --git a/runtime/csharp/Prompty.Core/Model/tools/ToolContext.cs b/runtime/csharp/Prompty.Core/Model/tools/ToolContext.cs index 7e7e3d72..e94becdd 100644 --- a/runtime/csharp/Prompty.Core/Model/tools/ToolContext.cs +++ b/runtime/csharp/Prompty.Core/Model/tools/ToolContext.cs @@ -1,3 +1,4 @@ +// // Copyright (c) Microsoft. All rights reserved. using System.Text.Json; using YamlDotNet.Serialization; @@ -6,13 +7,13 @@ namespace Prompty.Core; #pragma warning restore IDE0130 -/// -/// Context passed to tool handlers during agent loop execution. Provides -/// -/// access to the agent configuration, current conversation state, and -/// -/// arbitrary metadata for tool implementations that need broader context. -/// + /// + /// Context passed to tool handlers during agent loop execution. Provides + /// + /// access to the agent configuration, current conversation state, and + /// + /// arbitrary metadata for tool implementations that need broader context. + /// public partial class ToolContext { /// diff --git a/runtime/csharp/Prompty.Core/Model/tools/ToolDispatchResult.cs b/runtime/csharp/Prompty.Core/Model/tools/ToolDispatchResult.cs index 5fa86f43..6508ef0c 100644 --- a/runtime/csharp/Prompty.Core/Model/tools/ToolDispatchResult.cs +++ b/runtime/csharp/Prompty.Core/Model/tools/ToolDispatchResult.cs @@ -1,3 +1,4 @@ +// // Copyright (c) Microsoft. All rights reserved. using System.Text.Json; using YamlDotNet.Serialization; @@ -6,13 +7,13 @@ namespace Prompty.Core; #pragma warning restore IDE0130 -/// -/// The result of dispatching a single tool call. Pairs the tool call -/// -/// identifier with the tool's name and result for correlation in the -/// -/// agent loop's message assembly. -/// + /// + /// The result of dispatching a single tool call. Pairs the tool call + /// + /// identifier with the tool's name and result for correlation in the + /// + /// agent loop's message assembly. + /// public partial class ToolDispatchResult { /// diff --git a/runtime/csharp/Prompty.Core/Model/tracing/TraceFile.cs b/runtime/csharp/Prompty.Core/Model/tracing/TraceFile.cs index e931d2c5..841aef88 100644 --- a/runtime/csharp/Prompty.Core/Model/tracing/TraceFile.cs +++ b/runtime/csharp/Prompty.Core/Model/tracing/TraceFile.cs @@ -1,3 +1,4 @@ +// // Copyright (c) Microsoft. All rights reserved. using System.Text.Json; using YamlDotNet.Serialization; @@ -6,9 +7,9 @@ namespace Prompty.Core; #pragma warning restore IDE0130 -/// -/// The top-level .tracy file structure written by the file backend (§3.6.1). -/// + /// + /// The top-level .tracy file structure written by the file backend (§3.6.1). + /// public partial class TraceFile { /// diff --git a/runtime/csharp/Prompty.Core/Model/tracing/TraceSpan.cs b/runtime/csharp/Prompty.Core/Model/tracing/TraceSpan.cs index 5c7f746f..5136543f 100644 --- a/runtime/csharp/Prompty.Core/Model/tracing/TraceSpan.cs +++ b/runtime/csharp/Prompty.Core/Model/tracing/TraceSpan.cs @@ -1,3 +1,4 @@ +// // Copyright (c) Microsoft. All rights reserved. using System.Text.Json; using YamlDotNet.Serialization; @@ -6,13 +7,13 @@ namespace Prompty.Core; #pragma warning restore IDE0130 -/// -/// A single trace span capturing one pipeline stage or function invocation. -/// -/// Spans nest via the `__frames` field to form a tree representing the -/// -/// full execution (§3.6.1). -/// + /// + /// A single trace span capturing one pipeline stage or function invocation. + /// + /// Spans nest via the `__frames` field to form a tree representing the + /// + /// full execution (§3.6.1). + /// public partial class TraceSpan { /// diff --git a/runtime/csharp/Prompty.Core/Model/tracing/TraceTime.cs b/runtime/csharp/Prompty.Core/Model/tracing/TraceTime.cs index a33015c5..ff77d50c 100644 --- a/runtime/csharp/Prompty.Core/Model/tracing/TraceTime.cs +++ b/runtime/csharp/Prompty.Core/Model/tracing/TraceTime.cs @@ -1,3 +1,4 @@ +// // Copyright (c) Microsoft. All rights reserved. using System.Text.Json; using YamlDotNet.Serialization; @@ -6,9 +7,9 @@ namespace Prompty.Core; #pragma warning restore IDE0130 -/// -/// Timing information for a trace span. -/// + /// + /// Timing information for a trace span. + /// public partial class TraceTime { /// diff --git a/runtime/csharp/Prompty.Core/Model/wire/AnthropicImageBlock.cs b/runtime/csharp/Prompty.Core/Model/wire/AnthropicImageBlock.cs index cb38ff9f..6b4f6294 100644 --- a/runtime/csharp/Prompty.Core/Model/wire/AnthropicImageBlock.cs +++ b/runtime/csharp/Prompty.Core/Model/wire/AnthropicImageBlock.cs @@ -1,3 +1,4 @@ +// // Copyright (c) Microsoft. All rights reserved. using System.Text.Json; using YamlDotNet.Serialization; @@ -6,11 +7,11 @@ namespace Prompty.Core; #pragma warning restore IDE0130 -/// -/// An image content block using base64-encoded data. -/// -/// Anthropic requires images as base64 with an explicit media type. -/// + /// + /// An image content block using base64-encoded data. + /// + /// Anthropic requires images as base64 with an explicit media type. + /// public partial class AnthropicImageBlock { /// diff --git a/runtime/csharp/Prompty.Core/Model/wire/AnthropicImageSource.cs b/runtime/csharp/Prompty.Core/Model/wire/AnthropicImageSource.cs index 70daff2d..f535d756 100644 --- a/runtime/csharp/Prompty.Core/Model/wire/AnthropicImageSource.cs +++ b/runtime/csharp/Prompty.Core/Model/wire/AnthropicImageSource.cs @@ -1,3 +1,4 @@ +// // Copyright (c) Microsoft. All rights reserved. using System.Text.Json; using YamlDotNet.Serialization; @@ -6,9 +7,9 @@ namespace Prompty.Core; #pragma warning restore IDE0130 -/// -/// Source descriptor for an Anthropic base64 image. -/// + /// + /// Source descriptor for an Anthropic base64 image. + /// public partial class AnthropicImageSource { /// diff --git a/runtime/csharp/Prompty.Core/Model/wire/AnthropicMessagesRequest.cs b/runtime/csharp/Prompty.Core/Model/wire/AnthropicMessagesRequest.cs index d17002d2..cbf44f5b 100644 --- a/runtime/csharp/Prompty.Core/Model/wire/AnthropicMessagesRequest.cs +++ b/runtime/csharp/Prompty.Core/Model/wire/AnthropicMessagesRequest.cs @@ -1,3 +1,4 @@ +// // Copyright (c) Microsoft. All rights reserved. using System.Text.Json; using YamlDotNet.Serialization; @@ -6,9 +7,9 @@ namespace Prompty.Core; #pragma warning restore IDE0130 -/// -/// The full request body for the Anthropic Messages API (§7.5). -/// + /// + /// The full request body for the Anthropic Messages API (§7.5). + /// public partial class AnthropicMessagesRequest { /// diff --git a/runtime/csharp/Prompty.Core/Model/wire/AnthropicMessagesResponse.cs b/runtime/csharp/Prompty.Core/Model/wire/AnthropicMessagesResponse.cs index 5fce011e..d1229006 100644 --- a/runtime/csharp/Prompty.Core/Model/wire/AnthropicMessagesResponse.cs +++ b/runtime/csharp/Prompty.Core/Model/wire/AnthropicMessagesResponse.cs @@ -1,3 +1,4 @@ +// // Copyright (c) Microsoft. All rights reserved. using System.Text.Json; using YamlDotNet.Serialization; @@ -6,9 +7,9 @@ namespace Prompty.Core; #pragma warning restore IDE0130 -/// -/// The response body from the Anthropic Messages API. -/// + /// + /// The response body from the Anthropic Messages API. + /// public partial class AnthropicMessagesResponse { /// diff --git a/runtime/csharp/Prompty.Core/Model/wire/AnthropicTextBlock.cs b/runtime/csharp/Prompty.Core/Model/wire/AnthropicTextBlock.cs index 2dd16faa..3c9b4b14 100644 --- a/runtime/csharp/Prompty.Core/Model/wire/AnthropicTextBlock.cs +++ b/runtime/csharp/Prompty.Core/Model/wire/AnthropicTextBlock.cs @@ -1,3 +1,4 @@ +// // Copyright (c) Microsoft. All rights reserved. using System.Text.Json; using YamlDotNet.Serialization; @@ -6,9 +7,9 @@ namespace Prompty.Core; #pragma warning restore IDE0130 -/// -/// A text content block in Anthropic's array-of-blocks message format. -/// + /// + /// A text content block in Anthropic's array-of-blocks message format. + /// public partial class AnthropicTextBlock { /// diff --git a/runtime/csharp/Prompty.Core/Model/wire/AnthropicToolDefinition.cs b/runtime/csharp/Prompty.Core/Model/wire/AnthropicToolDefinition.cs index 8ccddd4f..e747210c 100644 --- a/runtime/csharp/Prompty.Core/Model/wire/AnthropicToolDefinition.cs +++ b/runtime/csharp/Prompty.Core/Model/wire/AnthropicToolDefinition.cs @@ -1,3 +1,4 @@ +// // Copyright (c) Microsoft. All rights reserved. using System.Text.Json; using YamlDotNet.Serialization; @@ -6,13 +7,13 @@ namespace Prompty.Core; #pragma warning restore IDE0130 -/// -/// A tool definition in Anthropic's format. Unlike OpenAI which wraps -/// -/// tools in `{type: "function", function: {...}}`, Anthropic uses a -/// -/// flat structure with `input_schema` (§7.5). -/// + /// + /// A tool definition in Anthropic's format. Unlike OpenAI which wraps + /// + /// tools in `{type: "function", function: {...}}`, Anthropic uses a + /// + /// flat structure with `input_schema` (§7.5). + /// public partial class AnthropicToolDefinition { /// diff --git a/runtime/csharp/Prompty.Core/Model/wire/AnthropicToolResultBlock.cs b/runtime/csharp/Prompty.Core/Model/wire/AnthropicToolResultBlock.cs index 7a2d9f97..0e9f86a2 100644 --- a/runtime/csharp/Prompty.Core/Model/wire/AnthropicToolResultBlock.cs +++ b/runtime/csharp/Prompty.Core/Model/wire/AnthropicToolResultBlock.cs @@ -1,3 +1,4 @@ +// // Copyright (c) Microsoft. All rights reserved. using System.Text.Json; using YamlDotNet.Serialization; @@ -6,9 +7,9 @@ namespace Prompty.Core; #pragma warning restore IDE0130 -/// -/// A tool result content block sent back to the API with the tool's output. -/// + /// + /// A tool result content block sent back to the API with the tool's output. + /// public partial class AnthropicToolResultBlock { /// diff --git a/runtime/csharp/Prompty.Core/Model/wire/AnthropicToolUseBlock.cs b/runtime/csharp/Prompty.Core/Model/wire/AnthropicToolUseBlock.cs index 74d9e54b..b32d3b10 100644 --- a/runtime/csharp/Prompty.Core/Model/wire/AnthropicToolUseBlock.cs +++ b/runtime/csharp/Prompty.Core/Model/wire/AnthropicToolUseBlock.cs @@ -1,3 +1,4 @@ +// // Copyright (c) Microsoft. All rights reserved. using System.Text.Json; using YamlDotNet.Serialization; @@ -6,11 +7,11 @@ namespace Prompty.Core; #pragma warning restore IDE0130 -/// -/// A tool use content block returned in an assistant message when -/// -/// the model wants to invoke a tool. -/// + /// + /// A tool use content block returned in an assistant message when + /// + /// the model wants to invoke a tool. + /// public partial class AnthropicToolUseBlock { /// diff --git a/runtime/csharp/Prompty.Core/Model/wire/AnthropicUsage.cs b/runtime/csharp/Prompty.Core/Model/wire/AnthropicUsage.cs index 6b5a92e0..ae1c3087 100644 --- a/runtime/csharp/Prompty.Core/Model/wire/AnthropicUsage.cs +++ b/runtime/csharp/Prompty.Core/Model/wire/AnthropicUsage.cs @@ -1,3 +1,4 @@ +// // Copyright (c) Microsoft. All rights reserved. using System.Text.Json; using YamlDotNet.Serialization; @@ -6,9 +7,9 @@ namespace Prompty.Core; #pragma warning restore IDE0130 -/// -/// Usage statistics returned in an Anthropic Messages API response. -/// + /// + /// Usage statistics returned in an Anthropic Messages API response. + /// public partial class AnthropicUsage { /// diff --git a/runtime/csharp/Prompty.Core/Model/wire/AnthropicWireMessage.cs b/runtime/csharp/Prompty.Core/Model/wire/AnthropicWireMessage.cs index 5d03e795..edc93a2b 100644 --- a/runtime/csharp/Prompty.Core/Model/wire/AnthropicWireMessage.cs +++ b/runtime/csharp/Prompty.Core/Model/wire/AnthropicWireMessage.cs @@ -1,3 +1,4 @@ +// // Copyright (c) Microsoft. All rights reserved. using System.Text.Json; using YamlDotNet.Serialization; @@ -6,13 +7,13 @@ namespace Prompty.Core; #pragma warning restore IDE0130 -/// -/// A single message in the Anthropic Messages API wire format. -/// -/// Anthropic always uses the array-of-blocks form for content, -/// -/// even when there is only one text block (§7.5). -/// + /// + /// A single message in the Anthropic Messages API wire format. + /// + /// Anthropic always uses the array-of-blocks form for content, + /// + /// even when there is only one text block (§7.5). + /// public partial class AnthropicWireMessage { /// diff --git a/runtime/csharp/Prompty.Core/Pipeline.cs b/runtime/csharp/Prompty.Core/Pipeline.cs index 1c5802a9..a476ca3a 100644 --- a/runtime/csharp/Prompty.Core/Pipeline.cs +++ b/runtime/csharp/Prompty.Core/Pipeline.cs @@ -10,6 +10,20 @@ namespace Prompty.Core; /// public static class Pipeline { + private static void EmitFailedTurnEnd(EventCallback? onEvent, Exception exception, int iterations, object? response = null) + { + var payload = new Dictionary + { + ["iterations"] = iterations, + ["status"] = exception is OperationCanceledException ? "cancelled" : "error" + }; + if (response is not null) + { + payload["response"] = response; + } + AgentEvents.EmitEvent(onEvent, AgentEventType.TurnEnd, payload); + } + // ----------------------------------------------------------------------- // Input Validation // ----------------------------------------------------------------------- @@ -241,6 +255,13 @@ public static async Task TurnAsync( emit("signature", "prompty.turn"); emit("inputs", new Dictionary { ["agent"] = agent.Name, ["label"] = label, ["maxIterations"] = maxIterations }); var messages = await PrepareAsync(agent, inputs); + AgentEvents.EmitEvent(onEvent, AgentEventType.TurnStart, + new Dictionary + { + ["agent"] = agent.Name, + ["inputs"] = inputs ?? new Dictionary(), + ["maxIterations"] = maxIterations + }); // Simple path: no tools on agent, no user tools, and no agent-loop features → single execute + process var hasAgentTools = agent.Tools is not null && agent.Tools.Count > 0; @@ -248,9 +269,44 @@ public static async Task TurnAsync( var hasAgentFeatures = guardrails is not null || steering is not null || contextBudget is not null; if (!hasAgentTools && !hasUserTools && !hasAgentFeatures) { - var response = await ExecuteAsync(agent, messages); - if (raw) return response; - return await ProcessAsync(agent, response); + AgentEvents.EmitEvent(onEvent, AgentEventType.LlmStart, + new Dictionary + { + ["provider"] = agent.Model?.Provider, + ["modelId"] = agent.Model?.Id, + ["messageCount"] = messages.Count, + ["attempt"] = 0 + }); + object response; + try + { + response = await ExecuteAsync(agent, messages); + } + catch (Exception ex) + { + EmitFailedTurnEnd(onEvent, ex, 0); + throw; + } + AgentEvents.EmitEvent(onEvent, AgentEventType.LlmComplete, new Dictionary()); + if (raw) + { + AgentEvents.EmitEvent(onEvent, AgentEventType.TurnEnd, + new Dictionary { ["iterations"] = 0, ["status"] = "success", ["response"] = response }); + return response; + } + object processed; + try + { + processed = await ProcessAsync(agent, response); + } + catch (Exception ex) + { + EmitFailedTurnEnd(onEvent, ex, 0, response); + throw; + } + AgentEvents.EmitEvent(onEvent, AgentEventType.TurnEnd, + new Dictionary { ["iterations"] = 0, ["status"] = "success", ["response"] = processed }); + return processed; } // Agent loop: execute → check tool_calls → dispatch tools → loop @@ -262,6 +318,7 @@ public static async Task TurnAsync( { if (iteration >= maxIterations) { + EmitFailedTurnEnd(onEvent, new InvalidOperationException("Agent loop exceeded maximum iterations."), iteration); throw new InvalidOperationException( $"Agent loop exceeded maximum iterations ({maxIterations})."); } @@ -275,6 +332,7 @@ public static async Task TurnAsync( { AgentEvents.EmitEvent(onEvent, AgentEventType.Cancelled, new Dictionary { ["iteration"] = iteration, ["reason"] = "cancellation_requested" }); + EmitFailedTurnEnd(onEvent, new OperationCanceledException(), iteration); throw; } @@ -314,6 +372,7 @@ public static async Task TurnAsync( { AgentEvents.EmitEvent(onEvent, AgentEventType.Error, new Dictionary { ["guardrail"] = "input", ["reason"] = inputCheck.Reason }); + EmitFailedTurnEnd(onEvent, new GuardrailError(inputCheck.Reason ?? "Input guardrail denied"), iteration); throw new GuardrailError(inputCheck.Reason ?? "Input guardrail denied"); } if (inputCheck.Rewrite is List rewrittenMessages) @@ -331,29 +390,66 @@ public static async Task TurnAsync( { AgentEvents.EmitEvent(onEvent, AgentEventType.Cancelled, new Dictionary { ["iteration"] = iteration, ["reason"] = "cancelled_before_llm" }); + EmitFailedTurnEnd(onEvent, new OperationCanceledException(), iteration); throw; } AgentEvents.EmitEvent(onEvent, AgentEventType.Status, new Dictionary { ["iteration"] = iteration, ["phase"] = "executing" }); - response2 = await InvokeWithRetryAsync(agent, messages, maxLlmRetries, onEvent, cancellationToken); + AgentEvents.EmitEvent(onEvent, AgentEventType.LlmStart, + new Dictionary + { + ["provider"] = agent.Model?.Provider, + ["modelId"] = agent.Model?.Id, + ["messageCount"] = messages.Count, + ["attempt"] = 0, + ["iteration"] = iteration + }); + try + { + response2 = await InvokeWithRetryAsync(agent, messages, maxLlmRetries, onEvent, cancellationToken); + } + catch (Exception ex) + { + EmitFailedTurnEnd(onEvent, ex, iteration); + throw; + } + AgentEvents.EmitEvent(onEvent, AgentEventType.LlmComplete, + new Dictionary { ["iteration"] = iteration }); // If response is a stream, consume it fully before processing. if (response2 is PromptyStream stream) { - await foreach (var chunk in stream) + try { - if (chunk is string tokenText && tokenText.Length > 0) + await foreach (var chunk in stream) { - AgentEvents.EmitEvent(onEvent, AgentEventType.Token, - new Dictionary { ["token"] = tokenText }); + if (chunk is string tokenText && tokenText.Length > 0) + { + AgentEvents.EmitEvent(onEvent, AgentEventType.Token, + new Dictionary { ["token"] = tokenText }); + } } } + catch (Exception ex) + { + EmitFailedTurnEnd(onEvent, ex, iteration, response2); + throw; + } response2 = stream; } - var result = raw ? response2! : await ProcessAsync(agent, response2!); + object result; + try + { + result = raw ? response2! : await ProcessAsync(agent, response2!); + } + catch (Exception ex) + { + EmitFailedTurnEnd(onEvent, ex, iteration, response2); + throw; + } if (result is ToolCallResult toolResult && toolResult.ToolCalls.Count > 0) { @@ -379,6 +475,8 @@ public static async Task TurnAsync( var deniedMsg = $"Tool denied by guardrail: {toolCheck.Reason}"; AgentEvents.EmitEvent(onEvent, AgentEventType.ToolResult, new Dictionary { ["tool"] = call.Name, ["result"] = deniedMsg }); + AgentEvents.EmitEvent(onEvent, AgentEventType.ToolCallComplete, + new Dictionary { ["name"] = call.Name, ["success"] = false, ["result"] = deniedMsg, ["errorKind"] = "guardrail_denied" }); tasks.Add(Task.FromResult((capturedIndex, deniedMsg))); continue; } @@ -393,13 +491,14 @@ public static async Task TurnAsync( tasks.Add(Task.Run(async () => { + var toolStarted = DateTimeOffset.UtcNow; string toolResponse; try { toolResponse = await Trace.TraceAsync("Prompty.Core.ToolDispatch.Execute", async (toolEmit) => { toolEmit("inputs", new Dictionary { ["tool"] = call.Name, ["arguments"] = call.Arguments }); - return await ToolDispatch.DispatchAsync(agent, call, tools, inputs); + return await ToolDispatch.DispatchAsync(agent, call, tools, inputs) ?? string.Empty; }); } catch (Exception ex) when (ex is not OperationCanceledException) @@ -408,11 +507,29 @@ public static async Task TurnAsync( AgentEvents.EmitEvent(onEvent, AgentEventType.Error, new Dictionary { ["tool"] = call.Name, ["error"] = ex.Message }); } + AgentEvents.EmitEvent(onEvent, AgentEventType.ToolCallComplete, + new Dictionary + { + ["name"] = call.Name, + ["success"] = !toolResponse.StartsWith("Error:", StringComparison.Ordinal), + ["result"] = toolResponse, + ["durationMs"] = (DateTimeOffset.UtcNow - toolStarted).TotalMilliseconds, + ["errorKind"] = toolResponse.StartsWith("Error:", StringComparison.Ordinal) ? "tool_error" : null + }); return (capturedIndex, toolResponse); })); } - var completed = await Task.WhenAll(tasks); + (int Index, string Result)[] completed; + try + { + completed = await Task.WhenAll(tasks); + } + catch (Exception ex) + { + EmitFailedTurnEnd(onEvent, ex, iteration); + throw; + } // Maintain order var ordered = new string[toolResult.ToolCalls.Count]; foreach (var (index, res) in completed) @@ -442,6 +559,8 @@ public static async Task TurnAsync( var deniedMsg = $"Tool denied by guardrail: {toolCheck.Reason}"; AgentEvents.EmitEvent(onEvent, AgentEventType.ToolResult, new Dictionary { ["tool"] = call.Name, ["result"] = deniedMsg }); + AgentEvents.EmitEvent(onEvent, AgentEventType.ToolCallComplete, + new Dictionary { ["name"] = call.Name, ["success"] = false, ["result"] = deniedMsg, ["errorKind"] = "guardrail_denied" }); toolResults.Add(deniedMsg); continue; } @@ -454,6 +573,7 @@ public static async Task TurnAsync( AgentEvents.EmitEvent(onEvent, AgentEventType.ToolCallStart, new Dictionary { ["tool"] = call.Name, ["arguments"] = call.Arguments }); + var toolStarted = DateTimeOffset.UtcNow; string toolResponse; try { @@ -469,16 +589,39 @@ public static async Task TurnAsync( AgentEvents.EmitEvent(onEvent, AgentEventType.Error, new Dictionary { ["tool"] = call.Name, ["error"] = ex.Message }); } + catch (OperationCanceledException ex) + { + EmitFailedTurnEnd(onEvent, ex, iteration); + throw; + } toolResults.Add(toolResponse); AgentEvents.EmitEvent(onEvent, AgentEventType.ToolResult, new Dictionary { ["tool"] = call.Name, ["result"] = toolResponse }); + AgentEvents.EmitEvent(onEvent, AgentEventType.ToolCallComplete, + new Dictionary + { + ["name"] = call.Name, + ["success"] = !toolResponse.StartsWith("Error:", StringComparison.Ordinal), + ["result"] = toolResponse, + ["durationMs"] = (DateTimeOffset.UtcNow - toolStarted).TotalMilliseconds, + ["errorKind"] = toolResponse.StartsWith("Error:", StringComparison.Ordinal) ? "tool_error" : null + }); } } // Delegate message formatting to the executor (provider-specific) - var toolMessages = executor.FormatToolMessages( - response2, toolResult.ToolCalls, toolResults, toolResult.Content); + List toolMessages; + try + { + toolMessages = executor.FormatToolMessages( + response2, toolResult.ToolCalls, toolResults, toolResult.Content); + } + catch (Exception ex) + { + EmitFailedTurnEnd(onEvent, ex, iteration, response2); + throw; + } messages.AddRange(toolMessages); AgentEvents.EmitEvent(onEvent, AgentEventType.MessagesUpdated, @@ -510,6 +653,7 @@ public static async Task TurnAsync( { AgentEvents.EmitEvent(onEvent, AgentEventType.Error, new Dictionary { ["guardrail"] = "output", ["reason"] = outputCheck.Reason }); + EmitFailedTurnEnd(onEvent, new GuardrailError(outputCheck.Reason ?? "Output guardrail denied"), iteration + 1, result); throw new GuardrailError(outputCheck.Reason ?? "Output guardrail denied"); } if (outputCheck.Rewrite is not null) @@ -520,6 +664,8 @@ public static async Task TurnAsync( AgentEvents.EmitEvent(onEvent, AgentEventType.Done, new Dictionary { ["iterations"] = iteration + 1 }); + AgentEvents.EmitEvent(onEvent, AgentEventType.TurnEnd, + new Dictionary { ["iterations"] = iteration + 1, ["status"] = "success", ["response"] = result }); return result!; } @@ -713,6 +859,14 @@ private static async Task InvokeWithRetryAsync( { ["message"] = $"LLM call failed, retrying (attempt {attempts + 1}/{maxRetries})..." }); + AgentEvents.EmitEvent(onEvent, AgentEventType.Retry, + new Dictionary + { + ["operation"] = "llm", + ["attempt"] = attempts + 1, + ["maxAttempts"] = maxRetries, + ["reason"] = ex.Message + }); // Exponential backoff with jitter, capped at 60s var backoff = Math.Min(Math.Pow(2, attempts) + Random.Shared.NextDouble(), 60); diff --git a/runtime/csharp/Prompty.Core/ReplayVerifier.cs b/runtime/csharp/Prompty.Core/ReplayVerifier.cs new file mode 100644 index 00000000..271a85d7 --- /dev/null +++ b/runtime/csharp/Prompty.Core/ReplayVerifier.cs @@ -0,0 +1,49 @@ +// Copyright (c) Microsoft. All rights reserved. + +using System.Text.Json; + +namespace Prompty.Core; + +public sealed class ReferenceReplayVerifier +{ + public ReplayVerificationResult Verify(ReplayVerificationRequest request) + { + var expected = request.Expected ?? []; + var actual = request.Actual ?? []; + var max = Math.Max(expected.Count, actual.Count); + var mismatches = new List(); + + for (var index = 0; index < max; index++) + { + var expectedRecord = index < expected.Count ? expected[index] : null; + var actualRecord = index < actual.Count ? actual[index] : null; + if (Comparable(expectedRecord) != Comparable(actualRecord)) + { + mismatches.Add(new ReplayMismatch + { + Index = index, + Expected = expectedRecord, + Actual = actualRecord, + Message = expectedRecord is null + ? "Unexpected extra replay record" + : actualRecord is null + ? "Missing replay record" + : "Replay record mismatch" + }); + } + } + + return new ReplayVerificationResult + { + Status = mismatches.Count == 0 ? ReplayVerificationStatus.Passed : ReplayVerificationStatus.Failed, + Mismatches = mismatches, + ExpectedCount = expected.Count, + ActualCount = actual.Count + }; + } + + private static string? Comparable(ReplayJournalRecord? record) + { + return record is null ? null : JsonSerializer.Serialize(record.Save()); + } +} diff --git a/runtime/csharp/Prompty.Core/TurnRunner.cs b/runtime/csharp/Prompty.Core/TurnRunner.cs new file mode 100644 index 00000000..928caaac --- /dev/null +++ b/runtime/csharp/Prompty.Core/TurnRunner.cs @@ -0,0 +1,239 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Prompty.Core; + +public sealed class ReferenceTurnRunner +{ + private readonly IEventSink _eventSink; + private readonly IEventJournalWriter _journal; + private readonly ICheckpointStore _checkpointStore; + private readonly IPermissionResolver _permissionResolver; + private readonly IHostToolExecutor _hostToolExecutor; + private readonly Func> _invokeModel; + private readonly Func _now; + private readonly Func _nextId; + + public ReferenceTurnRunner( + IEventSink eventSink, + IEventJournalWriter journal, + ICheckpointStore checkpointStore, + IPermissionResolver permissionResolver, + IHostToolExecutor hostToolExecutor, + Func> invokeModel, + Func? now = null, + Func? nextId = null) + { + _eventSink = eventSink; + _journal = journal; + _checkpointStore = checkpointStore; + _permissionResolver = permissionResolver; + _hostToolExecutor = hostToolExecutor; + _invokeModel = invokeModel; + _now = now ?? (() => DateTimeOffset.UtcNow.ToString("O")); + var sequence = 0; + _nextId = nextId ?? (prefix => $"{prefix}-{++sequence}"); + } + + public async Task RunAsync(RunTurnRequest request) + { + var options = request.Options ?? new TurnOptions(); + var inputs = request.Inputs ?? new Dictionary(); + var maxIterations = options.MaxIterations ?? 10; + var checkpoints = new List(); + var allToolResults = new List(); + var pendingToolResults = new List(); + object? output = null; + var hasFinalOutput = false; + var status = RunTurnStatus.Success; + var iterations = 0; + + RecordSession(SessionEventType.SessionStart, request.SessionId, request.TurnId, new Dictionary + { + ["sessionId"] = request.SessionId, + ["schemaVersion"] = "1" + }); + RecordTurn(TurnEventType.TurnStart, request.TurnId, 0, new Dictionary + { + ["inputs"] = inputs, + ["maxIterations"] = maxIterations + }); + + for (var iteration = 0; iteration < maxIterations; iteration++) + { + iterations = iteration + 1; + RecordTurn(TurnEventType.LlmStart, request.TurnId, iteration, new Dictionary { ["attempt"] = 0 }); + var modelResponse = await _invokeModel(new TurnModelRequest + { + SessionId = request.SessionId, + TurnId = request.TurnId, + Iteration = iteration, + Inputs = inputs, + Options = options, + ToolResults = pendingToolResults + }); + RecordTurn(TurnEventType.LlmComplete, request.TurnId, iteration, new Dictionary()); + + var checkpoint = await SaveCheckpointAsync(request.SessionId, request.TurnId, iteration, modelResponse); + checkpoints.Add(checkpoint); + + var toolRequests = modelResponse.ToolRequests ?? []; + if (toolRequests.Count == 0) + { + output = modelResponse.Output; + hasFinalOutput = true; + break; + } + + pendingToolResults = []; + foreach (var toolRequest in toolRequests) + { + var toolResult = await ResolveAndExecuteToolAsync(request.TurnId, iteration, toolRequest); + pendingToolResults.Add(toolResult); + allToolResults.Add(toolResult); + } + + RecordTurn(TurnEventType.MessagesUpdated, request.TurnId, iteration, new Dictionary + { + ["toolResults"] = pendingToolResults.Select(result => result.Save()).ToList() + }); + } + + if (!hasFinalOutput && pendingToolResults.Count > 0) + { + status = RunTurnStatus.Error; + output = new Dictionary { ["message"] = "Maximum turn iterations reached" }; + RecordTurn(TurnEventType.Error, request.TurnId, iterations, new Dictionary + { + ["errorKind"] = "max_iterations", + ["message"] = "Maximum turn iterations reached" + }); + } + + RecordTurn(TurnEventType.TurnEnd, request.TurnId, iterations, new Dictionary + { + ["iterations"] = iterations, + ["status"] = status == RunTurnStatus.Success ? "success" : "error", + ["response"] = output + }!); + RecordSession(SessionEventType.SessionEnd, request.SessionId, request.TurnId, new Dictionary + { + ["sessionId"] = request.SessionId, + ["status"] = status == RunTurnStatus.Success ? "success" : "error", + ["reason"] = "turn_complete" + }); + _journal.Close(new SessionSummary + { + SessionId = request.SessionId, + Status = status == RunTurnStatus.Success ? SessionSummaryStatus.Success : SessionSummaryStatus.Error, + Turns = 1, + Checkpoints = checkpoints.Count + }); + + return new RunTurnResult + { + SessionId = request.SessionId, + TurnId = request.TurnId, + Status = status, + Output = output, + Iterations = iterations, + ToolResults = allToolResults, + Checkpoints = checkpoints + }; + } + + private async Task SaveCheckpointAsync(string sessionId, string turnId, int iteration, TurnModelResponse response) + { + var state = new Dictionary + { + ["iteration"] = iteration, + ["output"] = response.Output, + ["toolRequests"] = (response.ToolRequests ?? []).Select(request => request.Save()).ToList() + }; + foreach (var (key, value) in response.CheckpointState ?? new Dictionary()) + { + state[key] = value; + } + + var checkpoint = new Checkpoint + { + Id = $"{turnId}-checkpoint-{iteration}", + SessionId = sessionId, + TurnId = turnId, + CheckpointNumber = iteration + 1, + Title = $"Turn {turnId} iteration {iteration}", + State = state!, + CreatedAt = _now() + }; + var saved = await _checkpointStore.SaveAsync(checkpoint); + RecordSession(SessionEventType.CheckpointCreated, sessionId, turnId, new Dictionary + { + ["checkpointId"] = saved.Id, + ["checkpointNumber"] = saved.CheckpointNumber + }!); + return saved; + } + + private async Task ResolveAndExecuteToolAsync(string turnId, int iteration, HostToolRequest toolRequest) + { + var permission = new PermissionRequest + { + RequestId = toolRequest.RequestId is null ? _nextId("permission") : $"{toolRequest.RequestId}-permission", + ToolCallId = toolRequest.ToolCallId, + Permission = "tool.execute", + Target = toolRequest.ToolName, + Details = toolRequest.Save() + }; + RecordTurn(TurnEventType.PermissionRequested, turnId, iteration, permission.Save()); + var decision = await _permissionResolver.RequestAsync(permission); + RecordTurn(TurnEventType.PermissionCompleted, turnId, iteration, decision.Save()); + + if (!decision.Approved) + { + return new HostToolResult + { + RequestId = toolRequest.RequestId, + ToolCallId = toolRequest.ToolCallId, + ToolName = toolRequest.ToolName, + Success = false, + ErrorKind = "permission_denied", + Result = new Dictionary { ["message"] = decision.Reason ?? "Permission denied" } + }; + } + + RecordTurn(TurnEventType.ToolExecutionStart, turnId, iteration, toolRequest.Save()); + var result = await _hostToolExecutor.ExecuteAsync(toolRequest); + RecordTurn(TurnEventType.ToolExecutionComplete, turnId, iteration, result.Save()); + RecordTurn(TurnEventType.ToolResult, turnId, iteration, result.Save()); + return result; + } + + private void RecordTurn(TurnEventType type, string turnId, int iteration, IDictionary payload) + { + var turnEvent = new TurnEvent + { + Id = _nextId("turn-event"), + Type = type, + Timestamp = _now(), + TurnId = turnId, + Iteration = iteration, + Payload = payload + }; + _eventSink.EmitTurn(turnEvent); + _journal.AppendTurn(turnEvent); + } + + private void RecordSession(SessionEventType type, string sessionId, string turnId, IDictionary payload) + { + var sessionEvent = new SessionEvent + { + Id = _nextId("session-event"), + Type = type, + Timestamp = _now(), + SessionId = sessionId, + TurnId = turnId, + Payload = payload + }; + _eventSink.EmitSession(sessionEvent); + _journal.AppendSession(sessionEvent); + } +} diff --git a/runtime/go/prompty/model/anonymous_connection_test.go b/runtime/go/prompty/model/anonymous_connection_test.go index 9fa7748d..ee56eba9 100644 --- a/runtime/go/prompty/model/anonymous_connection_test.go +++ b/runtime/go/prompty/model/anonymous_connection_test.go @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. package prompty_test @@ -62,6 +63,47 @@ endpoint: "https://{your-custom-endpoint}.openai.azure.com/" } } +// TestAnonymousConnectionFromJSON tests loading AnonymousConnection through the generated JSON helper +func TestAnonymousConnectionFromJSON(t *testing.T) { + jsonData := ` +{ + "kind": "anonymous", + "endpoint": "https://{your-custom-endpoint}.openai.azure.com/" +} +` + + instance, err := prompty.AnonymousConnectionFromJSON(jsonData) + if err != nil { + t.Fatalf("Failed to load AnonymousConnection from JSON helper: %v", err) + } + if instance.Kind != "anonymous" { + t.Errorf(`Expected Kind to be "anonymous", got %v`, instance.Kind) + } + if instance.Endpoint != "https://{your-custom-endpoint}.openai.azure.com/" { + t.Errorf(`Expected Endpoint to be "https://{your-custom-endpoint}.openai.azure.com/", got %v`, instance.Endpoint) + } +} + +// TestAnonymousConnectionFromYAML tests loading AnonymousConnection through the generated YAML helper +func TestAnonymousConnectionFromYAML(t *testing.T) { + yamlData := ` +kind: anonymous +endpoint: "https://{your-custom-endpoint}.openai.azure.com/" + +` + + instance, err := prompty.AnonymousConnectionFromYAML(yamlData) + if err != nil { + t.Fatalf("Failed to load AnonymousConnection from YAML helper: %v", err) + } + if instance.Kind != "anonymous" { + t.Errorf(`Expected Kind to be "anonymous", got %v`, instance.Kind) + } + if instance.Endpoint != "https://{your-custom-endpoint}.openai.azure.com/" { + t.Errorf(`Expected Endpoint to be "https://{your-custom-endpoint}.openai.azure.com/", got %v`, instance.Endpoint) + } +} + // TestAnonymousConnectionRoundtrip tests load -> save -> load produces equivalent data func TestAnonymousConnectionRoundtrip(t *testing.T) { jsonData := ` @@ -122,6 +164,17 @@ func TestAnonymousConnectionToJSON(t *testing.T) { if err := json.Unmarshal([]byte(jsonOutput), &parsed); err != nil { t.Fatalf("Failed to parse generated JSON: %v", err) } + + reloaded, err := prompty.LoadAnonymousConnection(parsed, ctx) + if err != nil { + t.Fatalf("Failed to reload generated JSON: %v", err) + } + if reloaded.Kind != "anonymous" { + t.Errorf(`Expected Kind to be "anonymous", got %v`, reloaded.Kind) + } + if reloaded.Endpoint != "https://{your-custom-endpoint}.openai.azure.com/" { + t.Errorf(`Expected Endpoint to be "https://{your-custom-endpoint}.openai.azure.com/", got %v`, reloaded.Endpoint) + } } // TestAnonymousConnectionToYAML tests that ToYAML produces valid YAML @@ -151,4 +204,22 @@ func TestAnonymousConnectionToYAML(t *testing.T) { if err := yaml.Unmarshal([]byte(yamlOutput), &parsed); err != nil { t.Fatalf("Failed to parse generated YAML: %v", err) } + + reloaded, err := prompty.LoadAnonymousConnection(parsed, ctx) + if err != nil { + t.Fatalf("Failed to reload generated YAML: %v", err) + } + if reloaded.Kind != "anonymous" { + t.Errorf(`Expected Kind to be "anonymous", got %v`, reloaded.Kind) + } + if reloaded.Endpoint != "https://{your-custom-endpoint}.openai.azure.com/" { + t.Errorf(`Expected Endpoint to be "https://{your-custom-endpoint}.openai.azure.com/", got %v`, reloaded.Endpoint) + } +} + +// TestAnonymousConnectionFromJSONInvalid rejects malformed JSON instead of silently defaulting +func TestAnonymousConnectionFromJSONInvalid(t *testing.T) { + if _, err := prompty.AnonymousConnectionFromJSON("{"); err == nil { + t.Fatalf("Expected malformed JSON to fail") + } } diff --git a/runtime/go/prompty/model/anthropic_image_block.go b/runtime/go/prompty/model/anthropic_image_block.go index 78dce7bd..ff4b9f2a 100644 --- a/runtime/go/prompty/model/anthropic_image_block.go +++ b/runtime/go/prompty/model/anthropic_image_block.go @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. // Group: wire package prompty @@ -38,7 +39,7 @@ func LoadAnthropicImageBlock(data interface{}, ctx *LoadContext) (AnthropicImage } // Save serializes AnthropicImageBlock to map[string]interface{} -func (obj *AnthropicImageBlock) Save(ctx *SaveContext) map[string]interface{} { +func (obj AnthropicImageBlock) Save(ctx *SaveContext) map[string]interface{} { result := make(map[string]interface{}) result["type"] = obj.Type @@ -62,11 +63,7 @@ func (obj *AnthropicImageBlock) ToJSON() (string, error) { func (obj *AnthropicImageBlock) ToYAML() (string, error) { ctx := NewSaveContext() data := obj.Save(ctx) - bytes, err := yaml.Marshal(data) - if err != nil { - return "", err - } - return string(bytes), nil + return marshalYAMLDocument(data) } // FromJSON creates AnthropicImageBlock from JSON string diff --git a/runtime/go/prompty/model/anthropic_image_block_test.go b/runtime/go/prompty/model/anthropic_image_block_test.go index 3df1459b..9cc5440c 100644 --- a/runtime/go/prompty/model/anthropic_image_block_test.go +++ b/runtime/go/prompty/model/anthropic_image_block_test.go @@ -1,3 +1,4 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. package prompty_test diff --git a/runtime/go/prompty/model/anthropic_image_source.go b/runtime/go/prompty/model/anthropic_image_source.go index 4ac704ec..b3afae85 100644 --- a/runtime/go/prompty/model/anthropic_image_source.go +++ b/runtime/go/prompty/model/anthropic_image_source.go @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. // Group: wire package prompty @@ -38,7 +39,7 @@ func LoadAnthropicImageSource(data interface{}, ctx *LoadContext) (AnthropicImag } // Save serializes AnthropicImageSource to map[string]interface{} -func (obj *AnthropicImageSource) Save(ctx *SaveContext) map[string]interface{} { +func (obj AnthropicImageSource) Save(ctx *SaveContext) map[string]interface{} { result := make(map[string]interface{}) result["type"] = obj.Type result["media_type"] = obj.MediaType @@ -62,11 +63,7 @@ func (obj *AnthropicImageSource) ToJSON() (string, error) { func (obj *AnthropicImageSource) ToYAML() (string, error) { ctx := NewSaveContext() data := obj.Save(ctx) - bytes, err := yaml.Marshal(data) - if err != nil { - return "", err - } - return string(bytes), nil + return marshalYAMLDocument(data) } // FromJSON creates AnthropicImageSource from JSON string diff --git a/runtime/go/prompty/model/anthropic_image_source_test.go b/runtime/go/prompty/model/anthropic_image_source_test.go index 3e847ad4..844d95fd 100644 --- a/runtime/go/prompty/model/anthropic_image_source_test.go +++ b/runtime/go/prompty/model/anthropic_image_source_test.go @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. package prompty_test @@ -62,6 +63,47 @@ data: iVBORw0KGgo... } } +// TestAnthropicImageSourceFromJSON tests loading AnthropicImageSource through the generated JSON helper +func TestAnthropicImageSourceFromJSON(t *testing.T) { + jsonData := ` +{ + "media_type": "image/png", + "data": "iVBORw0KGgo..." +} +` + + instance, err := prompty.AnthropicImageSourceFromJSON(jsonData) + if err != nil { + t.Fatalf("Failed to load AnthropicImageSource from JSON helper: %v", err) + } + if instance.MediaType != "image/png" { + t.Errorf(`Expected MediaType to be "image/png", got %v`, instance.MediaType) + } + if instance.Data != "iVBORw0KGgo..." { + t.Errorf(`Expected Data to be "iVBORw0KGgo...", got %v`, instance.Data) + } +} + +// TestAnthropicImageSourceFromYAML tests loading AnthropicImageSource through the generated YAML helper +func TestAnthropicImageSourceFromYAML(t *testing.T) { + yamlData := ` +media_type: image/png +data: iVBORw0KGgo... + +` + + instance, err := prompty.AnthropicImageSourceFromYAML(yamlData) + if err != nil { + t.Fatalf("Failed to load AnthropicImageSource from YAML helper: %v", err) + } + if instance.MediaType != "image/png" { + t.Errorf(`Expected MediaType to be "image/png", got %v`, instance.MediaType) + } + if instance.Data != "iVBORw0KGgo..." { + t.Errorf(`Expected Data to be "iVBORw0KGgo...", got %v`, instance.Data) + } +} + // TestAnthropicImageSourceRoundtrip tests load -> save -> load produces equivalent data func TestAnthropicImageSourceRoundtrip(t *testing.T) { jsonData := ` @@ -122,6 +164,17 @@ func TestAnthropicImageSourceToJSON(t *testing.T) { if err := json.Unmarshal([]byte(jsonOutput), &parsed); err != nil { t.Fatalf("Failed to parse generated JSON: %v", err) } + + reloaded, err := prompty.LoadAnthropicImageSource(parsed, ctx) + if err != nil { + t.Fatalf("Failed to reload generated JSON: %v", err) + } + if reloaded.MediaType != "image/png" { + t.Errorf(`Expected MediaType to be "image/png", got %v`, reloaded.MediaType) + } + if reloaded.Data != "iVBORw0KGgo..." { + t.Errorf(`Expected Data to be "iVBORw0KGgo...", got %v`, reloaded.Data) + } } // TestAnthropicImageSourceToYAML tests that ToYAML produces valid YAML @@ -151,4 +204,22 @@ func TestAnthropicImageSourceToYAML(t *testing.T) { if err := yaml.Unmarshal([]byte(yamlOutput), &parsed); err != nil { t.Fatalf("Failed to parse generated YAML: %v", err) } + + reloaded, err := prompty.LoadAnthropicImageSource(parsed, ctx) + if err != nil { + t.Fatalf("Failed to reload generated YAML: %v", err) + } + if reloaded.MediaType != "image/png" { + t.Errorf(`Expected MediaType to be "image/png", got %v`, reloaded.MediaType) + } + if reloaded.Data != "iVBORw0KGgo..." { + t.Errorf(`Expected Data to be "iVBORw0KGgo...", got %v`, reloaded.Data) + } +} + +// TestAnthropicImageSourceFromJSONInvalid rejects malformed JSON instead of silently defaulting +func TestAnthropicImageSourceFromJSONInvalid(t *testing.T) { + if _, err := prompty.AnthropicImageSourceFromJSON("{"); err == nil { + t.Fatalf("Expected malformed JSON to fail") + } } diff --git a/runtime/go/prompty/model/anthropic_messages_request.go b/runtime/go/prompty/model/anthropic_messages_request.go index fcf8cf84..3aad6783 100644 --- a/runtime/go/prompty/model/anthropic_messages_request.go +++ b/runtime/go/prompty/model/anthropic_messages_request.go @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. // Group: wire package prompty @@ -108,11 +109,14 @@ func LoadAnthropicMessagesRequest(data interface{}, ctx *LoadContext) (Anthropic result.TopK = &v } if val, ok := m["stop_sequences"]; ok && val != nil { - if arr, ok := val.([]interface{}); ok { + switch arr := val.(type) { + case []interface{}: result.StopSequences = make([]string, len(arr)) for i, v := range arr { result.StopSequences[i] = v.(string) } + case []string: + result.StopSequences = arr } } if val, ok := m["tools"]; ok && val != nil { @@ -132,7 +136,7 @@ func LoadAnthropicMessagesRequest(data interface{}, ctx *LoadContext) (Anthropic } // Save serializes AnthropicMessagesRequest to map[string]interface{} -func (obj *AnthropicMessagesRequest) Save(ctx *SaveContext) map[string]interface{} { +func (obj AnthropicMessagesRequest) Save(ctx *SaveContext) map[string]interface{} { result := make(map[string]interface{}) result["model"] = obj.Model if obj.Messages != nil { @@ -182,11 +186,7 @@ func (obj *AnthropicMessagesRequest) ToJSON() (string, error) { func (obj *AnthropicMessagesRequest) ToYAML() (string, error) { ctx := NewSaveContext() data := obj.Save(ctx) - bytes, err := yaml.Marshal(data) - if err != nil { - return "", err - } - return string(bytes), nil + return marshalYAMLDocument(data) } // FromJSON creates AnthropicMessagesRequest from JSON string diff --git a/runtime/go/prompty/model/anthropic_messages_request_test.go b/runtime/go/prompty/model/anthropic_messages_request_test.go index b8bffe53..b53c142b 100644 --- a/runtime/go/prompty/model/anthropic_messages_request_test.go +++ b/runtime/go/prompty/model/anthropic_messages_request_test.go @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. package prompty_test @@ -54,6 +55,12 @@ func TestAnthropicMessagesRequestLoadJSON(t *testing.T) { if instance.TopK == nil || *instance.TopK != 40 { t.Errorf(`Expected TopK to be 40, got %v`, instance.TopK) } + if len(instance.StopSequences) != 1 { + t.Fatalf("Expected StopSequences length to be 1, got %d", len(instance.StopSequences)) + } + if instance.StopSequences[0] != "\n\nHuman:" { + t.Errorf(`Expected StopSequences[0] to be "\n\nHuman:", got %v`, instance.StopSequences[0]) + } } // TestAnthropicMessagesRequestLoadYAML tests loading AnthropicMessagesRequest from YAML @@ -97,6 +104,102 @@ stop_sequences: if instance.TopK == nil || *instance.TopK != 40 { t.Errorf(`Expected TopK to be 40, got %v`, instance.TopK) } + if len(instance.StopSequences) != 1 { + t.Fatalf("Expected StopSequences length to be 1, got %d", len(instance.StopSequences)) + } + if instance.StopSequences[0] != "\n\nHuman:" { + t.Errorf(`Expected StopSequences[0] to be "\n\nHuman:", got %v`, instance.StopSequences[0]) + } +} + +// TestAnthropicMessagesRequestFromJSON tests loading AnthropicMessagesRequest through the generated JSON helper +func TestAnthropicMessagesRequestFromJSON(t *testing.T) { + jsonData := ` +{ + "model": "claude-sonnet-4-20250514", + "max_tokens": 4096, + "system": "You are a helpful assistant.", + "temperature": 0.7, + "top_p": 0.9, + "top_k": 40, + "stop_sequences": [ + "\n\nHuman:" + ] +} +` + + instance, err := prompty.AnthropicMessagesRequestFromJSON(jsonData) + if err != nil { + t.Fatalf("Failed to load AnthropicMessagesRequest from JSON helper: %v", err) + } + if instance.Model != "claude-sonnet-4-20250514" { + t.Errorf(`Expected Model to be "claude-sonnet-4-20250514", got %v`, instance.Model) + } + if instance.MaxTokens != 4096 { + t.Errorf(`Expected MaxTokens to be 4096, got %v`, instance.MaxTokens) + } + if instance.System == nil || *instance.System != "You are a helpful assistant." { + t.Errorf(`Expected System to be "You are a helpful assistant.", got %v`, instance.System) + } + if instance.Temperature == nil || *instance.Temperature != 0.7 { + t.Errorf(`Expected Temperature to be 0.7, got %v`, instance.Temperature) + } + if instance.TopP == nil || *instance.TopP != 0.9 { + t.Errorf(`Expected TopP to be 0.9, got %v`, instance.TopP) + } + if instance.TopK == nil || *instance.TopK != 40 { + t.Errorf(`Expected TopK to be 40, got %v`, instance.TopK) + } + if len(instance.StopSequences) != 1 { + t.Fatalf("Expected StopSequences length to be 1, got %d", len(instance.StopSequences)) + } + if instance.StopSequences[0] != "\n\nHuman:" { + t.Errorf(`Expected StopSequences[0] to be "\n\nHuman:", got %v`, instance.StopSequences[0]) + } +} + +// TestAnthropicMessagesRequestFromYAML tests loading AnthropicMessagesRequest through the generated YAML helper +func TestAnthropicMessagesRequestFromYAML(t *testing.T) { + yamlData := ` +model: claude-sonnet-4-20250514 +max_tokens: 4096 +system: You are a helpful assistant. +temperature: 0.7 +top_p: 0.9 +top_k: 40 +stop_sequences: + - "\n\nHuman:" + +` + + instance, err := prompty.AnthropicMessagesRequestFromYAML(yamlData) + if err != nil { + t.Fatalf("Failed to load AnthropicMessagesRequest from YAML helper: %v", err) + } + if instance.Model != "claude-sonnet-4-20250514" { + t.Errorf(`Expected Model to be "claude-sonnet-4-20250514", got %v`, instance.Model) + } + if instance.MaxTokens != 4096 { + t.Errorf(`Expected MaxTokens to be 4096, got %v`, instance.MaxTokens) + } + if instance.System == nil || *instance.System != "You are a helpful assistant." { + t.Errorf(`Expected System to be "You are a helpful assistant.", got %v`, instance.System) + } + if instance.Temperature == nil || *instance.Temperature != 0.7 { + t.Errorf(`Expected Temperature to be 0.7, got %v`, instance.Temperature) + } + if instance.TopP == nil || *instance.TopP != 0.9 { + t.Errorf(`Expected TopP to be 0.9, got %v`, instance.TopP) + } + if instance.TopK == nil || *instance.TopK != 40 { + t.Errorf(`Expected TopK to be 40, got %v`, instance.TopK) + } + if len(instance.StopSequences) != 1 { + t.Fatalf("Expected StopSequences length to be 1, got %d", len(instance.StopSequences)) + } + if instance.StopSequences[0] != "\n\nHuman:" { + t.Errorf(`Expected StopSequences[0] to be "\n\nHuman:", got %v`, instance.StopSequences[0]) + } } // TestAnthropicMessagesRequestRoundtrip tests load -> save -> load produces equivalent data @@ -149,6 +252,12 @@ func TestAnthropicMessagesRequestRoundtrip(t *testing.T) { if reloaded.TopK == nil || *reloaded.TopK != 40 { t.Errorf(`Expected TopK to be 40, got %v`, reloaded.TopK) } + if len(reloaded.StopSequences) != 1 { + t.Fatalf("Expected StopSequences length to be 1, got %d", len(reloaded.StopSequences)) + } + if reloaded.StopSequences[0] != "\n\nHuman:" { + t.Errorf(`Expected StopSequences[0] to be "\n\nHuman:", got %v`, reloaded.StopSequences[0]) + } } // TestAnthropicMessagesRequestToJSON tests that ToJSON produces valid JSON @@ -185,6 +294,35 @@ func TestAnthropicMessagesRequestToJSON(t *testing.T) { if err := json.Unmarshal([]byte(jsonOutput), &parsed); err != nil { t.Fatalf("Failed to parse generated JSON: %v", err) } + + reloaded, err := prompty.LoadAnthropicMessagesRequest(parsed, ctx) + if err != nil { + t.Fatalf("Failed to reload generated JSON: %v", err) + } + if reloaded.Model != "claude-sonnet-4-20250514" { + t.Errorf(`Expected Model to be "claude-sonnet-4-20250514", got %v`, reloaded.Model) + } + if reloaded.MaxTokens != 4096 { + t.Errorf(`Expected MaxTokens to be 4096, got %v`, reloaded.MaxTokens) + } + if reloaded.System == nil || *reloaded.System != "You are a helpful assistant." { + t.Errorf(`Expected System to be "You are a helpful assistant.", got %v`, reloaded.System) + } + if reloaded.Temperature == nil || *reloaded.Temperature != 0.7 { + t.Errorf(`Expected Temperature to be 0.7, got %v`, reloaded.Temperature) + } + if reloaded.TopP == nil || *reloaded.TopP != 0.9 { + t.Errorf(`Expected TopP to be 0.9, got %v`, reloaded.TopP) + } + if reloaded.TopK == nil || *reloaded.TopK != 40 { + t.Errorf(`Expected TopK to be 40, got %v`, reloaded.TopK) + } + if len(reloaded.StopSequences) != 1 { + t.Fatalf("Expected StopSequences length to be 1, got %d", len(reloaded.StopSequences)) + } + if reloaded.StopSequences[0] != "\n\nHuman:" { + t.Errorf(`Expected StopSequences[0] to be "\n\nHuman:", got %v`, reloaded.StopSequences[0]) + } } // TestAnthropicMessagesRequestToYAML tests that ToYAML produces valid YAML @@ -221,4 +359,40 @@ func TestAnthropicMessagesRequestToYAML(t *testing.T) { if err := yaml.Unmarshal([]byte(yamlOutput), &parsed); err != nil { t.Fatalf("Failed to parse generated YAML: %v", err) } + + reloaded, err := prompty.LoadAnthropicMessagesRequest(parsed, ctx) + if err != nil { + t.Fatalf("Failed to reload generated YAML: %v", err) + } + if reloaded.Model != "claude-sonnet-4-20250514" { + t.Errorf(`Expected Model to be "claude-sonnet-4-20250514", got %v`, reloaded.Model) + } + if reloaded.MaxTokens != 4096 { + t.Errorf(`Expected MaxTokens to be 4096, got %v`, reloaded.MaxTokens) + } + if reloaded.System == nil || *reloaded.System != "You are a helpful assistant." { + t.Errorf(`Expected System to be "You are a helpful assistant.", got %v`, reloaded.System) + } + if reloaded.Temperature == nil || *reloaded.Temperature != 0.7 { + t.Errorf(`Expected Temperature to be 0.7, got %v`, reloaded.Temperature) + } + if reloaded.TopP == nil || *reloaded.TopP != 0.9 { + t.Errorf(`Expected TopP to be 0.9, got %v`, reloaded.TopP) + } + if reloaded.TopK == nil || *reloaded.TopK != 40 { + t.Errorf(`Expected TopK to be 40, got %v`, reloaded.TopK) + } + if len(reloaded.StopSequences) != 1 { + t.Fatalf("Expected StopSequences length to be 1, got %d", len(reloaded.StopSequences)) + } + if reloaded.StopSequences[0] != "\n\nHuman:" { + t.Errorf(`Expected StopSequences[0] to be "\n\nHuman:", got %v`, reloaded.StopSequences[0]) + } +} + +// TestAnthropicMessagesRequestFromJSONInvalid rejects malformed JSON instead of silently defaulting +func TestAnthropicMessagesRequestFromJSONInvalid(t *testing.T) { + if _, err := prompty.AnthropicMessagesRequestFromJSON("{"); err == nil { + t.Fatalf("Expected malformed JSON to fail") + } } diff --git a/runtime/go/prompty/model/anthropic_messages_response.go b/runtime/go/prompty/model/anthropic_messages_response.go index 05642b13..3666cb9d 100644 --- a/runtime/go/prompty/model/anthropic_messages_response.go +++ b/runtime/go/prompty/model/anthropic_messages_response.go @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. // Group: wire package prompty @@ -37,7 +38,8 @@ func LoadAnthropicMessagesResponse(data interface{}, ctx *LoadContext) (Anthropi result.Role = string(val.(string)) } if val, ok := m["content"]; ok && val != nil { - if arr, ok := val.([]interface{}); ok { + switch arr := val.(type) { + case []interface{}: result.Content = arr } } @@ -59,7 +61,7 @@ func LoadAnthropicMessagesResponse(data interface{}, ctx *LoadContext) (Anthropi } // Save serializes AnthropicMessagesResponse to map[string]interface{} -func (obj *AnthropicMessagesResponse) Save(ctx *SaveContext) map[string]interface{} { +func (obj AnthropicMessagesResponse) Save(ctx *SaveContext) map[string]interface{} { result := make(map[string]interface{}) result["id"] = obj.Id result["type"] = obj.Type @@ -88,11 +90,7 @@ func (obj *AnthropicMessagesResponse) ToJSON() (string, error) { func (obj *AnthropicMessagesResponse) ToYAML() (string, error) { ctx := NewSaveContext() data := obj.Save(ctx) - bytes, err := yaml.Marshal(data) - if err != nil { - return "", err - } - return string(bytes), nil + return marshalYAMLDocument(data) } // FromJSON creates AnthropicMessagesResponse from JSON string diff --git a/runtime/go/prompty/model/anthropic_messages_response_test.go b/runtime/go/prompty/model/anthropic_messages_response_test.go index 9401e2c6..8be3d5cf 100644 --- a/runtime/go/prompty/model/anthropic_messages_response_test.go +++ b/runtime/go/prompty/model/anthropic_messages_response_test.go @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. package prompty_test @@ -70,6 +71,55 @@ stop_reason: end_turn } } +// TestAnthropicMessagesResponseFromJSON tests loading AnthropicMessagesResponse through the generated JSON helper +func TestAnthropicMessagesResponseFromJSON(t *testing.T) { + jsonData := ` +{ + "id": "msg_01XFDUDYJgAACzvnptvVoYEL", + "model": "claude-sonnet-4-20250514", + "stop_reason": "end_turn" +} +` + + instance, err := prompty.AnthropicMessagesResponseFromJSON(jsonData) + if err != nil { + t.Fatalf("Failed to load AnthropicMessagesResponse from JSON helper: %v", err) + } + if instance.Id != "msg_01XFDUDYJgAACzvnptvVoYEL" { + t.Errorf(`Expected Id to be "msg_01XFDUDYJgAACzvnptvVoYEL", got %v`, instance.Id) + } + if instance.Model != "claude-sonnet-4-20250514" { + t.Errorf(`Expected Model to be "claude-sonnet-4-20250514", got %v`, instance.Model) + } + if instance.StopReason != "end_turn" { + t.Errorf(`Expected StopReason to be "end_turn", got %v`, instance.StopReason) + } +} + +// TestAnthropicMessagesResponseFromYAML tests loading AnthropicMessagesResponse through the generated YAML helper +func TestAnthropicMessagesResponseFromYAML(t *testing.T) { + yamlData := ` +id: msg_01XFDUDYJgAACzvnptvVoYEL +model: claude-sonnet-4-20250514 +stop_reason: end_turn + +` + + instance, err := prompty.AnthropicMessagesResponseFromYAML(yamlData) + if err != nil { + t.Fatalf("Failed to load AnthropicMessagesResponse from YAML helper: %v", err) + } + if instance.Id != "msg_01XFDUDYJgAACzvnptvVoYEL" { + t.Errorf(`Expected Id to be "msg_01XFDUDYJgAACzvnptvVoYEL", got %v`, instance.Id) + } + if instance.Model != "claude-sonnet-4-20250514" { + t.Errorf(`Expected Model to be "claude-sonnet-4-20250514", got %v`, instance.Model) + } + if instance.StopReason != "end_turn" { + t.Errorf(`Expected StopReason to be "end_turn", got %v`, instance.StopReason) + } +} + // TestAnthropicMessagesResponseRoundtrip tests load -> save -> load produces equivalent data func TestAnthropicMessagesResponseRoundtrip(t *testing.T) { jsonData := ` @@ -135,6 +185,20 @@ func TestAnthropicMessagesResponseToJSON(t *testing.T) { if err := json.Unmarshal([]byte(jsonOutput), &parsed); err != nil { t.Fatalf("Failed to parse generated JSON: %v", err) } + + reloaded, err := prompty.LoadAnthropicMessagesResponse(parsed, ctx) + if err != nil { + t.Fatalf("Failed to reload generated JSON: %v", err) + } + if reloaded.Id != "msg_01XFDUDYJgAACzvnptvVoYEL" { + t.Errorf(`Expected Id to be "msg_01XFDUDYJgAACzvnptvVoYEL", got %v`, reloaded.Id) + } + if reloaded.Model != "claude-sonnet-4-20250514" { + t.Errorf(`Expected Model to be "claude-sonnet-4-20250514", got %v`, reloaded.Model) + } + if reloaded.StopReason != "end_turn" { + t.Errorf(`Expected StopReason to be "end_turn", got %v`, reloaded.StopReason) + } } // TestAnthropicMessagesResponseToYAML tests that ToYAML produces valid YAML @@ -165,4 +229,25 @@ func TestAnthropicMessagesResponseToYAML(t *testing.T) { if err := yaml.Unmarshal([]byte(yamlOutput), &parsed); err != nil { t.Fatalf("Failed to parse generated YAML: %v", err) } + + reloaded, err := prompty.LoadAnthropicMessagesResponse(parsed, ctx) + if err != nil { + t.Fatalf("Failed to reload generated YAML: %v", err) + } + if reloaded.Id != "msg_01XFDUDYJgAACzvnptvVoYEL" { + t.Errorf(`Expected Id to be "msg_01XFDUDYJgAACzvnptvVoYEL", got %v`, reloaded.Id) + } + if reloaded.Model != "claude-sonnet-4-20250514" { + t.Errorf(`Expected Model to be "claude-sonnet-4-20250514", got %v`, reloaded.Model) + } + if reloaded.StopReason != "end_turn" { + t.Errorf(`Expected StopReason to be "end_turn", got %v`, reloaded.StopReason) + } +} + +// TestAnthropicMessagesResponseFromJSONInvalid rejects malformed JSON instead of silently defaulting +func TestAnthropicMessagesResponseFromJSONInvalid(t *testing.T) { + if _, err := prompty.AnthropicMessagesResponseFromJSON("{"); err == nil { + t.Fatalf("Expected malformed JSON to fail") + } } diff --git a/runtime/go/prompty/model/anthropic_text_block.go b/runtime/go/prompty/model/anthropic_text_block.go index d2e51a71..f2d782e2 100644 --- a/runtime/go/prompty/model/anthropic_text_block.go +++ b/runtime/go/prompty/model/anthropic_text_block.go @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. // Group: wire package prompty @@ -34,7 +35,7 @@ func LoadAnthropicTextBlock(data interface{}, ctx *LoadContext) (AnthropicTextBl } // Save serializes AnthropicTextBlock to map[string]interface{} -func (obj *AnthropicTextBlock) Save(ctx *SaveContext) map[string]interface{} { +func (obj AnthropicTextBlock) Save(ctx *SaveContext) map[string]interface{} { result := make(map[string]interface{}) result["type"] = obj.Type result["text"] = obj.Text @@ -57,11 +58,7 @@ func (obj *AnthropicTextBlock) ToJSON() (string, error) { func (obj *AnthropicTextBlock) ToYAML() (string, error) { ctx := NewSaveContext() data := obj.Save(ctx) - bytes, err := yaml.Marshal(data) - if err != nil { - return "", err - } - return string(bytes), nil + return marshalYAMLDocument(data) } // FromJSON creates AnthropicTextBlock from JSON string diff --git a/runtime/go/prompty/model/anthropic_text_block_test.go b/runtime/go/prompty/model/anthropic_text_block_test.go index df466eac..71410762 100644 --- a/runtime/go/prompty/model/anthropic_text_block_test.go +++ b/runtime/go/prompty/model/anthropic_text_block_test.go @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. package prompty_test @@ -54,6 +55,39 @@ text: Hello, how can I help? } } +// TestAnthropicTextBlockFromJSON tests loading AnthropicTextBlock through the generated JSON helper +func TestAnthropicTextBlockFromJSON(t *testing.T) { + jsonData := ` +{ + "text": "Hello, how can I help?" +} +` + + instance, err := prompty.AnthropicTextBlockFromJSON(jsonData) + if err != nil { + t.Fatalf("Failed to load AnthropicTextBlock from JSON helper: %v", err) + } + if instance.Text != "Hello, how can I help?" { + t.Errorf(`Expected Text to be "Hello, how can I help?", got %v`, instance.Text) + } +} + +// TestAnthropicTextBlockFromYAML tests loading AnthropicTextBlock through the generated YAML helper +func TestAnthropicTextBlockFromYAML(t *testing.T) { + yamlData := ` +text: Hello, how can I help? + +` + + instance, err := prompty.AnthropicTextBlockFromYAML(yamlData) + if err != nil { + t.Fatalf("Failed to load AnthropicTextBlock from YAML helper: %v", err) + } + if instance.Text != "Hello, how can I help?" { + t.Errorf(`Expected Text to be "Hello, how can I help?", got %v`, instance.Text) + } +} + // TestAnthropicTextBlockRoundtrip tests load -> save -> load produces equivalent data func TestAnthropicTextBlockRoundtrip(t *testing.T) { jsonData := ` @@ -109,6 +143,14 @@ func TestAnthropicTextBlockToJSON(t *testing.T) { if err := json.Unmarshal([]byte(jsonOutput), &parsed); err != nil { t.Fatalf("Failed to parse generated JSON: %v", err) } + + reloaded, err := prompty.LoadAnthropicTextBlock(parsed, ctx) + if err != nil { + t.Fatalf("Failed to reload generated JSON: %v", err) + } + if reloaded.Text != "Hello, how can I help?" { + t.Errorf(`Expected Text to be "Hello, how can I help?", got %v`, reloaded.Text) + } } // TestAnthropicTextBlockToYAML tests that ToYAML produces valid YAML @@ -137,4 +179,19 @@ func TestAnthropicTextBlockToYAML(t *testing.T) { if err := yaml.Unmarshal([]byte(yamlOutput), &parsed); err != nil { t.Fatalf("Failed to parse generated YAML: %v", err) } + + reloaded, err := prompty.LoadAnthropicTextBlock(parsed, ctx) + if err != nil { + t.Fatalf("Failed to reload generated YAML: %v", err) + } + if reloaded.Text != "Hello, how can I help?" { + t.Errorf(`Expected Text to be "Hello, how can I help?", got %v`, reloaded.Text) + } +} + +// TestAnthropicTextBlockFromJSONInvalid rejects malformed JSON instead of silently defaulting +func TestAnthropicTextBlockFromJSONInvalid(t *testing.T) { + if _, err := prompty.AnthropicTextBlockFromJSON("{"); err == nil { + t.Fatalf("Expected malformed JSON to fail") + } } diff --git a/runtime/go/prompty/model/anthropic_tool_definition.go b/runtime/go/prompty/model/anthropic_tool_definition.go index dac8c6b8..eebdc3e1 100644 --- a/runtime/go/prompty/model/anthropic_tool_definition.go +++ b/runtime/go/prompty/model/anthropic_tool_definition.go @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. // Group: wire package prompty @@ -43,7 +44,7 @@ func LoadAnthropicToolDefinition(data interface{}, ctx *LoadContext) (AnthropicT } // Save serializes AnthropicToolDefinition to map[string]interface{} -func (obj *AnthropicToolDefinition) Save(ctx *SaveContext) map[string]interface{} { +func (obj AnthropicToolDefinition) Save(ctx *SaveContext) map[string]interface{} { result := make(map[string]interface{}) result["name"] = obj.Name if obj.Description != nil { @@ -69,11 +70,7 @@ func (obj *AnthropicToolDefinition) ToJSON() (string, error) { func (obj *AnthropicToolDefinition) ToYAML() (string, error) { ctx := NewSaveContext() data := obj.Save(ctx) - bytes, err := yaml.Marshal(data) - if err != nil { - return "", err - } - return string(bytes), nil + return marshalYAMLDocument(data) } // FromJSON creates AnthropicToolDefinition from JSON string diff --git a/runtime/go/prompty/model/anthropic_tool_definition_test.go b/runtime/go/prompty/model/anthropic_tool_definition_test.go index 363de7ce..239753d9 100644 --- a/runtime/go/prompty/model/anthropic_tool_definition_test.go +++ b/runtime/go/prompty/model/anthropic_tool_definition_test.go @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. package prompty_test @@ -62,6 +63,47 @@ description: Get the current weather for a city } } +// TestAnthropicToolDefinitionFromJSON tests loading AnthropicToolDefinition through the generated JSON helper +func TestAnthropicToolDefinitionFromJSON(t *testing.T) { + jsonData := ` +{ + "name": "get_weather", + "description": "Get the current weather for a city" +} +` + + instance, err := prompty.AnthropicToolDefinitionFromJSON(jsonData) + if err != nil { + t.Fatalf("Failed to load AnthropicToolDefinition from JSON helper: %v", err) + } + if instance.Name != "get_weather" { + t.Errorf(`Expected Name to be "get_weather", got %v`, instance.Name) + } + if instance.Description == nil || *instance.Description != "Get the current weather for a city" { + t.Errorf(`Expected Description to be "Get the current weather for a city", got %v`, instance.Description) + } +} + +// TestAnthropicToolDefinitionFromYAML tests loading AnthropicToolDefinition through the generated YAML helper +func TestAnthropicToolDefinitionFromYAML(t *testing.T) { + yamlData := ` +name: get_weather +description: Get the current weather for a city + +` + + instance, err := prompty.AnthropicToolDefinitionFromYAML(yamlData) + if err != nil { + t.Fatalf("Failed to load AnthropicToolDefinition from YAML helper: %v", err) + } + if instance.Name != "get_weather" { + t.Errorf(`Expected Name to be "get_weather", got %v`, instance.Name) + } + if instance.Description == nil || *instance.Description != "Get the current weather for a city" { + t.Errorf(`Expected Description to be "Get the current weather for a city", got %v`, instance.Description) + } +} + // TestAnthropicToolDefinitionRoundtrip tests load -> save -> load produces equivalent data func TestAnthropicToolDefinitionRoundtrip(t *testing.T) { jsonData := ` @@ -122,6 +164,17 @@ func TestAnthropicToolDefinitionToJSON(t *testing.T) { if err := json.Unmarshal([]byte(jsonOutput), &parsed); err != nil { t.Fatalf("Failed to parse generated JSON: %v", err) } + + reloaded, err := prompty.LoadAnthropicToolDefinition(parsed, ctx) + if err != nil { + t.Fatalf("Failed to reload generated JSON: %v", err) + } + if reloaded.Name != "get_weather" { + t.Errorf(`Expected Name to be "get_weather", got %v`, reloaded.Name) + } + if reloaded.Description == nil || *reloaded.Description != "Get the current weather for a city" { + t.Errorf(`Expected Description to be "Get the current weather for a city", got %v`, reloaded.Description) + } } // TestAnthropicToolDefinitionToYAML tests that ToYAML produces valid YAML @@ -151,4 +204,22 @@ func TestAnthropicToolDefinitionToYAML(t *testing.T) { if err := yaml.Unmarshal([]byte(yamlOutput), &parsed); err != nil { t.Fatalf("Failed to parse generated YAML: %v", err) } + + reloaded, err := prompty.LoadAnthropicToolDefinition(parsed, ctx) + if err != nil { + t.Fatalf("Failed to reload generated YAML: %v", err) + } + if reloaded.Name != "get_weather" { + t.Errorf(`Expected Name to be "get_weather", got %v`, reloaded.Name) + } + if reloaded.Description == nil || *reloaded.Description != "Get the current weather for a city" { + t.Errorf(`Expected Description to be "Get the current weather for a city", got %v`, reloaded.Description) + } +} + +// TestAnthropicToolDefinitionFromJSONInvalid rejects malformed JSON instead of silently defaulting +func TestAnthropicToolDefinitionFromJSONInvalid(t *testing.T) { + if _, err := prompty.AnthropicToolDefinitionFromJSON("{"); err == nil { + t.Fatalf("Expected malformed JSON to fail") + } } diff --git a/runtime/go/prompty/model/anthropic_tool_result_block.go b/runtime/go/prompty/model/anthropic_tool_result_block.go index ca544225..bb7ecfce 100644 --- a/runtime/go/prompty/model/anthropic_tool_result_block.go +++ b/runtime/go/prompty/model/anthropic_tool_result_block.go @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. // Group: wire package prompty @@ -38,7 +39,7 @@ func LoadAnthropicToolResultBlock(data interface{}, ctx *LoadContext) (Anthropic } // Save serializes AnthropicToolResultBlock to map[string]interface{} -func (obj *AnthropicToolResultBlock) Save(ctx *SaveContext) map[string]interface{} { +func (obj AnthropicToolResultBlock) Save(ctx *SaveContext) map[string]interface{} { result := make(map[string]interface{}) result["type"] = obj.Type result["tool_use_id"] = obj.ToolUseId @@ -62,11 +63,7 @@ func (obj *AnthropicToolResultBlock) ToJSON() (string, error) { func (obj *AnthropicToolResultBlock) ToYAML() (string, error) { ctx := NewSaveContext() data := obj.Save(ctx) - bytes, err := yaml.Marshal(data) - if err != nil { - return "", err - } - return string(bytes), nil + return marshalYAMLDocument(data) } // FromJSON creates AnthropicToolResultBlock from JSON string diff --git a/runtime/go/prompty/model/anthropic_tool_result_block_test.go b/runtime/go/prompty/model/anthropic_tool_result_block_test.go index 0dfacc78..ca0769dc 100644 --- a/runtime/go/prompty/model/anthropic_tool_result_block_test.go +++ b/runtime/go/prompty/model/anthropic_tool_result_block_test.go @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. package prompty_test @@ -62,6 +63,47 @@ content: 72°F and sunny in Paris } } +// TestAnthropicToolResultBlockFromJSON tests loading AnthropicToolResultBlock through the generated JSON helper +func TestAnthropicToolResultBlockFromJSON(t *testing.T) { + jsonData := ` +{ + "tool_use_id": "toolu_01A09q90qw90lq917835lq9", + "content": "72°F and sunny in Paris" +} +` + + instance, err := prompty.AnthropicToolResultBlockFromJSON(jsonData) + if err != nil { + t.Fatalf("Failed to load AnthropicToolResultBlock from JSON helper: %v", err) + } + if instance.ToolUseId != "toolu_01A09q90qw90lq917835lq9" { + t.Errorf(`Expected ToolUseId to be "toolu_01A09q90qw90lq917835lq9", got %v`, instance.ToolUseId) + } + if instance.Content != "72°F and sunny in Paris" { + t.Errorf(`Expected Content to be "72°F and sunny in Paris", got %v`, instance.Content) + } +} + +// TestAnthropicToolResultBlockFromYAML tests loading AnthropicToolResultBlock through the generated YAML helper +func TestAnthropicToolResultBlockFromYAML(t *testing.T) { + yamlData := ` +tool_use_id: toolu_01A09q90qw90lq917835lq9 +content: 72°F and sunny in Paris + +` + + instance, err := prompty.AnthropicToolResultBlockFromYAML(yamlData) + if err != nil { + t.Fatalf("Failed to load AnthropicToolResultBlock from YAML helper: %v", err) + } + if instance.ToolUseId != "toolu_01A09q90qw90lq917835lq9" { + t.Errorf(`Expected ToolUseId to be "toolu_01A09q90qw90lq917835lq9", got %v`, instance.ToolUseId) + } + if instance.Content != "72°F and sunny in Paris" { + t.Errorf(`Expected Content to be "72°F and sunny in Paris", got %v`, instance.Content) + } +} + // TestAnthropicToolResultBlockRoundtrip tests load -> save -> load produces equivalent data func TestAnthropicToolResultBlockRoundtrip(t *testing.T) { jsonData := ` @@ -122,6 +164,17 @@ func TestAnthropicToolResultBlockToJSON(t *testing.T) { if err := json.Unmarshal([]byte(jsonOutput), &parsed); err != nil { t.Fatalf("Failed to parse generated JSON: %v", err) } + + reloaded, err := prompty.LoadAnthropicToolResultBlock(parsed, ctx) + if err != nil { + t.Fatalf("Failed to reload generated JSON: %v", err) + } + if reloaded.ToolUseId != "toolu_01A09q90qw90lq917835lq9" { + t.Errorf(`Expected ToolUseId to be "toolu_01A09q90qw90lq917835lq9", got %v`, reloaded.ToolUseId) + } + if reloaded.Content != "72°F and sunny in Paris" { + t.Errorf(`Expected Content to be "72°F and sunny in Paris", got %v`, reloaded.Content) + } } // TestAnthropicToolResultBlockToYAML tests that ToYAML produces valid YAML @@ -151,4 +204,22 @@ func TestAnthropicToolResultBlockToYAML(t *testing.T) { if err := yaml.Unmarshal([]byte(yamlOutput), &parsed); err != nil { t.Fatalf("Failed to parse generated YAML: %v", err) } + + reloaded, err := prompty.LoadAnthropicToolResultBlock(parsed, ctx) + if err != nil { + t.Fatalf("Failed to reload generated YAML: %v", err) + } + if reloaded.ToolUseId != "toolu_01A09q90qw90lq917835lq9" { + t.Errorf(`Expected ToolUseId to be "toolu_01A09q90qw90lq917835lq9", got %v`, reloaded.ToolUseId) + } + if reloaded.Content != "72°F and sunny in Paris" { + t.Errorf(`Expected Content to be "72°F and sunny in Paris", got %v`, reloaded.Content) + } +} + +// TestAnthropicToolResultBlockFromJSONInvalid rejects malformed JSON instead of silently defaulting +func TestAnthropicToolResultBlockFromJSONInvalid(t *testing.T) { + if _, err := prompty.AnthropicToolResultBlockFromJSON("{"); err == nil { + t.Fatalf("Expected malformed JSON to fail") + } } diff --git a/runtime/go/prompty/model/anthropic_tool_use_block.go b/runtime/go/prompty/model/anthropic_tool_use_block.go index c6eaa502..35e8fdc6 100644 --- a/runtime/go/prompty/model/anthropic_tool_use_block.go +++ b/runtime/go/prompty/model/anthropic_tool_use_block.go @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. // Group: wire package prompty @@ -45,7 +46,7 @@ func LoadAnthropicToolUseBlock(data interface{}, ctx *LoadContext) (AnthropicToo } // Save serializes AnthropicToolUseBlock to map[string]interface{} -func (obj *AnthropicToolUseBlock) Save(ctx *SaveContext) map[string]interface{} { +func (obj AnthropicToolUseBlock) Save(ctx *SaveContext) map[string]interface{} { result := make(map[string]interface{}) result["type"] = obj.Type result["id"] = obj.Id @@ -70,11 +71,7 @@ func (obj *AnthropicToolUseBlock) ToJSON() (string, error) { func (obj *AnthropicToolUseBlock) ToYAML() (string, error) { ctx := NewSaveContext() data := obj.Save(ctx) - bytes, err := yaml.Marshal(data) - if err != nil { - return "", err - } - return string(bytes), nil + return marshalYAMLDocument(data) } // FromJSON creates AnthropicToolUseBlock from JSON string diff --git a/runtime/go/prompty/model/anthropic_tool_use_block_test.go b/runtime/go/prompty/model/anthropic_tool_use_block_test.go index 8f4c14ef..bd94d4a0 100644 --- a/runtime/go/prompty/model/anthropic_tool_use_block_test.go +++ b/runtime/go/prompty/model/anthropic_tool_use_block_test.go @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. package prompty_test @@ -38,6 +39,12 @@ func TestAnthropicToolUseBlockLoadJSON(t *testing.T) { if instance.Name != "get_weather" { t.Errorf(`Expected Name to be "get_weather", got %v`, instance.Name) } + if instance.Input == nil { + t.Fatalf("Expected Input to be populated") + } + if got := instance.Input["city"]; got != "Paris" { + t.Errorf(`Expected Input["city"] to be "Paris", got %v`, got) + } } // TestAnthropicToolUseBlockLoadYAML tests loading AnthropicToolUseBlock from YAML @@ -65,6 +72,70 @@ input: if instance.Name != "get_weather" { t.Errorf(`Expected Name to be "get_weather", got %v`, instance.Name) } + if instance.Input == nil { + t.Fatalf("Expected Input to be populated") + } + if got := instance.Input["city"]; got != "Paris" { + t.Errorf(`Expected Input["city"] to be "Paris", got %v`, got) + } +} + +// TestAnthropicToolUseBlockFromJSON tests loading AnthropicToolUseBlock through the generated JSON helper +func TestAnthropicToolUseBlockFromJSON(t *testing.T) { + jsonData := ` +{ + "id": "toolu_01A09q90qw90lq917835lq9", + "name": "get_weather", + "input": { + "city": "Paris" + } +} +` + + instance, err := prompty.AnthropicToolUseBlockFromJSON(jsonData) + if err != nil { + t.Fatalf("Failed to load AnthropicToolUseBlock from JSON helper: %v", err) + } + if instance.Id != "toolu_01A09q90qw90lq917835lq9" { + t.Errorf(`Expected Id to be "toolu_01A09q90qw90lq917835lq9", got %v`, instance.Id) + } + if instance.Name != "get_weather" { + t.Errorf(`Expected Name to be "get_weather", got %v`, instance.Name) + } + if instance.Input == nil { + t.Fatalf("Expected Input to be populated") + } + if got := instance.Input["city"]; got != "Paris" { + t.Errorf(`Expected Input["city"] to be "Paris", got %v`, got) + } +} + +// TestAnthropicToolUseBlockFromYAML tests loading AnthropicToolUseBlock through the generated YAML helper +func TestAnthropicToolUseBlockFromYAML(t *testing.T) { + yamlData := ` +id: toolu_01A09q90qw90lq917835lq9 +name: get_weather +input: + city: Paris + +` + + instance, err := prompty.AnthropicToolUseBlockFromYAML(yamlData) + if err != nil { + t.Fatalf("Failed to load AnthropicToolUseBlock from YAML helper: %v", err) + } + if instance.Id != "toolu_01A09q90qw90lq917835lq9" { + t.Errorf(`Expected Id to be "toolu_01A09q90qw90lq917835lq9", got %v`, instance.Id) + } + if instance.Name != "get_weather" { + t.Errorf(`Expected Name to be "get_weather", got %v`, instance.Name) + } + if instance.Input == nil { + t.Fatalf("Expected Input to be populated") + } + if got := instance.Input["city"]; got != "Paris" { + t.Errorf(`Expected Input["city"] to be "Paris", got %v`, got) + } } // TestAnthropicToolUseBlockRoundtrip tests load -> save -> load produces equivalent data @@ -101,6 +172,12 @@ func TestAnthropicToolUseBlockRoundtrip(t *testing.T) { if reloaded.Name != "get_weather" { t.Errorf(`Expected Name to be "get_weather", got %v`, reloaded.Name) } + if reloaded.Input == nil { + t.Fatalf("Expected Input to be populated") + } + if got := reloaded.Input["city"]; got != "Paris" { + t.Errorf(`Expected Input["city"] to be "Paris", got %v`, got) + } } // TestAnthropicToolUseBlockToJSON tests that ToJSON produces valid JSON @@ -133,6 +210,23 @@ func TestAnthropicToolUseBlockToJSON(t *testing.T) { if err := json.Unmarshal([]byte(jsonOutput), &parsed); err != nil { t.Fatalf("Failed to parse generated JSON: %v", err) } + + reloaded, err := prompty.LoadAnthropicToolUseBlock(parsed, ctx) + if err != nil { + t.Fatalf("Failed to reload generated JSON: %v", err) + } + if reloaded.Id != "toolu_01A09q90qw90lq917835lq9" { + t.Errorf(`Expected Id to be "toolu_01A09q90qw90lq917835lq9", got %v`, reloaded.Id) + } + if reloaded.Name != "get_weather" { + t.Errorf(`Expected Name to be "get_weather", got %v`, reloaded.Name) + } + if reloaded.Input == nil { + t.Fatalf("Expected Input to be populated") + } + if got := reloaded.Input["city"]; got != "Paris" { + t.Errorf(`Expected Input["city"] to be "Paris", got %v`, got) + } } // TestAnthropicToolUseBlockToYAML tests that ToYAML produces valid YAML @@ -165,4 +259,28 @@ func TestAnthropicToolUseBlockToYAML(t *testing.T) { if err := yaml.Unmarshal([]byte(yamlOutput), &parsed); err != nil { t.Fatalf("Failed to parse generated YAML: %v", err) } + + reloaded, err := prompty.LoadAnthropicToolUseBlock(parsed, ctx) + if err != nil { + t.Fatalf("Failed to reload generated YAML: %v", err) + } + if reloaded.Id != "toolu_01A09q90qw90lq917835lq9" { + t.Errorf(`Expected Id to be "toolu_01A09q90qw90lq917835lq9", got %v`, reloaded.Id) + } + if reloaded.Name != "get_weather" { + t.Errorf(`Expected Name to be "get_weather", got %v`, reloaded.Name) + } + if reloaded.Input == nil { + t.Fatalf("Expected Input to be populated") + } + if got := reloaded.Input["city"]; got != "Paris" { + t.Errorf(`Expected Input["city"] to be "Paris", got %v`, got) + } +} + +// TestAnthropicToolUseBlockFromJSONInvalid rejects malformed JSON instead of silently defaulting +func TestAnthropicToolUseBlockFromJSONInvalid(t *testing.T) { + if _, err := prompty.AnthropicToolUseBlockFromJSON("{"); err == nil { + t.Fatalf("Expected malformed JSON to fail") + } } diff --git a/runtime/go/prompty/model/anthropic_usage.go b/runtime/go/prompty/model/anthropic_usage.go index 67581468..1a95ac2e 100644 --- a/runtime/go/prompty/model/anthropic_usage.go +++ b/runtime/go/prompty/model/anthropic_usage.go @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. // Group: wire package prompty @@ -56,7 +57,7 @@ func LoadAnthropicUsage(data interface{}, ctx *LoadContext) (AnthropicUsage, err } // Save serializes AnthropicUsage to map[string]interface{} -func (obj *AnthropicUsage) Save(ctx *SaveContext) map[string]interface{} { +func (obj AnthropicUsage) Save(ctx *SaveContext) map[string]interface{} { result := make(map[string]interface{}) result["input_tokens"] = obj.InputTokens result["output_tokens"] = obj.OutputTokens @@ -79,11 +80,7 @@ func (obj *AnthropicUsage) ToJSON() (string, error) { func (obj *AnthropicUsage) ToYAML() (string, error) { ctx := NewSaveContext() data := obj.Save(ctx) - bytes, err := yaml.Marshal(data) - if err != nil { - return "", err - } - return string(bytes), nil + return marshalYAMLDocument(data) } // FromJSON creates AnthropicUsage from JSON string diff --git a/runtime/go/prompty/model/anthropic_usage_test.go b/runtime/go/prompty/model/anthropic_usage_test.go index 2269d991..e522865a 100644 --- a/runtime/go/prompty/model/anthropic_usage_test.go +++ b/runtime/go/prompty/model/anthropic_usage_test.go @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. package prompty_test @@ -62,6 +63,47 @@ output_tokens: 42 } } +// TestAnthropicUsageFromJSON tests loading AnthropicUsage through the generated JSON helper +func TestAnthropicUsageFromJSON(t *testing.T) { + jsonData := ` +{ + "input_tokens": 150, + "output_tokens": 42 +} +` + + instance, err := prompty.AnthropicUsageFromJSON(jsonData) + if err != nil { + t.Fatalf("Failed to load AnthropicUsage from JSON helper: %v", err) + } + if instance.InputTokens != 150 { + t.Errorf(`Expected InputTokens to be 150, got %v`, instance.InputTokens) + } + if instance.OutputTokens != 42 { + t.Errorf(`Expected OutputTokens to be 42, got %v`, instance.OutputTokens) + } +} + +// TestAnthropicUsageFromYAML tests loading AnthropicUsage through the generated YAML helper +func TestAnthropicUsageFromYAML(t *testing.T) { + yamlData := ` +input_tokens: 150 +output_tokens: 42 + +` + + instance, err := prompty.AnthropicUsageFromYAML(yamlData) + if err != nil { + t.Fatalf("Failed to load AnthropicUsage from YAML helper: %v", err) + } + if instance.InputTokens != 150 { + t.Errorf(`Expected InputTokens to be 150, got %v`, instance.InputTokens) + } + if instance.OutputTokens != 42 { + t.Errorf(`Expected OutputTokens to be 42, got %v`, instance.OutputTokens) + } +} + // TestAnthropicUsageRoundtrip tests load -> save -> load produces equivalent data func TestAnthropicUsageRoundtrip(t *testing.T) { jsonData := ` @@ -122,6 +164,17 @@ func TestAnthropicUsageToJSON(t *testing.T) { if err := json.Unmarshal([]byte(jsonOutput), &parsed); err != nil { t.Fatalf("Failed to parse generated JSON: %v", err) } + + reloaded, err := prompty.LoadAnthropicUsage(parsed, ctx) + if err != nil { + t.Fatalf("Failed to reload generated JSON: %v", err) + } + if reloaded.InputTokens != 150 { + t.Errorf(`Expected InputTokens to be 150, got %v`, reloaded.InputTokens) + } + if reloaded.OutputTokens != 42 { + t.Errorf(`Expected OutputTokens to be 42, got %v`, reloaded.OutputTokens) + } } // TestAnthropicUsageToYAML tests that ToYAML produces valid YAML @@ -151,4 +204,22 @@ func TestAnthropicUsageToYAML(t *testing.T) { if err := yaml.Unmarshal([]byte(yamlOutput), &parsed); err != nil { t.Fatalf("Failed to parse generated YAML: %v", err) } + + reloaded, err := prompty.LoadAnthropicUsage(parsed, ctx) + if err != nil { + t.Fatalf("Failed to reload generated YAML: %v", err) + } + if reloaded.InputTokens != 150 { + t.Errorf(`Expected InputTokens to be 150, got %v`, reloaded.InputTokens) + } + if reloaded.OutputTokens != 42 { + t.Errorf(`Expected OutputTokens to be 42, got %v`, reloaded.OutputTokens) + } +} + +// TestAnthropicUsageFromJSONInvalid rejects malformed JSON instead of silently defaulting +func TestAnthropicUsageFromJSONInvalid(t *testing.T) { + if _, err := prompty.AnthropicUsageFromJSON("{"); err == nil { + t.Fatalf("Expected malformed JSON to fail") + } } diff --git a/runtime/go/prompty/model/anthropic_wire_message.go b/runtime/go/prompty/model/anthropic_wire_message.go index 232fb159..60c23b37 100644 --- a/runtime/go/prompty/model/anthropic_wire_message.go +++ b/runtime/go/prompty/model/anthropic_wire_message.go @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. // Group: wire package prompty @@ -28,7 +29,8 @@ func LoadAnthropicWireMessage(data interface{}, ctx *LoadContext) (AnthropicWire result.Role = string(val.(string)) } if val, ok := m["content"]; ok && val != nil { - if arr, ok := val.([]interface{}); ok { + switch arr := val.(type) { + case []interface{}: result.Content = arr } } @@ -38,7 +40,7 @@ func LoadAnthropicWireMessage(data interface{}, ctx *LoadContext) (AnthropicWire } // Save serializes AnthropicWireMessage to map[string]interface{} -func (obj *AnthropicWireMessage) Save(ctx *SaveContext) map[string]interface{} { +func (obj AnthropicWireMessage) Save(ctx *SaveContext) map[string]interface{} { result := make(map[string]interface{}) result["role"] = obj.Role result["content"] = obj.Content @@ -61,11 +63,7 @@ func (obj *AnthropicWireMessage) ToJSON() (string, error) { func (obj *AnthropicWireMessage) ToYAML() (string, error) { ctx := NewSaveContext() data := obj.Save(ctx) - bytes, err := yaml.Marshal(data) - if err != nil { - return "", err - } - return string(bytes), nil + return marshalYAMLDocument(data) } // FromJSON creates AnthropicWireMessage from JSON string diff --git a/runtime/go/prompty/model/anthropic_wire_message_test.go b/runtime/go/prompty/model/anthropic_wire_message_test.go index 825ca03d..ed29e634 100644 --- a/runtime/go/prompty/model/anthropic_wire_message_test.go +++ b/runtime/go/prompty/model/anthropic_wire_message_test.go @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. package prompty_test @@ -54,6 +55,39 @@ role: user } } +// TestAnthropicWireMessageFromJSON tests loading AnthropicWireMessage through the generated JSON helper +func TestAnthropicWireMessageFromJSON(t *testing.T) { + jsonData := ` +{ + "role": "user" +} +` + + instance, err := prompty.AnthropicWireMessageFromJSON(jsonData) + if err != nil { + t.Fatalf("Failed to load AnthropicWireMessage from JSON helper: %v", err) + } + if instance.Role != "user" { + t.Errorf(`Expected Role to be "user", got %v`, instance.Role) + } +} + +// TestAnthropicWireMessageFromYAML tests loading AnthropicWireMessage through the generated YAML helper +func TestAnthropicWireMessageFromYAML(t *testing.T) { + yamlData := ` +role: user + +` + + instance, err := prompty.AnthropicWireMessageFromYAML(yamlData) + if err != nil { + t.Fatalf("Failed to load AnthropicWireMessage from YAML helper: %v", err) + } + if instance.Role != "user" { + t.Errorf(`Expected Role to be "user", got %v`, instance.Role) + } +} + // TestAnthropicWireMessageRoundtrip tests load -> save -> load produces equivalent data func TestAnthropicWireMessageRoundtrip(t *testing.T) { jsonData := ` @@ -109,6 +143,14 @@ func TestAnthropicWireMessageToJSON(t *testing.T) { if err := json.Unmarshal([]byte(jsonOutput), &parsed); err != nil { t.Fatalf("Failed to parse generated JSON: %v", err) } + + reloaded, err := prompty.LoadAnthropicWireMessage(parsed, ctx) + if err != nil { + t.Fatalf("Failed to reload generated JSON: %v", err) + } + if reloaded.Role != "user" { + t.Errorf(`Expected Role to be "user", got %v`, reloaded.Role) + } } // TestAnthropicWireMessageToYAML tests that ToYAML produces valid YAML @@ -137,4 +179,19 @@ func TestAnthropicWireMessageToYAML(t *testing.T) { if err := yaml.Unmarshal([]byte(yamlOutput), &parsed); err != nil { t.Fatalf("Failed to parse generated YAML: %v", err) } + + reloaded, err := prompty.LoadAnthropicWireMessage(parsed, ctx) + if err != nil { + t.Fatalf("Failed to reload generated YAML: %v", err) + } + if reloaded.Role != "user" { + t.Errorf(`Expected Role to be "user", got %v`, reloaded.Role) + } +} + +// TestAnthropicWireMessageFromJSONInvalid rejects malformed JSON instead of silently defaulting +func TestAnthropicWireMessageFromJSONInvalid(t *testing.T) { + if _, err := prompty.AnthropicWireMessageFromJSON("{"); err == nil { + t.Fatalf("Expected malformed JSON to fail") + } } diff --git a/runtime/go/prompty/model/api_key_connection_test.go b/runtime/go/prompty/model/api_key_connection_test.go index 704e1116..7dad4655 100644 --- a/runtime/go/prompty/model/api_key_connection_test.go +++ b/runtime/go/prompty/model/api_key_connection_test.go @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. package prompty_test @@ -70,6 +71,55 @@ apiKey: your-api-key } } +// TestApiKeyConnectionFromJSON tests loading ApiKeyConnection through the generated JSON helper +func TestApiKeyConnectionFromJSON(t *testing.T) { + jsonData := ` +{ + "kind": "key", + "endpoint": "https://{your-custom-endpoint}.openai.azure.com/", + "apiKey": "your-api-key" +} +` + + instance, err := prompty.ApiKeyConnectionFromJSON(jsonData) + if err != nil { + t.Fatalf("Failed to load ApiKeyConnection from JSON helper: %v", err) + } + if instance.Kind != "key" { + t.Errorf(`Expected Kind to be "key", got %v`, instance.Kind) + } + if instance.Endpoint != "https://{your-custom-endpoint}.openai.azure.com/" { + t.Errorf(`Expected Endpoint to be "https://{your-custom-endpoint}.openai.azure.com/", got %v`, instance.Endpoint) + } + if instance.ApiKey != "your-api-key" { + t.Errorf(`Expected ApiKey to be "your-api-key", got %v`, instance.ApiKey) + } +} + +// TestApiKeyConnectionFromYAML tests loading ApiKeyConnection through the generated YAML helper +func TestApiKeyConnectionFromYAML(t *testing.T) { + yamlData := ` +kind: key +endpoint: "https://{your-custom-endpoint}.openai.azure.com/" +apiKey: your-api-key + +` + + instance, err := prompty.ApiKeyConnectionFromYAML(yamlData) + if err != nil { + t.Fatalf("Failed to load ApiKeyConnection from YAML helper: %v", err) + } + if instance.Kind != "key" { + t.Errorf(`Expected Kind to be "key", got %v`, instance.Kind) + } + if instance.Endpoint != "https://{your-custom-endpoint}.openai.azure.com/" { + t.Errorf(`Expected Endpoint to be "https://{your-custom-endpoint}.openai.azure.com/", got %v`, instance.Endpoint) + } + if instance.ApiKey != "your-api-key" { + t.Errorf(`Expected ApiKey to be "your-api-key", got %v`, instance.ApiKey) + } +} + // TestApiKeyConnectionRoundtrip tests load -> save -> load produces equivalent data func TestApiKeyConnectionRoundtrip(t *testing.T) { jsonData := ` @@ -135,6 +185,20 @@ func TestApiKeyConnectionToJSON(t *testing.T) { if err := json.Unmarshal([]byte(jsonOutput), &parsed); err != nil { t.Fatalf("Failed to parse generated JSON: %v", err) } + + reloaded, err := prompty.LoadApiKeyConnection(parsed, ctx) + if err != nil { + t.Fatalf("Failed to reload generated JSON: %v", err) + } + if reloaded.Kind != "key" { + t.Errorf(`Expected Kind to be "key", got %v`, reloaded.Kind) + } + if reloaded.Endpoint != "https://{your-custom-endpoint}.openai.azure.com/" { + t.Errorf(`Expected Endpoint to be "https://{your-custom-endpoint}.openai.azure.com/", got %v`, reloaded.Endpoint) + } + if reloaded.ApiKey != "your-api-key" { + t.Errorf(`Expected ApiKey to be "your-api-key", got %v`, reloaded.ApiKey) + } } // TestApiKeyConnectionToYAML tests that ToYAML produces valid YAML @@ -165,4 +229,25 @@ func TestApiKeyConnectionToYAML(t *testing.T) { if err := yaml.Unmarshal([]byte(yamlOutput), &parsed); err != nil { t.Fatalf("Failed to parse generated YAML: %v", err) } + + reloaded, err := prompty.LoadApiKeyConnection(parsed, ctx) + if err != nil { + t.Fatalf("Failed to reload generated YAML: %v", err) + } + if reloaded.Kind != "key" { + t.Errorf(`Expected Kind to be "key", got %v`, reloaded.Kind) + } + if reloaded.Endpoint != "https://{your-custom-endpoint}.openai.azure.com/" { + t.Errorf(`Expected Endpoint to be "https://{your-custom-endpoint}.openai.azure.com/", got %v`, reloaded.Endpoint) + } + if reloaded.ApiKey != "your-api-key" { + t.Errorf(`Expected ApiKey to be "your-api-key", got %v`, reloaded.ApiKey) + } +} + +// TestApiKeyConnectionFromJSONInvalid rejects malformed JSON instead of silently defaulting +func TestApiKeyConnectionFromJSONInvalid(t *testing.T) { + if _, err := prompty.ApiKeyConnectionFromJSON("{"); err == nil { + t.Fatalf("Expected malformed JSON to fail") + } } diff --git a/runtime/go/prompty/model/array_property_test.go b/runtime/go/prompty/model/array_property_test.go index c487ac94..2373f3fa 100644 --- a/runtime/go/prompty/model/array_property_test.go +++ b/runtime/go/prompty/model/array_property_test.go @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. package prompty_test @@ -53,6 +54,38 @@ items: _ = instance // No scalar properties to validate } +// TestArrayPropertyFromJSON tests loading ArrayProperty through the generated JSON helper +func TestArrayPropertyFromJSON(t *testing.T) { + jsonData := ` +{ + "items": { + "kind": "string" + } +} +` + + instance, err := prompty.ArrayPropertyFromJSON(jsonData) + if err != nil { + t.Fatalf("Failed to load ArrayProperty from JSON helper: %v", err) + } + _ = instance // No scalar properties to validate +} + +// TestArrayPropertyFromYAML tests loading ArrayProperty through the generated YAML helper +func TestArrayPropertyFromYAML(t *testing.T) { + yamlData := ` +items: + kind: string + +` + + instance, err := prompty.ArrayPropertyFromYAML(yamlData) + if err != nil { + t.Fatalf("Failed to load ArrayProperty from YAML helper: %v", err) + } + _ = instance // No scalar properties to validate +} + // TestArrayPropertyRoundtrip tests load -> save -> load produces equivalent data func TestArrayPropertyRoundtrip(t *testing.T) { jsonData := ` @@ -110,6 +143,12 @@ func TestArrayPropertyToJSON(t *testing.T) { if err := json.Unmarshal([]byte(jsonOutput), &parsed); err != nil { t.Fatalf("Failed to parse generated JSON: %v", err) } + + reloaded, err := prompty.LoadArrayProperty(parsed, ctx) + if err != nil { + t.Fatalf("Failed to reload generated JSON: %v", err) + } + _ = reloaded // No scalar properties to validate } // TestArrayPropertyToYAML tests that ToYAML produces valid YAML @@ -140,4 +179,17 @@ func TestArrayPropertyToYAML(t *testing.T) { if err := yaml.Unmarshal([]byte(yamlOutput), &parsed); err != nil { t.Fatalf("Failed to parse generated YAML: %v", err) } + + reloaded, err := prompty.LoadArrayProperty(parsed, ctx) + if err != nil { + t.Fatalf("Failed to reload generated YAML: %v", err) + } + _ = reloaded // No scalar properties to validate +} + +// TestArrayPropertyFromJSONInvalid rejects malformed JSON instead of silently defaulting +func TestArrayPropertyFromJSONInvalid(t *testing.T) { + if _, err := prompty.ArrayPropertyFromJSON("{"); err == nil { + t.Fatalf("Expected malformed JSON to fail") + } } diff --git a/runtime/go/prompty/model/audio_part_test.go b/runtime/go/prompty/model/audio_part_test.go index 0d102fe6..6d8c43ba 100644 --- a/runtime/go/prompty/model/audio_part_test.go +++ b/runtime/go/prompty/model/audio_part_test.go @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. package prompty_test @@ -62,6 +63,47 @@ mediaType: audio/wav } } +// TestAudioPartFromJSON tests loading AudioPart through the generated JSON helper +func TestAudioPartFromJSON(t *testing.T) { + jsonData := ` +{ + "source": "https://example.com/audio.wav", + "mediaType": "audio/wav" +} +` + + instance, err := prompty.AudioPartFromJSON(jsonData) + if err != nil { + t.Fatalf("Failed to load AudioPart from JSON helper: %v", err) + } + if instance.Source != "https://example.com/audio.wav" { + t.Errorf(`Expected Source to be "https://example.com/audio.wav", got %v`, instance.Source) + } + if instance.MediaType == nil || *instance.MediaType != "audio/wav" { + t.Errorf(`Expected MediaType to be "audio/wav", got %v`, instance.MediaType) + } +} + +// TestAudioPartFromYAML tests loading AudioPart through the generated YAML helper +func TestAudioPartFromYAML(t *testing.T) { + yamlData := ` +source: "https://example.com/audio.wav" +mediaType: audio/wav + +` + + instance, err := prompty.AudioPartFromYAML(yamlData) + if err != nil { + t.Fatalf("Failed to load AudioPart from YAML helper: %v", err) + } + if instance.Source != "https://example.com/audio.wav" { + t.Errorf(`Expected Source to be "https://example.com/audio.wav", got %v`, instance.Source) + } + if instance.MediaType == nil || *instance.MediaType != "audio/wav" { + t.Errorf(`Expected MediaType to be "audio/wav", got %v`, instance.MediaType) + } +} + // TestAudioPartRoundtrip tests load -> save -> load produces equivalent data func TestAudioPartRoundtrip(t *testing.T) { jsonData := ` @@ -122,6 +164,17 @@ func TestAudioPartToJSON(t *testing.T) { if err := json.Unmarshal([]byte(jsonOutput), &parsed); err != nil { t.Fatalf("Failed to parse generated JSON: %v", err) } + + reloaded, err := prompty.LoadAudioPart(parsed, ctx) + if err != nil { + t.Fatalf("Failed to reload generated JSON: %v", err) + } + if reloaded.Source != "https://example.com/audio.wav" { + t.Errorf(`Expected Source to be "https://example.com/audio.wav", got %v`, reloaded.Source) + } + if reloaded.MediaType == nil || *reloaded.MediaType != "audio/wav" { + t.Errorf(`Expected MediaType to be "audio/wav", got %v`, reloaded.MediaType) + } } // TestAudioPartToYAML tests that ToYAML produces valid YAML @@ -151,4 +204,22 @@ func TestAudioPartToYAML(t *testing.T) { if err := yaml.Unmarshal([]byte(yamlOutput), &parsed); err != nil { t.Fatalf("Failed to parse generated YAML: %v", err) } + + reloaded, err := prompty.LoadAudioPart(parsed, ctx) + if err != nil { + t.Fatalf("Failed to reload generated YAML: %v", err) + } + if reloaded.Source != "https://example.com/audio.wav" { + t.Errorf(`Expected Source to be "https://example.com/audio.wav", got %v`, reloaded.Source) + } + if reloaded.MediaType == nil || *reloaded.MediaType != "audio/wav" { + t.Errorf(`Expected MediaType to be "audio/wav", got %v`, reloaded.MediaType) + } +} + +// TestAudioPartFromJSONInvalid rejects malformed JSON instead of silently defaulting +func TestAudioPartFromJSONInvalid(t *testing.T) { + if _, err := prompty.AudioPartFromJSON("{"); err == nil { + t.Fatalf("Expected malformed JSON to fail") + } } diff --git a/runtime/go/prompty/model/binding.go b/runtime/go/prompty/model/binding.go index 26db55e4..41ba4324 100644 --- a/runtime/go/prompty/model/binding.go +++ b/runtime/go/prompty/model/binding.go @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. // Group: tools package prompty @@ -41,7 +42,7 @@ func LoadBinding(data interface{}, ctx *LoadContext) (Binding, error) { } // Save serializes Binding to map[string]interface{} -func (obj *Binding) Save(ctx *SaveContext) map[string]interface{} { +func (obj Binding) Save(ctx *SaveContext) map[string]interface{} { result := make(map[string]interface{}) result["name"] = obj.Name result["input"] = obj.Input @@ -64,11 +65,7 @@ func (obj *Binding) ToJSON() (string, error) { func (obj *Binding) ToYAML() (string, error) { ctx := NewSaveContext() data := obj.Save(ctx) - bytes, err := yaml.Marshal(data) - if err != nil { - return "", err - } - return string(bytes), nil + return marshalYAMLDocument(data) } // FromJSON creates Binding from JSON string diff --git a/runtime/go/prompty/model/binding_test.go b/runtime/go/prompty/model/binding_test.go index dcb5032e..c8ab9dce 100644 --- a/runtime/go/prompty/model/binding_test.go +++ b/runtime/go/prompty/model/binding_test.go @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. package prompty_test @@ -62,6 +63,47 @@ input: input-variable } } +// TestBindingFromJSON tests loading Binding through the generated JSON helper +func TestBindingFromJSON(t *testing.T) { + jsonData := ` +{ + "name": "my-tool", + "input": "input-variable" +} +` + + instance, err := prompty.BindingFromJSON(jsonData) + if err != nil { + t.Fatalf("Failed to load Binding from JSON helper: %v", err) + } + if instance.Name != "my-tool" { + t.Errorf(`Expected Name to be "my-tool", got %v`, instance.Name) + } + if instance.Input != "input-variable" { + t.Errorf(`Expected Input to be "input-variable", got %v`, instance.Input) + } +} + +// TestBindingFromYAML tests loading Binding through the generated YAML helper +func TestBindingFromYAML(t *testing.T) { + yamlData := ` +name: my-tool +input: input-variable + +` + + instance, err := prompty.BindingFromYAML(yamlData) + if err != nil { + t.Fatalf("Failed to load Binding from YAML helper: %v", err) + } + if instance.Name != "my-tool" { + t.Errorf(`Expected Name to be "my-tool", got %v`, instance.Name) + } + if instance.Input != "input-variable" { + t.Errorf(`Expected Input to be "input-variable", got %v`, instance.Input) + } +} + // TestBindingRoundtrip tests load -> save -> load produces equivalent data func TestBindingRoundtrip(t *testing.T) { jsonData := ` @@ -122,6 +164,17 @@ func TestBindingToJSON(t *testing.T) { if err := json.Unmarshal([]byte(jsonOutput), &parsed); err != nil { t.Fatalf("Failed to parse generated JSON: %v", err) } + + reloaded, err := prompty.LoadBinding(parsed, ctx) + if err != nil { + t.Fatalf("Failed to reload generated JSON: %v", err) + } + if reloaded.Name != "my-tool" { + t.Errorf(`Expected Name to be "my-tool", got %v`, reloaded.Name) + } + if reloaded.Input != "input-variable" { + t.Errorf(`Expected Input to be "input-variable", got %v`, reloaded.Input) + } } // TestBindingToYAML tests that ToYAML produces valid YAML @@ -151,6 +204,24 @@ func TestBindingToYAML(t *testing.T) { if err := yaml.Unmarshal([]byte(yamlOutput), &parsed); err != nil { t.Fatalf("Failed to parse generated YAML: %v", err) } + + reloaded, err := prompty.LoadBinding(parsed, ctx) + if err != nil { + t.Fatalf("Failed to reload generated YAML: %v", err) + } + if reloaded.Name != "my-tool" { + t.Errorf(`Expected Name to be "my-tool", got %v`, reloaded.Name) + } + if reloaded.Input != "input-variable" { + t.Errorf(`Expected Input to be "input-variable", got %v`, reloaded.Input) + } +} + +// TestBindingFromJSONInvalid rejects malformed JSON instead of silently defaulting +func TestBindingFromJSONInvalid(t *testing.T) { + if _, err := prompty.BindingFromJSON("{"); err == nil { + t.Fatalf("Expected malformed JSON to fail") + } } // TestBindingFromString tests loading Binding from string diff --git a/runtime/go/prompty/model/checkpoint.go b/runtime/go/prompty/model/checkpoint.go new file mode 100644 index 00000000..ed9fd0a3 --- /dev/null +++ b/runtime/go/prompty/model/checkpoint.go @@ -0,0 +1,171 @@ +// +// Code generated by Typra emitter; DO NOT EDIT. +// Group: events + +package prompty + +import ( + "encoding/json" + + "gopkg.in/yaml.v3" +) + +// Checkpoint represents A persisted handoff point for a harness session. + +type Checkpoint struct { + Id *string `json:"id,omitempty" yaml:"id,omitempty"` + SessionId *string `json:"sessionId,omitempty" yaml:"sessionId,omitempty"` + TurnId *string `json:"turnId,omitempty" yaml:"turnId,omitempty"` + CheckpointNumber *int32 `json:"checkpointNumber,omitempty" yaml:"checkpointNumber,omitempty"` + Title string `json:"title" yaml:"title"` + Overview *string `json:"overview,omitempty" yaml:"overview,omitempty"` + State map[string]interface{} `json:"state,omitempty" yaml:"state,omitempty"` + Summary *string `json:"summary,omitempty" yaml:"summary,omitempty"` + Metadata map[string]interface{} `json:"metadata,omitempty" yaml:"metadata,omitempty"` + CreatedAt *string `json:"createdAt,omitempty" yaml:"createdAt,omitempty"` + Redaction *RedactionMetadata `json:"redaction,omitempty" yaml:"redaction,omitempty"` +} + +// LoadCheckpoint creates a Checkpoint from a map[string]interface{} +func LoadCheckpoint(data interface{}, ctx *LoadContext) (Checkpoint, error) { + result := Checkpoint{} + + // Load from map + if m, ok := data.(map[string]interface{}); ok { + if val, ok := m["id"]; ok && val != nil { + v := string(val.(string)) + result.Id = &v + } + if val, ok := m["sessionId"]; ok && val != nil { + v := string(val.(string)) + result.SessionId = &v + } + if val, ok := m["turnId"]; ok && val != nil { + v := string(val.(string)) + result.TurnId = &v + } + if val, ok := m["checkpointNumber"]; ok && val != nil { // Handle various numeric types from JSON/YAML/roundtrip + var v int32 + switch n := val.(type) { + case int: + v = int32(n) + case int32: + v = int32(n) + case int64: + v = int32(n) + case float64: + v = int32(n) + } + result.CheckpointNumber = &v + } + if val, ok := m["title"]; ok && val != nil { + result.Title = string(val.(string)) + } + if val, ok := m["overview"]; ok && val != nil { + v := string(val.(string)) + result.Overview = &v + } + if val, ok := m["state"]; ok && val != nil { + if m, ok := val.(map[string]interface{}); ok { + result.State = m + } + } + if val, ok := m["summary"]; ok && val != nil { + v := string(val.(string)) + result.Summary = &v + } + if val, ok := m["metadata"]; ok && val != nil { + if m, ok := val.(map[string]interface{}); ok { + result.Metadata = m + } + } + if val, ok := m["createdAt"]; ok && val != nil { + v := string(val.(string)) + result.CreatedAt = &v + } + if val, ok := m["redaction"]; ok && val != nil { + if m, ok := val.(map[string]interface{}); ok { + loaded, _ := LoadRedactionMetadata(m, ctx) + result.Redaction = &loaded + } + } + } + + return result, nil +} + +// Save serializes Checkpoint to map[string]interface{} +func (obj Checkpoint) Save(ctx *SaveContext) map[string]interface{} { + result := make(map[string]interface{}) + if obj.Id != nil { + result["id"] = *obj.Id + } + if obj.SessionId != nil { + result["sessionId"] = *obj.SessionId + } + if obj.TurnId != nil { + result["turnId"] = *obj.TurnId + } + if obj.CheckpointNumber != nil { + result["checkpointNumber"] = *obj.CheckpointNumber + } + result["title"] = obj.Title + if obj.Overview != nil { + result["overview"] = *obj.Overview + } + if obj.State != nil { + result["state"] = obj.State + } + if obj.Summary != nil { + result["summary"] = *obj.Summary + } + if obj.Metadata != nil { + result["metadata"] = obj.Metadata + } + if obj.CreatedAt != nil { + result["createdAt"] = *obj.CreatedAt + } + if obj.Redaction != nil { + result["redaction"] = obj.Redaction.Save(ctx) + } + + return result +} + +// ToJSON serializes Checkpoint to JSON string +func (obj *Checkpoint) ToJSON() (string, error) { + ctx := NewSaveContext() + data := obj.Save(ctx) + bytes, err := json.Marshal(data) + if err != nil { + return "", err + } + return string(bytes), nil +} + +// ToYAML serializes Checkpoint to YAML string +func (obj *Checkpoint) ToYAML() (string, error) { + ctx := NewSaveContext() + data := obj.Save(ctx) + return marshalYAMLDocument(data) +} + +// FromJSON creates Checkpoint from JSON string +func CheckpointFromJSON(jsonStr string) (Checkpoint, error) { + var data map[string]interface{} + if err := json.Unmarshal([]byte(jsonStr), &data); err != nil { + return Checkpoint{}, err + } + ctx := NewLoadContext() + return LoadCheckpoint(data, ctx) +} + +// FromYAML creates Checkpoint from YAML string +func CheckpointFromYAML(yamlStr string) (Checkpoint, error) { + var data map[string]interface{} + if err := yaml.Unmarshal([]byte(yamlStr), &data); err != nil { + return Checkpoint{}, err + } + ctx := NewLoadContext() + return LoadCheckpoint(data, ctx) +} diff --git a/runtime/go/prompty/model/checkpoint_store.go b/runtime/go/prompty/model/checkpoint_store.go new file mode 100644 index 00000000..8d52f2c1 --- /dev/null +++ b/runtime/go/prompty/model/checkpoint_store.go @@ -0,0 +1,16 @@ +// +// Code generated by Typra emitter; DO NOT EDIT. +// Group: pipeline + +package prompty + +// CheckpointStore represents Stores and retrieves resumable session checkpoints. + +type CheckpointStore interface { + // Save — Persist a session checkpoint and return the stored checkpoint + Save(checkpoint Checkpoint) (Checkpoint, error) + // Load — Load a checkpoint by session and checkpoint identifier + Load(sessionId string, checkpointId string) (*Checkpoint, error) + // ListCheckpoints — List checkpoints for a session + ListCheckpoints(sessionId string) ([]Checkpoint, error) +} diff --git a/runtime/go/prompty/model/checkpoint_test.go b/runtime/go/prompty/model/checkpoint_test.go new file mode 100644 index 00000000..528c0b2e --- /dev/null +++ b/runtime/go/prompty/model/checkpoint_test.go @@ -0,0 +1,337 @@ +// +// Code generated by Typra emitter; DO NOT EDIT. + +package prompty_test + +import ( + "encoding/json" + "testing" + + "gopkg.in/yaml.v3" + + "prompty/model" +) + +// TestCheckpointLoadJSON tests loading Checkpoint from JSON +func TestCheckpointLoadJSON(t *testing.T) { + jsonData := ` +{ + "id": "chk_abc123", + "sessionId": "sess_abc123", + "turnId": "turn_001", + "checkpointNumber": 3, + "title": "Added harness contracts", + "createdAt": "2026-06-09T20:00:00Z" +} +` + var data map[string]interface{} + if err := json.Unmarshal([]byte(jsonData), &data); err != nil { + t.Fatalf("Failed to parse JSON: %v", err) + } + + ctx := prompty.NewLoadContext() + instance, err := prompty.LoadCheckpoint(data, ctx) + if err != nil { + t.Fatalf("Failed to load Checkpoint: %v", err) + } + if instance.Id == nil || *instance.Id != "chk_abc123" { + t.Errorf(`Expected Id to be "chk_abc123", got %v`, instance.Id) + } + if instance.SessionId == nil || *instance.SessionId != "sess_abc123" { + t.Errorf(`Expected SessionId to be "sess_abc123", got %v`, instance.SessionId) + } + if instance.TurnId == nil || *instance.TurnId != "turn_001" { + t.Errorf(`Expected TurnId to be "turn_001", got %v`, instance.TurnId) + } + if instance.CheckpointNumber == nil || *instance.CheckpointNumber != 3 { + t.Errorf(`Expected CheckpointNumber to be 3, got %v`, instance.CheckpointNumber) + } + if instance.Title != "Added harness contracts" { + t.Errorf(`Expected Title to be "Added harness contracts", got %v`, instance.Title) + } + if instance.CreatedAt == nil || *instance.CreatedAt != "2026-06-09T20:00:00Z" { + t.Errorf(`Expected CreatedAt to be "2026-06-09T20:00:00Z", got %v`, instance.CreatedAt) + } +} + +// TestCheckpointLoadYAML tests loading Checkpoint from YAML +func TestCheckpointLoadYAML(t *testing.T) { + yamlData := ` +id: chk_abc123 +sessionId: sess_abc123 +turnId: turn_001 +checkpointNumber: 3 +title: Added harness contracts +createdAt: "2026-06-09T20:00:00Z" + +` + var data map[string]interface{} + if err := yaml.Unmarshal([]byte(yamlData), &data); err != nil { + t.Fatalf("Failed to parse YAML: %v", err) + } + + ctx := prompty.NewLoadContext() + instance, err := prompty.LoadCheckpoint(data, ctx) + if err != nil { + t.Fatalf("Failed to load Checkpoint: %v", err) + } + if instance.Id == nil || *instance.Id != "chk_abc123" { + t.Errorf(`Expected Id to be "chk_abc123", got %v`, instance.Id) + } + if instance.SessionId == nil || *instance.SessionId != "sess_abc123" { + t.Errorf(`Expected SessionId to be "sess_abc123", got %v`, instance.SessionId) + } + if instance.TurnId == nil || *instance.TurnId != "turn_001" { + t.Errorf(`Expected TurnId to be "turn_001", got %v`, instance.TurnId) + } + if instance.CheckpointNumber == nil || *instance.CheckpointNumber != 3 { + t.Errorf(`Expected CheckpointNumber to be 3, got %v`, instance.CheckpointNumber) + } + if instance.Title != "Added harness contracts" { + t.Errorf(`Expected Title to be "Added harness contracts", got %v`, instance.Title) + } + if instance.CreatedAt == nil || *instance.CreatedAt != "2026-06-09T20:00:00Z" { + t.Errorf(`Expected CreatedAt to be "2026-06-09T20:00:00Z", got %v`, instance.CreatedAt) + } +} + +// TestCheckpointFromJSON tests loading Checkpoint through the generated JSON helper +func TestCheckpointFromJSON(t *testing.T) { + jsonData := ` +{ + "id": "chk_abc123", + "sessionId": "sess_abc123", + "turnId": "turn_001", + "checkpointNumber": 3, + "title": "Added harness contracts", + "createdAt": "2026-06-09T20:00:00Z" +} +` + + instance, err := prompty.CheckpointFromJSON(jsonData) + if err != nil { + t.Fatalf("Failed to load Checkpoint from JSON helper: %v", err) + } + if instance.Id == nil || *instance.Id != "chk_abc123" { + t.Errorf(`Expected Id to be "chk_abc123", got %v`, instance.Id) + } + if instance.SessionId == nil || *instance.SessionId != "sess_abc123" { + t.Errorf(`Expected SessionId to be "sess_abc123", got %v`, instance.SessionId) + } + if instance.TurnId == nil || *instance.TurnId != "turn_001" { + t.Errorf(`Expected TurnId to be "turn_001", got %v`, instance.TurnId) + } + if instance.CheckpointNumber == nil || *instance.CheckpointNumber != 3 { + t.Errorf(`Expected CheckpointNumber to be 3, got %v`, instance.CheckpointNumber) + } + if instance.Title != "Added harness contracts" { + t.Errorf(`Expected Title to be "Added harness contracts", got %v`, instance.Title) + } + if instance.CreatedAt == nil || *instance.CreatedAt != "2026-06-09T20:00:00Z" { + t.Errorf(`Expected CreatedAt to be "2026-06-09T20:00:00Z", got %v`, instance.CreatedAt) + } +} + +// TestCheckpointFromYAML tests loading Checkpoint through the generated YAML helper +func TestCheckpointFromYAML(t *testing.T) { + yamlData := ` +id: chk_abc123 +sessionId: sess_abc123 +turnId: turn_001 +checkpointNumber: 3 +title: Added harness contracts +createdAt: "2026-06-09T20:00:00Z" + +` + + instance, err := prompty.CheckpointFromYAML(yamlData) + if err != nil { + t.Fatalf("Failed to load Checkpoint from YAML helper: %v", err) + } + if instance.Id == nil || *instance.Id != "chk_abc123" { + t.Errorf(`Expected Id to be "chk_abc123", got %v`, instance.Id) + } + if instance.SessionId == nil || *instance.SessionId != "sess_abc123" { + t.Errorf(`Expected SessionId to be "sess_abc123", got %v`, instance.SessionId) + } + if instance.TurnId == nil || *instance.TurnId != "turn_001" { + t.Errorf(`Expected TurnId to be "turn_001", got %v`, instance.TurnId) + } + if instance.CheckpointNumber == nil || *instance.CheckpointNumber != 3 { + t.Errorf(`Expected CheckpointNumber to be 3, got %v`, instance.CheckpointNumber) + } + if instance.Title != "Added harness contracts" { + t.Errorf(`Expected Title to be "Added harness contracts", got %v`, instance.Title) + } + if instance.CreatedAt == nil || *instance.CreatedAt != "2026-06-09T20:00:00Z" { + t.Errorf(`Expected CreatedAt to be "2026-06-09T20:00:00Z", got %v`, instance.CreatedAt) + } +} + +// TestCheckpointRoundtrip tests load -> save -> load produces equivalent data +func TestCheckpointRoundtrip(t *testing.T) { + jsonData := ` +{ + "id": "chk_abc123", + "sessionId": "sess_abc123", + "turnId": "turn_001", + "checkpointNumber": 3, + "title": "Added harness contracts", + "createdAt": "2026-06-09T20:00:00Z" +} +` + var data map[string]interface{} + if err := json.Unmarshal([]byte(jsonData), &data); err != nil { + t.Fatalf("Failed to parse JSON: %v", err) + } + + loadCtx := prompty.NewLoadContext() + instance, err := prompty.LoadCheckpoint(data, loadCtx) + if err != nil { + t.Fatalf("Failed to load Checkpoint: %v", err) + } + saveCtx := prompty.NewSaveContext() + savedData := instance.Save(saveCtx) + + reloaded, err := prompty.LoadCheckpoint(savedData, loadCtx) + if err != nil { + t.Fatalf("Failed to reload Checkpoint: %v", err) + } + if reloaded.Id == nil || *reloaded.Id != "chk_abc123" { + t.Errorf(`Expected Id to be "chk_abc123", got %v`, reloaded.Id) + } + if reloaded.SessionId == nil || *reloaded.SessionId != "sess_abc123" { + t.Errorf(`Expected SessionId to be "sess_abc123", got %v`, reloaded.SessionId) + } + if reloaded.TurnId == nil || *reloaded.TurnId != "turn_001" { + t.Errorf(`Expected TurnId to be "turn_001", got %v`, reloaded.TurnId) + } + if reloaded.CheckpointNumber == nil || *reloaded.CheckpointNumber != 3 { + t.Errorf(`Expected CheckpointNumber to be 3, got %v`, reloaded.CheckpointNumber) + } + if reloaded.Title != "Added harness contracts" { + t.Errorf(`Expected Title to be "Added harness contracts", got %v`, reloaded.Title) + } + if reloaded.CreatedAt == nil || *reloaded.CreatedAt != "2026-06-09T20:00:00Z" { + t.Errorf(`Expected CreatedAt to be "2026-06-09T20:00:00Z", got %v`, reloaded.CreatedAt) + } +} + +// TestCheckpointToJSON tests that ToJSON produces valid JSON +func TestCheckpointToJSON(t *testing.T) { + jsonData := ` +{ + "id": "chk_abc123", + "sessionId": "sess_abc123", + "turnId": "turn_001", + "checkpointNumber": 3, + "title": "Added harness contracts", + "createdAt": "2026-06-09T20:00:00Z" +} +` + var data map[string]interface{} + if err := json.Unmarshal([]byte(jsonData), &data); err != nil { + t.Fatalf("Failed to parse JSON: %v", err) + } + + ctx := prompty.NewLoadContext() + instance, err := prompty.LoadCheckpoint(data, ctx) + if err != nil { + t.Fatalf("Failed to load Checkpoint: %v", err) + } + jsonOutput, err := instance.ToJSON() + if err != nil { + t.Fatalf("Failed to convert to JSON: %v", err) + } + + var parsed map[string]interface{} + if err := json.Unmarshal([]byte(jsonOutput), &parsed); err != nil { + t.Fatalf("Failed to parse generated JSON: %v", err) + } + + reloaded, err := prompty.LoadCheckpoint(parsed, ctx) + if err != nil { + t.Fatalf("Failed to reload generated JSON: %v", err) + } + if reloaded.Id == nil || *reloaded.Id != "chk_abc123" { + t.Errorf(`Expected Id to be "chk_abc123", got %v`, reloaded.Id) + } + if reloaded.SessionId == nil || *reloaded.SessionId != "sess_abc123" { + t.Errorf(`Expected SessionId to be "sess_abc123", got %v`, reloaded.SessionId) + } + if reloaded.TurnId == nil || *reloaded.TurnId != "turn_001" { + t.Errorf(`Expected TurnId to be "turn_001", got %v`, reloaded.TurnId) + } + if reloaded.CheckpointNumber == nil || *reloaded.CheckpointNumber != 3 { + t.Errorf(`Expected CheckpointNumber to be 3, got %v`, reloaded.CheckpointNumber) + } + if reloaded.Title != "Added harness contracts" { + t.Errorf(`Expected Title to be "Added harness contracts", got %v`, reloaded.Title) + } + if reloaded.CreatedAt == nil || *reloaded.CreatedAt != "2026-06-09T20:00:00Z" { + t.Errorf(`Expected CreatedAt to be "2026-06-09T20:00:00Z", got %v`, reloaded.CreatedAt) + } +} + +// TestCheckpointToYAML tests that ToYAML produces valid YAML +func TestCheckpointToYAML(t *testing.T) { + jsonData := ` +{ + "id": "chk_abc123", + "sessionId": "sess_abc123", + "turnId": "turn_001", + "checkpointNumber": 3, + "title": "Added harness contracts", + "createdAt": "2026-06-09T20:00:00Z" +} +` + var data map[string]interface{} + if err := json.Unmarshal([]byte(jsonData), &data); err != nil { + t.Fatalf("Failed to parse JSON: %v", err) + } + + ctx := prompty.NewLoadContext() + instance, err := prompty.LoadCheckpoint(data, ctx) + if err != nil { + t.Fatalf("Failed to load Checkpoint: %v", err) + } + yamlOutput, err := instance.ToYAML() + if err != nil { + t.Fatalf("Failed to convert to YAML: %v", err) + } + + var parsed map[string]interface{} + if err := yaml.Unmarshal([]byte(yamlOutput), &parsed); err != nil { + t.Fatalf("Failed to parse generated YAML: %v", err) + } + + reloaded, err := prompty.LoadCheckpoint(parsed, ctx) + if err != nil { + t.Fatalf("Failed to reload generated YAML: %v", err) + } + if reloaded.Id == nil || *reloaded.Id != "chk_abc123" { + t.Errorf(`Expected Id to be "chk_abc123", got %v`, reloaded.Id) + } + if reloaded.SessionId == nil || *reloaded.SessionId != "sess_abc123" { + t.Errorf(`Expected SessionId to be "sess_abc123", got %v`, reloaded.SessionId) + } + if reloaded.TurnId == nil || *reloaded.TurnId != "turn_001" { + t.Errorf(`Expected TurnId to be "turn_001", got %v`, reloaded.TurnId) + } + if reloaded.CheckpointNumber == nil || *reloaded.CheckpointNumber != 3 { + t.Errorf(`Expected CheckpointNumber to be 3, got %v`, reloaded.CheckpointNumber) + } + if reloaded.Title != "Added harness contracts" { + t.Errorf(`Expected Title to be "Added harness contracts", got %v`, reloaded.Title) + } + if reloaded.CreatedAt == nil || *reloaded.CreatedAt != "2026-06-09T20:00:00Z" { + t.Errorf(`Expected CreatedAt to be "2026-06-09T20:00:00Z", got %v`, reloaded.CreatedAt) + } +} + +// TestCheckpointFromJSONInvalid rejects malformed JSON instead of silently defaulting +func TestCheckpointFromJSONInvalid(t *testing.T) { + if _, err := prompty.CheckpointFromJSON("{"); err == nil { + t.Fatalf("Expected malformed JSON to fail") + } +} diff --git a/runtime/go/prompty/model/compaction_complete_payload.go b/runtime/go/prompty/model/compaction_complete_payload.go index 09657dcb..b2a955e3 100644 --- a/runtime/go/prompty/model/compaction_complete_payload.go +++ b/runtime/go/prompty/model/compaction_complete_payload.go @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. // Group: events package prompty @@ -12,8 +13,9 @@ import ( // CompactionCompletePayload represents Payload for "compaction_complete" events — context compaction finished. type CompactionCompletePayload struct { - Removed int32 `json:"removed" yaml:"removed"` - Remaining int32 `json:"remaining" yaml:"remaining"` + Removed int32 `json:"removed" yaml:"removed"` + Remaining int32 `json:"remaining" yaml:"remaining"` + SummaryLength *int32 `json:"summaryLength,omitempty" yaml:"summaryLength,omitempty"` } // LoadCompactionCompletePayload creates a CompactionCompletePayload from a map[string]interface{} @@ -50,16 +52,33 @@ func LoadCompactionCompletePayload(data interface{}, ctx *LoadContext) (Compacti } result.Remaining = v } + if val, ok := m["summaryLength"]; ok && val != nil { // Handle various numeric types from JSON/YAML/roundtrip + var v int32 + switch n := val.(type) { + case int: + v = int32(n) + case int32: + v = int32(n) + case int64: + v = int32(n) + case float64: + v = int32(n) + } + result.SummaryLength = &v + } } return result, nil } // Save serializes CompactionCompletePayload to map[string]interface{} -func (obj *CompactionCompletePayload) Save(ctx *SaveContext) map[string]interface{} { +func (obj CompactionCompletePayload) Save(ctx *SaveContext) map[string]interface{} { result := make(map[string]interface{}) result["removed"] = obj.Removed result["remaining"] = obj.Remaining + if obj.SummaryLength != nil { + result["summaryLength"] = *obj.SummaryLength + } return result } @@ -79,11 +98,7 @@ func (obj *CompactionCompletePayload) ToJSON() (string, error) { func (obj *CompactionCompletePayload) ToYAML() (string, error) { ctx := NewSaveContext() data := obj.Save(ctx) - bytes, err := yaml.Marshal(data) - if err != nil { - return "", err - } - return string(bytes), nil + return marshalYAMLDocument(data) } // FromJSON creates CompactionCompletePayload from JSON string diff --git a/runtime/go/prompty/model/compaction_complete_payload_test.go b/runtime/go/prompty/model/compaction_complete_payload_test.go index c189204b..ee6ac4f9 100644 --- a/runtime/go/prompty/model/compaction_complete_payload_test.go +++ b/runtime/go/prompty/model/compaction_complete_payload_test.go @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. package prompty_test @@ -16,7 +17,8 @@ func TestCompactionCompletePayloadLoadJSON(t *testing.T) { jsonData := ` { "removed": 5, - "remaining": 3 + "remaining": 3, + "summaryLength": 1200 } ` var data map[string]interface{} @@ -35,6 +37,9 @@ func TestCompactionCompletePayloadLoadJSON(t *testing.T) { if instance.Remaining != 3 { t.Errorf(`Expected Remaining to be 3, got %v`, instance.Remaining) } + if instance.SummaryLength == nil || *instance.SummaryLength != 1200 { + t.Errorf(`Expected SummaryLength to be 1200, got %v`, instance.SummaryLength) + } } // TestCompactionCompletePayloadLoadYAML tests loading CompactionCompletePayload from YAML @@ -42,6 +47,7 @@ func TestCompactionCompletePayloadLoadYAML(t *testing.T) { yamlData := ` removed: 5 remaining: 3 +summaryLength: 1200 ` var data map[string]interface{} @@ -60,6 +66,58 @@ remaining: 3 if instance.Remaining != 3 { t.Errorf(`Expected Remaining to be 3, got %v`, instance.Remaining) } + if instance.SummaryLength == nil || *instance.SummaryLength != 1200 { + t.Errorf(`Expected SummaryLength to be 1200, got %v`, instance.SummaryLength) + } +} + +// TestCompactionCompletePayloadFromJSON tests loading CompactionCompletePayload through the generated JSON helper +func TestCompactionCompletePayloadFromJSON(t *testing.T) { + jsonData := ` +{ + "removed": 5, + "remaining": 3, + "summaryLength": 1200 +} +` + + instance, err := prompty.CompactionCompletePayloadFromJSON(jsonData) + if err != nil { + t.Fatalf("Failed to load CompactionCompletePayload from JSON helper: %v", err) + } + if instance.Removed != 5 { + t.Errorf(`Expected Removed to be 5, got %v`, instance.Removed) + } + if instance.Remaining != 3 { + t.Errorf(`Expected Remaining to be 3, got %v`, instance.Remaining) + } + if instance.SummaryLength == nil || *instance.SummaryLength != 1200 { + t.Errorf(`Expected SummaryLength to be 1200, got %v`, instance.SummaryLength) + } +} + +// TestCompactionCompletePayloadFromYAML tests loading CompactionCompletePayload through the generated YAML helper +func TestCompactionCompletePayloadFromYAML(t *testing.T) { + yamlData := ` +removed: 5 +remaining: 3 +summaryLength: 1200 + +` + + instance, err := prompty.CompactionCompletePayloadFromYAML(yamlData) + if err != nil { + t.Fatalf("Failed to load CompactionCompletePayload from YAML helper: %v", err) + } + if instance.Removed != 5 { + t.Errorf(`Expected Removed to be 5, got %v`, instance.Removed) + } + if instance.Remaining != 3 { + t.Errorf(`Expected Remaining to be 3, got %v`, instance.Remaining) + } + if instance.SummaryLength == nil || *instance.SummaryLength != 1200 { + t.Errorf(`Expected SummaryLength to be 1200, got %v`, instance.SummaryLength) + } } // TestCompactionCompletePayloadRoundtrip tests load -> save -> load produces equivalent data @@ -67,7 +125,8 @@ func TestCompactionCompletePayloadRoundtrip(t *testing.T) { jsonData := ` { "removed": 5, - "remaining": 3 + "remaining": 3, + "summaryLength": 1200 } ` var data map[string]interface{} @@ -93,6 +152,9 @@ func TestCompactionCompletePayloadRoundtrip(t *testing.T) { if reloaded.Remaining != 3 { t.Errorf(`Expected Remaining to be 3, got %v`, reloaded.Remaining) } + if reloaded.SummaryLength == nil || *reloaded.SummaryLength != 1200 { + t.Errorf(`Expected SummaryLength to be 1200, got %v`, reloaded.SummaryLength) + } } // TestCompactionCompletePayloadToJSON tests that ToJSON produces valid JSON @@ -100,7 +162,8 @@ func TestCompactionCompletePayloadToJSON(t *testing.T) { jsonData := ` { "removed": 5, - "remaining": 3 + "remaining": 3, + "summaryLength": 1200 } ` var data map[string]interface{} @@ -122,6 +185,20 @@ func TestCompactionCompletePayloadToJSON(t *testing.T) { if err := json.Unmarshal([]byte(jsonOutput), &parsed); err != nil { t.Fatalf("Failed to parse generated JSON: %v", err) } + + reloaded, err := prompty.LoadCompactionCompletePayload(parsed, ctx) + if err != nil { + t.Fatalf("Failed to reload generated JSON: %v", err) + } + if reloaded.Removed != 5 { + t.Errorf(`Expected Removed to be 5, got %v`, reloaded.Removed) + } + if reloaded.Remaining != 3 { + t.Errorf(`Expected Remaining to be 3, got %v`, reloaded.Remaining) + } + if reloaded.SummaryLength == nil || *reloaded.SummaryLength != 1200 { + t.Errorf(`Expected SummaryLength to be 1200, got %v`, reloaded.SummaryLength) + } } // TestCompactionCompletePayloadToYAML tests that ToYAML produces valid YAML @@ -129,7 +206,8 @@ func TestCompactionCompletePayloadToYAML(t *testing.T) { jsonData := ` { "removed": 5, - "remaining": 3 + "remaining": 3, + "summaryLength": 1200 } ` var data map[string]interface{} @@ -151,4 +229,25 @@ func TestCompactionCompletePayloadToYAML(t *testing.T) { if err := yaml.Unmarshal([]byte(yamlOutput), &parsed); err != nil { t.Fatalf("Failed to parse generated YAML: %v", err) } + + reloaded, err := prompty.LoadCompactionCompletePayload(parsed, ctx) + if err != nil { + t.Fatalf("Failed to reload generated YAML: %v", err) + } + if reloaded.Removed != 5 { + t.Errorf(`Expected Removed to be 5, got %v`, reloaded.Removed) + } + if reloaded.Remaining != 3 { + t.Errorf(`Expected Remaining to be 3, got %v`, reloaded.Remaining) + } + if reloaded.SummaryLength == nil || *reloaded.SummaryLength != 1200 { + t.Errorf(`Expected SummaryLength to be 1200, got %v`, reloaded.SummaryLength) + } +} + +// TestCompactionCompletePayloadFromJSONInvalid rejects malformed JSON instead of silently defaulting +func TestCompactionCompletePayloadFromJSONInvalid(t *testing.T) { + if _, err := prompty.CompactionCompletePayloadFromJSON("{"); err == nil { + t.Fatalf("Expected malformed JSON to fail") + } } diff --git a/runtime/go/prompty/model/compaction_config.go b/runtime/go/prompty/model/compaction_config.go index 7247164d..4a699760 100644 --- a/runtime/go/prompty/model/compaction_config.go +++ b/runtime/go/prompty/model/compaction_config.go @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. // Group: pipeline package prompty @@ -54,7 +55,7 @@ func LoadCompactionConfig(data interface{}, ctx *LoadContext) (CompactionConfig, } // Save serializes CompactionConfig to map[string]interface{} -func (obj *CompactionConfig) Save(ctx *SaveContext) map[string]interface{} { +func (obj CompactionConfig) Save(ctx *SaveContext) map[string]interface{} { result := make(map[string]interface{}) if obj.Strategy != nil { result["strategy"] = *obj.Strategy @@ -84,11 +85,7 @@ func (obj *CompactionConfig) ToJSON() (string, error) { func (obj *CompactionConfig) ToYAML() (string, error) { ctx := NewSaveContext() data := obj.Save(ctx) - bytes, err := yaml.Marshal(data) - if err != nil { - return "", err - } - return string(bytes), nil + return marshalYAMLDocument(data) } // FromJSON creates CompactionConfig from JSON string diff --git a/runtime/go/prompty/model/compaction_config_test.go b/runtime/go/prompty/model/compaction_config_test.go index 9f65c6f7..c6cfb88a 100644 --- a/runtime/go/prompty/model/compaction_config_test.go +++ b/runtime/go/prompty/model/compaction_config_test.go @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. package prompty_test @@ -38,6 +39,9 @@ func TestCompactionConfigLoadJSON(t *testing.T) { if instance.Budget == nil || *instance.Budget != 50000 { t.Errorf(`Expected Budget to be 50000, got %v`, instance.Budget) } + if instance.Options == nil { + t.Fatalf("Expected Options to be populated") + } } // TestCompactionConfigLoadYAML tests loading CompactionConfig from YAML @@ -65,6 +69,61 @@ options: if instance.Budget == nil || *instance.Budget != 50000 { t.Errorf(`Expected Budget to be 50000, got %v`, instance.Budget) } + if instance.Options == nil { + t.Fatalf("Expected Options to be populated") + } +} + +// TestCompactionConfigFromJSON tests loading CompactionConfig through the generated JSON helper +func TestCompactionConfigFromJSON(t *testing.T) { + jsonData := ` +{ + "strategy": "summarize", + "budget": 50000, + "options": { + "preserveSystemMessages": true + } +} +` + + instance, err := prompty.CompactionConfigFromJSON(jsonData) + if err != nil { + t.Fatalf("Failed to load CompactionConfig from JSON helper: %v", err) + } + if instance.Strategy == nil || *instance.Strategy != "summarize" { + t.Errorf(`Expected Strategy to be "summarize", got %v`, instance.Strategy) + } + if instance.Budget == nil || *instance.Budget != 50000 { + t.Errorf(`Expected Budget to be 50000, got %v`, instance.Budget) + } + if instance.Options == nil { + t.Fatalf("Expected Options to be populated") + } +} + +// TestCompactionConfigFromYAML tests loading CompactionConfig through the generated YAML helper +func TestCompactionConfigFromYAML(t *testing.T) { + yamlData := ` +strategy: summarize +budget: 50000 +options: + preserveSystemMessages: true + +` + + instance, err := prompty.CompactionConfigFromYAML(yamlData) + if err != nil { + t.Fatalf("Failed to load CompactionConfig from YAML helper: %v", err) + } + if instance.Strategy == nil || *instance.Strategy != "summarize" { + t.Errorf(`Expected Strategy to be "summarize", got %v`, instance.Strategy) + } + if instance.Budget == nil || *instance.Budget != 50000 { + t.Errorf(`Expected Budget to be 50000, got %v`, instance.Budget) + } + if instance.Options == nil { + t.Fatalf("Expected Options to be populated") + } } // TestCompactionConfigRoundtrip tests load -> save -> load produces equivalent data @@ -101,6 +160,9 @@ func TestCompactionConfigRoundtrip(t *testing.T) { if reloaded.Budget == nil || *reloaded.Budget != 50000 { t.Errorf(`Expected Budget to be 50000, got %v`, reloaded.Budget) } + if reloaded.Options == nil { + t.Fatalf("Expected Options to be populated") + } } // TestCompactionConfigToJSON tests that ToJSON produces valid JSON @@ -133,6 +195,20 @@ func TestCompactionConfigToJSON(t *testing.T) { if err := json.Unmarshal([]byte(jsonOutput), &parsed); err != nil { t.Fatalf("Failed to parse generated JSON: %v", err) } + + reloaded, err := prompty.LoadCompactionConfig(parsed, ctx) + if err != nil { + t.Fatalf("Failed to reload generated JSON: %v", err) + } + if reloaded.Strategy == nil || *reloaded.Strategy != "summarize" { + t.Errorf(`Expected Strategy to be "summarize", got %v`, reloaded.Strategy) + } + if reloaded.Budget == nil || *reloaded.Budget != 50000 { + t.Errorf(`Expected Budget to be 50000, got %v`, reloaded.Budget) + } + if reloaded.Options == nil { + t.Fatalf("Expected Options to be populated") + } } // TestCompactionConfigToYAML tests that ToYAML produces valid YAML @@ -165,4 +241,25 @@ func TestCompactionConfigToYAML(t *testing.T) { if err := yaml.Unmarshal([]byte(yamlOutput), &parsed); err != nil { t.Fatalf("Failed to parse generated YAML: %v", err) } + + reloaded, err := prompty.LoadCompactionConfig(parsed, ctx) + if err != nil { + t.Fatalf("Failed to reload generated YAML: %v", err) + } + if reloaded.Strategy == nil || *reloaded.Strategy != "summarize" { + t.Errorf(`Expected Strategy to be "summarize", got %v`, reloaded.Strategy) + } + if reloaded.Budget == nil || *reloaded.Budget != 50000 { + t.Errorf(`Expected Budget to be 50000, got %v`, reloaded.Budget) + } + if reloaded.Options == nil { + t.Fatalf("Expected Options to be populated") + } +} + +// TestCompactionConfigFromJSONInvalid rejects malformed JSON instead of silently defaulting +func TestCompactionConfigFromJSONInvalid(t *testing.T) { + if _, err := prompty.CompactionConfigFromJSON("{"); err == nil { + t.Fatalf("Expected malformed JSON to fail") + } } diff --git a/runtime/go/prompty/model/compaction_failed_payload.go b/runtime/go/prompty/model/compaction_failed_payload.go index c04c0757..4fabe9c0 100644 --- a/runtime/go/prompty/model/compaction_failed_payload.go +++ b/runtime/go/prompty/model/compaction_failed_payload.go @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. // Group: events package prompty @@ -30,7 +31,7 @@ func LoadCompactionFailedPayload(data interface{}, ctx *LoadContext) (Compaction } // Save serializes CompactionFailedPayload to map[string]interface{} -func (obj *CompactionFailedPayload) Save(ctx *SaveContext) map[string]interface{} { +func (obj CompactionFailedPayload) Save(ctx *SaveContext) map[string]interface{} { result := make(map[string]interface{}) result["message"] = obj.Message @@ -52,11 +53,7 @@ func (obj *CompactionFailedPayload) ToJSON() (string, error) { func (obj *CompactionFailedPayload) ToYAML() (string, error) { ctx := NewSaveContext() data := obj.Save(ctx) - bytes, err := yaml.Marshal(data) - if err != nil { - return "", err - } - return string(bytes), nil + return marshalYAMLDocument(data) } // FromJSON creates CompactionFailedPayload from JSON string diff --git a/runtime/go/prompty/model/compaction_failed_payload_test.go b/runtime/go/prompty/model/compaction_failed_payload_test.go index f8d732b2..833d5576 100644 --- a/runtime/go/prompty/model/compaction_failed_payload_test.go +++ b/runtime/go/prompty/model/compaction_failed_payload_test.go @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. package prompty_test @@ -54,6 +55,39 @@ message: Summarization prompt exceeded context window } } +// TestCompactionFailedPayloadFromJSON tests loading CompactionFailedPayload through the generated JSON helper +func TestCompactionFailedPayloadFromJSON(t *testing.T) { + jsonData := ` +{ + "message": "Summarization prompt exceeded context window" +} +` + + instance, err := prompty.CompactionFailedPayloadFromJSON(jsonData) + if err != nil { + t.Fatalf("Failed to load CompactionFailedPayload from JSON helper: %v", err) + } + if instance.Message != "Summarization prompt exceeded context window" { + t.Errorf(`Expected Message to be "Summarization prompt exceeded context window", got %v`, instance.Message) + } +} + +// TestCompactionFailedPayloadFromYAML tests loading CompactionFailedPayload through the generated YAML helper +func TestCompactionFailedPayloadFromYAML(t *testing.T) { + yamlData := ` +message: Summarization prompt exceeded context window + +` + + instance, err := prompty.CompactionFailedPayloadFromYAML(yamlData) + if err != nil { + t.Fatalf("Failed to load CompactionFailedPayload from YAML helper: %v", err) + } + if instance.Message != "Summarization prompt exceeded context window" { + t.Errorf(`Expected Message to be "Summarization prompt exceeded context window", got %v`, instance.Message) + } +} + // TestCompactionFailedPayloadRoundtrip tests load -> save -> load produces equivalent data func TestCompactionFailedPayloadRoundtrip(t *testing.T) { jsonData := ` @@ -109,6 +143,14 @@ func TestCompactionFailedPayloadToJSON(t *testing.T) { if err := json.Unmarshal([]byte(jsonOutput), &parsed); err != nil { t.Fatalf("Failed to parse generated JSON: %v", err) } + + reloaded, err := prompty.LoadCompactionFailedPayload(parsed, ctx) + if err != nil { + t.Fatalf("Failed to reload generated JSON: %v", err) + } + if reloaded.Message != "Summarization prompt exceeded context window" { + t.Errorf(`Expected Message to be "Summarization prompt exceeded context window", got %v`, reloaded.Message) + } } // TestCompactionFailedPayloadToYAML tests that ToYAML produces valid YAML @@ -137,4 +179,19 @@ func TestCompactionFailedPayloadToYAML(t *testing.T) { if err := yaml.Unmarshal([]byte(yamlOutput), &parsed); err != nil { t.Fatalf("Failed to parse generated YAML: %v", err) } + + reloaded, err := prompty.LoadCompactionFailedPayload(parsed, ctx) + if err != nil { + t.Fatalf("Failed to reload generated YAML: %v", err) + } + if reloaded.Message != "Summarization prompt exceeded context window" { + t.Errorf(`Expected Message to be "Summarization prompt exceeded context window", got %v`, reloaded.Message) + } +} + +// TestCompactionFailedPayloadFromJSONInvalid rejects malformed JSON instead of silently defaulting +func TestCompactionFailedPayloadFromJSONInvalid(t *testing.T) { + if _, err := prompty.CompactionFailedPayloadFromJSON("{"); err == nil { + t.Fatalf("Expected malformed JSON to fail") + } } diff --git a/runtime/go/prompty/model/compaction_start_payload.go b/runtime/go/prompty/model/compaction_start_payload.go new file mode 100644 index 00000000..1113823d --- /dev/null +++ b/runtime/go/prompty/model/compaction_start_payload.go @@ -0,0 +1,88 @@ +// +// Code generated by Typra emitter; DO NOT EDIT. +// Group: events + +package prompty + +import ( + "encoding/json" + + "gopkg.in/yaml.v3" +) + +// CompactionStartPayload represents Payload for "compaction_start" events — context compaction is beginning. + +type CompactionStartPayload struct { + DroppedCount int32 `json:"droppedCount" yaml:"droppedCount"` +} + +// LoadCompactionStartPayload creates a CompactionStartPayload from a map[string]interface{} +func LoadCompactionStartPayload(data interface{}, ctx *LoadContext) (CompactionStartPayload, error) { + result := CompactionStartPayload{} + + // Load from map + if m, ok := data.(map[string]interface{}); ok { + if val, ok := m["droppedCount"]; ok && val != nil { // Handle various numeric types from JSON/YAML/roundtrip + var v int32 + switch n := val.(type) { + case int: + v = int32(n) + case int32: + v = int32(n) + case int64: + v = int32(n) + case float64: + v = int32(n) + } + result.DroppedCount = v + } + } + + return result, nil +} + +// Save serializes CompactionStartPayload to map[string]interface{} +func (obj CompactionStartPayload) Save(ctx *SaveContext) map[string]interface{} { + result := make(map[string]interface{}) + result["droppedCount"] = obj.DroppedCount + + return result +} + +// ToJSON serializes CompactionStartPayload to JSON string +func (obj *CompactionStartPayload) ToJSON() (string, error) { + ctx := NewSaveContext() + data := obj.Save(ctx) + bytes, err := json.Marshal(data) + if err != nil { + return "", err + } + return string(bytes), nil +} + +// ToYAML serializes CompactionStartPayload to YAML string +func (obj *CompactionStartPayload) ToYAML() (string, error) { + ctx := NewSaveContext() + data := obj.Save(ctx) + return marshalYAMLDocument(data) +} + +// FromJSON creates CompactionStartPayload from JSON string +func CompactionStartPayloadFromJSON(jsonStr string) (CompactionStartPayload, error) { + var data map[string]interface{} + if err := json.Unmarshal([]byte(jsonStr), &data); err != nil { + return CompactionStartPayload{}, err + } + ctx := NewLoadContext() + return LoadCompactionStartPayload(data, ctx) +} + +// FromYAML creates CompactionStartPayload from YAML string +func CompactionStartPayloadFromYAML(yamlStr string) (CompactionStartPayload, error) { + var data map[string]interface{} + if err := yaml.Unmarshal([]byte(yamlStr), &data); err != nil { + return CompactionStartPayload{}, err + } + ctx := NewLoadContext() + return LoadCompactionStartPayload(data, ctx) +} diff --git a/runtime/go/prompty/model/compaction_start_payload_test.go b/runtime/go/prompty/model/compaction_start_payload_test.go new file mode 100644 index 00000000..cdaec953 --- /dev/null +++ b/runtime/go/prompty/model/compaction_start_payload_test.go @@ -0,0 +1,197 @@ +// +// Code generated by Typra emitter; DO NOT EDIT. + +package prompty_test + +import ( + "encoding/json" + "testing" + + "gopkg.in/yaml.v3" + + "prompty/model" +) + +// TestCompactionStartPayloadLoadJSON tests loading CompactionStartPayload from JSON +func TestCompactionStartPayloadLoadJSON(t *testing.T) { + jsonData := ` +{ + "droppedCount": 5 +} +` + var data map[string]interface{} + if err := json.Unmarshal([]byte(jsonData), &data); err != nil { + t.Fatalf("Failed to parse JSON: %v", err) + } + + ctx := prompty.NewLoadContext() + instance, err := prompty.LoadCompactionStartPayload(data, ctx) + if err != nil { + t.Fatalf("Failed to load CompactionStartPayload: %v", err) + } + if instance.DroppedCount != 5 { + t.Errorf(`Expected DroppedCount to be 5, got %v`, instance.DroppedCount) + } +} + +// TestCompactionStartPayloadLoadYAML tests loading CompactionStartPayload from YAML +func TestCompactionStartPayloadLoadYAML(t *testing.T) { + yamlData := ` +droppedCount: 5 + +` + var data map[string]interface{} + if err := yaml.Unmarshal([]byte(yamlData), &data); err != nil { + t.Fatalf("Failed to parse YAML: %v", err) + } + + ctx := prompty.NewLoadContext() + instance, err := prompty.LoadCompactionStartPayload(data, ctx) + if err != nil { + t.Fatalf("Failed to load CompactionStartPayload: %v", err) + } + if instance.DroppedCount != 5 { + t.Errorf(`Expected DroppedCount to be 5, got %v`, instance.DroppedCount) + } +} + +// TestCompactionStartPayloadFromJSON tests loading CompactionStartPayload through the generated JSON helper +func TestCompactionStartPayloadFromJSON(t *testing.T) { + jsonData := ` +{ + "droppedCount": 5 +} +` + + instance, err := prompty.CompactionStartPayloadFromJSON(jsonData) + if err != nil { + t.Fatalf("Failed to load CompactionStartPayload from JSON helper: %v", err) + } + if instance.DroppedCount != 5 { + t.Errorf(`Expected DroppedCount to be 5, got %v`, instance.DroppedCount) + } +} + +// TestCompactionStartPayloadFromYAML tests loading CompactionStartPayload through the generated YAML helper +func TestCompactionStartPayloadFromYAML(t *testing.T) { + yamlData := ` +droppedCount: 5 + +` + + instance, err := prompty.CompactionStartPayloadFromYAML(yamlData) + if err != nil { + t.Fatalf("Failed to load CompactionStartPayload from YAML helper: %v", err) + } + if instance.DroppedCount != 5 { + t.Errorf(`Expected DroppedCount to be 5, got %v`, instance.DroppedCount) + } +} + +// TestCompactionStartPayloadRoundtrip tests load -> save -> load produces equivalent data +func TestCompactionStartPayloadRoundtrip(t *testing.T) { + jsonData := ` +{ + "droppedCount": 5 +} +` + var data map[string]interface{} + if err := json.Unmarshal([]byte(jsonData), &data); err != nil { + t.Fatalf("Failed to parse JSON: %v", err) + } + + loadCtx := prompty.NewLoadContext() + instance, err := prompty.LoadCompactionStartPayload(data, loadCtx) + if err != nil { + t.Fatalf("Failed to load CompactionStartPayload: %v", err) + } + saveCtx := prompty.NewSaveContext() + savedData := instance.Save(saveCtx) + + reloaded, err := prompty.LoadCompactionStartPayload(savedData, loadCtx) + if err != nil { + t.Fatalf("Failed to reload CompactionStartPayload: %v", err) + } + if reloaded.DroppedCount != 5 { + t.Errorf(`Expected DroppedCount to be 5, got %v`, reloaded.DroppedCount) + } +} + +// TestCompactionStartPayloadToJSON tests that ToJSON produces valid JSON +func TestCompactionStartPayloadToJSON(t *testing.T) { + jsonData := ` +{ + "droppedCount": 5 +} +` + var data map[string]interface{} + if err := json.Unmarshal([]byte(jsonData), &data); err != nil { + t.Fatalf("Failed to parse JSON: %v", err) + } + + ctx := prompty.NewLoadContext() + instance, err := prompty.LoadCompactionStartPayload(data, ctx) + if err != nil { + t.Fatalf("Failed to load CompactionStartPayload: %v", err) + } + jsonOutput, err := instance.ToJSON() + if err != nil { + t.Fatalf("Failed to convert to JSON: %v", err) + } + + var parsed map[string]interface{} + if err := json.Unmarshal([]byte(jsonOutput), &parsed); err != nil { + t.Fatalf("Failed to parse generated JSON: %v", err) + } + + reloaded, err := prompty.LoadCompactionStartPayload(parsed, ctx) + if err != nil { + t.Fatalf("Failed to reload generated JSON: %v", err) + } + if reloaded.DroppedCount != 5 { + t.Errorf(`Expected DroppedCount to be 5, got %v`, reloaded.DroppedCount) + } +} + +// TestCompactionStartPayloadToYAML tests that ToYAML produces valid YAML +func TestCompactionStartPayloadToYAML(t *testing.T) { + jsonData := ` +{ + "droppedCount": 5 +} +` + var data map[string]interface{} + if err := json.Unmarshal([]byte(jsonData), &data); err != nil { + t.Fatalf("Failed to parse JSON: %v", err) + } + + ctx := prompty.NewLoadContext() + instance, err := prompty.LoadCompactionStartPayload(data, ctx) + if err != nil { + t.Fatalf("Failed to load CompactionStartPayload: %v", err) + } + yamlOutput, err := instance.ToYAML() + if err != nil { + t.Fatalf("Failed to convert to YAML: %v", err) + } + + var parsed map[string]interface{} + if err := yaml.Unmarshal([]byte(yamlOutput), &parsed); err != nil { + t.Fatalf("Failed to parse generated YAML: %v", err) + } + + reloaded, err := prompty.LoadCompactionStartPayload(parsed, ctx) + if err != nil { + t.Fatalf("Failed to reload generated YAML: %v", err) + } + if reloaded.DroppedCount != 5 { + t.Errorf(`Expected DroppedCount to be 5, got %v`, reloaded.DroppedCount) + } +} + +// TestCompactionStartPayloadFromJSONInvalid rejects malformed JSON instead of silently defaulting +func TestCompactionStartPayloadFromJSONInvalid(t *testing.T) { + if _, err := prompty.CompactionStartPayloadFromJSON("{"); err == nil { + t.Fatalf("Expected malformed JSON to fail") + } +} diff --git a/runtime/go/prompty/model/connection.go b/runtime/go/prompty/model/connection.go index 72a5867e..87e9b95a 100644 --- a/runtime/go/prompty/model/connection.go +++ b/runtime/go/prompty/model/connection.go @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. // Group: connection package prompty @@ -70,7 +71,7 @@ func LoadConnection(data interface{}, ctx *LoadContext) (interface{}, error) { } // Save serializes Connection to map[string]interface{} -func (obj *Connection) Save(ctx *SaveContext) map[string]interface{} { +func (obj Connection) Save(ctx *SaveContext) map[string]interface{} { result := make(map[string]interface{}) result["kind"] = obj.Kind if obj.AuthenticationMode != nil { @@ -98,11 +99,7 @@ func (obj *Connection) ToJSON() (string, error) { func (obj *Connection) ToYAML() (string, error) { ctx := NewSaveContext() data := obj.Save(ctx) - bytes, err := yaml.Marshal(data) - if err != nil { - return "", err - } - return string(bytes), nil + return marshalYAMLDocument(data) } // FromJSON creates Connection from JSON string @@ -157,7 +154,7 @@ func LoadReferenceConnection(data interface{}, ctx *LoadContext) (ReferenceConne } // Save serializes ReferenceConnection to map[string]interface{} -func (obj *ReferenceConnection) Save(ctx *SaveContext) map[string]interface{} { +func (obj ReferenceConnection) Save(ctx *SaveContext) map[string]interface{} { result := make(map[string]interface{}) result["kind"] = obj.Kind result["name"] = obj.Name @@ -183,11 +180,7 @@ func (obj *ReferenceConnection) ToJSON() (string, error) { func (obj *ReferenceConnection) ToYAML() (string, error) { ctx := NewSaveContext() data := obj.Save(ctx) - bytes, err := yaml.Marshal(data) - if err != nil { - return "", err - } - return string(bytes), nil + return marshalYAMLDocument(data) } // FromJSON creates ReferenceConnection from JSON string @@ -239,7 +232,7 @@ func LoadRemoteConnection(data interface{}, ctx *LoadContext) (RemoteConnection, } // Save serializes RemoteConnection to map[string]interface{} -func (obj *RemoteConnection) Save(ctx *SaveContext) map[string]interface{} { +func (obj RemoteConnection) Save(ctx *SaveContext) map[string]interface{} { result := make(map[string]interface{}) result["kind"] = obj.Kind result["name"] = obj.Name @@ -263,11 +256,7 @@ func (obj *RemoteConnection) ToJSON() (string, error) { func (obj *RemoteConnection) ToYAML() (string, error) { ctx := NewSaveContext() data := obj.Save(ctx) - bytes, err := yaml.Marshal(data) - if err != nil { - return "", err - } - return string(bytes), nil + return marshalYAMLDocument(data) } // FromJSON creates RemoteConnection from JSON string @@ -319,7 +308,7 @@ func LoadApiKeyConnection(data interface{}, ctx *LoadContext) (ApiKeyConnection, } // Save serializes ApiKeyConnection to map[string]interface{} -func (obj *ApiKeyConnection) Save(ctx *SaveContext) map[string]interface{} { +func (obj ApiKeyConnection) Save(ctx *SaveContext) map[string]interface{} { result := make(map[string]interface{}) result["kind"] = obj.Kind result["endpoint"] = obj.Endpoint @@ -343,11 +332,7 @@ func (obj *ApiKeyConnection) ToJSON() (string, error) { func (obj *ApiKeyConnection) ToYAML() (string, error) { ctx := NewSaveContext() data := obj.Save(ctx) - bytes, err := yaml.Marshal(data) - if err != nil { - return "", err - } - return string(bytes), nil + return marshalYAMLDocument(data) } // FromJSON creates ApiKeyConnection from JSON string @@ -394,7 +379,7 @@ func LoadAnonymousConnection(data interface{}, ctx *LoadContext) (AnonymousConne } // Save serializes AnonymousConnection to map[string]interface{} -func (obj *AnonymousConnection) Save(ctx *SaveContext) map[string]interface{} { +func (obj AnonymousConnection) Save(ctx *SaveContext) map[string]interface{} { result := make(map[string]interface{}) result["kind"] = obj.Kind result["endpoint"] = obj.Endpoint @@ -417,11 +402,7 @@ func (obj *AnonymousConnection) ToJSON() (string, error) { func (obj *AnonymousConnection) ToYAML() (string, error) { ctx := NewSaveContext() data := obj.Save(ctx) - bytes, err := yaml.Marshal(data) - if err != nil { - return "", err - } - return string(bytes), nil + return marshalYAMLDocument(data) } // FromJSON creates AnonymousConnection from JSON string @@ -479,11 +460,14 @@ func LoadOAuthConnection(data interface{}, ctx *LoadContext) (OAuthConnection, e result.TokenUrl = string(val.(string)) } if val, ok := m["scopes"]; ok && val != nil { - if arr, ok := val.([]interface{}); ok { + switch arr := val.(type) { + case []interface{}: result.Scopes = make([]string, len(arr)) for i, v := range arr { result.Scopes[i] = v.(string) } + case []string: + result.Scopes = arr } } } @@ -492,7 +476,7 @@ func LoadOAuthConnection(data interface{}, ctx *LoadContext) (OAuthConnection, e } // Save serializes OAuthConnection to map[string]interface{} -func (obj *OAuthConnection) Save(ctx *SaveContext) map[string]interface{} { +func (obj OAuthConnection) Save(ctx *SaveContext) map[string]interface{} { result := make(map[string]interface{}) result["kind"] = obj.Kind result["endpoint"] = obj.Endpoint @@ -519,11 +503,7 @@ func (obj *OAuthConnection) ToJSON() (string, error) { func (obj *OAuthConnection) ToYAML() (string, error) { ctx := NewSaveContext() data := obj.Save(ctx) - bytes, err := yaml.Marshal(data) - if err != nil { - return "", err - } - return string(bytes), nil + return marshalYAMLDocument(data) } // FromJSON creates OAuthConnection from JSON string @@ -583,7 +563,7 @@ func LoadFoundryConnection(data interface{}, ctx *LoadContext) (FoundryConnectio } // Save serializes FoundryConnection to map[string]interface{} -func (obj *FoundryConnection) Save(ctx *SaveContext) map[string]interface{} { +func (obj FoundryConnection) Save(ctx *SaveContext) map[string]interface{} { result := make(map[string]interface{}) result["kind"] = obj.Kind result["endpoint"] = obj.Endpoint @@ -612,11 +592,7 @@ func (obj *FoundryConnection) ToJSON() (string, error) { func (obj *FoundryConnection) ToYAML() (string, error) { ctx := NewSaveContext() data := obj.Save(ctx) - bytes, err := yaml.Marshal(data) - if err != nil { - return "", err - } - return string(bytes), nil + return marshalYAMLDocument(data) } // FromJSON creates FoundryConnection from JSON string diff --git a/runtime/go/prompty/model/connection_test.go b/runtime/go/prompty/model/connection_test.go index b3c7c131..93921abb 100644 --- a/runtime/go/prompty/model/connection_test.go +++ b/runtime/go/prompty/model/connection_test.go @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. package prompty_test @@ -58,6 +59,43 @@ usageDescription: This will allow the agent to respond to an email on your behal // Note: Validation skipped for polymorphic base types - test child types directly } +// TestConnectionFromJSON tests loading Connection through the generated JSON helper +func TestConnectionFromJSON(t *testing.T) { + jsonData := ` +{ + "kind": "reference", + "authenticationMode": "system", + "usageDescription": "This will allow the agent to respond to an email on your behalf" +} +` + + instance, err := prompty.ConnectionFromJSON(jsonData) + if err != nil { + t.Fatalf("Failed to load Connection from JSON helper: %v", err) + } + // Polymorphic types return interface{}, extract common fields via reflection or type-specific access + _ = instance // Load succeeded, exact type depends on discriminator + // Note: Validation skipped for polymorphic base types - test child types directly +} + +// TestConnectionFromYAML tests loading Connection through the generated YAML helper +func TestConnectionFromYAML(t *testing.T) { + yamlData := ` +kind: reference +authenticationMode: system +usageDescription: This will allow the agent to respond to an email on your behalf + +` + + instance, err := prompty.ConnectionFromYAML(yamlData) + if err != nil { + t.Fatalf("Failed to load Connection from YAML helper: %v", err) + } + // Polymorphic types return interface{}, extract common fields via reflection or type-specific access + _ = instance // Load succeeded, exact type depends on discriminator + // Note: Validation skipped for polymorphic base types - test child types directly +} + // TestConnectionRoundtrip tests load -> save -> load produces equivalent data func TestConnectionRoundtrip(t *testing.T) { jsonData := ` @@ -129,3 +167,10 @@ func TestConnectionToYAML(t *testing.T) { _ = instance // Load succeeded, exact type depends on discriminator // Note: ToYAML test skipped for polymorphic base types - test child types directly } + +// TestConnectionFromJSONInvalid rejects malformed JSON instead of silently defaulting +func TestConnectionFromJSONInvalid(t *testing.T) { + if _, err := prompty.ConnectionFromJSON("{"); err == nil { + t.Fatalf("Expected malformed JSON to fail") + } +} diff --git a/runtime/go/prompty/model/content_part.go b/runtime/go/prompty/model/content_part.go index a5e4bc58..c517478e 100644 --- a/runtime/go/prompty/model/content_part.go +++ b/runtime/go/prompty/model/content_part.go @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. // Group: conversation package prompty @@ -47,7 +48,7 @@ func LoadContentPart(data interface{}, ctx *LoadContext) (interface{}, error) { } // Save serializes ContentPart to map[string]interface{} -func (obj *ContentPart) Save(ctx *SaveContext) map[string]interface{} { +func (obj ContentPart) Save(ctx *SaveContext) map[string]interface{} { result := make(map[string]interface{}) result["kind"] = obj.Kind @@ -69,11 +70,7 @@ func (obj *ContentPart) ToJSON() (string, error) { func (obj *ContentPart) ToYAML() (string, error) { ctx := NewSaveContext() data := obj.Save(ctx) - bytes, err := yaml.Marshal(data) - if err != nil { - return "", err - } - return string(bytes), nil + return marshalYAMLDocument(data) } // FromJSON creates ContentPart from JSON string @@ -123,7 +120,7 @@ func LoadTextPart(data interface{}, ctx *LoadContext) (TextPart, error) { } // Save serializes TextPart to map[string]interface{} -func (obj *TextPart) Save(ctx *SaveContext) map[string]interface{} { +func (obj TextPart) Save(ctx *SaveContext) map[string]interface{} { result := make(map[string]interface{}) result["kind"] = obj.Kind result["value"] = obj.Value @@ -146,11 +143,7 @@ func (obj *TextPart) ToJSON() (string, error) { func (obj *TextPart) ToYAML() (string, error) { ctx := NewSaveContext() data := obj.Save(ctx) - bytes, err := yaml.Marshal(data) - if err != nil { - return "", err - } - return string(bytes), nil + return marshalYAMLDocument(data) } // FromJSON creates TextPart from JSON string @@ -208,7 +201,7 @@ func LoadImagePart(data interface{}, ctx *LoadContext) (ImagePart, error) { } // Save serializes ImagePart to map[string]interface{} -func (obj *ImagePart) Save(ctx *SaveContext) map[string]interface{} { +func (obj ImagePart) Save(ctx *SaveContext) map[string]interface{} { result := make(map[string]interface{}) result["kind"] = obj.Kind result["source"] = obj.Source @@ -237,11 +230,7 @@ func (obj *ImagePart) ToJSON() (string, error) { func (obj *ImagePart) ToYAML() (string, error) { ctx := NewSaveContext() data := obj.Save(ctx) - bytes, err := yaml.Marshal(data) - if err != nil { - return "", err - } - return string(bytes), nil + return marshalYAMLDocument(data) } // FromJSON creates ImagePart from JSON string @@ -294,7 +283,7 @@ func LoadFilePart(data interface{}, ctx *LoadContext) (FilePart, error) { } // Save serializes FilePart to map[string]interface{} -func (obj *FilePart) Save(ctx *SaveContext) map[string]interface{} { +func (obj FilePart) Save(ctx *SaveContext) map[string]interface{} { result := make(map[string]interface{}) result["kind"] = obj.Kind result["source"] = obj.Source @@ -320,11 +309,7 @@ func (obj *FilePart) ToJSON() (string, error) { func (obj *FilePart) ToYAML() (string, error) { ctx := NewSaveContext() data := obj.Save(ctx) - bytes, err := yaml.Marshal(data) - if err != nil { - return "", err - } - return string(bytes), nil + return marshalYAMLDocument(data) } // FromJSON creates FilePart from JSON string @@ -377,7 +362,7 @@ func LoadAudioPart(data interface{}, ctx *LoadContext) (AudioPart, error) { } // Save serializes AudioPart to map[string]interface{} -func (obj *AudioPart) Save(ctx *SaveContext) map[string]interface{} { +func (obj AudioPart) Save(ctx *SaveContext) map[string]interface{} { result := make(map[string]interface{}) result["kind"] = obj.Kind result["source"] = obj.Source @@ -403,11 +388,7 @@ func (obj *AudioPart) ToJSON() (string, error) { func (obj *AudioPart) ToYAML() (string, error) { ctx := NewSaveContext() data := obj.Save(ctx) - bytes, err := yaml.Marshal(data) - if err != nil { - return "", err - } - return string(bytes), nil + return marshalYAMLDocument(data) } // FromJSON creates AudioPart from JSON string diff --git a/runtime/go/prompty/model/content_part_test.go b/runtime/go/prompty/model/content_part_test.go index 3df1459b..9cc5440c 100644 --- a/runtime/go/prompty/model/content_part_test.go +++ b/runtime/go/prompty/model/content_part_test.go @@ -1,3 +1,4 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. package prompty_test diff --git a/runtime/go/prompty/model/context.go b/runtime/go/prompty/model/context.go index 5c54e776..f18a5c98 100644 --- a/runtime/go/prompty/model/context.go +++ b/runtime/go/prompty/model/context.go @@ -1,8 +1,18 @@ -// Code generated by Prompty emitter; DO NOT EDIT. -// Prompty Context +// +// Code generated by Typra emitter; DO NOT EDIT. +// Typra Context package prompty +import ( + "fmt" + "reflect" + "sort" + "strings" + + "gopkg.in/yaml.v3" +) + // LoadContext provides context for loading operations type LoadContext struct { // Add any context fields needed for loading @@ -30,3 +40,75 @@ func NewSaveContext() *SaveContext { func ptrOf[T any](v T) *T { return &v } + +func marshalYAMLDocument(data interface{}) (string, error) { + node := toYAMLNode(data) + bytes, err := yaml.Marshal(node) + if err != nil { + return "", err + } + return string(bytes), nil +} + +func toYAMLNode(value interface{}) *yaml.Node { + switch v := value.(type) { + case nil: + return &yaml.Node{Kind: yaml.ScalarNode, Tag: "!!null"} + case string: + node := &yaml.Node{Kind: yaml.ScalarNode, Tag: "!!str", Value: v} + if strings.Contains(v, "\n") { + node.Style = yaml.DoubleQuotedStyle + } + return node + case map[string]interface{}: + return mapToYAMLNode(v) + case []interface{}: + node := &yaml.Node{Kind: yaml.SequenceNode, Tag: "!!seq"} + for _, item := range v { + node.Content = append(node.Content, toYAMLNode(item)) + } + return node + } + + rv := reflect.ValueOf(value) + if rv.IsValid() { + switch rv.Kind() { + case reflect.Slice, reflect.Array: + node := &yaml.Node{Kind: yaml.SequenceNode, Tag: "!!seq"} + for i := 0; i < rv.Len(); i++ { + node.Content = append(node.Content, toYAMLNode(rv.Index(i).Interface())) + } + return node + case reflect.Map: + if rv.Type().Key().Kind() == reflect.String { + items := make(map[string]interface{}, rv.Len()) + for _, key := range rv.MapKeys() { + items[key.String()] = rv.MapIndex(key).Interface() + } + return mapToYAMLNode(items) + } + } + } + + node := &yaml.Node{} + if err := node.Encode(value); err == nil { + if node.Kind == yaml.ScalarNode && node.Tag == "!!str" && strings.Contains(node.Value, "\n") { + node.Style = yaml.DoubleQuotedStyle + } + return node + } + return &yaml.Node{Kind: yaml.ScalarNode, Tag: "!!str", Value: fmt.Sprint(value)} +} + +func mapToYAMLNode(items map[string]interface{}) *yaml.Node { + node := &yaml.Node{Kind: yaml.MappingNode, Tag: "!!map"} + keys := make([]string, 0, len(items)) + for key := range items { + keys = append(keys, key) + } + sort.Strings(keys) + for _, key := range keys { + node.Content = append(node.Content, &yaml.Node{Kind: yaml.ScalarNode, Tag: "!!str", Value: key}, toYAMLNode(items[key])) + } + return node +} diff --git a/runtime/go/prompty/model/custom_tool_test.go b/runtime/go/prompty/model/custom_tool_test.go index b8dff888..2087ebb5 100644 --- a/runtime/go/prompty/model/custom_tool_test.go +++ b/runtime/go/prompty/model/custom_tool_test.go @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. package prompty_test @@ -35,6 +36,9 @@ func TestCustomToolLoadJSON(t *testing.T) { t.Fatalf("Failed to load CustomTool: %v", err) } _ = instance // No scalar properties to validate + if instance.Options == nil { + t.Fatalf("Expected Options to be populated") + } } // TestCustomToolLoadYAML tests loading CustomTool from YAML @@ -58,6 +62,54 @@ options: t.Fatalf("Failed to load CustomTool: %v", err) } _ = instance // No scalar properties to validate + if instance.Options == nil { + t.Fatalf("Expected Options to be populated") + } +} + +// TestCustomToolFromJSON tests loading CustomTool through the generated JSON helper +func TestCustomToolFromJSON(t *testing.T) { + jsonData := ` +{ + "connection": { + "kind": "reference" + }, + "options": { + "timeout": 30, + "retries": 3 + } +} +` + + instance, err := prompty.CustomToolFromJSON(jsonData) + if err != nil { + t.Fatalf("Failed to load CustomTool from JSON helper: %v", err) + } + _ = instance // No scalar properties to validate + if instance.Options == nil { + t.Fatalf("Expected Options to be populated") + } +} + +// TestCustomToolFromYAML tests loading CustomTool through the generated YAML helper +func TestCustomToolFromYAML(t *testing.T) { + yamlData := ` +connection: + kind: reference +options: + timeout: 30 + retries: 3 + +` + + instance, err := prompty.CustomToolFromYAML(yamlData) + if err != nil { + t.Fatalf("Failed to load CustomTool from YAML helper: %v", err) + } + _ = instance // No scalar properties to validate + if instance.Options == nil { + t.Fatalf("Expected Options to be populated") + } } // TestCustomToolRoundtrip tests load -> save -> load produces equivalent data @@ -91,6 +143,9 @@ func TestCustomToolRoundtrip(t *testing.T) { t.Fatalf("Failed to reload CustomTool: %v", err) } _ = reloaded // No scalar properties to validate + if reloaded.Options == nil { + t.Fatalf("Expected Options to be populated") + } } // TestCustomToolToJSON tests that ToJSON produces valid JSON @@ -125,6 +180,15 @@ func TestCustomToolToJSON(t *testing.T) { if err := json.Unmarshal([]byte(jsonOutput), &parsed); err != nil { t.Fatalf("Failed to parse generated JSON: %v", err) } + + reloaded, err := prompty.LoadCustomTool(parsed, ctx) + if err != nil { + t.Fatalf("Failed to reload generated JSON: %v", err) + } + _ = reloaded // No scalar properties to validate + if reloaded.Options == nil { + t.Fatalf("Expected Options to be populated") + } } // TestCustomToolToYAML tests that ToYAML produces valid YAML @@ -159,4 +223,20 @@ func TestCustomToolToYAML(t *testing.T) { if err := yaml.Unmarshal([]byte(yamlOutput), &parsed); err != nil { t.Fatalf("Failed to parse generated YAML: %v", err) } + + reloaded, err := prompty.LoadCustomTool(parsed, ctx) + if err != nil { + t.Fatalf("Failed to reload generated YAML: %v", err) + } + _ = reloaded // No scalar properties to validate + if reloaded.Options == nil { + t.Fatalf("Expected Options to be populated") + } +} + +// TestCustomToolFromJSONInvalid rejects malformed JSON instead of silently defaulting +func TestCustomToolFromJSONInvalid(t *testing.T) { + if _, err := prompty.CustomToolFromJSON("{"); err == nil { + t.Fatalf("Expected malformed JSON to fail") + } } diff --git a/runtime/go/prompty/model/done_event_payload.go b/runtime/go/prompty/model/done_event_payload.go index 00eae878..92bb0325 100644 --- a/runtime/go/prompty/model/done_event_payload.go +++ b/runtime/go/prompty/model/done_event_payload.go @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. // Group: events package prompty @@ -12,8 +13,8 @@ import ( // DoneEventPayload represents Payload for "done" events — the agent loop completed successfully. type DoneEventPayload struct { - Response string `json:"response" yaml:"response"` - Messages []Message `json:"messages" yaml:"messages"` + Response interface{} `json:"response" yaml:"response"` + Messages []Message `json:"messages" yaml:"messages"` } // LoadDoneEventPayload creates a DoneEventPayload from a map[string]interface{} @@ -23,7 +24,7 @@ func LoadDoneEventPayload(data interface{}, ctx *LoadContext) (DoneEventPayload, // Load from map if m, ok := data.(map[string]interface{}); ok { if val, ok := m["response"]; ok && val != nil { - result.Response = string(val.(string)) + result.Response = val } if val, ok := m["messages"]; ok && val != nil { if arr, ok := val.([]interface{}); ok { @@ -42,7 +43,7 @@ func LoadDoneEventPayload(data interface{}, ctx *LoadContext) (DoneEventPayload, } // Save serializes DoneEventPayload to map[string]interface{} -func (obj *DoneEventPayload) Save(ctx *SaveContext) map[string]interface{} { +func (obj DoneEventPayload) Save(ctx *SaveContext) map[string]interface{} { result := make(map[string]interface{}) result["response"] = obj.Response if obj.Messages != nil { @@ -71,11 +72,7 @@ func (obj *DoneEventPayload) ToJSON() (string, error) { func (obj *DoneEventPayload) ToYAML() (string, error) { ctx := NewSaveContext() data := obj.Save(ctx) - bytes, err := yaml.Marshal(data) - if err != nil { - return "", err - } - return string(bytes), nil + return marshalYAMLDocument(data) } // FromJSON creates DoneEventPayload from JSON string diff --git a/runtime/go/prompty/model/done_event_payload_test.go b/runtime/go/prompty/model/done_event_payload_test.go index 08fabf2f..9cc5440c 100644 --- a/runtime/go/prompty/model/done_event_payload_test.go +++ b/runtime/go/prompty/model/done_event_payload_test.go @@ -1,140 +1,4 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. package prompty_test - -import ( - "encoding/json" - "testing" - - "gopkg.in/yaml.v3" - - "prompty/model" -) - -// TestDoneEventPayloadLoadJSON tests loading DoneEventPayload from JSON -func TestDoneEventPayloadLoadJSON(t *testing.T) { - jsonData := ` -{ - "response": "The weather in Paris is 72°F and sunny." -} -` - var data map[string]interface{} - if err := json.Unmarshal([]byte(jsonData), &data); err != nil { - t.Fatalf("Failed to parse JSON: %v", err) - } - - ctx := prompty.NewLoadContext() - instance, err := prompty.LoadDoneEventPayload(data, ctx) - if err != nil { - t.Fatalf("Failed to load DoneEventPayload: %v", err) - } - if instance.Response != "The weather in Paris is 72°F and sunny." { - t.Errorf(`Expected Response to be "The weather in Paris is 72°F and sunny.", got %v`, instance.Response) - } -} - -// TestDoneEventPayloadLoadYAML tests loading DoneEventPayload from YAML -func TestDoneEventPayloadLoadYAML(t *testing.T) { - yamlData := ` -response: The weather in Paris is 72°F and sunny. - -` - var data map[string]interface{} - if err := yaml.Unmarshal([]byte(yamlData), &data); err != nil { - t.Fatalf("Failed to parse YAML: %v", err) - } - - ctx := prompty.NewLoadContext() - instance, err := prompty.LoadDoneEventPayload(data, ctx) - if err != nil { - t.Fatalf("Failed to load DoneEventPayload: %v", err) - } - if instance.Response != "The weather in Paris is 72°F and sunny." { - t.Errorf(`Expected Response to be "The weather in Paris is 72°F and sunny.", got %v`, instance.Response) - } -} - -// TestDoneEventPayloadRoundtrip tests load -> save -> load produces equivalent data -func TestDoneEventPayloadRoundtrip(t *testing.T) { - jsonData := ` -{ - "response": "The weather in Paris is 72°F and sunny." -} -` - var data map[string]interface{} - if err := json.Unmarshal([]byte(jsonData), &data); err != nil { - t.Fatalf("Failed to parse JSON: %v", err) - } - - loadCtx := prompty.NewLoadContext() - instance, err := prompty.LoadDoneEventPayload(data, loadCtx) - if err != nil { - t.Fatalf("Failed to load DoneEventPayload: %v", err) - } - saveCtx := prompty.NewSaveContext() - savedData := instance.Save(saveCtx) - - reloaded, err := prompty.LoadDoneEventPayload(savedData, loadCtx) - if err != nil { - t.Fatalf("Failed to reload DoneEventPayload: %v", err) - } - if reloaded.Response != "The weather in Paris is 72°F and sunny." { - t.Errorf(`Expected Response to be "The weather in Paris is 72°F and sunny.", got %v`, reloaded.Response) - } -} - -// TestDoneEventPayloadToJSON tests that ToJSON produces valid JSON -func TestDoneEventPayloadToJSON(t *testing.T) { - jsonData := ` -{ - "response": "The weather in Paris is 72°F and sunny." -} -` - var data map[string]interface{} - if err := json.Unmarshal([]byte(jsonData), &data); err != nil { - t.Fatalf("Failed to parse JSON: %v", err) - } - - ctx := prompty.NewLoadContext() - instance, err := prompty.LoadDoneEventPayload(data, ctx) - if err != nil { - t.Fatalf("Failed to load DoneEventPayload: %v", err) - } - jsonOutput, err := instance.ToJSON() - if err != nil { - t.Fatalf("Failed to convert to JSON: %v", err) - } - - var parsed map[string]interface{} - if err := json.Unmarshal([]byte(jsonOutput), &parsed); err != nil { - t.Fatalf("Failed to parse generated JSON: %v", err) - } -} - -// TestDoneEventPayloadToYAML tests that ToYAML produces valid YAML -func TestDoneEventPayloadToYAML(t *testing.T) { - jsonData := ` -{ - "response": "The weather in Paris is 72°F and sunny." -} -` - var data map[string]interface{} - if err := json.Unmarshal([]byte(jsonData), &data); err != nil { - t.Fatalf("Failed to parse JSON: %v", err) - } - - ctx := prompty.NewLoadContext() - instance, err := prompty.LoadDoneEventPayload(data, ctx) - if err != nil { - t.Fatalf("Failed to load DoneEventPayload: %v", err) - } - yamlOutput, err := instance.ToYAML() - if err != nil { - t.Fatalf("Failed to convert to YAML: %v", err) - } - - var parsed map[string]interface{} - if err := yaml.Unmarshal([]byte(yamlOutput), &parsed); err != nil { - t.Fatalf("Failed to parse generated YAML: %v", err) - } -} diff --git a/runtime/go/prompty/model/error_chunk_test.go b/runtime/go/prompty/model/error_chunk_test.go index fea253a4..b3255ee0 100644 --- a/runtime/go/prompty/model/error_chunk_test.go +++ b/runtime/go/prompty/model/error_chunk_test.go @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. package prompty_test @@ -54,6 +55,39 @@ message: Rate limit exceeded } } +// TestErrorChunkFromJSON tests loading ErrorChunk through the generated JSON helper +func TestErrorChunkFromJSON(t *testing.T) { + jsonData := ` +{ + "message": "Rate limit exceeded" +} +` + + instance, err := prompty.ErrorChunkFromJSON(jsonData) + if err != nil { + t.Fatalf("Failed to load ErrorChunk from JSON helper: %v", err) + } + if instance.Message != "Rate limit exceeded" { + t.Errorf(`Expected Message to be "Rate limit exceeded", got %v`, instance.Message) + } +} + +// TestErrorChunkFromYAML tests loading ErrorChunk through the generated YAML helper +func TestErrorChunkFromYAML(t *testing.T) { + yamlData := ` +message: Rate limit exceeded + +` + + instance, err := prompty.ErrorChunkFromYAML(yamlData) + if err != nil { + t.Fatalf("Failed to load ErrorChunk from YAML helper: %v", err) + } + if instance.Message != "Rate limit exceeded" { + t.Errorf(`Expected Message to be "Rate limit exceeded", got %v`, instance.Message) + } +} + // TestErrorChunkRoundtrip tests load -> save -> load produces equivalent data func TestErrorChunkRoundtrip(t *testing.T) { jsonData := ` @@ -109,6 +143,14 @@ func TestErrorChunkToJSON(t *testing.T) { if err := json.Unmarshal([]byte(jsonOutput), &parsed); err != nil { t.Fatalf("Failed to parse generated JSON: %v", err) } + + reloaded, err := prompty.LoadErrorChunk(parsed, ctx) + if err != nil { + t.Fatalf("Failed to reload generated JSON: %v", err) + } + if reloaded.Message != "Rate limit exceeded" { + t.Errorf(`Expected Message to be "Rate limit exceeded", got %v`, reloaded.Message) + } } // TestErrorChunkToYAML tests that ToYAML produces valid YAML @@ -137,4 +179,19 @@ func TestErrorChunkToYAML(t *testing.T) { if err := yaml.Unmarshal([]byte(yamlOutput), &parsed); err != nil { t.Fatalf("Failed to parse generated YAML: %v", err) } + + reloaded, err := prompty.LoadErrorChunk(parsed, ctx) + if err != nil { + t.Fatalf("Failed to reload generated YAML: %v", err) + } + if reloaded.Message != "Rate limit exceeded" { + t.Errorf(`Expected Message to be "Rate limit exceeded", got %v`, reloaded.Message) + } +} + +// TestErrorChunkFromJSONInvalid rejects malformed JSON instead of silently defaulting +func TestErrorChunkFromJSONInvalid(t *testing.T) { + if _, err := prompty.ErrorChunkFromJSON("{"); err == nil { + t.Fatalf("Expected malformed JSON to fail") + } } diff --git a/runtime/go/prompty/model/error_event_payload.go b/runtime/go/prompty/model/error_event_payload.go index 9e3dd0d0..e2bc5922 100644 --- a/runtime/go/prompty/model/error_event_payload.go +++ b/runtime/go/prompty/model/error_event_payload.go @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. // Group: events package prompty @@ -12,7 +13,9 @@ import ( // ErrorEventPayload represents Payload for "error" events — an error occurred during the loop. type ErrorEventPayload struct { - Message string `json:"message" yaml:"message"` + Message string `json:"message" yaml:"message"` + ErrorKind *string `json:"errorKind,omitempty" yaml:"errorKind,omitempty"` + Phase *string `json:"phase,omitempty" yaml:"phase,omitempty"` } // LoadErrorEventPayload creates a ErrorEventPayload from a map[string]interface{} @@ -24,15 +27,29 @@ func LoadErrorEventPayload(data interface{}, ctx *LoadContext) (ErrorEventPayloa if val, ok := m["message"]; ok && val != nil { result.Message = string(val.(string)) } + if val, ok := m["errorKind"]; ok && val != nil { + v := string(val.(string)) + result.ErrorKind = &v + } + if val, ok := m["phase"]; ok && val != nil { + v := string(val.(string)) + result.Phase = &v + } } return result, nil } // Save serializes ErrorEventPayload to map[string]interface{} -func (obj *ErrorEventPayload) Save(ctx *SaveContext) map[string]interface{} { +func (obj ErrorEventPayload) Save(ctx *SaveContext) map[string]interface{} { result := make(map[string]interface{}) result["message"] = obj.Message + if obj.ErrorKind != nil { + result["errorKind"] = *obj.ErrorKind + } + if obj.Phase != nil { + result["phase"] = *obj.Phase + } return result } @@ -52,11 +69,7 @@ func (obj *ErrorEventPayload) ToJSON() (string, error) { func (obj *ErrorEventPayload) ToYAML() (string, error) { ctx := NewSaveContext() data := obj.Save(ctx) - bytes, err := yaml.Marshal(data) - if err != nil { - return "", err - } - return string(bytes), nil + return marshalYAMLDocument(data) } // FromJSON creates ErrorEventPayload from JSON string diff --git a/runtime/go/prompty/model/error_event_payload_test.go b/runtime/go/prompty/model/error_event_payload_test.go index b1f93ef5..a9d58594 100644 --- a/runtime/go/prompty/model/error_event_payload_test.go +++ b/runtime/go/prompty/model/error_event_payload_test.go @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. package prompty_test @@ -15,7 +16,9 @@ import ( func TestErrorEventPayloadLoadJSON(t *testing.T) { jsonData := ` { - "message": "Rate limit exceeded" + "message": "Rate limit exceeded", + "errorKind": "rate_limit", + "phase": "llm" } ` var data map[string]interface{} @@ -31,12 +34,20 @@ func TestErrorEventPayloadLoadJSON(t *testing.T) { if instance.Message != "Rate limit exceeded" { t.Errorf(`Expected Message to be "Rate limit exceeded", got %v`, instance.Message) } + if instance.ErrorKind == nil || *instance.ErrorKind != "rate_limit" { + t.Errorf(`Expected ErrorKind to be "rate_limit", got %v`, instance.ErrorKind) + } + if instance.Phase == nil || *instance.Phase != "llm" { + t.Errorf(`Expected Phase to be "llm", got %v`, instance.Phase) + } } // TestErrorEventPayloadLoadYAML tests loading ErrorEventPayload from YAML func TestErrorEventPayloadLoadYAML(t *testing.T) { yamlData := ` message: Rate limit exceeded +errorKind: rate_limit +phase: llm ` var data map[string]interface{} @@ -52,13 +63,70 @@ message: Rate limit exceeded if instance.Message != "Rate limit exceeded" { t.Errorf(`Expected Message to be "Rate limit exceeded", got %v`, instance.Message) } + if instance.ErrorKind == nil || *instance.ErrorKind != "rate_limit" { + t.Errorf(`Expected ErrorKind to be "rate_limit", got %v`, instance.ErrorKind) + } + if instance.Phase == nil || *instance.Phase != "llm" { + t.Errorf(`Expected Phase to be "llm", got %v`, instance.Phase) + } +} + +// TestErrorEventPayloadFromJSON tests loading ErrorEventPayload through the generated JSON helper +func TestErrorEventPayloadFromJSON(t *testing.T) { + jsonData := ` +{ + "message": "Rate limit exceeded", + "errorKind": "rate_limit", + "phase": "llm" +} +` + + instance, err := prompty.ErrorEventPayloadFromJSON(jsonData) + if err != nil { + t.Fatalf("Failed to load ErrorEventPayload from JSON helper: %v", err) + } + if instance.Message != "Rate limit exceeded" { + t.Errorf(`Expected Message to be "Rate limit exceeded", got %v`, instance.Message) + } + if instance.ErrorKind == nil || *instance.ErrorKind != "rate_limit" { + t.Errorf(`Expected ErrorKind to be "rate_limit", got %v`, instance.ErrorKind) + } + if instance.Phase == nil || *instance.Phase != "llm" { + t.Errorf(`Expected Phase to be "llm", got %v`, instance.Phase) + } +} + +// TestErrorEventPayloadFromYAML tests loading ErrorEventPayload through the generated YAML helper +func TestErrorEventPayloadFromYAML(t *testing.T) { + yamlData := ` +message: Rate limit exceeded +errorKind: rate_limit +phase: llm + +` + + instance, err := prompty.ErrorEventPayloadFromYAML(yamlData) + if err != nil { + t.Fatalf("Failed to load ErrorEventPayload from YAML helper: %v", err) + } + if instance.Message != "Rate limit exceeded" { + t.Errorf(`Expected Message to be "Rate limit exceeded", got %v`, instance.Message) + } + if instance.ErrorKind == nil || *instance.ErrorKind != "rate_limit" { + t.Errorf(`Expected ErrorKind to be "rate_limit", got %v`, instance.ErrorKind) + } + if instance.Phase == nil || *instance.Phase != "llm" { + t.Errorf(`Expected Phase to be "llm", got %v`, instance.Phase) + } } // TestErrorEventPayloadRoundtrip tests load -> save -> load produces equivalent data func TestErrorEventPayloadRoundtrip(t *testing.T) { jsonData := ` { - "message": "Rate limit exceeded" + "message": "Rate limit exceeded", + "errorKind": "rate_limit", + "phase": "llm" } ` var data map[string]interface{} @@ -81,13 +149,21 @@ func TestErrorEventPayloadRoundtrip(t *testing.T) { if reloaded.Message != "Rate limit exceeded" { t.Errorf(`Expected Message to be "Rate limit exceeded", got %v`, reloaded.Message) } + if reloaded.ErrorKind == nil || *reloaded.ErrorKind != "rate_limit" { + t.Errorf(`Expected ErrorKind to be "rate_limit", got %v`, reloaded.ErrorKind) + } + if reloaded.Phase == nil || *reloaded.Phase != "llm" { + t.Errorf(`Expected Phase to be "llm", got %v`, reloaded.Phase) + } } // TestErrorEventPayloadToJSON tests that ToJSON produces valid JSON func TestErrorEventPayloadToJSON(t *testing.T) { jsonData := ` { - "message": "Rate limit exceeded" + "message": "Rate limit exceeded", + "errorKind": "rate_limit", + "phase": "llm" } ` var data map[string]interface{} @@ -109,13 +185,29 @@ func TestErrorEventPayloadToJSON(t *testing.T) { if err := json.Unmarshal([]byte(jsonOutput), &parsed); err != nil { t.Fatalf("Failed to parse generated JSON: %v", err) } + + reloaded, err := prompty.LoadErrorEventPayload(parsed, ctx) + if err != nil { + t.Fatalf("Failed to reload generated JSON: %v", err) + } + if reloaded.Message != "Rate limit exceeded" { + t.Errorf(`Expected Message to be "Rate limit exceeded", got %v`, reloaded.Message) + } + if reloaded.ErrorKind == nil || *reloaded.ErrorKind != "rate_limit" { + t.Errorf(`Expected ErrorKind to be "rate_limit", got %v`, reloaded.ErrorKind) + } + if reloaded.Phase == nil || *reloaded.Phase != "llm" { + t.Errorf(`Expected Phase to be "llm", got %v`, reloaded.Phase) + } } // TestErrorEventPayloadToYAML tests that ToYAML produces valid YAML func TestErrorEventPayloadToYAML(t *testing.T) { jsonData := ` { - "message": "Rate limit exceeded" + "message": "Rate limit exceeded", + "errorKind": "rate_limit", + "phase": "llm" } ` var data map[string]interface{} @@ -137,4 +229,25 @@ func TestErrorEventPayloadToYAML(t *testing.T) { if err := yaml.Unmarshal([]byte(yamlOutput), &parsed); err != nil { t.Fatalf("Failed to parse generated YAML: %v", err) } + + reloaded, err := prompty.LoadErrorEventPayload(parsed, ctx) + if err != nil { + t.Fatalf("Failed to reload generated YAML: %v", err) + } + if reloaded.Message != "Rate limit exceeded" { + t.Errorf(`Expected Message to be "Rate limit exceeded", got %v`, reloaded.Message) + } + if reloaded.ErrorKind == nil || *reloaded.ErrorKind != "rate_limit" { + t.Errorf(`Expected ErrorKind to be "rate_limit", got %v`, reloaded.ErrorKind) + } + if reloaded.Phase == nil || *reloaded.Phase != "llm" { + t.Errorf(`Expected Phase to be "llm", got %v`, reloaded.Phase) + } +} + +// TestErrorEventPayloadFromJSONInvalid rejects malformed JSON instead of silently defaulting +func TestErrorEventPayloadFromJSONInvalid(t *testing.T) { + if _, err := prompty.ErrorEventPayloadFromJSON("{"); err == nil { + t.Fatalf("Expected malformed JSON to fail") + } } diff --git a/runtime/go/prompty/model/event_journal_writer.go b/runtime/go/prompty/model/event_journal_writer.go new file mode 100644 index 00000000..bfd2885c --- /dev/null +++ b/runtime/go/prompty/model/event_journal_writer.go @@ -0,0 +1,16 @@ +// +// Code generated by Typra emitter; DO NOT EDIT. +// Group: pipeline + +package prompty + +// EventJournalWriter represents Persists typed events to a durable replay journal. + +type EventJournalWriter interface { + // AppendTurn — Append a turn event to a durable replay journal + AppendTurn(turnEvent TurnEvent) (bool, error) + // AppendSession — Append a session event to a durable replay journal + AppendSession(sessionEvent SessionEvent) (bool, error) + // Close — Finalize the journal with an optional session summary + Close(summary *SessionSummary) (bool, error) +} diff --git a/runtime/go/prompty/model/event_sink.go b/runtime/go/prompty/model/event_sink.go new file mode 100644 index 00000000..2d68bb04 --- /dev/null +++ b/runtime/go/prompty/model/event_sink.go @@ -0,0 +1,14 @@ +// +// Code generated by Typra emitter; DO NOT EDIT. +// Group: pipeline + +package prompty + +// EventSink represents Receives typed turn and session events from a harness. + +type EventSink interface { + // EmitTurn — Emit a typed turn event to a host sink + EmitTurn(turnEvent TurnEvent) (bool, error) + // EmitSession — Emit a typed session event to a host sink + EmitSession(sessionEvent SessionEvent) (bool, error) +} diff --git a/runtime/go/prompty/model/executor.go b/runtime/go/prompty/model/executor.go index 45f46545..8571abf7 100644 --- a/runtime/go/prompty/model/executor.go +++ b/runtime/go/prompty/model/executor.go @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. // Group: pipeline package prompty diff --git a/runtime/go/prompty/model/file_not_found_error.go b/runtime/go/prompty/model/file_not_found_error.go index b20cae9f..7a47f4d7 100644 --- a/runtime/go/prompty/model/file_not_found_error.go +++ b/runtime/go/prompty/model/file_not_found_error.go @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. // Group: core package prompty @@ -35,7 +36,7 @@ func LoadFileNotFoundError(data interface{}, ctx *LoadContext) (FileNotFoundErro } // Save serializes FileNotFoundError to map[string]interface{} -func (obj *FileNotFoundError) Save(ctx *SaveContext) map[string]interface{} { +func (obj FileNotFoundError) Save(ctx *SaveContext) map[string]interface{} { result := make(map[string]interface{}) result["message"] = obj.Message result["path"] = obj.Path @@ -58,11 +59,7 @@ func (obj *FileNotFoundError) ToJSON() (string, error) { func (obj *FileNotFoundError) ToYAML() (string, error) { ctx := NewSaveContext() data := obj.Save(ctx) - bytes, err := yaml.Marshal(data) - if err != nil { - return "", err - } - return string(bytes), nil + return marshalYAMLDocument(data) } // FromJSON creates FileNotFoundError from JSON string diff --git a/runtime/go/prompty/model/file_not_found_error_test.go b/runtime/go/prompty/model/file_not_found_error_test.go index ac823786..aabfacf9 100644 --- a/runtime/go/prompty/model/file_not_found_error_test.go +++ b/runtime/go/prompty/model/file_not_found_error_test.go @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. package prompty_test @@ -62,6 +63,47 @@ path: ./chat.prompty } } +// TestFileNotFoundErrorFromJSON tests loading FileNotFoundError through the generated JSON helper +func TestFileNotFoundErrorFromJSON(t *testing.T) { + jsonData := ` +{ + "message": "Prompty file not found: ./chat.prompty", + "path": "./chat.prompty" +} +` + + instance, err := prompty.FileNotFoundErrorFromJSON(jsonData) + if err != nil { + t.Fatalf("Failed to load FileNotFoundError from JSON helper: %v", err) + } + if instance.Message != "Prompty file not found: ./chat.prompty" { + t.Errorf(`Expected Message to be "Prompty file not found: ./chat.prompty", got %v`, instance.Message) + } + if instance.Path != "./chat.prompty" { + t.Errorf(`Expected Path to be "./chat.prompty", got %v`, instance.Path) + } +} + +// TestFileNotFoundErrorFromYAML tests loading FileNotFoundError through the generated YAML helper +func TestFileNotFoundErrorFromYAML(t *testing.T) { + yamlData := ` +message: "Prompty file not found: ./chat.prompty" +path: ./chat.prompty + +` + + instance, err := prompty.FileNotFoundErrorFromYAML(yamlData) + if err != nil { + t.Fatalf("Failed to load FileNotFoundError from YAML helper: %v", err) + } + if instance.Message != "Prompty file not found: ./chat.prompty" { + t.Errorf(`Expected Message to be "Prompty file not found: ./chat.prompty", got %v`, instance.Message) + } + if instance.Path != "./chat.prompty" { + t.Errorf(`Expected Path to be "./chat.prompty", got %v`, instance.Path) + } +} + // TestFileNotFoundErrorRoundtrip tests load -> save -> load produces equivalent data func TestFileNotFoundErrorRoundtrip(t *testing.T) { jsonData := ` @@ -122,6 +164,17 @@ func TestFileNotFoundErrorToJSON(t *testing.T) { if err := json.Unmarshal([]byte(jsonOutput), &parsed); err != nil { t.Fatalf("Failed to parse generated JSON: %v", err) } + + reloaded, err := prompty.LoadFileNotFoundError(parsed, ctx) + if err != nil { + t.Fatalf("Failed to reload generated JSON: %v", err) + } + if reloaded.Message != "Prompty file not found: ./chat.prompty" { + t.Errorf(`Expected Message to be "Prompty file not found: ./chat.prompty", got %v`, reloaded.Message) + } + if reloaded.Path != "./chat.prompty" { + t.Errorf(`Expected Path to be "./chat.prompty", got %v`, reloaded.Path) + } } // TestFileNotFoundErrorToYAML tests that ToYAML produces valid YAML @@ -151,4 +204,22 @@ func TestFileNotFoundErrorToYAML(t *testing.T) { if err := yaml.Unmarshal([]byte(yamlOutput), &parsed); err != nil { t.Fatalf("Failed to parse generated YAML: %v", err) } + + reloaded, err := prompty.LoadFileNotFoundError(parsed, ctx) + if err != nil { + t.Fatalf("Failed to reload generated YAML: %v", err) + } + if reloaded.Message != "Prompty file not found: ./chat.prompty" { + t.Errorf(`Expected Message to be "Prompty file not found: ./chat.prompty", got %v`, reloaded.Message) + } + if reloaded.Path != "./chat.prompty" { + t.Errorf(`Expected Path to be "./chat.prompty", got %v`, reloaded.Path) + } +} + +// TestFileNotFoundErrorFromJSONInvalid rejects malformed JSON instead of silently defaulting +func TestFileNotFoundErrorFromJSONInvalid(t *testing.T) { + if _, err := prompty.FileNotFoundErrorFromJSON("{"); err == nil { + t.Fatalf("Expected malformed JSON to fail") + } } diff --git a/runtime/go/prompty/model/file_part_test.go b/runtime/go/prompty/model/file_part_test.go index 7cdb9712..7068dff3 100644 --- a/runtime/go/prompty/model/file_part_test.go +++ b/runtime/go/prompty/model/file_part_test.go @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. package prompty_test @@ -62,6 +63,47 @@ mediaType: application/pdf } } +// TestFilePartFromJSON tests loading FilePart through the generated JSON helper +func TestFilePartFromJSON(t *testing.T) { + jsonData := ` +{ + "source": "https://example.com/document.pdf", + "mediaType": "application/pdf" +} +` + + instance, err := prompty.FilePartFromJSON(jsonData) + if err != nil { + t.Fatalf("Failed to load FilePart from JSON helper: %v", err) + } + if instance.Source != "https://example.com/document.pdf" { + t.Errorf(`Expected Source to be "https://example.com/document.pdf", got %v`, instance.Source) + } + if instance.MediaType == nil || *instance.MediaType != "application/pdf" { + t.Errorf(`Expected MediaType to be "application/pdf", got %v`, instance.MediaType) + } +} + +// TestFilePartFromYAML tests loading FilePart through the generated YAML helper +func TestFilePartFromYAML(t *testing.T) { + yamlData := ` +source: "https://example.com/document.pdf" +mediaType: application/pdf + +` + + instance, err := prompty.FilePartFromYAML(yamlData) + if err != nil { + t.Fatalf("Failed to load FilePart from YAML helper: %v", err) + } + if instance.Source != "https://example.com/document.pdf" { + t.Errorf(`Expected Source to be "https://example.com/document.pdf", got %v`, instance.Source) + } + if instance.MediaType == nil || *instance.MediaType != "application/pdf" { + t.Errorf(`Expected MediaType to be "application/pdf", got %v`, instance.MediaType) + } +} + // TestFilePartRoundtrip tests load -> save -> load produces equivalent data func TestFilePartRoundtrip(t *testing.T) { jsonData := ` @@ -122,6 +164,17 @@ func TestFilePartToJSON(t *testing.T) { if err := json.Unmarshal([]byte(jsonOutput), &parsed); err != nil { t.Fatalf("Failed to parse generated JSON: %v", err) } + + reloaded, err := prompty.LoadFilePart(parsed, ctx) + if err != nil { + t.Fatalf("Failed to reload generated JSON: %v", err) + } + if reloaded.Source != "https://example.com/document.pdf" { + t.Errorf(`Expected Source to be "https://example.com/document.pdf", got %v`, reloaded.Source) + } + if reloaded.MediaType == nil || *reloaded.MediaType != "application/pdf" { + t.Errorf(`Expected MediaType to be "application/pdf", got %v`, reloaded.MediaType) + } } // TestFilePartToYAML tests that ToYAML produces valid YAML @@ -151,4 +204,22 @@ func TestFilePartToYAML(t *testing.T) { if err := yaml.Unmarshal([]byte(yamlOutput), &parsed); err != nil { t.Fatalf("Failed to parse generated YAML: %v", err) } + + reloaded, err := prompty.LoadFilePart(parsed, ctx) + if err != nil { + t.Fatalf("Failed to reload generated YAML: %v", err) + } + if reloaded.Source != "https://example.com/document.pdf" { + t.Errorf(`Expected Source to be "https://example.com/document.pdf", got %v`, reloaded.Source) + } + if reloaded.MediaType == nil || *reloaded.MediaType != "application/pdf" { + t.Errorf(`Expected MediaType to be "application/pdf", got %v`, reloaded.MediaType) + } +} + +// TestFilePartFromJSONInvalid rejects malformed JSON instead of silently defaulting +func TestFilePartFromJSONInvalid(t *testing.T) { + if _, err := prompty.FilePartFromJSON("{"); err == nil { + t.Fatalf("Expected malformed JSON to fail") + } } diff --git a/runtime/go/prompty/model/format_config.go b/runtime/go/prompty/model/format_config.go index 93b285bf..8cfb37b1 100644 --- a/runtime/go/prompty/model/format_config.go +++ b/runtime/go/prompty/model/format_config.go @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. // Group: template package prompty @@ -48,7 +49,7 @@ func LoadFormatConfig(data interface{}, ctx *LoadContext) (FormatConfig, error) } // Save serializes FormatConfig to map[string]interface{} -func (obj *FormatConfig) Save(ctx *SaveContext) map[string]interface{} { +func (obj FormatConfig) Save(ctx *SaveContext) map[string]interface{} { result := make(map[string]interface{}) result["kind"] = obj.Kind if obj.Strict != nil { @@ -76,11 +77,7 @@ func (obj *FormatConfig) ToJSON() (string, error) { func (obj *FormatConfig) ToYAML() (string, error) { ctx := NewSaveContext() data := obj.Save(ctx) - bytes, err := yaml.Marshal(data) - if err != nil { - return "", err - } - return string(bytes), nil + return marshalYAMLDocument(data) } // FromJSON creates FormatConfig from JSON string diff --git a/runtime/go/prompty/model/format_config_test.go b/runtime/go/prompty/model/format_config_test.go index 9e3eebcb..35d46b22 100644 --- a/runtime/go/prompty/model/format_config_test.go +++ b/runtime/go/prompty/model/format_config_test.go @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. package prompty_test @@ -61,6 +62,46 @@ options: // Note: Validation skipped for polymorphic base types - test child types directly } +// TestFormatConfigFromJSON tests loading FormatConfig through the generated JSON helper +func TestFormatConfigFromJSON(t *testing.T) { + jsonData := ` +{ + "kind": "mustache", + "strict": true, + "options": { + "key": "value" + } +} +` + + instance, err := prompty.FormatConfigFromJSON(jsonData) + if err != nil { + t.Fatalf("Failed to load FormatConfig from JSON helper: %v", err) + } + // Polymorphic types return interface{}, extract common fields via reflection or type-specific access + _ = instance // Load succeeded, exact type depends on discriminator + // Note: Validation skipped for polymorphic base types - test child types directly +} + +// TestFormatConfigFromYAML tests loading FormatConfig through the generated YAML helper +func TestFormatConfigFromYAML(t *testing.T) { + yamlData := ` +kind: mustache +strict: true +options: + key: value + +` + + instance, err := prompty.FormatConfigFromYAML(yamlData) + if err != nil { + t.Fatalf("Failed to load FormatConfig from YAML helper: %v", err) + } + // Polymorphic types return interface{}, extract common fields via reflection or type-specific access + _ = instance // Load succeeded, exact type depends on discriminator + // Note: Validation skipped for polymorphic base types - test child types directly +} + // TestFormatConfigRoundtrip tests load -> save -> load produces equivalent data func TestFormatConfigRoundtrip(t *testing.T) { jsonData := ` @@ -139,6 +180,13 @@ func TestFormatConfigToYAML(t *testing.T) { // Note: ToYAML test skipped for polymorphic base types - test child types directly } +// TestFormatConfigFromJSONInvalid rejects malformed JSON instead of silently defaulting +func TestFormatConfigFromJSONInvalid(t *testing.T) { + if _, err := prompty.FormatConfigFromJSON("{"); err == nil { + t.Fatalf("Expected malformed JSON to fail") + } +} + // TestFormatConfigFromFormat tests loading FormatConfig from string func TestFormatConfigFromFormat(t *testing.T) { ctx := prompty.NewLoadContext() diff --git a/runtime/go/prompty/model/foundry_connection_test.go b/runtime/go/prompty/model/foundry_connection_test.go index 53238e31..629ad433 100644 --- a/runtime/go/prompty/model/foundry_connection_test.go +++ b/runtime/go/prompty/model/foundry_connection_test.go @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. package prompty_test @@ -78,6 +79,63 @@ connectionType: model } } +// TestFoundryConnectionFromJSON tests loading FoundryConnection through the generated JSON helper +func TestFoundryConnectionFromJSON(t *testing.T) { + jsonData := ` +{ + "kind": "foundry", + "endpoint": "https://myresource.services.ai.azure.com/api/projects/myproject", + "name": "my-openai-connection", + "connectionType": "model" +} +` + + instance, err := prompty.FoundryConnectionFromJSON(jsonData) + if err != nil { + t.Fatalf("Failed to load FoundryConnection from JSON helper: %v", err) + } + if instance.Kind != "foundry" { + t.Errorf(`Expected Kind to be "foundry", got %v`, instance.Kind) + } + if instance.Endpoint != "https://myresource.services.ai.azure.com/api/projects/myproject" { + t.Errorf(`Expected Endpoint to be "https://myresource.services.ai.azure.com/api/projects/myproject", got %v`, instance.Endpoint) + } + if instance.Name == nil || *instance.Name != "my-openai-connection" { + t.Errorf(`Expected Name to be "my-openai-connection", got %v`, instance.Name) + } + if instance.ConnectionType == nil || *instance.ConnectionType != "model" { + t.Errorf(`Expected ConnectionType to be "model", got %v`, instance.ConnectionType) + } +} + +// TestFoundryConnectionFromYAML tests loading FoundryConnection through the generated YAML helper +func TestFoundryConnectionFromYAML(t *testing.T) { + yamlData := ` +kind: foundry +endpoint: "https://myresource.services.ai.azure.com/api/projects/myproject" +name: my-openai-connection +connectionType: model + +` + + instance, err := prompty.FoundryConnectionFromYAML(yamlData) + if err != nil { + t.Fatalf("Failed to load FoundryConnection from YAML helper: %v", err) + } + if instance.Kind != "foundry" { + t.Errorf(`Expected Kind to be "foundry", got %v`, instance.Kind) + } + if instance.Endpoint != "https://myresource.services.ai.azure.com/api/projects/myproject" { + t.Errorf(`Expected Endpoint to be "https://myresource.services.ai.azure.com/api/projects/myproject", got %v`, instance.Endpoint) + } + if instance.Name == nil || *instance.Name != "my-openai-connection" { + t.Errorf(`Expected Name to be "my-openai-connection", got %v`, instance.Name) + } + if instance.ConnectionType == nil || *instance.ConnectionType != "model" { + t.Errorf(`Expected ConnectionType to be "model", got %v`, instance.ConnectionType) + } +} + // TestFoundryConnectionRoundtrip tests load -> save -> load produces equivalent data func TestFoundryConnectionRoundtrip(t *testing.T) { jsonData := ` @@ -148,6 +206,23 @@ func TestFoundryConnectionToJSON(t *testing.T) { if err := json.Unmarshal([]byte(jsonOutput), &parsed); err != nil { t.Fatalf("Failed to parse generated JSON: %v", err) } + + reloaded, err := prompty.LoadFoundryConnection(parsed, ctx) + if err != nil { + t.Fatalf("Failed to reload generated JSON: %v", err) + } + if reloaded.Kind != "foundry" { + t.Errorf(`Expected Kind to be "foundry", got %v`, reloaded.Kind) + } + if reloaded.Endpoint != "https://myresource.services.ai.azure.com/api/projects/myproject" { + t.Errorf(`Expected Endpoint to be "https://myresource.services.ai.azure.com/api/projects/myproject", got %v`, reloaded.Endpoint) + } + if reloaded.Name == nil || *reloaded.Name != "my-openai-connection" { + t.Errorf(`Expected Name to be "my-openai-connection", got %v`, reloaded.Name) + } + if reloaded.ConnectionType == nil || *reloaded.ConnectionType != "model" { + t.Errorf(`Expected ConnectionType to be "model", got %v`, reloaded.ConnectionType) + } } // TestFoundryConnectionToYAML tests that ToYAML produces valid YAML @@ -179,4 +254,28 @@ func TestFoundryConnectionToYAML(t *testing.T) { if err := yaml.Unmarshal([]byte(yamlOutput), &parsed); err != nil { t.Fatalf("Failed to parse generated YAML: %v", err) } + + reloaded, err := prompty.LoadFoundryConnection(parsed, ctx) + if err != nil { + t.Fatalf("Failed to reload generated YAML: %v", err) + } + if reloaded.Kind != "foundry" { + t.Errorf(`Expected Kind to be "foundry", got %v`, reloaded.Kind) + } + if reloaded.Endpoint != "https://myresource.services.ai.azure.com/api/projects/myproject" { + t.Errorf(`Expected Endpoint to be "https://myresource.services.ai.azure.com/api/projects/myproject", got %v`, reloaded.Endpoint) + } + if reloaded.Name == nil || *reloaded.Name != "my-openai-connection" { + t.Errorf(`Expected Name to be "my-openai-connection", got %v`, reloaded.Name) + } + if reloaded.ConnectionType == nil || *reloaded.ConnectionType != "model" { + t.Errorf(`Expected ConnectionType to be "model", got %v`, reloaded.ConnectionType) + } +} + +// TestFoundryConnectionFromJSONInvalid rejects malformed JSON instead of silently defaulting +func TestFoundryConnectionFromJSONInvalid(t *testing.T) { + if _, err := prompty.FoundryConnectionFromJSON("{"); err == nil { + t.Fatalf("Expected malformed JSON to fail") + } } diff --git a/runtime/go/prompty/model/function_tool_test.go b/runtime/go/prompty/model/function_tool_test.go index e961bd94..c70f8d50 100644 --- a/runtime/go/prompty/model/function_tool_test.go +++ b/runtime/go/prompty/model/function_tool_test.go @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. package prompty_test @@ -86,6 +87,71 @@ strict: true } } +// TestFunctionToolFromJSON tests loading FunctionTool through the generated JSON helper +func TestFunctionToolFromJSON(t *testing.T) { + jsonData := ` +{ + "kind": "function", + "parameters": { + "firstName": { + "kind": "string", + "default": "Jane" + }, + "lastName": { + "kind": "string", + "default": "Doe" + }, + "question": { + "kind": "string", + "default": "What is the meaning of life?" + } + }, + "strict": true +} +` + + instance, err := prompty.FunctionToolFromJSON(jsonData) + if err != nil { + t.Fatalf("Failed to load FunctionTool from JSON helper: %v", err) + } + if instance.Kind != "function" { + t.Errorf(`Expected Kind to be "function", got %v`, instance.Kind) + } + if instance.Strict == nil || *instance.Strict != true { + t.Errorf(`Expected Strict to be true, got %v`, instance.Strict) + } +} + +// TestFunctionToolFromYAML tests loading FunctionTool through the generated YAML helper +func TestFunctionToolFromYAML(t *testing.T) { + yamlData := ` +kind: function +parameters: + firstName: + kind: string + default: Jane + lastName: + kind: string + default: Doe + question: + kind: string + default: What is the meaning of life? +strict: true + +` + + instance, err := prompty.FunctionToolFromYAML(yamlData) + if err != nil { + t.Fatalf("Failed to load FunctionTool from YAML helper: %v", err) + } + if instance.Kind != "function" { + t.Errorf(`Expected Kind to be "function", got %v`, instance.Kind) + } + if instance.Strict == nil || *instance.Strict != true { + t.Errorf(`Expected Strict to be true, got %v`, instance.Strict) + } +} + // TestFunctionToolRoundtrip tests load -> save -> load produces equivalent data func TestFunctionToolRoundtrip(t *testing.T) { jsonData := ` @@ -174,6 +240,17 @@ func TestFunctionToolToJSON(t *testing.T) { if err := json.Unmarshal([]byte(jsonOutput), &parsed); err != nil { t.Fatalf("Failed to parse generated JSON: %v", err) } + + reloaded, err := prompty.LoadFunctionTool(parsed, ctx) + if err != nil { + t.Fatalf("Failed to reload generated JSON: %v", err) + } + if reloaded.Kind != "function" { + t.Errorf(`Expected Kind to be "function", got %v`, reloaded.Kind) + } + if reloaded.Strict == nil || *reloaded.Strict != true { + t.Errorf(`Expected Strict to be true, got %v`, reloaded.Strict) + } } // TestFunctionToolToYAML tests that ToYAML produces valid YAML @@ -217,6 +294,17 @@ func TestFunctionToolToYAML(t *testing.T) { if err := yaml.Unmarshal([]byte(yamlOutput), &parsed); err != nil { t.Fatalf("Failed to parse generated YAML: %v", err) } + + reloaded, err := prompty.LoadFunctionTool(parsed, ctx) + if err != nil { + t.Fatalf("Failed to reload generated YAML: %v", err) + } + if reloaded.Kind != "function" { + t.Errorf(`Expected Kind to be "function", got %v`, reloaded.Kind) + } + if reloaded.Strict == nil || *reloaded.Strict != true { + t.Errorf(`Expected Strict to be true, got %v`, reloaded.Strict) + } } // TestFunctionToolLoadJSON1 tests loading FunctionTool from JSON @@ -260,6 +348,9 @@ func TestFunctionToolLoadJSON1(t *testing.T) { if instance.Strict == nil || *instance.Strict != true { t.Errorf(`Expected Strict to be true, got %v`, instance.Strict) } + if len(instance.Parameters) != 3 { + t.Fatalf("Expected Parameters length to be 3, got %d", len(instance.Parameters)) + } } // TestFunctionToolLoadYAML1 tests loading FunctionTool from YAML @@ -295,6 +386,83 @@ strict: true if instance.Strict == nil || *instance.Strict != true { t.Errorf(`Expected Strict to be true, got %v`, instance.Strict) } + if len(instance.Parameters) != 3 { + t.Fatalf("Expected Parameters length to be 3, got %d", len(instance.Parameters)) + } +} + +// TestFunctionToolFromJSON1 tests loading FunctionTool through the generated JSON helper +func TestFunctionToolFromJSON1(t *testing.T) { + jsonData := ` +{ + "kind": "function", + "parameters": [ + { + "name": "firstName", + "kind": "string", + "default": "Jane" + }, + { + "name": "lastName", + "kind": "string", + "default": "Doe" + }, + { + "name": "question", + "kind": "string", + "default": "What is the meaning of life?" + } + ], + "strict": true +} +` + + instance, err := prompty.FunctionToolFromJSON(jsonData) + if err != nil { + t.Fatalf("Failed to load FunctionTool from JSON helper: %v", err) + } + if instance.Kind != "function" { + t.Errorf(`Expected Kind to be "function", got %v`, instance.Kind) + } + if instance.Strict == nil || *instance.Strict != true { + t.Errorf(`Expected Strict to be true, got %v`, instance.Strict) + } + if len(instance.Parameters) != 3 { + t.Fatalf("Expected Parameters length to be 3, got %d", len(instance.Parameters)) + } +} + +// TestFunctionToolFromYAML1 tests loading FunctionTool through the generated YAML helper +func TestFunctionToolFromYAML1(t *testing.T) { + yamlData := ` +kind: function +parameters: + - name: firstName + kind: string + default: Jane + - name: lastName + kind: string + default: Doe + - name: question + kind: string + default: What is the meaning of life? +strict: true + +` + + instance, err := prompty.FunctionToolFromYAML(yamlData) + if err != nil { + t.Fatalf("Failed to load FunctionTool from YAML helper: %v", err) + } + if instance.Kind != "function" { + t.Errorf(`Expected Kind to be "function", got %v`, instance.Kind) + } + if instance.Strict == nil || *instance.Strict != true { + t.Errorf(`Expected Strict to be true, got %v`, instance.Strict) + } + if len(instance.Parameters) != 3 { + t.Fatalf("Expected Parameters length to be 3, got %d", len(instance.Parameters)) + } } // TestFunctionToolRoundtrip1 tests load -> save -> load produces equivalent data @@ -345,6 +513,9 @@ func TestFunctionToolRoundtrip1(t *testing.T) { if reloaded.Strict == nil || *reloaded.Strict != true { t.Errorf(`Expected Strict to be true, got %v`, reloaded.Strict) } + if len(reloaded.Parameters) != 3 { + t.Fatalf("Expected Parameters length to be 3, got %d", len(reloaded.Parameters)) + } } // TestFunctionToolToJSON1 tests that ToJSON produces valid JSON @@ -391,6 +562,20 @@ func TestFunctionToolToJSON1(t *testing.T) { if err := json.Unmarshal([]byte(jsonOutput), &parsed); err != nil { t.Fatalf("Failed to parse generated JSON: %v", err) } + + reloaded, err := prompty.LoadFunctionTool(parsed, ctx) + if err != nil { + t.Fatalf("Failed to reload generated JSON: %v", err) + } + if reloaded.Kind != "function" { + t.Errorf(`Expected Kind to be "function", got %v`, reloaded.Kind) + } + if reloaded.Strict == nil || *reloaded.Strict != true { + t.Errorf(`Expected Strict to be true, got %v`, reloaded.Strict) + } + if len(reloaded.Parameters) != 3 { + t.Fatalf("Expected Parameters length to be 3, got %d", len(reloaded.Parameters)) + } } // TestFunctionToolToYAML1 tests that ToYAML produces valid YAML @@ -437,4 +622,25 @@ func TestFunctionToolToYAML1(t *testing.T) { if err := yaml.Unmarshal([]byte(yamlOutput), &parsed); err != nil { t.Fatalf("Failed to parse generated YAML: %v", err) } + + reloaded, err := prompty.LoadFunctionTool(parsed, ctx) + if err != nil { + t.Fatalf("Failed to reload generated YAML: %v", err) + } + if reloaded.Kind != "function" { + t.Errorf(`Expected Kind to be "function", got %v`, reloaded.Kind) + } + if reloaded.Strict == nil || *reloaded.Strict != true { + t.Errorf(`Expected Strict to be true, got %v`, reloaded.Strict) + } + if len(reloaded.Parameters) != 3 { + t.Fatalf("Expected Parameters length to be 3, got %d", len(reloaded.Parameters)) + } +} + +// TestFunctionToolFromJSONInvalid rejects malformed JSON instead of silently defaulting +func TestFunctionToolFromJSONInvalid(t *testing.T) { + if _, err := prompty.FunctionToolFromJSON("{"); err == nil { + t.Fatalf("Expected malformed JSON to fail") + } } diff --git a/runtime/go/prompty/model/guardrail_result.go b/runtime/go/prompty/model/guardrail_result.go index c072506a..9373ec97 100644 --- a/runtime/go/prompty/model/guardrail_result.go +++ b/runtime/go/prompty/model/guardrail_result.go @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. // Group: agent package prompty @@ -41,7 +42,7 @@ func LoadGuardrailResult(data interface{}, ctx *LoadContext) (GuardrailResult, e } // Save serializes GuardrailResult to map[string]interface{} -func (obj *GuardrailResult) Save(ctx *SaveContext) map[string]interface{} { +func (obj GuardrailResult) Save(ctx *SaveContext) map[string]interface{} { result := make(map[string]interface{}) result["allowed"] = obj.Allowed if obj.Reason != nil { @@ -69,11 +70,7 @@ func (obj *GuardrailResult) ToJSON() (string, error) { func (obj *GuardrailResult) ToYAML() (string, error) { ctx := NewSaveContext() data := obj.Save(ctx) - bytes, err := yaml.Marshal(data) - if err != nil { - return "", err - } - return string(bytes), nil + return marshalYAMLDocument(data) } // FromJSON creates GuardrailResult from JSON string diff --git a/runtime/go/prompty/model/guardrail_result_test.go b/runtime/go/prompty/model/guardrail_result_test.go index 7b064d8e..cf59b9fa 100644 --- a/runtime/go/prompty/model/guardrail_result_test.go +++ b/runtime/go/prompty/model/guardrail_result_test.go @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. package prompty_test @@ -62,6 +63,47 @@ reason: Content is safe } } +// TestGuardrailResultFromJSON tests loading GuardrailResult through the generated JSON helper +func TestGuardrailResultFromJSON(t *testing.T) { + jsonData := ` +{ + "allowed": true, + "reason": "Content is safe" +} +` + + instance, err := prompty.GuardrailResultFromJSON(jsonData) + if err != nil { + t.Fatalf("Failed to load GuardrailResult from JSON helper: %v", err) + } + if instance.Allowed != true { + t.Errorf(`Expected Allowed to be true, got %v`, instance.Allowed) + } + if instance.Reason == nil || *instance.Reason != "Content is safe" { + t.Errorf(`Expected Reason to be "Content is safe", got %v`, instance.Reason) + } +} + +// TestGuardrailResultFromYAML tests loading GuardrailResult through the generated YAML helper +func TestGuardrailResultFromYAML(t *testing.T) { + yamlData := ` +allowed: true +reason: Content is safe + +` + + instance, err := prompty.GuardrailResultFromYAML(yamlData) + if err != nil { + t.Fatalf("Failed to load GuardrailResult from YAML helper: %v", err) + } + if instance.Allowed != true { + t.Errorf(`Expected Allowed to be true, got %v`, instance.Allowed) + } + if instance.Reason == nil || *instance.Reason != "Content is safe" { + t.Errorf(`Expected Reason to be "Content is safe", got %v`, instance.Reason) + } +} + // TestGuardrailResultRoundtrip tests load -> save -> load produces equivalent data func TestGuardrailResultRoundtrip(t *testing.T) { jsonData := ` @@ -122,6 +164,17 @@ func TestGuardrailResultToJSON(t *testing.T) { if err := json.Unmarshal([]byte(jsonOutput), &parsed); err != nil { t.Fatalf("Failed to parse generated JSON: %v", err) } + + reloaded, err := prompty.LoadGuardrailResult(parsed, ctx) + if err != nil { + t.Fatalf("Failed to reload generated JSON: %v", err) + } + if reloaded.Allowed != true { + t.Errorf(`Expected Allowed to be true, got %v`, reloaded.Allowed) + } + if reloaded.Reason == nil || *reloaded.Reason != "Content is safe" { + t.Errorf(`Expected Reason to be "Content is safe", got %v`, reloaded.Reason) + } } // TestGuardrailResultToYAML tests that ToYAML produces valid YAML @@ -151,4 +204,22 @@ func TestGuardrailResultToYAML(t *testing.T) { if err := yaml.Unmarshal([]byte(yamlOutput), &parsed); err != nil { t.Fatalf("Failed to parse generated YAML: %v", err) } + + reloaded, err := prompty.LoadGuardrailResult(parsed, ctx) + if err != nil { + t.Fatalf("Failed to reload generated YAML: %v", err) + } + if reloaded.Allowed != true { + t.Errorf(`Expected Allowed to be true, got %v`, reloaded.Allowed) + } + if reloaded.Reason == nil || *reloaded.Reason != "Content is safe" { + t.Errorf(`Expected Reason to be "Content is safe", got %v`, reloaded.Reason) + } +} + +// TestGuardrailResultFromJSONInvalid rejects malformed JSON instead of silently defaulting +func TestGuardrailResultFromJSONInvalid(t *testing.T) { + if _, err := prompty.GuardrailResultFromJSON("{"); err == nil { + t.Fatalf("Expected malformed JSON to fail") + } } diff --git a/runtime/go/prompty/model/harness_adapters.go b/runtime/go/prompty/model/harness_adapters.go new file mode 100644 index 00000000..bfd3e38d --- /dev/null +++ b/runtime/go/prompty/model/harness_adapters.go @@ -0,0 +1,244 @@ +package prompty + +import ( + "encoding/json" + "fmt" + "os" + "path/filepath" + "sort" + "sync" + "time" +) + +// CollectingEventSink captures emitted turn and session events in memory. +type CollectingEventSink struct { + mu sync.Mutex + TurnEvents []TurnEvent + SessionEvents []SessionEvent +} + +func (s *CollectingEventSink) EmitTurn(turnEvent TurnEvent) (bool, error) { + s.mu.Lock() + defer s.mu.Unlock() + s.TurnEvents = append(s.TurnEvents, turnEvent) + return true, nil +} + +func (s *CollectingEventSink) EmitSession(sessionEvent SessionEvent) (bool, error) { + s.mu.Lock() + defer s.mu.Unlock() + s.SessionEvents = append(s.SessionEvents, sessionEvent) + return true, nil +} + +// JsonlEventJournalWriter appends replayable event journal records as newline-delimited JSON. +type JsonlEventJournalWriter struct { + mu sync.Mutex + Path string + closed bool +} + +func NewJsonlEventJournalWriter(path string) (*JsonlEventJournalWriter, error) { + if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil { + return nil, err + } + return &JsonlEventJournalWriter{Path: path}, nil +} + +func (w *JsonlEventJournalWriter) AppendTurn(turnEvent TurnEvent) (bool, error) { + return w.write(map[string]interface{}{"kind": "turn", "event": turnEvent.Save(NewSaveContext())}) +} + +func (w *JsonlEventJournalWriter) AppendSession(sessionEvent SessionEvent) (bool, error) { + return w.write(map[string]interface{}{"kind": "session", "event": sessionEvent.Save(NewSaveContext())}) +} + +func (w *JsonlEventJournalWriter) Close(summary *SessionSummary) (bool, error) { + if summary != nil { + if ok, err := w.write(map[string]interface{}{"kind": "summary", "summary": summary.Save(NewSaveContext())}); !ok || err != nil { + return ok, err + } + } + w.mu.Lock() + w.closed = true + w.mu.Unlock() + return true, nil +} + +func (w *JsonlEventJournalWriter) write(record map[string]interface{}) (ok bool, err error) { + w.mu.Lock() + defer w.mu.Unlock() + if w.closed { + return false, fmt.Errorf("trace writer is closed") + } + file, err := os.OpenFile(w.Path, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0o644) + if err != nil { + return false, err + } + defer func() { + if closeErr := file.Close(); closeErr != nil && err == nil { + ok = false + err = closeErr + } + }() + bytes, err := json.Marshal(record) + if err != nil { + return false, err + } + if _, err := file.Write(append(bytes, '\n')); err != nil { + return false, err + } + return true, nil +} + +// InMemoryCheckpointStore stores checkpoints by session and checkpoint identifier. +type InMemoryCheckpointStore struct { + mu sync.Mutex + checkpoints map[string]Checkpoint +} + +func NewInMemoryCheckpointStore() *InMemoryCheckpointStore { + return &InMemoryCheckpointStore{checkpoints: map[string]Checkpoint{}} +} + +func (s *InMemoryCheckpointStore) Save(checkpoint Checkpoint) (Checkpoint, error) { + key, err := requireCheckpointKey(checkpoint) + if err != nil { + return Checkpoint{}, err + } + s.mu.Lock() + defer s.mu.Unlock() + s.checkpoints[key] = checkpoint + return checkpoint, nil +} + +func (s *InMemoryCheckpointStore) Load(sessionId string, checkpointId string) (*Checkpoint, error) { + s.mu.Lock() + defer s.mu.Unlock() + checkpoint, ok := s.checkpoints[checkpointKey(sessionId, checkpointId)] + if !ok { + return nil, nil + } + return &checkpoint, nil +} + +func (s *InMemoryCheckpointStore) ListCheckpoints(sessionId string) ([]Checkpoint, error) { + s.mu.Lock() + defer s.mu.Unlock() + checkpoints := []Checkpoint{} + for _, checkpoint := range s.checkpoints { + if checkpoint.SessionId != nil && *checkpoint.SessionId == sessionId { + checkpoints = append(checkpoints, checkpoint) + } + } + sort.Slice(checkpoints, func(i int, j int) bool { + left := "" + right := "" + if checkpoints[i].Id != nil { + left = *checkpoints[i].Id + } + if checkpoints[j].Id != nil { + right = *checkpoints[j].Id + } + return left < right + }) + return checkpoints, nil +} + +func checkpointKey(sessionId string, checkpointId string) string { + return sessionId + "\x00" + checkpointId +} + +func requireCheckpointKey(checkpoint Checkpoint) (string, error) { + if checkpoint.SessionId == nil || *checkpoint.SessionId == "" { + return "", fmt.Errorf("checkpoint sessionId is required") + } + if checkpoint.Id == nil || *checkpoint.Id == "" { + return "", fmt.Errorf("checkpoint id is required") + } + return checkpointKey(*checkpoint.SessionId, *checkpoint.Id), nil +} + +// AllowAllPermissionResolver resolves every permission request as approved. +type AllowAllPermissionResolver struct{} + +func (r AllowAllPermissionResolver) Request(request PermissionRequest) (PermissionDecision, error) { + reason := "allow_all" + return PermissionDecision{ + RequestId: request.RequestId, + ToolCallId: request.ToolCallId, + Permission: request.Permission, + Approved: true, + Reason: &reason, + }, nil +} + +// DenyAllPermissionResolver resolves every permission request as denied. +type DenyAllPermissionResolver struct{} + +func (r DenyAllPermissionResolver) Request(request PermissionRequest) (PermissionDecision, error) { + reason := "deny_all" + return PermissionDecision{ + RequestId: request.RequestId, + ToolCallId: request.ToolCallId, + Permission: request.Permission, + Approved: false, + Reason: &reason, + }, nil +} + +type HostToolHandler func(arguments map[string]interface{}, request HostToolRequest) (interface{}, error) + +// FunctionHostToolExecutor dispatches host tool requests to registered functions. +type FunctionHostToolExecutor struct { + Handlers map[string]HostToolHandler +} + +func (e FunctionHostToolExecutor) Execute(request HostToolRequest) (HostToolResult, error) { + started := time.Now() + handler, ok := e.Handlers[request.ToolName] + if !ok { + errorKind := "not_found" + result := interface{}(map[string]interface{}{"message": fmt.Sprintf("No host tool registered for '%s'", request.ToolName)}) + durationMs := float64(time.Since(started).Microseconds()) / 1000 + return HostToolResult{ + RequestId: request.RequestId, + ToolCallId: request.ToolCallId, + ToolName: request.ToolName, + Success: false, + Result: &result, + DurationMs: &durationMs, + ErrorKind: &errorKind, + }, nil + } + + arguments := request.Arguments + if arguments == nil { + arguments = map[string]interface{}{} + } + result, err := handler(arguments, request) + durationMs := float64(time.Since(started).Microseconds()) / 1000 + if err != nil { + errorKind := "exception" + errorResult := interface{}(map[string]interface{}{"message": err.Error()}) + return HostToolResult{ + RequestId: request.RequestId, + ToolCallId: request.ToolCallId, + ToolName: request.ToolName, + Success: false, + Result: &errorResult, + DurationMs: &durationMs, + ErrorKind: &errorKind, + }, nil + } + + resultValue := interface{}(result) + return HostToolResult{ + RequestId: request.RequestId, + ToolCallId: request.ToolCallId, + ToolName: request.ToolName, + Success: true, + Result: &resultValue, + DurationMs: &durationMs, + }, nil +} diff --git a/runtime/go/prompty/model/harness_adapters_test.go b/runtime/go/prompty/model/harness_adapters_test.go new file mode 100644 index 00000000..e845bc2c --- /dev/null +++ b/runtime/go/prompty/model/harness_adapters_test.go @@ -0,0 +1,240 @@ +package prompty + +import ( + "encoding/json" + "fmt" + "os" + "path/filepath" + "strings" + "testing" +) + +func testTurnEvent() TurnEvent { + return TurnEvent{ + Id: "turn-event", + Type: TurnEventTypeTurnStart, + Timestamp: "2026-06-10T00:00:00Z", + Payload: map[string]interface{}{"phase": "start"}, + } +} + +func testSessionEvent() SessionEvent { + sessionId := "session-1" + return SessionEvent{ + Id: "session-event", + Type: SessionEventTypeSessionStart, + Timestamp: "2026-06-10T00:00:00Z", + SessionId: &sessionId, + Payload: map[string]interface{}{"phase": "start"}, + } +} + +func TestCollectingEventSink(t *testing.T) { + sink := &CollectingEventSink{} + + if ok, err := sink.EmitTurn(testTurnEvent()); !ok || err != nil { + t.Fatalf("EmitTurn failed: %v", err) + } + if ok, err := sink.EmitSession(testSessionEvent()); !ok || err != nil { + t.Fatalf("EmitSession failed: %v", err) + } + if sink.TurnEvents[0].Id != "turn-event" { + t.Fatalf("unexpected turn event: %#v", sink.TurnEvents[0]) + } + if sink.SessionEvents[0].Id != "session-event" { + t.Fatalf("unexpected session event: %#v", sink.SessionEvents[0]) + } +} + +func TestJsonlEventJournalWriter(t *testing.T) { + path := filepath.Join(t.TempDir(), "trace.jsonl") + writer, err := NewJsonlEventJournalWriter(path) + if err != nil { + t.Fatal(err) + } + + if ok, err := writer.AppendTurn(testTurnEvent()); !ok || err != nil { + t.Fatalf("AppendTurn failed: %v", err) + } + if ok, err := writer.AppendSession(testSessionEvent()); !ok || err != nil { + t.Fatalf("AppendSession failed: %v", err) + } + if ok, err := writer.Close(&SessionSummary{SessionId: "session-1"}); !ok || err != nil { + t.Fatalf("Close failed: %v", err) + } + + content, err := os.ReadFile(path) + if err != nil { + t.Fatal(err) + } + lines := splitLines(string(content)) + if len(lines) != 3 { + t.Fatalf("expected 3 lines, got %d", len(lines)) + } + var records []map[string]interface{} + for _, line := range lines { + var record map[string]interface{} + if err := json.Unmarshal([]byte(line), &record); err != nil { + t.Fatal(err) + } + records = append(records, record) + } + if records[0]["kind"] != "turn" || records[1]["kind"] != "session" || records[2]["kind"] != "summary" { + t.Fatalf("unexpected record kinds: %#v", records) + } +} + +func TestJsonlEventJournalWriterAfterClose(t *testing.T) { + path := filepath.Join(t.TempDir(), "trace.jsonl") + writer, err := NewJsonlEventJournalWriter(path) + if err != nil { + t.Fatal(err) + } + + if ok, err := writer.Close(nil); !ok || err != nil { + t.Fatalf("Close failed: %v", err) + } + if ok, err := writer.AppendTurn(testTurnEvent()); ok || err == nil { + t.Fatalf("expected closed writer failure, ok=%v err=%v", ok, err) + } +} + +func TestInMemoryCheckpointStore(t *testing.T) { + store := NewInMemoryCheckpointStore() + sessionId := "session-1" + checkpointId := "checkpoint-1" + checkpoint := Checkpoint{Id: &checkpointId, SessionId: &sessionId, Title: "First"} + + saved, err := store.Save(checkpoint) + if err != nil { + t.Fatal(err) + } + if saved.Title != "First" { + t.Fatalf("unexpected checkpoint: %#v", saved) + } + loaded, err := store.Load("session-1", "checkpoint-1") + if err != nil { + t.Fatal(err) + } + if loaded == nil || loaded.Title != "First" { + t.Fatalf("unexpected loaded checkpoint: %#v", loaded) + } + missing, err := store.Load("session-1", "missing") + if err != nil { + t.Fatal(err) + } + if missing != nil { + t.Fatalf("expected nil missing checkpoint, got %#v", missing) + } + listed, err := store.ListCheckpoints("session-1") + if err != nil { + t.Fatal(err) + } + if len(listed) != 1 { + t.Fatalf("expected one checkpoint, got %d", len(listed)) + } +} + +func TestInMemoryCheckpointStoreRequiresKeys(t *testing.T) { + store := NewInMemoryCheckpointStore() + checkpointId := "checkpoint-1" + sessionId := "session-1" + + if _, err := store.Save(Checkpoint{Id: &checkpointId}); err == nil { + t.Fatal("expected missing sessionId error") + } + if _, err := store.Save(Checkpoint{SessionId: &sessionId}); err == nil { + t.Fatal("expected missing id error") + } +} + +func TestPermissionResolvers(t *testing.T) { + requestId := "permission-1" + toolCallId := "tool-call-1" + request := PermissionRequest{RequestId: &requestId, ToolCallId: &toolCallId, Permission: "tool.execute"} + + allow, err := AllowAllPermissionResolver{}.Request(request) + if err != nil { + t.Fatal(err) + } + deny, err := DenyAllPermissionResolver{}.Request(request) + if err != nil { + t.Fatal(err) + } + if !allow.Approved || *allow.Reason != "allow_all" || *allow.RequestId != "permission-1" { + t.Fatalf("unexpected allow decision: %#v", allow) + } + if deny.Approved || *deny.Reason != "deny_all" { + t.Fatalf("unexpected deny decision: %#v", deny) + } +} + +func TestFunctionHostToolExecutor(t *testing.T) { + requestId := "exec-1" + executor := FunctionHostToolExecutor{Handlers: map[string]HostToolHandler{ + "add": func(arguments map[string]interface{}, request HostToolRequest) (interface{}, error) { + return int(arguments["a"].(int)) + int(arguments["b"].(int)), nil + }, + }} + + result, err := executor.Execute(HostToolRequest{ + RequestId: &requestId, + ToolName: "add", + Arguments: map[string]interface{}{"a": 2, "b": 3}, + }) + if err != nil { + t.Fatal(err) + } + if !result.Success || *result.Result != 5 { + t.Fatalf("unexpected success result: %#v", result) + } +} + +func TestFunctionHostToolExecutorEmptyArguments(t *testing.T) { + executor := FunctionHostToolExecutor{Handlers: map[string]HostToolHandler{ + "count": func(arguments map[string]interface{}, request HostToolRequest) (interface{}, error) { + return len(arguments), nil + }, + }} + + result, err := executor.Execute(HostToolRequest{ToolName: "count"}) + if err != nil { + t.Fatal(err) + } + if !result.Success || *result.Result != 0 { + t.Fatalf("unexpected success result: %#v", result) + } +} + +func TestFunctionHostToolExecutorFailures(t *testing.T) { + executor := FunctionHostToolExecutor{Handlers: map[string]HostToolHandler{ + "fail": func(arguments map[string]interface{}, request HostToolRequest) (interface{}, error) { + return nil, fmt.Errorf("boom") + }, + }} + + missing, err := executor.Execute(HostToolRequest{ToolName: "missing"}) + if err != nil { + t.Fatal(err) + } + thrown, err := executor.Execute(HostToolRequest{ToolName: "fail"}) + if err != nil { + t.Fatal(err) + } + if missing.Success || *missing.ErrorKind != "not_found" { + t.Fatalf("unexpected missing result: %#v", missing) + } + if thrown.Success || *thrown.ErrorKind != "exception" { + t.Fatalf("unexpected thrown result: %#v", thrown) + } +} + +func splitLines(content string) []string { + var lines []string + for _, line := range strings.Split(content, "\n") { + if line != "" { + lines = append(lines, line) + } + } + return lines +} diff --git a/runtime/go/prompty/model/harness_context.go b/runtime/go/prompty/model/harness_context.go new file mode 100644 index 00000000..adc63ec7 --- /dev/null +++ b/runtime/go/prompty/model/harness_context.go @@ -0,0 +1,99 @@ +// +// Code generated by Typra emitter; DO NOT EDIT. +// Group: events + +package prompty + +import ( + "encoding/json" + + "gopkg.in/yaml.v3" +) + +// HarnessContext represents Execution context associated with a harness session. Host-specific +// environments can store detailed profiles in metadata without making the core +// contract depend on one source-control provider or workspace shape. + +type HarnessContext struct { + Cwd *string `json:"cwd,omitempty" yaml:"cwd,omitempty"` + GitRoot *string `json:"gitRoot,omitempty" yaml:"gitRoot,omitempty"` + Metadata map[string]interface{} `json:"metadata,omitempty" yaml:"metadata,omitempty"` +} + +// LoadHarnessContext creates a HarnessContext from a map[string]interface{} +func LoadHarnessContext(data interface{}, ctx *LoadContext) (HarnessContext, error) { + result := HarnessContext{} + + // Load from map + if m, ok := data.(map[string]interface{}); ok { + if val, ok := m["cwd"]; ok && val != nil { + v := string(val.(string)) + result.Cwd = &v + } + if val, ok := m["gitRoot"]; ok && val != nil { + v := string(val.(string)) + result.GitRoot = &v + } + if val, ok := m["metadata"]; ok && val != nil { + if m, ok := val.(map[string]interface{}); ok { + result.Metadata = m + } + } + } + + return result, nil +} + +// Save serializes HarnessContext to map[string]interface{} +func (obj HarnessContext) Save(ctx *SaveContext) map[string]interface{} { + result := make(map[string]interface{}) + if obj.Cwd != nil { + result["cwd"] = *obj.Cwd + } + if obj.GitRoot != nil { + result["gitRoot"] = *obj.GitRoot + } + if obj.Metadata != nil { + result["metadata"] = obj.Metadata + } + + return result +} + +// ToJSON serializes HarnessContext to JSON string +func (obj *HarnessContext) ToJSON() (string, error) { + ctx := NewSaveContext() + data := obj.Save(ctx) + bytes, err := json.Marshal(data) + if err != nil { + return "", err + } + return string(bytes), nil +} + +// ToYAML serializes HarnessContext to YAML string +func (obj *HarnessContext) ToYAML() (string, error) { + ctx := NewSaveContext() + data := obj.Save(ctx) + return marshalYAMLDocument(data) +} + +// FromJSON creates HarnessContext from JSON string +func HarnessContextFromJSON(jsonStr string) (HarnessContext, error) { + var data map[string]interface{} + if err := json.Unmarshal([]byte(jsonStr), &data); err != nil { + return HarnessContext{}, err + } + ctx := NewLoadContext() + return LoadHarnessContext(data, ctx) +} + +// FromYAML creates HarnessContext from YAML string +func HarnessContextFromYAML(yamlStr string) (HarnessContext, error) { + var data map[string]interface{} + if err := yaml.Unmarshal([]byte(yamlStr), &data); err != nil { + return HarnessContext{}, err + } + ctx := NewLoadContext() + return LoadHarnessContext(data, ctx) +} diff --git a/runtime/go/prompty/model/harness_context_test.go b/runtime/go/prompty/model/harness_context_test.go new file mode 100644 index 00000000..ad293fa5 --- /dev/null +++ b/runtime/go/prompty/model/harness_context_test.go @@ -0,0 +1,225 @@ +// +// Code generated by Typra emitter; DO NOT EDIT. + +package prompty_test + +import ( + "encoding/json" + "testing" + + "gopkg.in/yaml.v3" + + "prompty/model" +) + +// TestHarnessContextLoadJSON tests loading HarnessContext from JSON +func TestHarnessContextLoadJSON(t *testing.T) { + jsonData := ` +{ + "cwd": "/workspace/project", + "gitRoot": "/workspace/project" +} +` + var data map[string]interface{} + if err := json.Unmarshal([]byte(jsonData), &data); err != nil { + t.Fatalf("Failed to parse JSON: %v", err) + } + + ctx := prompty.NewLoadContext() + instance, err := prompty.LoadHarnessContext(data, ctx) + if err != nil { + t.Fatalf("Failed to load HarnessContext: %v", err) + } + if instance.Cwd == nil || *instance.Cwd != "/workspace/project" { + t.Errorf(`Expected Cwd to be "/workspace/project", got %v`, instance.Cwd) + } + if instance.GitRoot == nil || *instance.GitRoot != "/workspace/project" { + t.Errorf(`Expected GitRoot to be "/workspace/project", got %v`, instance.GitRoot) + } +} + +// TestHarnessContextLoadYAML tests loading HarnessContext from YAML +func TestHarnessContextLoadYAML(t *testing.T) { + yamlData := ` +cwd: /workspace/project +gitRoot: /workspace/project + +` + var data map[string]interface{} + if err := yaml.Unmarshal([]byte(yamlData), &data); err != nil { + t.Fatalf("Failed to parse YAML: %v", err) + } + + ctx := prompty.NewLoadContext() + instance, err := prompty.LoadHarnessContext(data, ctx) + if err != nil { + t.Fatalf("Failed to load HarnessContext: %v", err) + } + if instance.Cwd == nil || *instance.Cwd != "/workspace/project" { + t.Errorf(`Expected Cwd to be "/workspace/project", got %v`, instance.Cwd) + } + if instance.GitRoot == nil || *instance.GitRoot != "/workspace/project" { + t.Errorf(`Expected GitRoot to be "/workspace/project", got %v`, instance.GitRoot) + } +} + +// TestHarnessContextFromJSON tests loading HarnessContext through the generated JSON helper +func TestHarnessContextFromJSON(t *testing.T) { + jsonData := ` +{ + "cwd": "/workspace/project", + "gitRoot": "/workspace/project" +} +` + + instance, err := prompty.HarnessContextFromJSON(jsonData) + if err != nil { + t.Fatalf("Failed to load HarnessContext from JSON helper: %v", err) + } + if instance.Cwd == nil || *instance.Cwd != "/workspace/project" { + t.Errorf(`Expected Cwd to be "/workspace/project", got %v`, instance.Cwd) + } + if instance.GitRoot == nil || *instance.GitRoot != "/workspace/project" { + t.Errorf(`Expected GitRoot to be "/workspace/project", got %v`, instance.GitRoot) + } +} + +// TestHarnessContextFromYAML tests loading HarnessContext through the generated YAML helper +func TestHarnessContextFromYAML(t *testing.T) { + yamlData := ` +cwd: /workspace/project +gitRoot: /workspace/project + +` + + instance, err := prompty.HarnessContextFromYAML(yamlData) + if err != nil { + t.Fatalf("Failed to load HarnessContext from YAML helper: %v", err) + } + if instance.Cwd == nil || *instance.Cwd != "/workspace/project" { + t.Errorf(`Expected Cwd to be "/workspace/project", got %v`, instance.Cwd) + } + if instance.GitRoot == nil || *instance.GitRoot != "/workspace/project" { + t.Errorf(`Expected GitRoot to be "/workspace/project", got %v`, instance.GitRoot) + } +} + +// TestHarnessContextRoundtrip tests load -> save -> load produces equivalent data +func TestHarnessContextRoundtrip(t *testing.T) { + jsonData := ` +{ + "cwd": "/workspace/project", + "gitRoot": "/workspace/project" +} +` + var data map[string]interface{} + if err := json.Unmarshal([]byte(jsonData), &data); err != nil { + t.Fatalf("Failed to parse JSON: %v", err) + } + + loadCtx := prompty.NewLoadContext() + instance, err := prompty.LoadHarnessContext(data, loadCtx) + if err != nil { + t.Fatalf("Failed to load HarnessContext: %v", err) + } + saveCtx := prompty.NewSaveContext() + savedData := instance.Save(saveCtx) + + reloaded, err := prompty.LoadHarnessContext(savedData, loadCtx) + if err != nil { + t.Fatalf("Failed to reload HarnessContext: %v", err) + } + if reloaded.Cwd == nil || *reloaded.Cwd != "/workspace/project" { + t.Errorf(`Expected Cwd to be "/workspace/project", got %v`, reloaded.Cwd) + } + if reloaded.GitRoot == nil || *reloaded.GitRoot != "/workspace/project" { + t.Errorf(`Expected GitRoot to be "/workspace/project", got %v`, reloaded.GitRoot) + } +} + +// TestHarnessContextToJSON tests that ToJSON produces valid JSON +func TestHarnessContextToJSON(t *testing.T) { + jsonData := ` +{ + "cwd": "/workspace/project", + "gitRoot": "/workspace/project" +} +` + var data map[string]interface{} + if err := json.Unmarshal([]byte(jsonData), &data); err != nil { + t.Fatalf("Failed to parse JSON: %v", err) + } + + ctx := prompty.NewLoadContext() + instance, err := prompty.LoadHarnessContext(data, ctx) + if err != nil { + t.Fatalf("Failed to load HarnessContext: %v", err) + } + jsonOutput, err := instance.ToJSON() + if err != nil { + t.Fatalf("Failed to convert to JSON: %v", err) + } + + var parsed map[string]interface{} + if err := json.Unmarshal([]byte(jsonOutput), &parsed); err != nil { + t.Fatalf("Failed to parse generated JSON: %v", err) + } + + reloaded, err := prompty.LoadHarnessContext(parsed, ctx) + if err != nil { + t.Fatalf("Failed to reload generated JSON: %v", err) + } + if reloaded.Cwd == nil || *reloaded.Cwd != "/workspace/project" { + t.Errorf(`Expected Cwd to be "/workspace/project", got %v`, reloaded.Cwd) + } + if reloaded.GitRoot == nil || *reloaded.GitRoot != "/workspace/project" { + t.Errorf(`Expected GitRoot to be "/workspace/project", got %v`, reloaded.GitRoot) + } +} + +// TestHarnessContextToYAML tests that ToYAML produces valid YAML +func TestHarnessContextToYAML(t *testing.T) { + jsonData := ` +{ + "cwd": "/workspace/project", + "gitRoot": "/workspace/project" +} +` + var data map[string]interface{} + if err := json.Unmarshal([]byte(jsonData), &data); err != nil { + t.Fatalf("Failed to parse JSON: %v", err) + } + + ctx := prompty.NewLoadContext() + instance, err := prompty.LoadHarnessContext(data, ctx) + if err != nil { + t.Fatalf("Failed to load HarnessContext: %v", err) + } + yamlOutput, err := instance.ToYAML() + if err != nil { + t.Fatalf("Failed to convert to YAML: %v", err) + } + + var parsed map[string]interface{} + if err := yaml.Unmarshal([]byte(yamlOutput), &parsed); err != nil { + t.Fatalf("Failed to parse generated YAML: %v", err) + } + + reloaded, err := prompty.LoadHarnessContext(parsed, ctx) + if err != nil { + t.Fatalf("Failed to reload generated YAML: %v", err) + } + if reloaded.Cwd == nil || *reloaded.Cwd != "/workspace/project" { + t.Errorf(`Expected Cwd to be "/workspace/project", got %v`, reloaded.Cwd) + } + if reloaded.GitRoot == nil || *reloaded.GitRoot != "/workspace/project" { + t.Errorf(`Expected GitRoot to be "/workspace/project", got %v`, reloaded.GitRoot) + } +} + +// TestHarnessContextFromJSONInvalid rejects malformed JSON instead of silently defaulting +func TestHarnessContextFromJSONInvalid(t *testing.T) { + if _, err := prompty.HarnessContextFromJSON("{"); err == nil { + t.Fatalf("Expected malformed JSON to fail") + } +} diff --git a/runtime/go/prompty/model/hook_end_payload.go b/runtime/go/prompty/model/hook_end_payload.go new file mode 100644 index 00000000..f7eee827 --- /dev/null +++ b/runtime/go/prompty/model/hook_end_payload.go @@ -0,0 +1,150 @@ +// +// Code generated by Typra emitter; DO NOT EDIT. +// Group: events + +package prompty + +import ( + "encoding/json" + + "gopkg.in/yaml.v3" +) + +// HookEndScope represents the allowed values for HookEndScope. +type HookEndScope string + +const ( + HookEndScopeTurn HookEndScope = "turn" + HookEndScopeSession HookEndScope = "session" +) + +// HookEndPayload represents Payload for "hook_end" events — a host lifecycle hook finished. + +type HookEndPayload struct { + HookInvocationId string `json:"hookInvocationId" yaml:"hookInvocationId"` + HookType string `json:"hookType" yaml:"hookType"` + Scope *HookEndScope `json:"scope,omitempty" yaml:"scope,omitempty"` + Success bool `json:"success" yaml:"success"` + Output map[string]interface{} `json:"output,omitempty" yaml:"output,omitempty"` + DurationMs *float64 `json:"durationMs,omitempty" yaml:"durationMs,omitempty"` + Error *string `json:"error,omitempty" yaml:"error,omitempty"` + Redaction *RedactionMetadata `json:"redaction,omitempty" yaml:"redaction,omitempty"` +} + +// LoadHookEndPayload creates a HookEndPayload from a map[string]interface{} +func LoadHookEndPayload(data interface{}, ctx *LoadContext) (HookEndPayload, error) { + result := HookEndPayload{} + + // Load from map + if m, ok := data.(map[string]interface{}); ok { + if val, ok := m["hookInvocationId"]; ok && val != nil { + result.HookInvocationId = string(val.(string)) + } + if val, ok := m["hookType"]; ok && val != nil { + result.HookType = string(val.(string)) + } + if val, ok := m["scope"]; ok && val != nil { + v := HookEndScope(val.(string)) + result.Scope = &v + } + if val, ok := m["success"]; ok && val != nil { + result.Success = val.(bool) + } + if val, ok := m["output"]; ok && val != nil { + if m, ok := val.(map[string]interface{}); ok { + result.Output = m + } + } + if val, ok := m["durationMs"]; ok && val != nil { // Handle various numeric types from JSON/YAML/roundtrip + var v float64 + switch n := val.(type) { + case int: + v = float64(n) + case int32: + v = float64(n) + case int64: + v = float64(n) + case float32: + v = float64(n) + case float64: + v = n + } + result.DurationMs = &v + } + if val, ok := m["error"]; ok && val != nil { + v := string(val.(string)) + result.Error = &v + } + if val, ok := m["redaction"]; ok && val != nil { + if m, ok := val.(map[string]interface{}); ok { + loaded, _ := LoadRedactionMetadata(m, ctx) + result.Redaction = &loaded + } + } + } + + return result, nil +} + +// Save serializes HookEndPayload to map[string]interface{} +func (obj HookEndPayload) Save(ctx *SaveContext) map[string]interface{} { + result := make(map[string]interface{}) + result["hookInvocationId"] = obj.HookInvocationId + result["hookType"] = obj.HookType + if obj.Scope != nil { + result["scope"] = string(*obj.Scope) + } + result["success"] = obj.Success + if obj.Output != nil { + result["output"] = obj.Output + } + if obj.DurationMs != nil { + result["durationMs"] = *obj.DurationMs + } + if obj.Error != nil { + result["error"] = *obj.Error + } + if obj.Redaction != nil { + result["redaction"] = obj.Redaction.Save(ctx) + } + + return result +} + +// ToJSON serializes HookEndPayload to JSON string +func (obj *HookEndPayload) ToJSON() (string, error) { + ctx := NewSaveContext() + data := obj.Save(ctx) + bytes, err := json.Marshal(data) + if err != nil { + return "", err + } + return string(bytes), nil +} + +// ToYAML serializes HookEndPayload to YAML string +func (obj *HookEndPayload) ToYAML() (string, error) { + ctx := NewSaveContext() + data := obj.Save(ctx) + return marshalYAMLDocument(data) +} + +// FromJSON creates HookEndPayload from JSON string +func HookEndPayloadFromJSON(jsonStr string) (HookEndPayload, error) { + var data map[string]interface{} + if err := json.Unmarshal([]byte(jsonStr), &data); err != nil { + return HookEndPayload{}, err + } + ctx := NewLoadContext() + return LoadHookEndPayload(data, ctx) +} + +// FromYAML creates HookEndPayload from YAML string +func HookEndPayloadFromYAML(yamlStr string) (HookEndPayload, error) { + var data map[string]interface{} + if err := yaml.Unmarshal([]byte(yamlStr), &data); err != nil { + return HookEndPayload{}, err + } + ctx := NewLoadContext() + return LoadHookEndPayload(data, ctx) +} diff --git a/runtime/go/prompty/model/hook_end_payload_test.go b/runtime/go/prompty/model/hook_end_payload_test.go new file mode 100644 index 00000000..d3d4fa1d --- /dev/null +++ b/runtime/go/prompty/model/hook_end_payload_test.go @@ -0,0 +1,309 @@ +// +// Code generated by Typra emitter; DO NOT EDIT. + +package prompty_test + +import ( + "encoding/json" + "testing" + + "gopkg.in/yaml.v3" + + "prompty/model" +) + +// TestHookEndPayloadLoadJSON tests loading HookEndPayload from JSON +func TestHookEndPayloadLoadJSON(t *testing.T) { + jsonData := ` +{ + "hookInvocationId": "hook_abc123", + "hookType": "preToolUse", + "success": true, + "durationMs": 12, + "error": "hook failed" +} +` + var data map[string]interface{} + if err := json.Unmarshal([]byte(jsonData), &data); err != nil { + t.Fatalf("Failed to parse JSON: %v", err) + } + + ctx := prompty.NewLoadContext() + instance, err := prompty.LoadHookEndPayload(data, ctx) + if err != nil { + t.Fatalf("Failed to load HookEndPayload: %v", err) + } + if instance.HookInvocationId != "hook_abc123" { + t.Errorf(`Expected HookInvocationId to be "hook_abc123", got %v`, instance.HookInvocationId) + } + if instance.HookType != "preToolUse" { + t.Errorf(`Expected HookType to be "preToolUse", got %v`, instance.HookType) + } + if instance.Success != true { + t.Errorf(`Expected Success to be true, got %v`, instance.Success) + } + if instance.DurationMs == nil || *instance.DurationMs != 12 { + t.Errorf(`Expected DurationMs to be 12, got %v`, instance.DurationMs) + } + if instance.Error == nil || *instance.Error != "hook failed" { + t.Errorf(`Expected Error to be "hook failed", got %v`, instance.Error) + } +} + +// TestHookEndPayloadLoadYAML tests loading HookEndPayload from YAML +func TestHookEndPayloadLoadYAML(t *testing.T) { + yamlData := ` +hookInvocationId: hook_abc123 +hookType: preToolUse +success: true +durationMs: 12 +error: hook failed + +` + var data map[string]interface{} + if err := yaml.Unmarshal([]byte(yamlData), &data); err != nil { + t.Fatalf("Failed to parse YAML: %v", err) + } + + ctx := prompty.NewLoadContext() + instance, err := prompty.LoadHookEndPayload(data, ctx) + if err != nil { + t.Fatalf("Failed to load HookEndPayload: %v", err) + } + if instance.HookInvocationId != "hook_abc123" { + t.Errorf(`Expected HookInvocationId to be "hook_abc123", got %v`, instance.HookInvocationId) + } + if instance.HookType != "preToolUse" { + t.Errorf(`Expected HookType to be "preToolUse", got %v`, instance.HookType) + } + if instance.Success != true { + t.Errorf(`Expected Success to be true, got %v`, instance.Success) + } + if instance.DurationMs == nil || *instance.DurationMs != 12 { + t.Errorf(`Expected DurationMs to be 12, got %v`, instance.DurationMs) + } + if instance.Error == nil || *instance.Error != "hook failed" { + t.Errorf(`Expected Error to be "hook failed", got %v`, instance.Error) + } +} + +// TestHookEndPayloadFromJSON tests loading HookEndPayload through the generated JSON helper +func TestHookEndPayloadFromJSON(t *testing.T) { + jsonData := ` +{ + "hookInvocationId": "hook_abc123", + "hookType": "preToolUse", + "success": true, + "durationMs": 12, + "error": "hook failed" +} +` + + instance, err := prompty.HookEndPayloadFromJSON(jsonData) + if err != nil { + t.Fatalf("Failed to load HookEndPayload from JSON helper: %v", err) + } + if instance.HookInvocationId != "hook_abc123" { + t.Errorf(`Expected HookInvocationId to be "hook_abc123", got %v`, instance.HookInvocationId) + } + if instance.HookType != "preToolUse" { + t.Errorf(`Expected HookType to be "preToolUse", got %v`, instance.HookType) + } + if instance.Success != true { + t.Errorf(`Expected Success to be true, got %v`, instance.Success) + } + if instance.DurationMs == nil || *instance.DurationMs != 12 { + t.Errorf(`Expected DurationMs to be 12, got %v`, instance.DurationMs) + } + if instance.Error == nil || *instance.Error != "hook failed" { + t.Errorf(`Expected Error to be "hook failed", got %v`, instance.Error) + } +} + +// TestHookEndPayloadFromYAML tests loading HookEndPayload through the generated YAML helper +func TestHookEndPayloadFromYAML(t *testing.T) { + yamlData := ` +hookInvocationId: hook_abc123 +hookType: preToolUse +success: true +durationMs: 12 +error: hook failed + +` + + instance, err := prompty.HookEndPayloadFromYAML(yamlData) + if err != nil { + t.Fatalf("Failed to load HookEndPayload from YAML helper: %v", err) + } + if instance.HookInvocationId != "hook_abc123" { + t.Errorf(`Expected HookInvocationId to be "hook_abc123", got %v`, instance.HookInvocationId) + } + if instance.HookType != "preToolUse" { + t.Errorf(`Expected HookType to be "preToolUse", got %v`, instance.HookType) + } + if instance.Success != true { + t.Errorf(`Expected Success to be true, got %v`, instance.Success) + } + if instance.DurationMs == nil || *instance.DurationMs != 12 { + t.Errorf(`Expected DurationMs to be 12, got %v`, instance.DurationMs) + } + if instance.Error == nil || *instance.Error != "hook failed" { + t.Errorf(`Expected Error to be "hook failed", got %v`, instance.Error) + } +} + +// TestHookEndPayloadRoundtrip tests load -> save -> load produces equivalent data +func TestHookEndPayloadRoundtrip(t *testing.T) { + jsonData := ` +{ + "hookInvocationId": "hook_abc123", + "hookType": "preToolUse", + "success": true, + "durationMs": 12, + "error": "hook failed" +} +` + var data map[string]interface{} + if err := json.Unmarshal([]byte(jsonData), &data); err != nil { + t.Fatalf("Failed to parse JSON: %v", err) + } + + loadCtx := prompty.NewLoadContext() + instance, err := prompty.LoadHookEndPayload(data, loadCtx) + if err != nil { + t.Fatalf("Failed to load HookEndPayload: %v", err) + } + saveCtx := prompty.NewSaveContext() + savedData := instance.Save(saveCtx) + + reloaded, err := prompty.LoadHookEndPayload(savedData, loadCtx) + if err != nil { + t.Fatalf("Failed to reload HookEndPayload: %v", err) + } + if reloaded.HookInvocationId != "hook_abc123" { + t.Errorf(`Expected HookInvocationId to be "hook_abc123", got %v`, reloaded.HookInvocationId) + } + if reloaded.HookType != "preToolUse" { + t.Errorf(`Expected HookType to be "preToolUse", got %v`, reloaded.HookType) + } + if reloaded.Success != true { + t.Errorf(`Expected Success to be true, got %v`, reloaded.Success) + } + if reloaded.DurationMs == nil || *reloaded.DurationMs != 12 { + t.Errorf(`Expected DurationMs to be 12, got %v`, reloaded.DurationMs) + } + if reloaded.Error == nil || *reloaded.Error != "hook failed" { + t.Errorf(`Expected Error to be "hook failed", got %v`, reloaded.Error) + } +} + +// TestHookEndPayloadToJSON tests that ToJSON produces valid JSON +func TestHookEndPayloadToJSON(t *testing.T) { + jsonData := ` +{ + "hookInvocationId": "hook_abc123", + "hookType": "preToolUse", + "success": true, + "durationMs": 12, + "error": "hook failed" +} +` + var data map[string]interface{} + if err := json.Unmarshal([]byte(jsonData), &data); err != nil { + t.Fatalf("Failed to parse JSON: %v", err) + } + + ctx := prompty.NewLoadContext() + instance, err := prompty.LoadHookEndPayload(data, ctx) + if err != nil { + t.Fatalf("Failed to load HookEndPayload: %v", err) + } + jsonOutput, err := instance.ToJSON() + if err != nil { + t.Fatalf("Failed to convert to JSON: %v", err) + } + + var parsed map[string]interface{} + if err := json.Unmarshal([]byte(jsonOutput), &parsed); err != nil { + t.Fatalf("Failed to parse generated JSON: %v", err) + } + + reloaded, err := prompty.LoadHookEndPayload(parsed, ctx) + if err != nil { + t.Fatalf("Failed to reload generated JSON: %v", err) + } + if reloaded.HookInvocationId != "hook_abc123" { + t.Errorf(`Expected HookInvocationId to be "hook_abc123", got %v`, reloaded.HookInvocationId) + } + if reloaded.HookType != "preToolUse" { + t.Errorf(`Expected HookType to be "preToolUse", got %v`, reloaded.HookType) + } + if reloaded.Success != true { + t.Errorf(`Expected Success to be true, got %v`, reloaded.Success) + } + if reloaded.DurationMs == nil || *reloaded.DurationMs != 12 { + t.Errorf(`Expected DurationMs to be 12, got %v`, reloaded.DurationMs) + } + if reloaded.Error == nil || *reloaded.Error != "hook failed" { + t.Errorf(`Expected Error to be "hook failed", got %v`, reloaded.Error) + } +} + +// TestHookEndPayloadToYAML tests that ToYAML produces valid YAML +func TestHookEndPayloadToYAML(t *testing.T) { + jsonData := ` +{ + "hookInvocationId": "hook_abc123", + "hookType": "preToolUse", + "success": true, + "durationMs": 12, + "error": "hook failed" +} +` + var data map[string]interface{} + if err := json.Unmarshal([]byte(jsonData), &data); err != nil { + t.Fatalf("Failed to parse JSON: %v", err) + } + + ctx := prompty.NewLoadContext() + instance, err := prompty.LoadHookEndPayload(data, ctx) + if err != nil { + t.Fatalf("Failed to load HookEndPayload: %v", err) + } + yamlOutput, err := instance.ToYAML() + if err != nil { + t.Fatalf("Failed to convert to YAML: %v", err) + } + + var parsed map[string]interface{} + if err := yaml.Unmarshal([]byte(yamlOutput), &parsed); err != nil { + t.Fatalf("Failed to parse generated YAML: %v", err) + } + + reloaded, err := prompty.LoadHookEndPayload(parsed, ctx) + if err != nil { + t.Fatalf("Failed to reload generated YAML: %v", err) + } + if reloaded.HookInvocationId != "hook_abc123" { + t.Errorf(`Expected HookInvocationId to be "hook_abc123", got %v`, reloaded.HookInvocationId) + } + if reloaded.HookType != "preToolUse" { + t.Errorf(`Expected HookType to be "preToolUse", got %v`, reloaded.HookType) + } + if reloaded.Success != true { + t.Errorf(`Expected Success to be true, got %v`, reloaded.Success) + } + if reloaded.DurationMs == nil || *reloaded.DurationMs != 12 { + t.Errorf(`Expected DurationMs to be 12, got %v`, reloaded.DurationMs) + } + if reloaded.Error == nil || *reloaded.Error != "hook failed" { + t.Errorf(`Expected Error to be "hook failed", got %v`, reloaded.Error) + } +} + +// TestHookEndPayloadFromJSONInvalid rejects malformed JSON instead of silently defaulting +func TestHookEndPayloadFromJSONInvalid(t *testing.T) { + if _, err := prompty.HookEndPayloadFromJSON("{"); err == nil { + t.Fatalf("Expected malformed JSON to fail") + } +} diff --git a/runtime/go/prompty/model/hook_start_payload.go b/runtime/go/prompty/model/hook_start_payload.go new file mode 100644 index 00000000..03559e20 --- /dev/null +++ b/runtime/go/prompty/model/hook_start_payload.go @@ -0,0 +1,117 @@ +// +// Code generated by Typra emitter; DO NOT EDIT. +// Group: events + +package prompty + +import ( + "encoding/json" + + "gopkg.in/yaml.v3" +) + +// HookStartScope represents the allowed values for HookStartScope. +type HookStartScope string + +const ( + HookStartScopeTurn HookStartScope = "turn" + HookStartScopeSession HookStartScope = "session" +) + +// HookStartPayload represents Payload for "hook_start" events — a host lifecycle hook is beginning. + +type HookStartPayload struct { + HookInvocationId string `json:"hookInvocationId" yaml:"hookInvocationId"` + HookType string `json:"hookType" yaml:"hookType"` + Scope *HookStartScope `json:"scope,omitempty" yaml:"scope,omitempty"` + Input map[string]interface{} `json:"input,omitempty" yaml:"input,omitempty"` + Redaction *RedactionMetadata `json:"redaction,omitempty" yaml:"redaction,omitempty"` +} + +// LoadHookStartPayload creates a HookStartPayload from a map[string]interface{} +func LoadHookStartPayload(data interface{}, ctx *LoadContext) (HookStartPayload, error) { + result := HookStartPayload{} + + // Load from map + if m, ok := data.(map[string]interface{}); ok { + if val, ok := m["hookInvocationId"]; ok && val != nil { + result.HookInvocationId = string(val.(string)) + } + if val, ok := m["hookType"]; ok && val != nil { + result.HookType = string(val.(string)) + } + if val, ok := m["scope"]; ok && val != nil { + v := HookStartScope(val.(string)) + result.Scope = &v + } + if val, ok := m["input"]; ok && val != nil { + if m, ok := val.(map[string]interface{}); ok { + result.Input = m + } + } + if val, ok := m["redaction"]; ok && val != nil { + if m, ok := val.(map[string]interface{}); ok { + loaded, _ := LoadRedactionMetadata(m, ctx) + result.Redaction = &loaded + } + } + } + + return result, nil +} + +// Save serializes HookStartPayload to map[string]interface{} +func (obj HookStartPayload) Save(ctx *SaveContext) map[string]interface{} { + result := make(map[string]interface{}) + result["hookInvocationId"] = obj.HookInvocationId + result["hookType"] = obj.HookType + if obj.Scope != nil { + result["scope"] = string(*obj.Scope) + } + if obj.Input != nil { + result["input"] = obj.Input + } + if obj.Redaction != nil { + result["redaction"] = obj.Redaction.Save(ctx) + } + + return result +} + +// ToJSON serializes HookStartPayload to JSON string +func (obj *HookStartPayload) ToJSON() (string, error) { + ctx := NewSaveContext() + data := obj.Save(ctx) + bytes, err := json.Marshal(data) + if err != nil { + return "", err + } + return string(bytes), nil +} + +// ToYAML serializes HookStartPayload to YAML string +func (obj *HookStartPayload) ToYAML() (string, error) { + ctx := NewSaveContext() + data := obj.Save(ctx) + return marshalYAMLDocument(data) +} + +// FromJSON creates HookStartPayload from JSON string +func HookStartPayloadFromJSON(jsonStr string) (HookStartPayload, error) { + var data map[string]interface{} + if err := json.Unmarshal([]byte(jsonStr), &data); err != nil { + return HookStartPayload{}, err + } + ctx := NewLoadContext() + return LoadHookStartPayload(data, ctx) +} + +// FromYAML creates HookStartPayload from YAML string +func HookStartPayloadFromYAML(yamlStr string) (HookStartPayload, error) { + var data map[string]interface{} + if err := yaml.Unmarshal([]byte(yamlStr), &data); err != nil { + return HookStartPayload{}, err + } + ctx := NewLoadContext() + return LoadHookStartPayload(data, ctx) +} diff --git a/runtime/go/prompty/model/hook_start_payload_test.go b/runtime/go/prompty/model/hook_start_payload_test.go new file mode 100644 index 00000000..108aa7c4 --- /dev/null +++ b/runtime/go/prompty/model/hook_start_payload_test.go @@ -0,0 +1,225 @@ +// +// Code generated by Typra emitter; DO NOT EDIT. + +package prompty_test + +import ( + "encoding/json" + "testing" + + "gopkg.in/yaml.v3" + + "prompty/model" +) + +// TestHookStartPayloadLoadJSON tests loading HookStartPayload from JSON +func TestHookStartPayloadLoadJSON(t *testing.T) { + jsonData := ` +{ + "hookInvocationId": "hook_abc123", + "hookType": "preToolUse" +} +` + var data map[string]interface{} + if err := json.Unmarshal([]byte(jsonData), &data); err != nil { + t.Fatalf("Failed to parse JSON: %v", err) + } + + ctx := prompty.NewLoadContext() + instance, err := prompty.LoadHookStartPayload(data, ctx) + if err != nil { + t.Fatalf("Failed to load HookStartPayload: %v", err) + } + if instance.HookInvocationId != "hook_abc123" { + t.Errorf(`Expected HookInvocationId to be "hook_abc123", got %v`, instance.HookInvocationId) + } + if instance.HookType != "preToolUse" { + t.Errorf(`Expected HookType to be "preToolUse", got %v`, instance.HookType) + } +} + +// TestHookStartPayloadLoadYAML tests loading HookStartPayload from YAML +func TestHookStartPayloadLoadYAML(t *testing.T) { + yamlData := ` +hookInvocationId: hook_abc123 +hookType: preToolUse + +` + var data map[string]interface{} + if err := yaml.Unmarshal([]byte(yamlData), &data); err != nil { + t.Fatalf("Failed to parse YAML: %v", err) + } + + ctx := prompty.NewLoadContext() + instance, err := prompty.LoadHookStartPayload(data, ctx) + if err != nil { + t.Fatalf("Failed to load HookStartPayload: %v", err) + } + if instance.HookInvocationId != "hook_abc123" { + t.Errorf(`Expected HookInvocationId to be "hook_abc123", got %v`, instance.HookInvocationId) + } + if instance.HookType != "preToolUse" { + t.Errorf(`Expected HookType to be "preToolUse", got %v`, instance.HookType) + } +} + +// TestHookStartPayloadFromJSON tests loading HookStartPayload through the generated JSON helper +func TestHookStartPayloadFromJSON(t *testing.T) { + jsonData := ` +{ + "hookInvocationId": "hook_abc123", + "hookType": "preToolUse" +} +` + + instance, err := prompty.HookStartPayloadFromJSON(jsonData) + if err != nil { + t.Fatalf("Failed to load HookStartPayload from JSON helper: %v", err) + } + if instance.HookInvocationId != "hook_abc123" { + t.Errorf(`Expected HookInvocationId to be "hook_abc123", got %v`, instance.HookInvocationId) + } + if instance.HookType != "preToolUse" { + t.Errorf(`Expected HookType to be "preToolUse", got %v`, instance.HookType) + } +} + +// TestHookStartPayloadFromYAML tests loading HookStartPayload through the generated YAML helper +func TestHookStartPayloadFromYAML(t *testing.T) { + yamlData := ` +hookInvocationId: hook_abc123 +hookType: preToolUse + +` + + instance, err := prompty.HookStartPayloadFromYAML(yamlData) + if err != nil { + t.Fatalf("Failed to load HookStartPayload from YAML helper: %v", err) + } + if instance.HookInvocationId != "hook_abc123" { + t.Errorf(`Expected HookInvocationId to be "hook_abc123", got %v`, instance.HookInvocationId) + } + if instance.HookType != "preToolUse" { + t.Errorf(`Expected HookType to be "preToolUse", got %v`, instance.HookType) + } +} + +// TestHookStartPayloadRoundtrip tests load -> save -> load produces equivalent data +func TestHookStartPayloadRoundtrip(t *testing.T) { + jsonData := ` +{ + "hookInvocationId": "hook_abc123", + "hookType": "preToolUse" +} +` + var data map[string]interface{} + if err := json.Unmarshal([]byte(jsonData), &data); err != nil { + t.Fatalf("Failed to parse JSON: %v", err) + } + + loadCtx := prompty.NewLoadContext() + instance, err := prompty.LoadHookStartPayload(data, loadCtx) + if err != nil { + t.Fatalf("Failed to load HookStartPayload: %v", err) + } + saveCtx := prompty.NewSaveContext() + savedData := instance.Save(saveCtx) + + reloaded, err := prompty.LoadHookStartPayload(savedData, loadCtx) + if err != nil { + t.Fatalf("Failed to reload HookStartPayload: %v", err) + } + if reloaded.HookInvocationId != "hook_abc123" { + t.Errorf(`Expected HookInvocationId to be "hook_abc123", got %v`, reloaded.HookInvocationId) + } + if reloaded.HookType != "preToolUse" { + t.Errorf(`Expected HookType to be "preToolUse", got %v`, reloaded.HookType) + } +} + +// TestHookStartPayloadToJSON tests that ToJSON produces valid JSON +func TestHookStartPayloadToJSON(t *testing.T) { + jsonData := ` +{ + "hookInvocationId": "hook_abc123", + "hookType": "preToolUse" +} +` + var data map[string]interface{} + if err := json.Unmarshal([]byte(jsonData), &data); err != nil { + t.Fatalf("Failed to parse JSON: %v", err) + } + + ctx := prompty.NewLoadContext() + instance, err := prompty.LoadHookStartPayload(data, ctx) + if err != nil { + t.Fatalf("Failed to load HookStartPayload: %v", err) + } + jsonOutput, err := instance.ToJSON() + if err != nil { + t.Fatalf("Failed to convert to JSON: %v", err) + } + + var parsed map[string]interface{} + if err := json.Unmarshal([]byte(jsonOutput), &parsed); err != nil { + t.Fatalf("Failed to parse generated JSON: %v", err) + } + + reloaded, err := prompty.LoadHookStartPayload(parsed, ctx) + if err != nil { + t.Fatalf("Failed to reload generated JSON: %v", err) + } + if reloaded.HookInvocationId != "hook_abc123" { + t.Errorf(`Expected HookInvocationId to be "hook_abc123", got %v`, reloaded.HookInvocationId) + } + if reloaded.HookType != "preToolUse" { + t.Errorf(`Expected HookType to be "preToolUse", got %v`, reloaded.HookType) + } +} + +// TestHookStartPayloadToYAML tests that ToYAML produces valid YAML +func TestHookStartPayloadToYAML(t *testing.T) { + jsonData := ` +{ + "hookInvocationId": "hook_abc123", + "hookType": "preToolUse" +} +` + var data map[string]interface{} + if err := json.Unmarshal([]byte(jsonData), &data); err != nil { + t.Fatalf("Failed to parse JSON: %v", err) + } + + ctx := prompty.NewLoadContext() + instance, err := prompty.LoadHookStartPayload(data, ctx) + if err != nil { + t.Fatalf("Failed to load HookStartPayload: %v", err) + } + yamlOutput, err := instance.ToYAML() + if err != nil { + t.Fatalf("Failed to convert to YAML: %v", err) + } + + var parsed map[string]interface{} + if err := yaml.Unmarshal([]byte(yamlOutput), &parsed); err != nil { + t.Fatalf("Failed to parse generated YAML: %v", err) + } + + reloaded, err := prompty.LoadHookStartPayload(parsed, ctx) + if err != nil { + t.Fatalf("Failed to reload generated YAML: %v", err) + } + if reloaded.HookInvocationId != "hook_abc123" { + t.Errorf(`Expected HookInvocationId to be "hook_abc123", got %v`, reloaded.HookInvocationId) + } + if reloaded.HookType != "preToolUse" { + t.Errorf(`Expected HookType to be "preToolUse", got %v`, reloaded.HookType) + } +} + +// TestHookStartPayloadFromJSONInvalid rejects malformed JSON instead of silently defaulting +func TestHookStartPayloadFromJSONInvalid(t *testing.T) { + if _, err := prompty.HookStartPayloadFromJSON("{"); err == nil { + t.Fatalf("Expected malformed JSON to fail") + } +} diff --git a/runtime/go/prompty/model/host_tool_executor.go b/runtime/go/prompty/model/host_tool_executor.go new file mode 100644 index 00000000..5a19f6ac --- /dev/null +++ b/runtime/go/prompty/model/host_tool_executor.go @@ -0,0 +1,12 @@ +// +// Code generated by Typra emitter; DO NOT EDIT. +// Group: pipeline + +package prompty + +// HostToolExecutor represents Executes host tools after policy and permission checks. + +type HostToolExecutor interface { + // Execute — Execute a concrete host tool request and return its completion payload + Execute(request HostToolRequest) (HostToolResult, error) +} diff --git a/runtime/go/prompty/model/host_tool_request.go b/runtime/go/prompty/model/host_tool_request.go new file mode 100644 index 00000000..6aff94ad --- /dev/null +++ b/runtime/go/prompty/model/host_tool_request.go @@ -0,0 +1,110 @@ +// +// Code generated by Typra emitter; DO NOT EDIT. +// Group: events + +package prompty + +import ( + "encoding/json" + + "gopkg.in/yaml.v3" +) + +// HostToolRequest represents Request passed to a host tool executor after policy and permission checks. + +type HostToolRequest struct { + RequestId *string `json:"requestId,omitempty" yaml:"requestId,omitempty"` + ToolCallId *string `json:"toolCallId,omitempty" yaml:"toolCallId,omitempty"` + ToolName string `json:"toolName" yaml:"toolName"` + Arguments map[string]interface{} `json:"arguments,omitempty" yaml:"arguments,omitempty"` + WorkingDirectory *string `json:"workingDirectory,omitempty" yaml:"workingDirectory,omitempty"` +} + +// LoadHostToolRequest creates a HostToolRequest from a map[string]interface{} +func LoadHostToolRequest(data interface{}, ctx *LoadContext) (HostToolRequest, error) { + result := HostToolRequest{} + + // Load from map + if m, ok := data.(map[string]interface{}); ok { + if val, ok := m["requestId"]; ok && val != nil { + v := string(val.(string)) + result.RequestId = &v + } + if val, ok := m["toolCallId"]; ok && val != nil { + v := string(val.(string)) + result.ToolCallId = &v + } + if val, ok := m["toolName"]; ok && val != nil { + result.ToolName = string(val.(string)) + } + if val, ok := m["arguments"]; ok && val != nil { + if m, ok := val.(map[string]interface{}); ok { + result.Arguments = m + } + } + if val, ok := m["workingDirectory"]; ok && val != nil { + v := string(val.(string)) + result.WorkingDirectory = &v + } + } + + return result, nil +} + +// Save serializes HostToolRequest to map[string]interface{} +func (obj HostToolRequest) Save(ctx *SaveContext) map[string]interface{} { + result := make(map[string]interface{}) + if obj.RequestId != nil { + result["requestId"] = *obj.RequestId + } + if obj.ToolCallId != nil { + result["toolCallId"] = *obj.ToolCallId + } + result["toolName"] = obj.ToolName + if obj.Arguments != nil { + result["arguments"] = obj.Arguments + } + if obj.WorkingDirectory != nil { + result["workingDirectory"] = *obj.WorkingDirectory + } + + return result +} + +// ToJSON serializes HostToolRequest to JSON string +func (obj *HostToolRequest) ToJSON() (string, error) { + ctx := NewSaveContext() + data := obj.Save(ctx) + bytes, err := json.Marshal(data) + if err != nil { + return "", err + } + return string(bytes), nil +} + +// ToYAML serializes HostToolRequest to YAML string +func (obj *HostToolRequest) ToYAML() (string, error) { + ctx := NewSaveContext() + data := obj.Save(ctx) + return marshalYAMLDocument(data) +} + +// FromJSON creates HostToolRequest from JSON string +func HostToolRequestFromJSON(jsonStr string) (HostToolRequest, error) { + var data map[string]interface{} + if err := json.Unmarshal([]byte(jsonStr), &data); err != nil { + return HostToolRequest{}, err + } + ctx := NewLoadContext() + return LoadHostToolRequest(data, ctx) +} + +// FromYAML creates HostToolRequest from YAML string +func HostToolRequestFromYAML(yamlStr string) (HostToolRequest, error) { + var data map[string]interface{} + if err := yaml.Unmarshal([]byte(yamlStr), &data); err != nil { + return HostToolRequest{}, err + } + ctx := NewLoadContext() + return LoadHostToolRequest(data, ctx) +} diff --git a/runtime/go/prompty/model/host_tool_request_test.go b/runtime/go/prompty/model/host_tool_request_test.go new file mode 100644 index 00000000..6d246d50 --- /dev/null +++ b/runtime/go/prompty/model/host_tool_request_test.go @@ -0,0 +1,281 @@ +// +// Code generated by Typra emitter; DO NOT EDIT. + +package prompty_test + +import ( + "encoding/json" + "testing" + + "gopkg.in/yaml.v3" + + "prompty/model" +) + +// TestHostToolRequestLoadJSON tests loading HostToolRequest from JSON +func TestHostToolRequestLoadJSON(t *testing.T) { + jsonData := ` +{ + "requestId": "exec_abc123", + "toolCallId": "call_abc123", + "toolName": "powershell", + "workingDirectory": "/workspace/project" +} +` + var data map[string]interface{} + if err := json.Unmarshal([]byte(jsonData), &data); err != nil { + t.Fatalf("Failed to parse JSON: %v", err) + } + + ctx := prompty.NewLoadContext() + instance, err := prompty.LoadHostToolRequest(data, ctx) + if err != nil { + t.Fatalf("Failed to load HostToolRequest: %v", err) + } + if instance.RequestId == nil || *instance.RequestId != "exec_abc123" { + t.Errorf(`Expected RequestId to be "exec_abc123", got %v`, instance.RequestId) + } + if instance.ToolCallId == nil || *instance.ToolCallId != "call_abc123" { + t.Errorf(`Expected ToolCallId to be "call_abc123", got %v`, instance.ToolCallId) + } + if instance.ToolName != "powershell" { + t.Errorf(`Expected ToolName to be "powershell", got %v`, instance.ToolName) + } + if instance.WorkingDirectory == nil || *instance.WorkingDirectory != "/workspace/project" { + t.Errorf(`Expected WorkingDirectory to be "/workspace/project", got %v`, instance.WorkingDirectory) + } +} + +// TestHostToolRequestLoadYAML tests loading HostToolRequest from YAML +func TestHostToolRequestLoadYAML(t *testing.T) { + yamlData := ` +requestId: exec_abc123 +toolCallId: call_abc123 +toolName: powershell +workingDirectory: /workspace/project + +` + var data map[string]interface{} + if err := yaml.Unmarshal([]byte(yamlData), &data); err != nil { + t.Fatalf("Failed to parse YAML: %v", err) + } + + ctx := prompty.NewLoadContext() + instance, err := prompty.LoadHostToolRequest(data, ctx) + if err != nil { + t.Fatalf("Failed to load HostToolRequest: %v", err) + } + if instance.RequestId == nil || *instance.RequestId != "exec_abc123" { + t.Errorf(`Expected RequestId to be "exec_abc123", got %v`, instance.RequestId) + } + if instance.ToolCallId == nil || *instance.ToolCallId != "call_abc123" { + t.Errorf(`Expected ToolCallId to be "call_abc123", got %v`, instance.ToolCallId) + } + if instance.ToolName != "powershell" { + t.Errorf(`Expected ToolName to be "powershell", got %v`, instance.ToolName) + } + if instance.WorkingDirectory == nil || *instance.WorkingDirectory != "/workspace/project" { + t.Errorf(`Expected WorkingDirectory to be "/workspace/project", got %v`, instance.WorkingDirectory) + } +} + +// TestHostToolRequestFromJSON tests loading HostToolRequest through the generated JSON helper +func TestHostToolRequestFromJSON(t *testing.T) { + jsonData := ` +{ + "requestId": "exec_abc123", + "toolCallId": "call_abc123", + "toolName": "powershell", + "workingDirectory": "/workspace/project" +} +` + + instance, err := prompty.HostToolRequestFromJSON(jsonData) + if err != nil { + t.Fatalf("Failed to load HostToolRequest from JSON helper: %v", err) + } + if instance.RequestId == nil || *instance.RequestId != "exec_abc123" { + t.Errorf(`Expected RequestId to be "exec_abc123", got %v`, instance.RequestId) + } + if instance.ToolCallId == nil || *instance.ToolCallId != "call_abc123" { + t.Errorf(`Expected ToolCallId to be "call_abc123", got %v`, instance.ToolCallId) + } + if instance.ToolName != "powershell" { + t.Errorf(`Expected ToolName to be "powershell", got %v`, instance.ToolName) + } + if instance.WorkingDirectory == nil || *instance.WorkingDirectory != "/workspace/project" { + t.Errorf(`Expected WorkingDirectory to be "/workspace/project", got %v`, instance.WorkingDirectory) + } +} + +// TestHostToolRequestFromYAML tests loading HostToolRequest through the generated YAML helper +func TestHostToolRequestFromYAML(t *testing.T) { + yamlData := ` +requestId: exec_abc123 +toolCallId: call_abc123 +toolName: powershell +workingDirectory: /workspace/project + +` + + instance, err := prompty.HostToolRequestFromYAML(yamlData) + if err != nil { + t.Fatalf("Failed to load HostToolRequest from YAML helper: %v", err) + } + if instance.RequestId == nil || *instance.RequestId != "exec_abc123" { + t.Errorf(`Expected RequestId to be "exec_abc123", got %v`, instance.RequestId) + } + if instance.ToolCallId == nil || *instance.ToolCallId != "call_abc123" { + t.Errorf(`Expected ToolCallId to be "call_abc123", got %v`, instance.ToolCallId) + } + if instance.ToolName != "powershell" { + t.Errorf(`Expected ToolName to be "powershell", got %v`, instance.ToolName) + } + if instance.WorkingDirectory == nil || *instance.WorkingDirectory != "/workspace/project" { + t.Errorf(`Expected WorkingDirectory to be "/workspace/project", got %v`, instance.WorkingDirectory) + } +} + +// TestHostToolRequestRoundtrip tests load -> save -> load produces equivalent data +func TestHostToolRequestRoundtrip(t *testing.T) { + jsonData := ` +{ + "requestId": "exec_abc123", + "toolCallId": "call_abc123", + "toolName": "powershell", + "workingDirectory": "/workspace/project" +} +` + var data map[string]interface{} + if err := json.Unmarshal([]byte(jsonData), &data); err != nil { + t.Fatalf("Failed to parse JSON: %v", err) + } + + loadCtx := prompty.NewLoadContext() + instance, err := prompty.LoadHostToolRequest(data, loadCtx) + if err != nil { + t.Fatalf("Failed to load HostToolRequest: %v", err) + } + saveCtx := prompty.NewSaveContext() + savedData := instance.Save(saveCtx) + + reloaded, err := prompty.LoadHostToolRequest(savedData, loadCtx) + if err != nil { + t.Fatalf("Failed to reload HostToolRequest: %v", err) + } + if reloaded.RequestId == nil || *reloaded.RequestId != "exec_abc123" { + t.Errorf(`Expected RequestId to be "exec_abc123", got %v`, reloaded.RequestId) + } + if reloaded.ToolCallId == nil || *reloaded.ToolCallId != "call_abc123" { + t.Errorf(`Expected ToolCallId to be "call_abc123", got %v`, reloaded.ToolCallId) + } + if reloaded.ToolName != "powershell" { + t.Errorf(`Expected ToolName to be "powershell", got %v`, reloaded.ToolName) + } + if reloaded.WorkingDirectory == nil || *reloaded.WorkingDirectory != "/workspace/project" { + t.Errorf(`Expected WorkingDirectory to be "/workspace/project", got %v`, reloaded.WorkingDirectory) + } +} + +// TestHostToolRequestToJSON tests that ToJSON produces valid JSON +func TestHostToolRequestToJSON(t *testing.T) { + jsonData := ` +{ + "requestId": "exec_abc123", + "toolCallId": "call_abc123", + "toolName": "powershell", + "workingDirectory": "/workspace/project" +} +` + var data map[string]interface{} + if err := json.Unmarshal([]byte(jsonData), &data); err != nil { + t.Fatalf("Failed to parse JSON: %v", err) + } + + ctx := prompty.NewLoadContext() + instance, err := prompty.LoadHostToolRequest(data, ctx) + if err != nil { + t.Fatalf("Failed to load HostToolRequest: %v", err) + } + jsonOutput, err := instance.ToJSON() + if err != nil { + t.Fatalf("Failed to convert to JSON: %v", err) + } + + var parsed map[string]interface{} + if err := json.Unmarshal([]byte(jsonOutput), &parsed); err != nil { + t.Fatalf("Failed to parse generated JSON: %v", err) + } + + reloaded, err := prompty.LoadHostToolRequest(parsed, ctx) + if err != nil { + t.Fatalf("Failed to reload generated JSON: %v", err) + } + if reloaded.RequestId == nil || *reloaded.RequestId != "exec_abc123" { + t.Errorf(`Expected RequestId to be "exec_abc123", got %v`, reloaded.RequestId) + } + if reloaded.ToolCallId == nil || *reloaded.ToolCallId != "call_abc123" { + t.Errorf(`Expected ToolCallId to be "call_abc123", got %v`, reloaded.ToolCallId) + } + if reloaded.ToolName != "powershell" { + t.Errorf(`Expected ToolName to be "powershell", got %v`, reloaded.ToolName) + } + if reloaded.WorkingDirectory == nil || *reloaded.WorkingDirectory != "/workspace/project" { + t.Errorf(`Expected WorkingDirectory to be "/workspace/project", got %v`, reloaded.WorkingDirectory) + } +} + +// TestHostToolRequestToYAML tests that ToYAML produces valid YAML +func TestHostToolRequestToYAML(t *testing.T) { + jsonData := ` +{ + "requestId": "exec_abc123", + "toolCallId": "call_abc123", + "toolName": "powershell", + "workingDirectory": "/workspace/project" +} +` + var data map[string]interface{} + if err := json.Unmarshal([]byte(jsonData), &data); err != nil { + t.Fatalf("Failed to parse JSON: %v", err) + } + + ctx := prompty.NewLoadContext() + instance, err := prompty.LoadHostToolRequest(data, ctx) + if err != nil { + t.Fatalf("Failed to load HostToolRequest: %v", err) + } + yamlOutput, err := instance.ToYAML() + if err != nil { + t.Fatalf("Failed to convert to YAML: %v", err) + } + + var parsed map[string]interface{} + if err := yaml.Unmarshal([]byte(yamlOutput), &parsed); err != nil { + t.Fatalf("Failed to parse generated YAML: %v", err) + } + + reloaded, err := prompty.LoadHostToolRequest(parsed, ctx) + if err != nil { + t.Fatalf("Failed to reload generated YAML: %v", err) + } + if reloaded.RequestId == nil || *reloaded.RequestId != "exec_abc123" { + t.Errorf(`Expected RequestId to be "exec_abc123", got %v`, reloaded.RequestId) + } + if reloaded.ToolCallId == nil || *reloaded.ToolCallId != "call_abc123" { + t.Errorf(`Expected ToolCallId to be "call_abc123", got %v`, reloaded.ToolCallId) + } + if reloaded.ToolName != "powershell" { + t.Errorf(`Expected ToolName to be "powershell", got %v`, reloaded.ToolName) + } + if reloaded.WorkingDirectory == nil || *reloaded.WorkingDirectory != "/workspace/project" { + t.Errorf(`Expected WorkingDirectory to be "/workspace/project", got %v`, reloaded.WorkingDirectory) + } +} + +// TestHostToolRequestFromJSONInvalid rejects malformed JSON instead of silently defaulting +func TestHostToolRequestFromJSONInvalid(t *testing.T) { + if _, err := prompty.HostToolRequestFromJSON("{"); err == nil { + t.Fatalf("Expected malformed JSON to fail") + } +} diff --git a/runtime/go/prompty/model/host_tool_result.go b/runtime/go/prompty/model/host_tool_result.go new file mode 100644 index 00000000..e64ee93b --- /dev/null +++ b/runtime/go/prompty/model/host_tool_result.go @@ -0,0 +1,160 @@ +// +// Code generated by Typra emitter; DO NOT EDIT. +// Group: events + +package prompty + +import ( + "encoding/json" + + "gopkg.in/yaml.v3" +) + +// HostToolResult represents Result returned by a host tool executor. + +type HostToolResult struct { + RequestId *string `json:"requestId,omitempty" yaml:"requestId,omitempty"` + ToolCallId *string `json:"toolCallId,omitempty" yaml:"toolCallId,omitempty"` + ToolName string `json:"toolName" yaml:"toolName"` + Success bool `json:"success" yaml:"success"` + Result *interface{} `json:"result,omitempty" yaml:"result,omitempty"` + ExitCode *int32 `json:"exitCode,omitempty" yaml:"exitCode,omitempty"` + DurationMs *float64 `json:"durationMs,omitempty" yaml:"durationMs,omitempty"` + ErrorKind *string `json:"errorKind,omitempty" yaml:"errorKind,omitempty"` + Telemetry map[string]interface{} `json:"telemetry,omitempty" yaml:"telemetry,omitempty"` +} + +// LoadHostToolResult creates a HostToolResult from a map[string]interface{} +func LoadHostToolResult(data interface{}, ctx *LoadContext) (HostToolResult, error) { + result := HostToolResult{} + + // Load from map + if m, ok := data.(map[string]interface{}); ok { + if val, ok := m["requestId"]; ok && val != nil { + v := string(val.(string)) + result.RequestId = &v + } + if val, ok := m["toolCallId"]; ok && val != nil { + v := string(val.(string)) + result.ToolCallId = &v + } + if val, ok := m["toolName"]; ok && val != nil { + result.ToolName = string(val.(string)) + } + if val, ok := m["success"]; ok && val != nil { + result.Success = val.(bool) + } + if val, ok := m["result"]; ok && val != nil { + result.Result = &val + } + if val, ok := m["exitCode"]; ok && val != nil { // Handle various numeric types from JSON/YAML/roundtrip + var v int32 + switch n := val.(type) { + case int: + v = int32(n) + case int32: + v = int32(n) + case int64: + v = int32(n) + case float64: + v = int32(n) + } + result.ExitCode = &v + } + if val, ok := m["durationMs"]; ok && val != nil { // Handle various numeric types from JSON/YAML/roundtrip + var v float64 + switch n := val.(type) { + case int: + v = float64(n) + case int32: + v = float64(n) + case int64: + v = float64(n) + case float32: + v = float64(n) + case float64: + v = n + } + result.DurationMs = &v + } + if val, ok := m["errorKind"]; ok && val != nil { + v := string(val.(string)) + result.ErrorKind = &v + } + if val, ok := m["telemetry"]; ok && val != nil { + if m, ok := val.(map[string]interface{}); ok { + result.Telemetry = m + } + } + } + + return result, nil +} + +// Save serializes HostToolResult to map[string]interface{} +func (obj HostToolResult) Save(ctx *SaveContext) map[string]interface{} { + result := make(map[string]interface{}) + if obj.RequestId != nil { + result["requestId"] = *obj.RequestId + } + if obj.ToolCallId != nil { + result["toolCallId"] = *obj.ToolCallId + } + result["toolName"] = obj.ToolName + result["success"] = obj.Success + if obj.Result != nil { + result["result"] = *obj.Result + } + if obj.ExitCode != nil { + result["exitCode"] = *obj.ExitCode + } + if obj.DurationMs != nil { + result["durationMs"] = *obj.DurationMs + } + if obj.ErrorKind != nil { + result["errorKind"] = *obj.ErrorKind + } + if obj.Telemetry != nil { + result["telemetry"] = obj.Telemetry + } + + return result +} + +// ToJSON serializes HostToolResult to JSON string +func (obj *HostToolResult) ToJSON() (string, error) { + ctx := NewSaveContext() + data := obj.Save(ctx) + bytes, err := json.Marshal(data) + if err != nil { + return "", err + } + return string(bytes), nil +} + +// ToYAML serializes HostToolResult to YAML string +func (obj *HostToolResult) ToYAML() (string, error) { + ctx := NewSaveContext() + data := obj.Save(ctx) + return marshalYAMLDocument(data) +} + +// FromJSON creates HostToolResult from JSON string +func HostToolResultFromJSON(jsonStr string) (HostToolResult, error) { + var data map[string]interface{} + if err := json.Unmarshal([]byte(jsonStr), &data); err != nil { + return HostToolResult{}, err + } + ctx := NewLoadContext() + return LoadHostToolResult(data, ctx) +} + +// FromYAML creates HostToolResult from YAML string +func HostToolResultFromYAML(yamlStr string) (HostToolResult, error) { + var data map[string]interface{} + if err := yaml.Unmarshal([]byte(yamlStr), &data); err != nil { + return HostToolResult{}, err + } + ctx := NewLoadContext() + return LoadHostToolResult(data, ctx) +} diff --git a/runtime/go/prompty/model/host_tool_result_test.go b/runtime/go/prompty/model/host_tool_result_test.go new file mode 100644 index 00000000..87370ce0 --- /dev/null +++ b/runtime/go/prompty/model/host_tool_result_test.go @@ -0,0 +1,365 @@ +// +// Code generated by Typra emitter; DO NOT EDIT. + +package prompty_test + +import ( + "encoding/json" + "testing" + + "gopkg.in/yaml.v3" + + "prompty/model" +) + +// TestHostToolResultLoadJSON tests loading HostToolResult from JSON +func TestHostToolResultLoadJSON(t *testing.T) { + jsonData := ` +{ + "requestId": "exec_abc123", + "toolCallId": "call_abc123", + "toolName": "powershell", + "success": true, + "exitCode": 0, + "durationMs": 250, + "errorKind": "timeout" +} +` + var data map[string]interface{} + if err := json.Unmarshal([]byte(jsonData), &data); err != nil { + t.Fatalf("Failed to parse JSON: %v", err) + } + + ctx := prompty.NewLoadContext() + instance, err := prompty.LoadHostToolResult(data, ctx) + if err != nil { + t.Fatalf("Failed to load HostToolResult: %v", err) + } + if instance.RequestId == nil || *instance.RequestId != "exec_abc123" { + t.Errorf(`Expected RequestId to be "exec_abc123", got %v`, instance.RequestId) + } + if instance.ToolCallId == nil || *instance.ToolCallId != "call_abc123" { + t.Errorf(`Expected ToolCallId to be "call_abc123", got %v`, instance.ToolCallId) + } + if instance.ToolName != "powershell" { + t.Errorf(`Expected ToolName to be "powershell", got %v`, instance.ToolName) + } + if instance.Success != true { + t.Errorf(`Expected Success to be true, got %v`, instance.Success) + } + if instance.ExitCode == nil || *instance.ExitCode != 0 { + t.Errorf(`Expected ExitCode to be 0, got %v`, instance.ExitCode) + } + if instance.DurationMs == nil || *instance.DurationMs != 250 { + t.Errorf(`Expected DurationMs to be 250, got %v`, instance.DurationMs) + } + if instance.ErrorKind == nil || *instance.ErrorKind != "timeout" { + t.Errorf(`Expected ErrorKind to be "timeout", got %v`, instance.ErrorKind) + } +} + +// TestHostToolResultLoadYAML tests loading HostToolResult from YAML +func TestHostToolResultLoadYAML(t *testing.T) { + yamlData := ` +requestId: exec_abc123 +toolCallId: call_abc123 +toolName: powershell +success: true +exitCode: 0 +durationMs: 250 +errorKind: timeout + +` + var data map[string]interface{} + if err := yaml.Unmarshal([]byte(yamlData), &data); err != nil { + t.Fatalf("Failed to parse YAML: %v", err) + } + + ctx := prompty.NewLoadContext() + instance, err := prompty.LoadHostToolResult(data, ctx) + if err != nil { + t.Fatalf("Failed to load HostToolResult: %v", err) + } + if instance.RequestId == nil || *instance.RequestId != "exec_abc123" { + t.Errorf(`Expected RequestId to be "exec_abc123", got %v`, instance.RequestId) + } + if instance.ToolCallId == nil || *instance.ToolCallId != "call_abc123" { + t.Errorf(`Expected ToolCallId to be "call_abc123", got %v`, instance.ToolCallId) + } + if instance.ToolName != "powershell" { + t.Errorf(`Expected ToolName to be "powershell", got %v`, instance.ToolName) + } + if instance.Success != true { + t.Errorf(`Expected Success to be true, got %v`, instance.Success) + } + if instance.ExitCode == nil || *instance.ExitCode != 0 { + t.Errorf(`Expected ExitCode to be 0, got %v`, instance.ExitCode) + } + if instance.DurationMs == nil || *instance.DurationMs != 250 { + t.Errorf(`Expected DurationMs to be 250, got %v`, instance.DurationMs) + } + if instance.ErrorKind == nil || *instance.ErrorKind != "timeout" { + t.Errorf(`Expected ErrorKind to be "timeout", got %v`, instance.ErrorKind) + } +} + +// TestHostToolResultFromJSON tests loading HostToolResult through the generated JSON helper +func TestHostToolResultFromJSON(t *testing.T) { + jsonData := ` +{ + "requestId": "exec_abc123", + "toolCallId": "call_abc123", + "toolName": "powershell", + "success": true, + "exitCode": 0, + "durationMs": 250, + "errorKind": "timeout" +} +` + + instance, err := prompty.HostToolResultFromJSON(jsonData) + if err != nil { + t.Fatalf("Failed to load HostToolResult from JSON helper: %v", err) + } + if instance.RequestId == nil || *instance.RequestId != "exec_abc123" { + t.Errorf(`Expected RequestId to be "exec_abc123", got %v`, instance.RequestId) + } + if instance.ToolCallId == nil || *instance.ToolCallId != "call_abc123" { + t.Errorf(`Expected ToolCallId to be "call_abc123", got %v`, instance.ToolCallId) + } + if instance.ToolName != "powershell" { + t.Errorf(`Expected ToolName to be "powershell", got %v`, instance.ToolName) + } + if instance.Success != true { + t.Errorf(`Expected Success to be true, got %v`, instance.Success) + } + if instance.ExitCode == nil || *instance.ExitCode != 0 { + t.Errorf(`Expected ExitCode to be 0, got %v`, instance.ExitCode) + } + if instance.DurationMs == nil || *instance.DurationMs != 250 { + t.Errorf(`Expected DurationMs to be 250, got %v`, instance.DurationMs) + } + if instance.ErrorKind == nil || *instance.ErrorKind != "timeout" { + t.Errorf(`Expected ErrorKind to be "timeout", got %v`, instance.ErrorKind) + } +} + +// TestHostToolResultFromYAML tests loading HostToolResult through the generated YAML helper +func TestHostToolResultFromYAML(t *testing.T) { + yamlData := ` +requestId: exec_abc123 +toolCallId: call_abc123 +toolName: powershell +success: true +exitCode: 0 +durationMs: 250 +errorKind: timeout + +` + + instance, err := prompty.HostToolResultFromYAML(yamlData) + if err != nil { + t.Fatalf("Failed to load HostToolResult from YAML helper: %v", err) + } + if instance.RequestId == nil || *instance.RequestId != "exec_abc123" { + t.Errorf(`Expected RequestId to be "exec_abc123", got %v`, instance.RequestId) + } + if instance.ToolCallId == nil || *instance.ToolCallId != "call_abc123" { + t.Errorf(`Expected ToolCallId to be "call_abc123", got %v`, instance.ToolCallId) + } + if instance.ToolName != "powershell" { + t.Errorf(`Expected ToolName to be "powershell", got %v`, instance.ToolName) + } + if instance.Success != true { + t.Errorf(`Expected Success to be true, got %v`, instance.Success) + } + if instance.ExitCode == nil || *instance.ExitCode != 0 { + t.Errorf(`Expected ExitCode to be 0, got %v`, instance.ExitCode) + } + if instance.DurationMs == nil || *instance.DurationMs != 250 { + t.Errorf(`Expected DurationMs to be 250, got %v`, instance.DurationMs) + } + if instance.ErrorKind == nil || *instance.ErrorKind != "timeout" { + t.Errorf(`Expected ErrorKind to be "timeout", got %v`, instance.ErrorKind) + } +} + +// TestHostToolResultRoundtrip tests load -> save -> load produces equivalent data +func TestHostToolResultRoundtrip(t *testing.T) { + jsonData := ` +{ + "requestId": "exec_abc123", + "toolCallId": "call_abc123", + "toolName": "powershell", + "success": true, + "exitCode": 0, + "durationMs": 250, + "errorKind": "timeout" +} +` + var data map[string]interface{} + if err := json.Unmarshal([]byte(jsonData), &data); err != nil { + t.Fatalf("Failed to parse JSON: %v", err) + } + + loadCtx := prompty.NewLoadContext() + instance, err := prompty.LoadHostToolResult(data, loadCtx) + if err != nil { + t.Fatalf("Failed to load HostToolResult: %v", err) + } + saveCtx := prompty.NewSaveContext() + savedData := instance.Save(saveCtx) + + reloaded, err := prompty.LoadHostToolResult(savedData, loadCtx) + if err != nil { + t.Fatalf("Failed to reload HostToolResult: %v", err) + } + if reloaded.RequestId == nil || *reloaded.RequestId != "exec_abc123" { + t.Errorf(`Expected RequestId to be "exec_abc123", got %v`, reloaded.RequestId) + } + if reloaded.ToolCallId == nil || *reloaded.ToolCallId != "call_abc123" { + t.Errorf(`Expected ToolCallId to be "call_abc123", got %v`, reloaded.ToolCallId) + } + if reloaded.ToolName != "powershell" { + t.Errorf(`Expected ToolName to be "powershell", got %v`, reloaded.ToolName) + } + if reloaded.Success != true { + t.Errorf(`Expected Success to be true, got %v`, reloaded.Success) + } + if reloaded.ExitCode == nil || *reloaded.ExitCode != 0 { + t.Errorf(`Expected ExitCode to be 0, got %v`, reloaded.ExitCode) + } + if reloaded.DurationMs == nil || *reloaded.DurationMs != 250 { + t.Errorf(`Expected DurationMs to be 250, got %v`, reloaded.DurationMs) + } + if reloaded.ErrorKind == nil || *reloaded.ErrorKind != "timeout" { + t.Errorf(`Expected ErrorKind to be "timeout", got %v`, reloaded.ErrorKind) + } +} + +// TestHostToolResultToJSON tests that ToJSON produces valid JSON +func TestHostToolResultToJSON(t *testing.T) { + jsonData := ` +{ + "requestId": "exec_abc123", + "toolCallId": "call_abc123", + "toolName": "powershell", + "success": true, + "exitCode": 0, + "durationMs": 250, + "errorKind": "timeout" +} +` + var data map[string]interface{} + if err := json.Unmarshal([]byte(jsonData), &data); err != nil { + t.Fatalf("Failed to parse JSON: %v", err) + } + + ctx := prompty.NewLoadContext() + instance, err := prompty.LoadHostToolResult(data, ctx) + if err != nil { + t.Fatalf("Failed to load HostToolResult: %v", err) + } + jsonOutput, err := instance.ToJSON() + if err != nil { + t.Fatalf("Failed to convert to JSON: %v", err) + } + + var parsed map[string]interface{} + if err := json.Unmarshal([]byte(jsonOutput), &parsed); err != nil { + t.Fatalf("Failed to parse generated JSON: %v", err) + } + + reloaded, err := prompty.LoadHostToolResult(parsed, ctx) + if err != nil { + t.Fatalf("Failed to reload generated JSON: %v", err) + } + if reloaded.RequestId == nil || *reloaded.RequestId != "exec_abc123" { + t.Errorf(`Expected RequestId to be "exec_abc123", got %v`, reloaded.RequestId) + } + if reloaded.ToolCallId == nil || *reloaded.ToolCallId != "call_abc123" { + t.Errorf(`Expected ToolCallId to be "call_abc123", got %v`, reloaded.ToolCallId) + } + if reloaded.ToolName != "powershell" { + t.Errorf(`Expected ToolName to be "powershell", got %v`, reloaded.ToolName) + } + if reloaded.Success != true { + t.Errorf(`Expected Success to be true, got %v`, reloaded.Success) + } + if reloaded.ExitCode == nil || *reloaded.ExitCode != 0 { + t.Errorf(`Expected ExitCode to be 0, got %v`, reloaded.ExitCode) + } + if reloaded.DurationMs == nil || *reloaded.DurationMs != 250 { + t.Errorf(`Expected DurationMs to be 250, got %v`, reloaded.DurationMs) + } + if reloaded.ErrorKind == nil || *reloaded.ErrorKind != "timeout" { + t.Errorf(`Expected ErrorKind to be "timeout", got %v`, reloaded.ErrorKind) + } +} + +// TestHostToolResultToYAML tests that ToYAML produces valid YAML +func TestHostToolResultToYAML(t *testing.T) { + jsonData := ` +{ + "requestId": "exec_abc123", + "toolCallId": "call_abc123", + "toolName": "powershell", + "success": true, + "exitCode": 0, + "durationMs": 250, + "errorKind": "timeout" +} +` + var data map[string]interface{} + if err := json.Unmarshal([]byte(jsonData), &data); err != nil { + t.Fatalf("Failed to parse JSON: %v", err) + } + + ctx := prompty.NewLoadContext() + instance, err := prompty.LoadHostToolResult(data, ctx) + if err != nil { + t.Fatalf("Failed to load HostToolResult: %v", err) + } + yamlOutput, err := instance.ToYAML() + if err != nil { + t.Fatalf("Failed to convert to YAML: %v", err) + } + + var parsed map[string]interface{} + if err := yaml.Unmarshal([]byte(yamlOutput), &parsed); err != nil { + t.Fatalf("Failed to parse generated YAML: %v", err) + } + + reloaded, err := prompty.LoadHostToolResult(parsed, ctx) + if err != nil { + t.Fatalf("Failed to reload generated YAML: %v", err) + } + if reloaded.RequestId == nil || *reloaded.RequestId != "exec_abc123" { + t.Errorf(`Expected RequestId to be "exec_abc123", got %v`, reloaded.RequestId) + } + if reloaded.ToolCallId == nil || *reloaded.ToolCallId != "call_abc123" { + t.Errorf(`Expected ToolCallId to be "call_abc123", got %v`, reloaded.ToolCallId) + } + if reloaded.ToolName != "powershell" { + t.Errorf(`Expected ToolName to be "powershell", got %v`, reloaded.ToolName) + } + if reloaded.Success != true { + t.Errorf(`Expected Success to be true, got %v`, reloaded.Success) + } + if reloaded.ExitCode == nil || *reloaded.ExitCode != 0 { + t.Errorf(`Expected ExitCode to be 0, got %v`, reloaded.ExitCode) + } + if reloaded.DurationMs == nil || *reloaded.DurationMs != 250 { + t.Errorf(`Expected DurationMs to be 250, got %v`, reloaded.DurationMs) + } + if reloaded.ErrorKind == nil || *reloaded.ErrorKind != "timeout" { + t.Errorf(`Expected ErrorKind to be "timeout", got %v`, reloaded.ErrorKind) + } +} + +// TestHostToolResultFromJSONInvalid rejects malformed JSON instead of silently defaulting +func TestHostToolResultFromJSONInvalid(t *testing.T) { + if _, err := prompty.HostToolResultFromJSON("{"); err == nil { + t.Fatalf("Expected malformed JSON to fail") + } +} diff --git a/runtime/go/prompty/model/image_part_test.go b/runtime/go/prompty/model/image_part_test.go index fa60b8eb..93bc37fb 100644 --- a/runtime/go/prompty/model/image_part_test.go +++ b/runtime/go/prompty/model/image_part_test.go @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. package prompty_test @@ -70,6 +71,55 @@ mediaType: image/png } } +// TestImagePartFromJSON tests loading ImagePart through the generated JSON helper +func TestImagePartFromJSON(t *testing.T) { + jsonData := ` +{ + "source": "https://example.com/image.png", + "detail": "auto", + "mediaType": "image/png" +} +` + + instance, err := prompty.ImagePartFromJSON(jsonData) + if err != nil { + t.Fatalf("Failed to load ImagePart from JSON helper: %v", err) + } + if instance.Source != "https://example.com/image.png" { + t.Errorf(`Expected Source to be "https://example.com/image.png", got %v`, instance.Source) + } + if instance.Detail == nil || *instance.Detail != "auto" { + t.Errorf(`Expected Detail to be "auto", got %v`, instance.Detail) + } + if instance.MediaType == nil || *instance.MediaType != "image/png" { + t.Errorf(`Expected MediaType to be "image/png", got %v`, instance.MediaType) + } +} + +// TestImagePartFromYAML tests loading ImagePart through the generated YAML helper +func TestImagePartFromYAML(t *testing.T) { + yamlData := ` +source: "https://example.com/image.png" +detail: auto +mediaType: image/png + +` + + instance, err := prompty.ImagePartFromYAML(yamlData) + if err != nil { + t.Fatalf("Failed to load ImagePart from YAML helper: %v", err) + } + if instance.Source != "https://example.com/image.png" { + t.Errorf(`Expected Source to be "https://example.com/image.png", got %v`, instance.Source) + } + if instance.Detail == nil || *instance.Detail != "auto" { + t.Errorf(`Expected Detail to be "auto", got %v`, instance.Detail) + } + if instance.MediaType == nil || *instance.MediaType != "image/png" { + t.Errorf(`Expected MediaType to be "image/png", got %v`, instance.MediaType) + } +} + // TestImagePartRoundtrip tests load -> save -> load produces equivalent data func TestImagePartRoundtrip(t *testing.T) { jsonData := ` @@ -135,6 +185,20 @@ func TestImagePartToJSON(t *testing.T) { if err := json.Unmarshal([]byte(jsonOutput), &parsed); err != nil { t.Fatalf("Failed to parse generated JSON: %v", err) } + + reloaded, err := prompty.LoadImagePart(parsed, ctx) + if err != nil { + t.Fatalf("Failed to reload generated JSON: %v", err) + } + if reloaded.Source != "https://example.com/image.png" { + t.Errorf(`Expected Source to be "https://example.com/image.png", got %v`, reloaded.Source) + } + if reloaded.Detail == nil || *reloaded.Detail != "auto" { + t.Errorf(`Expected Detail to be "auto", got %v`, reloaded.Detail) + } + if reloaded.MediaType == nil || *reloaded.MediaType != "image/png" { + t.Errorf(`Expected MediaType to be "image/png", got %v`, reloaded.MediaType) + } } // TestImagePartToYAML tests that ToYAML produces valid YAML @@ -165,4 +229,25 @@ func TestImagePartToYAML(t *testing.T) { if err := yaml.Unmarshal([]byte(yamlOutput), &parsed); err != nil { t.Fatalf("Failed to parse generated YAML: %v", err) } + + reloaded, err := prompty.LoadImagePart(parsed, ctx) + if err != nil { + t.Fatalf("Failed to reload generated YAML: %v", err) + } + if reloaded.Source != "https://example.com/image.png" { + t.Errorf(`Expected Source to be "https://example.com/image.png", got %v`, reloaded.Source) + } + if reloaded.Detail == nil || *reloaded.Detail != "auto" { + t.Errorf(`Expected Detail to be "auto", got %v`, reloaded.Detail) + } + if reloaded.MediaType == nil || *reloaded.MediaType != "image/png" { + t.Errorf(`Expected MediaType to be "image/png", got %v`, reloaded.MediaType) + } +} + +// TestImagePartFromJSONInvalid rejects malformed JSON instead of silently defaulting +func TestImagePartFromJSONInvalid(t *testing.T) { + if _, err := prompty.ImagePartFromJSON("{"); err == nil { + t.Fatalf("Expected malformed JSON to fail") + } } diff --git a/runtime/go/prompty/model/invoker_error.go b/runtime/go/prompty/model/invoker_error.go index 48067ce7..4d784afd 100644 --- a/runtime/go/prompty/model/invoker_error.go +++ b/runtime/go/prompty/model/invoker_error.go @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. // Group: core package prompty @@ -40,7 +41,7 @@ func LoadInvokerError(data interface{}, ctx *LoadContext) (InvokerError, error) } // Save serializes InvokerError to map[string]interface{} -func (obj *InvokerError) Save(ctx *SaveContext) map[string]interface{} { +func (obj InvokerError) Save(ctx *SaveContext) map[string]interface{} { result := make(map[string]interface{}) result["message"] = obj.Message result["component"] = obj.Component @@ -64,11 +65,7 @@ func (obj *InvokerError) ToJSON() (string, error) { func (obj *InvokerError) ToYAML() (string, error) { ctx := NewSaveContext() data := obj.Save(ctx) - bytes, err := yaml.Marshal(data) - if err != nil { - return "", err - } - return string(bytes), nil + return marshalYAMLDocument(data) } // FromJSON creates InvokerError from JSON string diff --git a/runtime/go/prompty/model/invoker_error_test.go b/runtime/go/prompty/model/invoker_error_test.go index 8a46c697..7d8ae6ae 100644 --- a/runtime/go/prompty/model/invoker_error_test.go +++ b/runtime/go/prompty/model/invoker_error_test.go @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. package prompty_test @@ -70,6 +71,55 @@ key: jinja2 } } +// TestInvokerErrorFromJSON tests loading InvokerError through the generated JSON helper +func TestInvokerErrorFromJSON(t *testing.T) { + jsonData := ` +{ + "message": "No renderer registered for key: jinja2", + "component": "renderer", + "key": "jinja2" +} +` + + instance, err := prompty.InvokerErrorFromJSON(jsonData) + if err != nil { + t.Fatalf("Failed to load InvokerError from JSON helper: %v", err) + } + if instance.Message != "No renderer registered for key: jinja2" { + t.Errorf(`Expected Message to be "No renderer registered for key: jinja2", got %v`, instance.Message) + } + if instance.Component != "renderer" { + t.Errorf(`Expected Component to be "renderer", got %v`, instance.Component) + } + if instance.Key != "jinja2" { + t.Errorf(`Expected Key to be "jinja2", got %v`, instance.Key) + } +} + +// TestInvokerErrorFromYAML tests loading InvokerError through the generated YAML helper +func TestInvokerErrorFromYAML(t *testing.T) { + yamlData := ` +message: "No renderer registered for key: jinja2" +component: renderer +key: jinja2 + +` + + instance, err := prompty.InvokerErrorFromYAML(yamlData) + if err != nil { + t.Fatalf("Failed to load InvokerError from YAML helper: %v", err) + } + if instance.Message != "No renderer registered for key: jinja2" { + t.Errorf(`Expected Message to be "No renderer registered for key: jinja2", got %v`, instance.Message) + } + if instance.Component != "renderer" { + t.Errorf(`Expected Component to be "renderer", got %v`, instance.Component) + } + if instance.Key != "jinja2" { + t.Errorf(`Expected Key to be "jinja2", got %v`, instance.Key) + } +} + // TestInvokerErrorRoundtrip tests load -> save -> load produces equivalent data func TestInvokerErrorRoundtrip(t *testing.T) { jsonData := ` @@ -135,6 +185,20 @@ func TestInvokerErrorToJSON(t *testing.T) { if err := json.Unmarshal([]byte(jsonOutput), &parsed); err != nil { t.Fatalf("Failed to parse generated JSON: %v", err) } + + reloaded, err := prompty.LoadInvokerError(parsed, ctx) + if err != nil { + t.Fatalf("Failed to reload generated JSON: %v", err) + } + if reloaded.Message != "No renderer registered for key: jinja2" { + t.Errorf(`Expected Message to be "No renderer registered for key: jinja2", got %v`, reloaded.Message) + } + if reloaded.Component != "renderer" { + t.Errorf(`Expected Component to be "renderer", got %v`, reloaded.Component) + } + if reloaded.Key != "jinja2" { + t.Errorf(`Expected Key to be "jinja2", got %v`, reloaded.Key) + } } // TestInvokerErrorToYAML tests that ToYAML produces valid YAML @@ -165,4 +229,25 @@ func TestInvokerErrorToYAML(t *testing.T) { if err := yaml.Unmarshal([]byte(yamlOutput), &parsed); err != nil { t.Fatalf("Failed to parse generated YAML: %v", err) } + + reloaded, err := prompty.LoadInvokerError(parsed, ctx) + if err != nil { + t.Fatalf("Failed to reload generated YAML: %v", err) + } + if reloaded.Message != "No renderer registered for key: jinja2" { + t.Errorf(`Expected Message to be "No renderer registered for key: jinja2", got %v`, reloaded.Message) + } + if reloaded.Component != "renderer" { + t.Errorf(`Expected Component to be "renderer", got %v`, reloaded.Component) + } + if reloaded.Key != "jinja2" { + t.Errorf(`Expected Key to be "jinja2", got %v`, reloaded.Key) + } +} + +// TestInvokerErrorFromJSONInvalid rejects malformed JSON instead of silently defaulting +func TestInvokerErrorFromJSONInvalid(t *testing.T) { + if _, err := prompty.InvokerErrorFromJSON("{"); err == nil { + t.Fatalf("Expected malformed JSON to fail") + } } diff --git a/runtime/go/prompty/model/llm_complete_payload.go b/runtime/go/prompty/model/llm_complete_payload.go new file mode 100644 index 00000000..d2079b2e --- /dev/null +++ b/runtime/go/prompty/model/llm_complete_payload.go @@ -0,0 +1,118 @@ +// +// Code generated by Typra emitter; DO NOT EDIT. +// Group: events + +package prompty + +import ( + "encoding/json" + + "gopkg.in/yaml.v3" +) + +// LlmCompletePayload represents Payload for "llm_complete" events — an LLM request completed. + +type LlmCompletePayload struct { + RequestId *string `json:"requestId,omitempty" yaml:"requestId,omitempty"` + ServiceRequestId *string `json:"serviceRequestId,omitempty" yaml:"serviceRequestId,omitempty"` + Usage *TokenUsage `json:"usage,omitempty" yaml:"usage,omitempty"` + DurationMs *float64 `json:"durationMs,omitempty" yaml:"durationMs,omitempty"` +} + +// LoadLlmCompletePayload creates a LlmCompletePayload from a map[string]interface{} +func LoadLlmCompletePayload(data interface{}, ctx *LoadContext) (LlmCompletePayload, error) { + result := LlmCompletePayload{} + + // Load from map + if m, ok := data.(map[string]interface{}); ok { + if val, ok := m["requestId"]; ok && val != nil { + v := string(val.(string)) + result.RequestId = &v + } + if val, ok := m["serviceRequestId"]; ok && val != nil { + v := string(val.(string)) + result.ServiceRequestId = &v + } + if val, ok := m["usage"]; ok && val != nil { + if m, ok := val.(map[string]interface{}); ok { + loaded, _ := LoadTokenUsage(m, ctx) + result.Usage = &loaded + } + } + if val, ok := m["durationMs"]; ok && val != nil { // Handle various numeric types from JSON/YAML/roundtrip + var v float64 + switch n := val.(type) { + case int: + v = float64(n) + case int32: + v = float64(n) + case int64: + v = float64(n) + case float32: + v = float64(n) + case float64: + v = n + } + result.DurationMs = &v + } + } + + return result, nil +} + +// Save serializes LlmCompletePayload to map[string]interface{} +func (obj LlmCompletePayload) Save(ctx *SaveContext) map[string]interface{} { + result := make(map[string]interface{}) + if obj.RequestId != nil { + result["requestId"] = *obj.RequestId + } + if obj.ServiceRequestId != nil { + result["serviceRequestId"] = *obj.ServiceRequestId + } + if obj.Usage != nil { + result["usage"] = obj.Usage.Save(ctx) + } + if obj.DurationMs != nil { + result["durationMs"] = *obj.DurationMs + } + + return result +} + +// ToJSON serializes LlmCompletePayload to JSON string +func (obj *LlmCompletePayload) ToJSON() (string, error) { + ctx := NewSaveContext() + data := obj.Save(ctx) + bytes, err := json.Marshal(data) + if err != nil { + return "", err + } + return string(bytes), nil +} + +// ToYAML serializes LlmCompletePayload to YAML string +func (obj *LlmCompletePayload) ToYAML() (string, error) { + ctx := NewSaveContext() + data := obj.Save(ctx) + return marshalYAMLDocument(data) +} + +// FromJSON creates LlmCompletePayload from JSON string +func LlmCompletePayloadFromJSON(jsonStr string) (LlmCompletePayload, error) { + var data map[string]interface{} + if err := json.Unmarshal([]byte(jsonStr), &data); err != nil { + return LlmCompletePayload{}, err + } + ctx := NewLoadContext() + return LoadLlmCompletePayload(data, ctx) +} + +// FromYAML creates LlmCompletePayload from YAML string +func LlmCompletePayloadFromYAML(yamlStr string) (LlmCompletePayload, error) { + var data map[string]interface{} + if err := yaml.Unmarshal([]byte(yamlStr), &data); err != nil { + return LlmCompletePayload{}, err + } + ctx := NewLoadContext() + return LoadLlmCompletePayload(data, ctx) +} diff --git a/runtime/go/prompty/model/llm_complete_payload_test.go b/runtime/go/prompty/model/llm_complete_payload_test.go new file mode 100644 index 00000000..7836064e --- /dev/null +++ b/runtime/go/prompty/model/llm_complete_payload_test.go @@ -0,0 +1,253 @@ +// +// Code generated by Typra emitter; DO NOT EDIT. + +package prompty_test + +import ( + "encoding/json" + "testing" + + "gopkg.in/yaml.v3" + + "prompty/model" +) + +// TestLlmCompletePayloadLoadJSON tests loading LlmCompletePayload from JSON +func TestLlmCompletePayloadLoadJSON(t *testing.T) { + jsonData := ` +{ + "requestId": "req_abc123", + "serviceRequestId": "srv_abc123", + "durationMs": 820 +} +` + var data map[string]interface{} + if err := json.Unmarshal([]byte(jsonData), &data); err != nil { + t.Fatalf("Failed to parse JSON: %v", err) + } + + ctx := prompty.NewLoadContext() + instance, err := prompty.LoadLlmCompletePayload(data, ctx) + if err != nil { + t.Fatalf("Failed to load LlmCompletePayload: %v", err) + } + if instance.RequestId == nil || *instance.RequestId != "req_abc123" { + t.Errorf(`Expected RequestId to be "req_abc123", got %v`, instance.RequestId) + } + if instance.ServiceRequestId == nil || *instance.ServiceRequestId != "srv_abc123" { + t.Errorf(`Expected ServiceRequestId to be "srv_abc123", got %v`, instance.ServiceRequestId) + } + if instance.DurationMs == nil || *instance.DurationMs != 820 { + t.Errorf(`Expected DurationMs to be 820, got %v`, instance.DurationMs) + } +} + +// TestLlmCompletePayloadLoadYAML tests loading LlmCompletePayload from YAML +func TestLlmCompletePayloadLoadYAML(t *testing.T) { + yamlData := ` +requestId: req_abc123 +serviceRequestId: srv_abc123 +durationMs: 820 + +` + var data map[string]interface{} + if err := yaml.Unmarshal([]byte(yamlData), &data); err != nil { + t.Fatalf("Failed to parse YAML: %v", err) + } + + ctx := prompty.NewLoadContext() + instance, err := prompty.LoadLlmCompletePayload(data, ctx) + if err != nil { + t.Fatalf("Failed to load LlmCompletePayload: %v", err) + } + if instance.RequestId == nil || *instance.RequestId != "req_abc123" { + t.Errorf(`Expected RequestId to be "req_abc123", got %v`, instance.RequestId) + } + if instance.ServiceRequestId == nil || *instance.ServiceRequestId != "srv_abc123" { + t.Errorf(`Expected ServiceRequestId to be "srv_abc123", got %v`, instance.ServiceRequestId) + } + if instance.DurationMs == nil || *instance.DurationMs != 820 { + t.Errorf(`Expected DurationMs to be 820, got %v`, instance.DurationMs) + } +} + +// TestLlmCompletePayloadFromJSON tests loading LlmCompletePayload through the generated JSON helper +func TestLlmCompletePayloadFromJSON(t *testing.T) { + jsonData := ` +{ + "requestId": "req_abc123", + "serviceRequestId": "srv_abc123", + "durationMs": 820 +} +` + + instance, err := prompty.LlmCompletePayloadFromJSON(jsonData) + if err != nil { + t.Fatalf("Failed to load LlmCompletePayload from JSON helper: %v", err) + } + if instance.RequestId == nil || *instance.RequestId != "req_abc123" { + t.Errorf(`Expected RequestId to be "req_abc123", got %v`, instance.RequestId) + } + if instance.ServiceRequestId == nil || *instance.ServiceRequestId != "srv_abc123" { + t.Errorf(`Expected ServiceRequestId to be "srv_abc123", got %v`, instance.ServiceRequestId) + } + if instance.DurationMs == nil || *instance.DurationMs != 820 { + t.Errorf(`Expected DurationMs to be 820, got %v`, instance.DurationMs) + } +} + +// TestLlmCompletePayloadFromYAML tests loading LlmCompletePayload through the generated YAML helper +func TestLlmCompletePayloadFromYAML(t *testing.T) { + yamlData := ` +requestId: req_abc123 +serviceRequestId: srv_abc123 +durationMs: 820 + +` + + instance, err := prompty.LlmCompletePayloadFromYAML(yamlData) + if err != nil { + t.Fatalf("Failed to load LlmCompletePayload from YAML helper: %v", err) + } + if instance.RequestId == nil || *instance.RequestId != "req_abc123" { + t.Errorf(`Expected RequestId to be "req_abc123", got %v`, instance.RequestId) + } + if instance.ServiceRequestId == nil || *instance.ServiceRequestId != "srv_abc123" { + t.Errorf(`Expected ServiceRequestId to be "srv_abc123", got %v`, instance.ServiceRequestId) + } + if instance.DurationMs == nil || *instance.DurationMs != 820 { + t.Errorf(`Expected DurationMs to be 820, got %v`, instance.DurationMs) + } +} + +// TestLlmCompletePayloadRoundtrip tests load -> save -> load produces equivalent data +func TestLlmCompletePayloadRoundtrip(t *testing.T) { + jsonData := ` +{ + "requestId": "req_abc123", + "serviceRequestId": "srv_abc123", + "durationMs": 820 +} +` + var data map[string]interface{} + if err := json.Unmarshal([]byte(jsonData), &data); err != nil { + t.Fatalf("Failed to parse JSON: %v", err) + } + + loadCtx := prompty.NewLoadContext() + instance, err := prompty.LoadLlmCompletePayload(data, loadCtx) + if err != nil { + t.Fatalf("Failed to load LlmCompletePayload: %v", err) + } + saveCtx := prompty.NewSaveContext() + savedData := instance.Save(saveCtx) + + reloaded, err := prompty.LoadLlmCompletePayload(savedData, loadCtx) + if err != nil { + t.Fatalf("Failed to reload LlmCompletePayload: %v", err) + } + if reloaded.RequestId == nil || *reloaded.RequestId != "req_abc123" { + t.Errorf(`Expected RequestId to be "req_abc123", got %v`, reloaded.RequestId) + } + if reloaded.ServiceRequestId == nil || *reloaded.ServiceRequestId != "srv_abc123" { + t.Errorf(`Expected ServiceRequestId to be "srv_abc123", got %v`, reloaded.ServiceRequestId) + } + if reloaded.DurationMs == nil || *reloaded.DurationMs != 820 { + t.Errorf(`Expected DurationMs to be 820, got %v`, reloaded.DurationMs) + } +} + +// TestLlmCompletePayloadToJSON tests that ToJSON produces valid JSON +func TestLlmCompletePayloadToJSON(t *testing.T) { + jsonData := ` +{ + "requestId": "req_abc123", + "serviceRequestId": "srv_abc123", + "durationMs": 820 +} +` + var data map[string]interface{} + if err := json.Unmarshal([]byte(jsonData), &data); err != nil { + t.Fatalf("Failed to parse JSON: %v", err) + } + + ctx := prompty.NewLoadContext() + instance, err := prompty.LoadLlmCompletePayload(data, ctx) + if err != nil { + t.Fatalf("Failed to load LlmCompletePayload: %v", err) + } + jsonOutput, err := instance.ToJSON() + if err != nil { + t.Fatalf("Failed to convert to JSON: %v", err) + } + + var parsed map[string]interface{} + if err := json.Unmarshal([]byte(jsonOutput), &parsed); err != nil { + t.Fatalf("Failed to parse generated JSON: %v", err) + } + + reloaded, err := prompty.LoadLlmCompletePayload(parsed, ctx) + if err != nil { + t.Fatalf("Failed to reload generated JSON: %v", err) + } + if reloaded.RequestId == nil || *reloaded.RequestId != "req_abc123" { + t.Errorf(`Expected RequestId to be "req_abc123", got %v`, reloaded.RequestId) + } + if reloaded.ServiceRequestId == nil || *reloaded.ServiceRequestId != "srv_abc123" { + t.Errorf(`Expected ServiceRequestId to be "srv_abc123", got %v`, reloaded.ServiceRequestId) + } + if reloaded.DurationMs == nil || *reloaded.DurationMs != 820 { + t.Errorf(`Expected DurationMs to be 820, got %v`, reloaded.DurationMs) + } +} + +// TestLlmCompletePayloadToYAML tests that ToYAML produces valid YAML +func TestLlmCompletePayloadToYAML(t *testing.T) { + jsonData := ` +{ + "requestId": "req_abc123", + "serviceRequestId": "srv_abc123", + "durationMs": 820 +} +` + var data map[string]interface{} + if err := json.Unmarshal([]byte(jsonData), &data); err != nil { + t.Fatalf("Failed to parse JSON: %v", err) + } + + ctx := prompty.NewLoadContext() + instance, err := prompty.LoadLlmCompletePayload(data, ctx) + if err != nil { + t.Fatalf("Failed to load LlmCompletePayload: %v", err) + } + yamlOutput, err := instance.ToYAML() + if err != nil { + t.Fatalf("Failed to convert to YAML: %v", err) + } + + var parsed map[string]interface{} + if err := yaml.Unmarshal([]byte(yamlOutput), &parsed); err != nil { + t.Fatalf("Failed to parse generated YAML: %v", err) + } + + reloaded, err := prompty.LoadLlmCompletePayload(parsed, ctx) + if err != nil { + t.Fatalf("Failed to reload generated YAML: %v", err) + } + if reloaded.RequestId == nil || *reloaded.RequestId != "req_abc123" { + t.Errorf(`Expected RequestId to be "req_abc123", got %v`, reloaded.RequestId) + } + if reloaded.ServiceRequestId == nil || *reloaded.ServiceRequestId != "srv_abc123" { + t.Errorf(`Expected ServiceRequestId to be "srv_abc123", got %v`, reloaded.ServiceRequestId) + } + if reloaded.DurationMs == nil || *reloaded.DurationMs != 820 { + t.Errorf(`Expected DurationMs to be 820, got %v`, reloaded.DurationMs) + } +} + +// TestLlmCompletePayloadFromJSONInvalid rejects malformed JSON instead of silently defaulting +func TestLlmCompletePayloadFromJSONInvalid(t *testing.T) { + if _, err := prompty.LlmCompletePayloadFromJSON("{"); err == nil { + t.Fatalf("Expected malformed JSON to fail") + } +} diff --git a/runtime/go/prompty/model/llm_start_payload.go b/runtime/go/prompty/model/llm_start_payload.go new file mode 100644 index 00000000..5f275694 --- /dev/null +++ b/runtime/go/prompty/model/llm_start_payload.go @@ -0,0 +1,124 @@ +// +// Code generated by Typra emitter; DO NOT EDIT. +// Group: events + +package prompty + +import ( + "encoding/json" + + "gopkg.in/yaml.v3" +) + +// LlmStartPayload represents Payload for "llm_start" events — an LLM request is about to be sent. + +type LlmStartPayload struct { + Provider *string `json:"provider,omitempty" yaml:"provider,omitempty"` + ModelId *string `json:"modelId,omitempty" yaml:"modelId,omitempty"` + MessageCount *int32 `json:"messageCount,omitempty" yaml:"messageCount,omitempty"` + Attempt *int32 `json:"attempt,omitempty" yaml:"attempt,omitempty"` +} + +// LoadLlmStartPayload creates a LlmStartPayload from a map[string]interface{} +func LoadLlmStartPayload(data interface{}, ctx *LoadContext) (LlmStartPayload, error) { + result := LlmStartPayload{} + + // Load from map + if m, ok := data.(map[string]interface{}); ok { + if val, ok := m["provider"]; ok && val != nil { + v := string(val.(string)) + result.Provider = &v + } + if val, ok := m["modelId"]; ok && val != nil { + v := string(val.(string)) + result.ModelId = &v + } + if val, ok := m["messageCount"]; ok && val != nil { // Handle various numeric types from JSON/YAML/roundtrip + var v int32 + switch n := val.(type) { + case int: + v = int32(n) + case int32: + v = int32(n) + case int64: + v = int32(n) + case float64: + v = int32(n) + } + result.MessageCount = &v + } + if val, ok := m["attempt"]; ok && val != nil { // Handle various numeric types from JSON/YAML/roundtrip + var v int32 + switch n := val.(type) { + case int: + v = int32(n) + case int32: + v = int32(n) + case int64: + v = int32(n) + case float64: + v = int32(n) + } + result.Attempt = &v + } + } + + return result, nil +} + +// Save serializes LlmStartPayload to map[string]interface{} +func (obj LlmStartPayload) Save(ctx *SaveContext) map[string]interface{} { + result := make(map[string]interface{}) + if obj.Provider != nil { + result["provider"] = *obj.Provider + } + if obj.ModelId != nil { + result["modelId"] = *obj.ModelId + } + if obj.MessageCount != nil { + result["messageCount"] = *obj.MessageCount + } + if obj.Attempt != nil { + result["attempt"] = *obj.Attempt + } + + return result +} + +// ToJSON serializes LlmStartPayload to JSON string +func (obj *LlmStartPayload) ToJSON() (string, error) { + ctx := NewSaveContext() + data := obj.Save(ctx) + bytes, err := json.Marshal(data) + if err != nil { + return "", err + } + return string(bytes), nil +} + +// ToYAML serializes LlmStartPayload to YAML string +func (obj *LlmStartPayload) ToYAML() (string, error) { + ctx := NewSaveContext() + data := obj.Save(ctx) + return marshalYAMLDocument(data) +} + +// FromJSON creates LlmStartPayload from JSON string +func LlmStartPayloadFromJSON(jsonStr string) (LlmStartPayload, error) { + var data map[string]interface{} + if err := json.Unmarshal([]byte(jsonStr), &data); err != nil { + return LlmStartPayload{}, err + } + ctx := NewLoadContext() + return LoadLlmStartPayload(data, ctx) +} + +// FromYAML creates LlmStartPayload from YAML string +func LlmStartPayloadFromYAML(yamlStr string) (LlmStartPayload, error) { + var data map[string]interface{} + if err := yaml.Unmarshal([]byte(yamlStr), &data); err != nil { + return LlmStartPayload{}, err + } + ctx := NewLoadContext() + return LoadLlmStartPayload(data, ctx) +} diff --git a/runtime/go/prompty/model/llm_start_payload_test.go b/runtime/go/prompty/model/llm_start_payload_test.go new file mode 100644 index 00000000..38cecf57 --- /dev/null +++ b/runtime/go/prompty/model/llm_start_payload_test.go @@ -0,0 +1,281 @@ +// +// Code generated by Typra emitter; DO NOT EDIT. + +package prompty_test + +import ( + "encoding/json" + "testing" + + "gopkg.in/yaml.v3" + + "prompty/model" +) + +// TestLlmStartPayloadLoadJSON tests loading LlmStartPayload from JSON +func TestLlmStartPayloadLoadJSON(t *testing.T) { + jsonData := ` +{ + "provider": "openai", + "modelId": "gpt-4o-mini", + "messageCount": 4, + "attempt": 0 +} +` + var data map[string]interface{} + if err := json.Unmarshal([]byte(jsonData), &data); err != nil { + t.Fatalf("Failed to parse JSON: %v", err) + } + + ctx := prompty.NewLoadContext() + instance, err := prompty.LoadLlmStartPayload(data, ctx) + if err != nil { + t.Fatalf("Failed to load LlmStartPayload: %v", err) + } + if instance.Provider == nil || *instance.Provider != "openai" { + t.Errorf(`Expected Provider to be "openai", got %v`, instance.Provider) + } + if instance.ModelId == nil || *instance.ModelId != "gpt-4o-mini" { + t.Errorf(`Expected ModelId to be "gpt-4o-mini", got %v`, instance.ModelId) + } + if instance.MessageCount == nil || *instance.MessageCount != 4 { + t.Errorf(`Expected MessageCount to be 4, got %v`, instance.MessageCount) + } + if instance.Attempt == nil || *instance.Attempt != 0 { + t.Errorf(`Expected Attempt to be 0, got %v`, instance.Attempt) + } +} + +// TestLlmStartPayloadLoadYAML tests loading LlmStartPayload from YAML +func TestLlmStartPayloadLoadYAML(t *testing.T) { + yamlData := ` +provider: openai +modelId: gpt-4o-mini +messageCount: 4 +attempt: 0 + +` + var data map[string]interface{} + if err := yaml.Unmarshal([]byte(yamlData), &data); err != nil { + t.Fatalf("Failed to parse YAML: %v", err) + } + + ctx := prompty.NewLoadContext() + instance, err := prompty.LoadLlmStartPayload(data, ctx) + if err != nil { + t.Fatalf("Failed to load LlmStartPayload: %v", err) + } + if instance.Provider == nil || *instance.Provider != "openai" { + t.Errorf(`Expected Provider to be "openai", got %v`, instance.Provider) + } + if instance.ModelId == nil || *instance.ModelId != "gpt-4o-mini" { + t.Errorf(`Expected ModelId to be "gpt-4o-mini", got %v`, instance.ModelId) + } + if instance.MessageCount == nil || *instance.MessageCount != 4 { + t.Errorf(`Expected MessageCount to be 4, got %v`, instance.MessageCount) + } + if instance.Attempt == nil || *instance.Attempt != 0 { + t.Errorf(`Expected Attempt to be 0, got %v`, instance.Attempt) + } +} + +// TestLlmStartPayloadFromJSON tests loading LlmStartPayload through the generated JSON helper +func TestLlmStartPayloadFromJSON(t *testing.T) { + jsonData := ` +{ + "provider": "openai", + "modelId": "gpt-4o-mini", + "messageCount": 4, + "attempt": 0 +} +` + + instance, err := prompty.LlmStartPayloadFromJSON(jsonData) + if err != nil { + t.Fatalf("Failed to load LlmStartPayload from JSON helper: %v", err) + } + if instance.Provider == nil || *instance.Provider != "openai" { + t.Errorf(`Expected Provider to be "openai", got %v`, instance.Provider) + } + if instance.ModelId == nil || *instance.ModelId != "gpt-4o-mini" { + t.Errorf(`Expected ModelId to be "gpt-4o-mini", got %v`, instance.ModelId) + } + if instance.MessageCount == nil || *instance.MessageCount != 4 { + t.Errorf(`Expected MessageCount to be 4, got %v`, instance.MessageCount) + } + if instance.Attempt == nil || *instance.Attempt != 0 { + t.Errorf(`Expected Attempt to be 0, got %v`, instance.Attempt) + } +} + +// TestLlmStartPayloadFromYAML tests loading LlmStartPayload through the generated YAML helper +func TestLlmStartPayloadFromYAML(t *testing.T) { + yamlData := ` +provider: openai +modelId: gpt-4o-mini +messageCount: 4 +attempt: 0 + +` + + instance, err := prompty.LlmStartPayloadFromYAML(yamlData) + if err != nil { + t.Fatalf("Failed to load LlmStartPayload from YAML helper: %v", err) + } + if instance.Provider == nil || *instance.Provider != "openai" { + t.Errorf(`Expected Provider to be "openai", got %v`, instance.Provider) + } + if instance.ModelId == nil || *instance.ModelId != "gpt-4o-mini" { + t.Errorf(`Expected ModelId to be "gpt-4o-mini", got %v`, instance.ModelId) + } + if instance.MessageCount == nil || *instance.MessageCount != 4 { + t.Errorf(`Expected MessageCount to be 4, got %v`, instance.MessageCount) + } + if instance.Attempt == nil || *instance.Attempt != 0 { + t.Errorf(`Expected Attempt to be 0, got %v`, instance.Attempt) + } +} + +// TestLlmStartPayloadRoundtrip tests load -> save -> load produces equivalent data +func TestLlmStartPayloadRoundtrip(t *testing.T) { + jsonData := ` +{ + "provider": "openai", + "modelId": "gpt-4o-mini", + "messageCount": 4, + "attempt": 0 +} +` + var data map[string]interface{} + if err := json.Unmarshal([]byte(jsonData), &data); err != nil { + t.Fatalf("Failed to parse JSON: %v", err) + } + + loadCtx := prompty.NewLoadContext() + instance, err := prompty.LoadLlmStartPayload(data, loadCtx) + if err != nil { + t.Fatalf("Failed to load LlmStartPayload: %v", err) + } + saveCtx := prompty.NewSaveContext() + savedData := instance.Save(saveCtx) + + reloaded, err := prompty.LoadLlmStartPayload(savedData, loadCtx) + if err != nil { + t.Fatalf("Failed to reload LlmStartPayload: %v", err) + } + if reloaded.Provider == nil || *reloaded.Provider != "openai" { + t.Errorf(`Expected Provider to be "openai", got %v`, reloaded.Provider) + } + if reloaded.ModelId == nil || *reloaded.ModelId != "gpt-4o-mini" { + t.Errorf(`Expected ModelId to be "gpt-4o-mini", got %v`, reloaded.ModelId) + } + if reloaded.MessageCount == nil || *reloaded.MessageCount != 4 { + t.Errorf(`Expected MessageCount to be 4, got %v`, reloaded.MessageCount) + } + if reloaded.Attempt == nil || *reloaded.Attempt != 0 { + t.Errorf(`Expected Attempt to be 0, got %v`, reloaded.Attempt) + } +} + +// TestLlmStartPayloadToJSON tests that ToJSON produces valid JSON +func TestLlmStartPayloadToJSON(t *testing.T) { + jsonData := ` +{ + "provider": "openai", + "modelId": "gpt-4o-mini", + "messageCount": 4, + "attempt": 0 +} +` + var data map[string]interface{} + if err := json.Unmarshal([]byte(jsonData), &data); err != nil { + t.Fatalf("Failed to parse JSON: %v", err) + } + + ctx := prompty.NewLoadContext() + instance, err := prompty.LoadLlmStartPayload(data, ctx) + if err != nil { + t.Fatalf("Failed to load LlmStartPayload: %v", err) + } + jsonOutput, err := instance.ToJSON() + if err != nil { + t.Fatalf("Failed to convert to JSON: %v", err) + } + + var parsed map[string]interface{} + if err := json.Unmarshal([]byte(jsonOutput), &parsed); err != nil { + t.Fatalf("Failed to parse generated JSON: %v", err) + } + + reloaded, err := prompty.LoadLlmStartPayload(parsed, ctx) + if err != nil { + t.Fatalf("Failed to reload generated JSON: %v", err) + } + if reloaded.Provider == nil || *reloaded.Provider != "openai" { + t.Errorf(`Expected Provider to be "openai", got %v`, reloaded.Provider) + } + if reloaded.ModelId == nil || *reloaded.ModelId != "gpt-4o-mini" { + t.Errorf(`Expected ModelId to be "gpt-4o-mini", got %v`, reloaded.ModelId) + } + if reloaded.MessageCount == nil || *reloaded.MessageCount != 4 { + t.Errorf(`Expected MessageCount to be 4, got %v`, reloaded.MessageCount) + } + if reloaded.Attempt == nil || *reloaded.Attempt != 0 { + t.Errorf(`Expected Attempt to be 0, got %v`, reloaded.Attempt) + } +} + +// TestLlmStartPayloadToYAML tests that ToYAML produces valid YAML +func TestLlmStartPayloadToYAML(t *testing.T) { + jsonData := ` +{ + "provider": "openai", + "modelId": "gpt-4o-mini", + "messageCount": 4, + "attempt": 0 +} +` + var data map[string]interface{} + if err := json.Unmarshal([]byte(jsonData), &data); err != nil { + t.Fatalf("Failed to parse JSON: %v", err) + } + + ctx := prompty.NewLoadContext() + instance, err := prompty.LoadLlmStartPayload(data, ctx) + if err != nil { + t.Fatalf("Failed to load LlmStartPayload: %v", err) + } + yamlOutput, err := instance.ToYAML() + if err != nil { + t.Fatalf("Failed to convert to YAML: %v", err) + } + + var parsed map[string]interface{} + if err := yaml.Unmarshal([]byte(yamlOutput), &parsed); err != nil { + t.Fatalf("Failed to parse generated YAML: %v", err) + } + + reloaded, err := prompty.LoadLlmStartPayload(parsed, ctx) + if err != nil { + t.Fatalf("Failed to reload generated YAML: %v", err) + } + if reloaded.Provider == nil || *reloaded.Provider != "openai" { + t.Errorf(`Expected Provider to be "openai", got %v`, reloaded.Provider) + } + if reloaded.ModelId == nil || *reloaded.ModelId != "gpt-4o-mini" { + t.Errorf(`Expected ModelId to be "gpt-4o-mini", got %v`, reloaded.ModelId) + } + if reloaded.MessageCount == nil || *reloaded.MessageCount != 4 { + t.Errorf(`Expected MessageCount to be 4, got %v`, reloaded.MessageCount) + } + if reloaded.Attempt == nil || *reloaded.Attempt != 0 { + t.Errorf(`Expected Attempt to be 0, got %v`, reloaded.Attempt) + } +} + +// TestLlmStartPayloadFromJSONInvalid rejects malformed JSON instead of silently defaulting +func TestLlmStartPayloadFromJSONInvalid(t *testing.T) { + if _, err := prompty.LlmStartPayloadFromJSON("{"); err == nil { + t.Fatalf("Expected malformed JSON to fail") + } +} diff --git a/runtime/go/prompty/model/mcp_approval_mode.go b/runtime/go/prompty/model/mcp_approval_mode.go index bc93f3a5..ba5e468b 100644 --- a/runtime/go/prompty/model/mcp_approval_mode.go +++ b/runtime/go/prompty/model/mcp_approval_mode.go @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. // Group: tools package prompty @@ -45,19 +46,25 @@ func LoadMcpApprovalMode(data interface{}, ctx *LoadContext) (McpApprovalMode, e result.Kind = mcpApprovalModeKind(val.(string)) } if val, ok := m["alwaysRequireApprovalTools"]; ok && val != nil { - if arr, ok := val.([]interface{}); ok { + switch arr := val.(type) { + case []interface{}: result.AlwaysRequireApprovalTools = make([]string, len(arr)) for i, v := range arr { result.AlwaysRequireApprovalTools[i] = v.(string) } + case []string: + result.AlwaysRequireApprovalTools = arr } } if val, ok := m["neverRequireApprovalTools"]; ok && val != nil { - if arr, ok := val.([]interface{}); ok { + switch arr := val.(type) { + case []interface{}: result.NeverRequireApprovalTools = make([]string, len(arr)) for i, v := range arr { result.NeverRequireApprovalTools[i] = v.(string) } + case []string: + result.NeverRequireApprovalTools = arr } } } @@ -66,7 +73,7 @@ func LoadMcpApprovalMode(data interface{}, ctx *LoadContext) (McpApprovalMode, e } // Save serializes McpApprovalMode to map[string]interface{} -func (obj *McpApprovalMode) Save(ctx *SaveContext) map[string]interface{} { +func (obj McpApprovalMode) Save(ctx *SaveContext) map[string]interface{} { result := make(map[string]interface{}) result["kind"] = string(obj.Kind) result["alwaysRequireApprovalTools"] = obj.AlwaysRequireApprovalTools @@ -90,11 +97,7 @@ func (obj *McpApprovalMode) ToJSON() (string, error) { func (obj *McpApprovalMode) ToYAML() (string, error) { ctx := NewSaveContext() data := obj.Save(ctx) - bytes, err := yaml.Marshal(data) - if err != nil { - return "", err - } - return string(bytes), nil + return marshalYAMLDocument(data) } // FromJSON creates McpApprovalMode from JSON string diff --git a/runtime/go/prompty/model/mcp_approval_mode_test.go b/runtime/go/prompty/model/mcp_approval_mode_test.go index 5d85c3ff..7662e204 100644 --- a/runtime/go/prompty/model/mcp_approval_mode_test.go +++ b/runtime/go/prompty/model/mcp_approval_mode_test.go @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. package prompty_test @@ -37,6 +38,18 @@ func TestMcpApprovalModeLoadJSON(t *testing.T) { if instance.Kind != "never" { t.Errorf(`Expected Kind to be "never", got %v`, instance.Kind) } + if len(instance.AlwaysRequireApprovalTools) != 1 { + t.Fatalf("Expected AlwaysRequireApprovalTools length to be 1, got %d", len(instance.AlwaysRequireApprovalTools)) + } + if instance.AlwaysRequireApprovalTools[0] != "operation1" { + t.Errorf(`Expected AlwaysRequireApprovalTools[0] to be "operation1", got %v`, instance.AlwaysRequireApprovalTools[0]) + } + if len(instance.NeverRequireApprovalTools) != 1 { + t.Fatalf("Expected NeverRequireApprovalTools length to be 1, got %d", len(instance.NeverRequireApprovalTools)) + } + if instance.NeverRequireApprovalTools[0] != "operation2" { + t.Errorf(`Expected NeverRequireApprovalTools[0] to be "operation2", got %v`, instance.NeverRequireApprovalTools[0]) + } } // TestMcpApprovalModeLoadYAML tests loading McpApprovalMode from YAML @@ -62,6 +75,85 @@ neverRequireApprovalTools: if instance.Kind != "never" { t.Errorf(`Expected Kind to be "never", got %v`, instance.Kind) } + if len(instance.AlwaysRequireApprovalTools) != 1 { + t.Fatalf("Expected AlwaysRequireApprovalTools length to be 1, got %d", len(instance.AlwaysRequireApprovalTools)) + } + if instance.AlwaysRequireApprovalTools[0] != "operation1" { + t.Errorf(`Expected AlwaysRequireApprovalTools[0] to be "operation1", got %v`, instance.AlwaysRequireApprovalTools[0]) + } + if len(instance.NeverRequireApprovalTools) != 1 { + t.Fatalf("Expected NeverRequireApprovalTools length to be 1, got %d", len(instance.NeverRequireApprovalTools)) + } + if instance.NeverRequireApprovalTools[0] != "operation2" { + t.Errorf(`Expected NeverRequireApprovalTools[0] to be "operation2", got %v`, instance.NeverRequireApprovalTools[0]) + } +} + +// TestMcpApprovalModeFromJSON tests loading McpApprovalMode through the generated JSON helper +func TestMcpApprovalModeFromJSON(t *testing.T) { + jsonData := ` +{ + "kind": "never", + "alwaysRequireApprovalTools": [ + "operation1" + ], + "neverRequireApprovalTools": [ + "operation2" + ] +} +` + + instance, err := prompty.McpApprovalModeFromJSON(jsonData) + if err != nil { + t.Fatalf("Failed to load McpApprovalMode from JSON helper: %v", err) + } + if instance.Kind != "never" { + t.Errorf(`Expected Kind to be "never", got %v`, instance.Kind) + } + if len(instance.AlwaysRequireApprovalTools) != 1 { + t.Fatalf("Expected AlwaysRequireApprovalTools length to be 1, got %d", len(instance.AlwaysRequireApprovalTools)) + } + if instance.AlwaysRequireApprovalTools[0] != "operation1" { + t.Errorf(`Expected AlwaysRequireApprovalTools[0] to be "operation1", got %v`, instance.AlwaysRequireApprovalTools[0]) + } + if len(instance.NeverRequireApprovalTools) != 1 { + t.Fatalf("Expected NeverRequireApprovalTools length to be 1, got %d", len(instance.NeverRequireApprovalTools)) + } + if instance.NeverRequireApprovalTools[0] != "operation2" { + t.Errorf(`Expected NeverRequireApprovalTools[0] to be "operation2", got %v`, instance.NeverRequireApprovalTools[0]) + } +} + +// TestMcpApprovalModeFromYAML tests loading McpApprovalMode through the generated YAML helper +func TestMcpApprovalModeFromYAML(t *testing.T) { + yamlData := ` +kind: never +alwaysRequireApprovalTools: + - operation1 +neverRequireApprovalTools: + - operation2 + +` + + instance, err := prompty.McpApprovalModeFromYAML(yamlData) + if err != nil { + t.Fatalf("Failed to load McpApprovalMode from YAML helper: %v", err) + } + if instance.Kind != "never" { + t.Errorf(`Expected Kind to be "never", got %v`, instance.Kind) + } + if len(instance.AlwaysRequireApprovalTools) != 1 { + t.Fatalf("Expected AlwaysRequireApprovalTools length to be 1, got %d", len(instance.AlwaysRequireApprovalTools)) + } + if instance.AlwaysRequireApprovalTools[0] != "operation1" { + t.Errorf(`Expected AlwaysRequireApprovalTools[0] to be "operation1", got %v`, instance.AlwaysRequireApprovalTools[0]) + } + if len(instance.NeverRequireApprovalTools) != 1 { + t.Fatalf("Expected NeverRequireApprovalTools length to be 1, got %d", len(instance.NeverRequireApprovalTools)) + } + if instance.NeverRequireApprovalTools[0] != "operation2" { + t.Errorf(`Expected NeverRequireApprovalTools[0] to be "operation2", got %v`, instance.NeverRequireApprovalTools[0]) + } } // TestMcpApprovalModeRoundtrip tests load -> save -> load produces equivalent data @@ -97,6 +189,18 @@ func TestMcpApprovalModeRoundtrip(t *testing.T) { if reloaded.Kind != "never" { t.Errorf(`Expected Kind to be "never", got %v`, reloaded.Kind) } + if len(reloaded.AlwaysRequireApprovalTools) != 1 { + t.Fatalf("Expected AlwaysRequireApprovalTools length to be 1, got %d", len(reloaded.AlwaysRequireApprovalTools)) + } + if reloaded.AlwaysRequireApprovalTools[0] != "operation1" { + t.Errorf(`Expected AlwaysRequireApprovalTools[0] to be "operation1", got %v`, reloaded.AlwaysRequireApprovalTools[0]) + } + if len(reloaded.NeverRequireApprovalTools) != 1 { + t.Fatalf("Expected NeverRequireApprovalTools length to be 1, got %d", len(reloaded.NeverRequireApprovalTools)) + } + if reloaded.NeverRequireApprovalTools[0] != "operation2" { + t.Errorf(`Expected NeverRequireApprovalTools[0] to be "operation2", got %v`, reloaded.NeverRequireApprovalTools[0]) + } } // TestMcpApprovalModeToJSON tests that ToJSON produces valid JSON @@ -131,6 +235,26 @@ func TestMcpApprovalModeToJSON(t *testing.T) { if err := json.Unmarshal([]byte(jsonOutput), &parsed); err != nil { t.Fatalf("Failed to parse generated JSON: %v", err) } + + reloaded, err := prompty.LoadMcpApprovalMode(parsed, ctx) + if err != nil { + t.Fatalf("Failed to reload generated JSON: %v", err) + } + if reloaded.Kind != "never" { + t.Errorf(`Expected Kind to be "never", got %v`, reloaded.Kind) + } + if len(reloaded.AlwaysRequireApprovalTools) != 1 { + t.Fatalf("Expected AlwaysRequireApprovalTools length to be 1, got %d", len(reloaded.AlwaysRequireApprovalTools)) + } + if reloaded.AlwaysRequireApprovalTools[0] != "operation1" { + t.Errorf(`Expected AlwaysRequireApprovalTools[0] to be "operation1", got %v`, reloaded.AlwaysRequireApprovalTools[0]) + } + if len(reloaded.NeverRequireApprovalTools) != 1 { + t.Fatalf("Expected NeverRequireApprovalTools length to be 1, got %d", len(reloaded.NeverRequireApprovalTools)) + } + if reloaded.NeverRequireApprovalTools[0] != "operation2" { + t.Errorf(`Expected NeverRequireApprovalTools[0] to be "operation2", got %v`, reloaded.NeverRequireApprovalTools[0]) + } } // TestMcpApprovalModeToYAML tests that ToYAML produces valid YAML @@ -165,6 +289,33 @@ func TestMcpApprovalModeToYAML(t *testing.T) { if err := yaml.Unmarshal([]byte(yamlOutput), &parsed); err != nil { t.Fatalf("Failed to parse generated YAML: %v", err) } + + reloaded, err := prompty.LoadMcpApprovalMode(parsed, ctx) + if err != nil { + t.Fatalf("Failed to reload generated YAML: %v", err) + } + if reloaded.Kind != "never" { + t.Errorf(`Expected Kind to be "never", got %v`, reloaded.Kind) + } + if len(reloaded.AlwaysRequireApprovalTools) != 1 { + t.Fatalf("Expected AlwaysRequireApprovalTools length to be 1, got %d", len(reloaded.AlwaysRequireApprovalTools)) + } + if reloaded.AlwaysRequireApprovalTools[0] != "operation1" { + t.Errorf(`Expected AlwaysRequireApprovalTools[0] to be "operation1", got %v`, reloaded.AlwaysRequireApprovalTools[0]) + } + if len(reloaded.NeverRequireApprovalTools) != 1 { + t.Fatalf("Expected NeverRequireApprovalTools length to be 1, got %d", len(reloaded.NeverRequireApprovalTools)) + } + if reloaded.NeverRequireApprovalTools[0] != "operation2" { + t.Errorf(`Expected NeverRequireApprovalTools[0] to be "operation2", got %v`, reloaded.NeverRequireApprovalTools[0]) + } +} + +// TestMcpApprovalModeFromJSONInvalid rejects malformed JSON instead of silently defaulting +func TestMcpApprovalModeFromJSONInvalid(t *testing.T) { + if _, err := prompty.McpApprovalModeFromJSON("{"); err == nil { + t.Fatalf("Expected malformed JSON to fail") + } } // TestMcpApprovalModeFromKind tests loading McpApprovalMode from string diff --git a/runtime/go/prompty/model/mcp_tool_test.go b/runtime/go/prompty/model/mcp_tool_test.go index 4f1f83d1..08824694 100644 --- a/runtime/go/prompty/model/mcp_tool_test.go +++ b/runtime/go/prompty/model/mcp_tool_test.go @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. package prompty_test @@ -49,6 +50,18 @@ func TestMcpToolLoadJSON(t *testing.T) { if instance.ServerDescription == nil || *instance.ServerDescription != "This tool allows access to MCP services." { t.Errorf(`Expected ServerDescription to be "This tool allows access to MCP services.", got %v`, instance.ServerDescription) } + if instance.ApprovalMode.Kind != "always" { + t.Errorf(`Expected ApprovalMode.Kind to be "always", got %v`, instance.ApprovalMode.Kind) + } + if len(instance.AllowedTools) != 2 { + t.Fatalf("Expected AllowedTools length to be 2, got %d", len(instance.AllowedTools)) + } + if instance.AllowedTools[0] != "operation1" { + t.Errorf(`Expected AllowedTools[0] to be "operation1", got %v`, instance.AllowedTools[0]) + } + if instance.AllowedTools[1] != "operation2" { + t.Errorf(`Expected AllowedTools[1] to be "operation2", got %v`, instance.AllowedTools[1]) + } } // TestMcpToolLoadYAML tests loading McpTool from YAML @@ -85,6 +98,108 @@ allowedTools: if instance.ServerDescription == nil || *instance.ServerDescription != "This tool allows access to MCP services." { t.Errorf(`Expected ServerDescription to be "This tool allows access to MCP services.", got %v`, instance.ServerDescription) } + if instance.ApprovalMode.Kind != "always" { + t.Errorf(`Expected ApprovalMode.Kind to be "always", got %v`, instance.ApprovalMode.Kind) + } + if len(instance.AllowedTools) != 2 { + t.Fatalf("Expected AllowedTools length to be 2, got %d", len(instance.AllowedTools)) + } + if instance.AllowedTools[0] != "operation1" { + t.Errorf(`Expected AllowedTools[0] to be "operation1", got %v`, instance.AllowedTools[0]) + } + if instance.AllowedTools[1] != "operation2" { + t.Errorf(`Expected AllowedTools[1] to be "operation2", got %v`, instance.AllowedTools[1]) + } +} + +// TestMcpToolFromJSON tests loading McpTool through the generated JSON helper +func TestMcpToolFromJSON(t *testing.T) { + jsonData := ` +{ + "kind": "mcp", + "connection": { + "kind": "reference" + }, + "serverName": "My MCP Server", + "serverDescription": "This tool allows access to MCP services.", + "approvalMode": { + "kind": "always" + }, + "allowedTools": [ + "operation1", + "operation2" + ] +} +` + + instance, err := prompty.McpToolFromJSON(jsonData) + if err != nil { + t.Fatalf("Failed to load McpTool from JSON helper: %v", err) + } + if instance.Kind != "mcp" { + t.Errorf(`Expected Kind to be "mcp", got %v`, instance.Kind) + } + if instance.ServerName != "My MCP Server" { + t.Errorf(`Expected ServerName to be "My MCP Server", got %v`, instance.ServerName) + } + if instance.ServerDescription == nil || *instance.ServerDescription != "This tool allows access to MCP services." { + t.Errorf(`Expected ServerDescription to be "This tool allows access to MCP services.", got %v`, instance.ServerDescription) + } + if instance.ApprovalMode.Kind != "always" { + t.Errorf(`Expected ApprovalMode.Kind to be "always", got %v`, instance.ApprovalMode.Kind) + } + if len(instance.AllowedTools) != 2 { + t.Fatalf("Expected AllowedTools length to be 2, got %d", len(instance.AllowedTools)) + } + if instance.AllowedTools[0] != "operation1" { + t.Errorf(`Expected AllowedTools[0] to be "operation1", got %v`, instance.AllowedTools[0]) + } + if instance.AllowedTools[1] != "operation2" { + t.Errorf(`Expected AllowedTools[1] to be "operation2", got %v`, instance.AllowedTools[1]) + } +} + +// TestMcpToolFromYAML tests loading McpTool through the generated YAML helper +func TestMcpToolFromYAML(t *testing.T) { + yamlData := ` +kind: mcp +connection: + kind: reference +serverName: My MCP Server +serverDescription: This tool allows access to MCP services. +approvalMode: + kind: always +allowedTools: + - operation1 + - operation2 + +` + + instance, err := prompty.McpToolFromYAML(yamlData) + if err != nil { + t.Fatalf("Failed to load McpTool from YAML helper: %v", err) + } + if instance.Kind != "mcp" { + t.Errorf(`Expected Kind to be "mcp", got %v`, instance.Kind) + } + if instance.ServerName != "My MCP Server" { + t.Errorf(`Expected ServerName to be "My MCP Server", got %v`, instance.ServerName) + } + if instance.ServerDescription == nil || *instance.ServerDescription != "This tool allows access to MCP services." { + t.Errorf(`Expected ServerDescription to be "This tool allows access to MCP services.", got %v`, instance.ServerDescription) + } + if instance.ApprovalMode.Kind != "always" { + t.Errorf(`Expected ApprovalMode.Kind to be "always", got %v`, instance.ApprovalMode.Kind) + } + if len(instance.AllowedTools) != 2 { + t.Fatalf("Expected AllowedTools length to be 2, got %d", len(instance.AllowedTools)) + } + if instance.AllowedTools[0] != "operation1" { + t.Errorf(`Expected AllowedTools[0] to be "operation1", got %v`, instance.AllowedTools[0]) + } + if instance.AllowedTools[1] != "operation2" { + t.Errorf(`Expected AllowedTools[1] to be "operation2", got %v`, instance.AllowedTools[1]) + } } // TestMcpToolRoundtrip tests load -> save -> load produces equivalent data @@ -132,6 +247,18 @@ func TestMcpToolRoundtrip(t *testing.T) { if reloaded.ServerDescription == nil || *reloaded.ServerDescription != "This tool allows access to MCP services." { t.Errorf(`Expected ServerDescription to be "This tool allows access to MCP services.", got %v`, reloaded.ServerDescription) } + if reloaded.ApprovalMode.Kind != "always" { + t.Errorf(`Expected ApprovalMode.Kind to be "always", got %v`, reloaded.ApprovalMode.Kind) + } + if len(reloaded.AllowedTools) != 2 { + t.Fatalf("Expected AllowedTools length to be 2, got %d", len(reloaded.AllowedTools)) + } + if reloaded.AllowedTools[0] != "operation1" { + t.Errorf(`Expected AllowedTools[0] to be "operation1", got %v`, reloaded.AllowedTools[0]) + } + if reloaded.AllowedTools[1] != "operation2" { + t.Errorf(`Expected AllowedTools[1] to be "operation2", got %v`, reloaded.AllowedTools[1]) + } } // TestMcpToolToJSON tests that ToJSON produces valid JSON @@ -172,6 +299,32 @@ func TestMcpToolToJSON(t *testing.T) { if err := json.Unmarshal([]byte(jsonOutput), &parsed); err != nil { t.Fatalf("Failed to parse generated JSON: %v", err) } + + reloaded, err := prompty.LoadMcpTool(parsed, ctx) + if err != nil { + t.Fatalf("Failed to reload generated JSON: %v", err) + } + if reloaded.Kind != "mcp" { + t.Errorf(`Expected Kind to be "mcp", got %v`, reloaded.Kind) + } + if reloaded.ServerName != "My MCP Server" { + t.Errorf(`Expected ServerName to be "My MCP Server", got %v`, reloaded.ServerName) + } + if reloaded.ServerDescription == nil || *reloaded.ServerDescription != "This tool allows access to MCP services." { + t.Errorf(`Expected ServerDescription to be "This tool allows access to MCP services.", got %v`, reloaded.ServerDescription) + } + if reloaded.ApprovalMode.Kind != "always" { + t.Errorf(`Expected ApprovalMode.Kind to be "always", got %v`, reloaded.ApprovalMode.Kind) + } + if len(reloaded.AllowedTools) != 2 { + t.Fatalf("Expected AllowedTools length to be 2, got %d", len(reloaded.AllowedTools)) + } + if reloaded.AllowedTools[0] != "operation1" { + t.Errorf(`Expected AllowedTools[0] to be "operation1", got %v`, reloaded.AllowedTools[0]) + } + if reloaded.AllowedTools[1] != "operation2" { + t.Errorf(`Expected AllowedTools[1] to be "operation2", got %v`, reloaded.AllowedTools[1]) + } } // TestMcpToolToYAML tests that ToYAML produces valid YAML @@ -212,4 +365,37 @@ func TestMcpToolToYAML(t *testing.T) { if err := yaml.Unmarshal([]byte(yamlOutput), &parsed); err != nil { t.Fatalf("Failed to parse generated YAML: %v", err) } + + reloaded, err := prompty.LoadMcpTool(parsed, ctx) + if err != nil { + t.Fatalf("Failed to reload generated YAML: %v", err) + } + if reloaded.Kind != "mcp" { + t.Errorf(`Expected Kind to be "mcp", got %v`, reloaded.Kind) + } + if reloaded.ServerName != "My MCP Server" { + t.Errorf(`Expected ServerName to be "My MCP Server", got %v`, reloaded.ServerName) + } + if reloaded.ServerDescription == nil || *reloaded.ServerDescription != "This tool allows access to MCP services." { + t.Errorf(`Expected ServerDescription to be "This tool allows access to MCP services.", got %v`, reloaded.ServerDescription) + } + if reloaded.ApprovalMode.Kind != "always" { + t.Errorf(`Expected ApprovalMode.Kind to be "always", got %v`, reloaded.ApprovalMode.Kind) + } + if len(reloaded.AllowedTools) != 2 { + t.Fatalf("Expected AllowedTools length to be 2, got %d", len(reloaded.AllowedTools)) + } + if reloaded.AllowedTools[0] != "operation1" { + t.Errorf(`Expected AllowedTools[0] to be "operation1", got %v`, reloaded.AllowedTools[0]) + } + if reloaded.AllowedTools[1] != "operation2" { + t.Errorf(`Expected AllowedTools[1] to be "operation2", got %v`, reloaded.AllowedTools[1]) + } +} + +// TestMcpToolFromJSONInvalid rejects malformed JSON instead of silently defaulting +func TestMcpToolFromJSONInvalid(t *testing.T) { + if _, err := prompty.McpToolFromJSON("{"); err == nil { + t.Fatalf("Expected malformed JSON to fail") + } } diff --git a/runtime/go/prompty/model/message.go b/runtime/go/prompty/model/message.go index 3cdb2f4f..cb0da922 100644 --- a/runtime/go/prompty/model/message.go +++ b/runtime/go/prompty/model/message.go @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. // Group: conversation package prompty @@ -61,7 +62,7 @@ func LoadMessage(data interface{}, ctx *LoadContext) (Message, error) { } // Save serializes Message to map[string]interface{} -func (obj *Message) Save(ctx *SaveContext) map[string]interface{} { +func (obj Message) Save(ctx *SaveContext) map[string]interface{} { result := make(map[string]interface{}) result["role"] = string(obj.Role) if obj.Parts != nil { @@ -99,11 +100,7 @@ func (obj *Message) ToJSON() (string, error) { func (obj *Message) ToYAML() (string, error) { ctx := NewSaveContext() data := obj.Save(ctx) - bytes, err := yaml.Marshal(data) - if err != nil { - return "", err - } - return string(bytes), nil + return marshalYAMLDocument(data) } // FromJSON creates Message from JSON string diff --git a/runtime/go/prompty/model/message_test.go b/runtime/go/prompty/model/message_test.go index 1168d19d..fd960e71 100644 --- a/runtime/go/prompty/model/message_test.go +++ b/runtime/go/prompty/model/message_test.go @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. package prompty_test @@ -40,6 +41,25 @@ func TestMessageLoadJSON(t *testing.T) { if instance.Role != "user" { t.Errorf(`Expected Role to be "user", got %v`, instance.Role) } + if len(instance.Parts) != 1 { + t.Fatalf("Expected Parts length to be 1, got %d", len(instance.Parts)) + } + parts0Value, ok := instance.Parts[0].(prompty.TextPart) + if !ok { + t.Fatalf("Expected Parts[0] to be prompty.TextPart, got %T", instance.Parts[0]) + } + if parts0Value.Kind != "text" { + t.Errorf(`Expected Kind to be "text", got %v`, parts0Value.Kind) + } + if parts0Value.Value != "Hello!" { + t.Errorf(`Expected Value to be "Hello!", got %v`, parts0Value.Value) + } + if instance.Metadata == nil { + t.Fatalf("Expected Metadata to be populated") + } + if got := instance.Metadata["source"]; got != "user-input" { + t.Errorf(`Expected Metadata["source"] to be "user-input", got %v`, got) + } } // TestMessageLoadYAML tests loading Message from YAML @@ -66,6 +86,110 @@ metadata: if instance.Role != "user" { t.Errorf(`Expected Role to be "user", got %v`, instance.Role) } + if len(instance.Parts) != 1 { + t.Fatalf("Expected Parts length to be 1, got %d", len(instance.Parts)) + } + parts0Value, ok := instance.Parts[0].(prompty.TextPart) + if !ok { + t.Fatalf("Expected Parts[0] to be prompty.TextPart, got %T", instance.Parts[0]) + } + if parts0Value.Kind != "text" { + t.Errorf(`Expected Kind to be "text", got %v`, parts0Value.Kind) + } + if parts0Value.Value != "Hello!" { + t.Errorf(`Expected Value to be "Hello!", got %v`, parts0Value.Value) + } + if instance.Metadata == nil { + t.Fatalf("Expected Metadata to be populated") + } + if got := instance.Metadata["source"]; got != "user-input" { + t.Errorf(`Expected Metadata["source"] to be "user-input", got %v`, got) + } +} + +// TestMessageFromJSON tests loading Message through the generated JSON helper +func TestMessageFromJSON(t *testing.T) { + jsonData := ` +{ + "role": "user", + "parts": [ + { + "kind": "text", + "value": "Hello!" + } + ], + "metadata": { + "source": "user-input" + } +} +` + + instance, err := prompty.MessageFromJSON(jsonData) + if err != nil { + t.Fatalf("Failed to load Message from JSON helper: %v", err) + } + if instance.Role != "user" { + t.Errorf(`Expected Role to be "user", got %v`, instance.Role) + } + if len(instance.Parts) != 1 { + t.Fatalf("Expected Parts length to be 1, got %d", len(instance.Parts)) + } + parts0Value, ok := instance.Parts[0].(prompty.TextPart) + if !ok { + t.Fatalf("Expected Parts[0] to be prompty.TextPart, got %T", instance.Parts[0]) + } + if parts0Value.Kind != "text" { + t.Errorf(`Expected Kind to be "text", got %v`, parts0Value.Kind) + } + if parts0Value.Value != "Hello!" { + t.Errorf(`Expected Value to be "Hello!", got %v`, parts0Value.Value) + } + if instance.Metadata == nil { + t.Fatalf("Expected Metadata to be populated") + } + if got := instance.Metadata["source"]; got != "user-input" { + t.Errorf(`Expected Metadata["source"] to be "user-input", got %v`, got) + } +} + +// TestMessageFromYAML tests loading Message through the generated YAML helper +func TestMessageFromYAML(t *testing.T) { + yamlData := ` +role: user +parts: + - kind: text + value: Hello! +metadata: + source: user-input + +` + + instance, err := prompty.MessageFromYAML(yamlData) + if err != nil { + t.Fatalf("Failed to load Message from YAML helper: %v", err) + } + if instance.Role != "user" { + t.Errorf(`Expected Role to be "user", got %v`, instance.Role) + } + if len(instance.Parts) != 1 { + t.Fatalf("Expected Parts length to be 1, got %d", len(instance.Parts)) + } + parts0Value, ok := instance.Parts[0].(prompty.TextPart) + if !ok { + t.Fatalf("Expected Parts[0] to be prompty.TextPart, got %T", instance.Parts[0]) + } + if parts0Value.Kind != "text" { + t.Errorf(`Expected Kind to be "text", got %v`, parts0Value.Kind) + } + if parts0Value.Value != "Hello!" { + t.Errorf(`Expected Value to be "Hello!", got %v`, parts0Value.Value) + } + if instance.Metadata == nil { + t.Fatalf("Expected Metadata to be populated") + } + if got := instance.Metadata["source"]; got != "user-input" { + t.Errorf(`Expected Metadata["source"] to be "user-input", got %v`, got) + } } // TestMessageRoundtrip tests load -> save -> load produces equivalent data @@ -104,6 +228,25 @@ func TestMessageRoundtrip(t *testing.T) { if reloaded.Role != "user" { t.Errorf(`Expected Role to be "user", got %v`, reloaded.Role) } + if len(reloaded.Parts) != 1 { + t.Fatalf("Expected Parts length to be 1, got %d", len(reloaded.Parts)) + } + parts0Value, ok := reloaded.Parts[0].(prompty.TextPart) + if !ok { + t.Fatalf("Expected Parts[0] to be prompty.TextPart, got %T", reloaded.Parts[0]) + } + if parts0Value.Kind != "text" { + t.Errorf(`Expected Kind to be "text", got %v`, parts0Value.Kind) + } + if parts0Value.Value != "Hello!" { + t.Errorf(`Expected Value to be "Hello!", got %v`, parts0Value.Value) + } + if reloaded.Metadata == nil { + t.Fatalf("Expected Metadata to be populated") + } + if got := reloaded.Metadata["source"]; got != "user-input" { + t.Errorf(`Expected Metadata["source"] to be "user-input", got %v`, got) + } } // TestMessageToJSON tests that ToJSON produces valid JSON @@ -141,6 +284,33 @@ func TestMessageToJSON(t *testing.T) { if err := json.Unmarshal([]byte(jsonOutput), &parsed); err != nil { t.Fatalf("Failed to parse generated JSON: %v", err) } + + reloaded, err := prompty.LoadMessage(parsed, ctx) + if err != nil { + t.Fatalf("Failed to reload generated JSON: %v", err) + } + if reloaded.Role != "user" { + t.Errorf(`Expected Role to be "user", got %v`, reloaded.Role) + } + if len(reloaded.Parts) != 1 { + t.Fatalf("Expected Parts length to be 1, got %d", len(reloaded.Parts)) + } + parts0Value, ok := reloaded.Parts[0].(prompty.TextPart) + if !ok { + t.Fatalf("Expected Parts[0] to be prompty.TextPart, got %T", reloaded.Parts[0]) + } + if parts0Value.Kind != "text" { + t.Errorf(`Expected Kind to be "text", got %v`, parts0Value.Kind) + } + if parts0Value.Value != "Hello!" { + t.Errorf(`Expected Value to be "Hello!", got %v`, parts0Value.Value) + } + if reloaded.Metadata == nil { + t.Fatalf("Expected Metadata to be populated") + } + if got := reloaded.Metadata["source"]; got != "user-input" { + t.Errorf(`Expected Metadata["source"] to be "user-input", got %v`, got) + } } // TestMessageToYAML tests that ToYAML produces valid YAML @@ -178,4 +348,38 @@ func TestMessageToYAML(t *testing.T) { if err := yaml.Unmarshal([]byte(yamlOutput), &parsed); err != nil { t.Fatalf("Failed to parse generated YAML: %v", err) } + + reloaded, err := prompty.LoadMessage(parsed, ctx) + if err != nil { + t.Fatalf("Failed to reload generated YAML: %v", err) + } + if reloaded.Role != "user" { + t.Errorf(`Expected Role to be "user", got %v`, reloaded.Role) + } + if len(reloaded.Parts) != 1 { + t.Fatalf("Expected Parts length to be 1, got %d", len(reloaded.Parts)) + } + parts0Value, ok := reloaded.Parts[0].(prompty.TextPart) + if !ok { + t.Fatalf("Expected Parts[0] to be prompty.TextPart, got %T", reloaded.Parts[0]) + } + if parts0Value.Kind != "text" { + t.Errorf(`Expected Kind to be "text", got %v`, parts0Value.Kind) + } + if parts0Value.Value != "Hello!" { + t.Errorf(`Expected Value to be "Hello!", got %v`, parts0Value.Value) + } + if reloaded.Metadata == nil { + t.Fatalf("Expected Metadata to be populated") + } + if got := reloaded.Metadata["source"]; got != "user-input" { + t.Errorf(`Expected Metadata["source"] to be "user-input", got %v`, got) + } +} + +// TestMessageFromJSONInvalid rejects malformed JSON instead of silently defaulting +func TestMessageFromJSONInvalid(t *testing.T) { + if _, err := prompty.MessageFromJSON("{"); err == nil { + t.Fatalf("Expected malformed JSON to fail") + } } diff --git a/runtime/go/prompty/model/messages_updated_payload.go b/runtime/go/prompty/model/messages_updated_payload.go index 8923f0ab..93bff1dd 100644 --- a/runtime/go/prompty/model/messages_updated_payload.go +++ b/runtime/go/prompty/model/messages_updated_payload.go @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. // Group: events package prompty @@ -12,7 +13,10 @@ import ( // MessagesUpdatedPayload represents Payload for "messages_updated" events — the conversation state has changed. type MessagesUpdatedPayload struct { - Messages []Message `json:"messages" yaml:"messages"` + Messages []Message `json:"messages,omitempty" yaml:"messages,omitempty"` + Reason *string `json:"reason,omitempty" yaml:"reason,omitempty"` + Appended []Message `json:"appended,omitempty" yaml:"appended,omitempty"` + Removed *int32 `json:"removed,omitempty" yaml:"removed,omitempty"` } // LoadMessagesUpdatedPayload creates a MessagesUpdatedPayload from a map[string]interface{} @@ -32,13 +36,42 @@ func LoadMessagesUpdatedPayload(data interface{}, ctx *LoadContext) (MessagesUpd } } } + if val, ok := m["reason"]; ok && val != nil { + v := string(val.(string)) + result.Reason = &v + } + if val, ok := m["appended"]; ok && val != nil { + if arr, ok := val.([]interface{}); ok { + result.Appended = make([]Message, len(arr)) + for i, v := range arr { + if item, ok := v.(map[string]interface{}); ok { + loaded, _ := LoadMessage(item, ctx) + result.Appended[i] = loaded + } + } + } + } + if val, ok := m["removed"]; ok && val != nil { // Handle various numeric types from JSON/YAML/roundtrip + var v int32 + switch n := val.(type) { + case int: + v = int32(n) + case int32: + v = int32(n) + case int64: + v = int32(n) + case float64: + v = int32(n) + } + result.Removed = &v + } } return result, nil } // Save serializes MessagesUpdatedPayload to map[string]interface{} -func (obj *MessagesUpdatedPayload) Save(ctx *SaveContext) map[string]interface{} { +func (obj MessagesUpdatedPayload) Save(ctx *SaveContext) map[string]interface{} { result := make(map[string]interface{}) if obj.Messages != nil { arr := make([]interface{}, len(obj.Messages)) @@ -47,6 +80,19 @@ func (obj *MessagesUpdatedPayload) Save(ctx *SaveContext) map[string]interface{} } result["messages"] = arr } + if obj.Reason != nil { + result["reason"] = *obj.Reason + } + if obj.Appended != nil { + arr := make([]interface{}, len(obj.Appended)) + for i, item := range obj.Appended { + arr[i] = item.Save(ctx) + } + result["appended"] = arr + } + if obj.Removed != nil { + result["removed"] = *obj.Removed + } return result } @@ -66,11 +112,7 @@ func (obj *MessagesUpdatedPayload) ToJSON() (string, error) { func (obj *MessagesUpdatedPayload) ToYAML() (string, error) { ctx := NewSaveContext() data := obj.Save(ctx) - bytes, err := yaml.Marshal(data) - if err != nil { - return "", err - } - return string(bytes), nil + return marshalYAMLDocument(data) } // FromJSON creates MessagesUpdatedPayload from JSON string diff --git a/runtime/go/prompty/model/messages_updated_payload_test.go b/runtime/go/prompty/model/messages_updated_payload_test.go index 3df1459b..df120cd5 100644 --- a/runtime/go/prompty/model/messages_updated_payload_test.go +++ b/runtime/go/prompty/model/messages_updated_payload_test.go @@ -1,3 +1,225 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. package prompty_test + +import ( + "encoding/json" + "testing" + + "gopkg.in/yaml.v3" + + "prompty/model" +) + +// TestMessagesUpdatedPayloadLoadJSON tests loading MessagesUpdatedPayload from JSON +func TestMessagesUpdatedPayloadLoadJSON(t *testing.T) { + jsonData := ` +{ + "reason": "tool_results", + "removed": 2 +} +` + var data map[string]interface{} + if err := json.Unmarshal([]byte(jsonData), &data); err != nil { + t.Fatalf("Failed to parse JSON: %v", err) + } + + ctx := prompty.NewLoadContext() + instance, err := prompty.LoadMessagesUpdatedPayload(data, ctx) + if err != nil { + t.Fatalf("Failed to load MessagesUpdatedPayload: %v", err) + } + if instance.Reason == nil || *instance.Reason != "tool_results" { + t.Errorf(`Expected Reason to be "tool_results", got %v`, instance.Reason) + } + if instance.Removed == nil || *instance.Removed != 2 { + t.Errorf(`Expected Removed to be 2, got %v`, instance.Removed) + } +} + +// TestMessagesUpdatedPayloadLoadYAML tests loading MessagesUpdatedPayload from YAML +func TestMessagesUpdatedPayloadLoadYAML(t *testing.T) { + yamlData := ` +reason: tool_results +removed: 2 + +` + var data map[string]interface{} + if err := yaml.Unmarshal([]byte(yamlData), &data); err != nil { + t.Fatalf("Failed to parse YAML: %v", err) + } + + ctx := prompty.NewLoadContext() + instance, err := prompty.LoadMessagesUpdatedPayload(data, ctx) + if err != nil { + t.Fatalf("Failed to load MessagesUpdatedPayload: %v", err) + } + if instance.Reason == nil || *instance.Reason != "tool_results" { + t.Errorf(`Expected Reason to be "tool_results", got %v`, instance.Reason) + } + if instance.Removed == nil || *instance.Removed != 2 { + t.Errorf(`Expected Removed to be 2, got %v`, instance.Removed) + } +} + +// TestMessagesUpdatedPayloadFromJSON tests loading MessagesUpdatedPayload through the generated JSON helper +func TestMessagesUpdatedPayloadFromJSON(t *testing.T) { + jsonData := ` +{ + "reason": "tool_results", + "removed": 2 +} +` + + instance, err := prompty.MessagesUpdatedPayloadFromJSON(jsonData) + if err != nil { + t.Fatalf("Failed to load MessagesUpdatedPayload from JSON helper: %v", err) + } + if instance.Reason == nil || *instance.Reason != "tool_results" { + t.Errorf(`Expected Reason to be "tool_results", got %v`, instance.Reason) + } + if instance.Removed == nil || *instance.Removed != 2 { + t.Errorf(`Expected Removed to be 2, got %v`, instance.Removed) + } +} + +// TestMessagesUpdatedPayloadFromYAML tests loading MessagesUpdatedPayload through the generated YAML helper +func TestMessagesUpdatedPayloadFromYAML(t *testing.T) { + yamlData := ` +reason: tool_results +removed: 2 + +` + + instance, err := prompty.MessagesUpdatedPayloadFromYAML(yamlData) + if err != nil { + t.Fatalf("Failed to load MessagesUpdatedPayload from YAML helper: %v", err) + } + if instance.Reason == nil || *instance.Reason != "tool_results" { + t.Errorf(`Expected Reason to be "tool_results", got %v`, instance.Reason) + } + if instance.Removed == nil || *instance.Removed != 2 { + t.Errorf(`Expected Removed to be 2, got %v`, instance.Removed) + } +} + +// TestMessagesUpdatedPayloadRoundtrip tests load -> save -> load produces equivalent data +func TestMessagesUpdatedPayloadRoundtrip(t *testing.T) { + jsonData := ` +{ + "reason": "tool_results", + "removed": 2 +} +` + var data map[string]interface{} + if err := json.Unmarshal([]byte(jsonData), &data); err != nil { + t.Fatalf("Failed to parse JSON: %v", err) + } + + loadCtx := prompty.NewLoadContext() + instance, err := prompty.LoadMessagesUpdatedPayload(data, loadCtx) + if err != nil { + t.Fatalf("Failed to load MessagesUpdatedPayload: %v", err) + } + saveCtx := prompty.NewSaveContext() + savedData := instance.Save(saveCtx) + + reloaded, err := prompty.LoadMessagesUpdatedPayload(savedData, loadCtx) + if err != nil { + t.Fatalf("Failed to reload MessagesUpdatedPayload: %v", err) + } + if reloaded.Reason == nil || *reloaded.Reason != "tool_results" { + t.Errorf(`Expected Reason to be "tool_results", got %v`, reloaded.Reason) + } + if reloaded.Removed == nil || *reloaded.Removed != 2 { + t.Errorf(`Expected Removed to be 2, got %v`, reloaded.Removed) + } +} + +// TestMessagesUpdatedPayloadToJSON tests that ToJSON produces valid JSON +func TestMessagesUpdatedPayloadToJSON(t *testing.T) { + jsonData := ` +{ + "reason": "tool_results", + "removed": 2 +} +` + var data map[string]interface{} + if err := json.Unmarshal([]byte(jsonData), &data); err != nil { + t.Fatalf("Failed to parse JSON: %v", err) + } + + ctx := prompty.NewLoadContext() + instance, err := prompty.LoadMessagesUpdatedPayload(data, ctx) + if err != nil { + t.Fatalf("Failed to load MessagesUpdatedPayload: %v", err) + } + jsonOutput, err := instance.ToJSON() + if err != nil { + t.Fatalf("Failed to convert to JSON: %v", err) + } + + var parsed map[string]interface{} + if err := json.Unmarshal([]byte(jsonOutput), &parsed); err != nil { + t.Fatalf("Failed to parse generated JSON: %v", err) + } + + reloaded, err := prompty.LoadMessagesUpdatedPayload(parsed, ctx) + if err != nil { + t.Fatalf("Failed to reload generated JSON: %v", err) + } + if reloaded.Reason == nil || *reloaded.Reason != "tool_results" { + t.Errorf(`Expected Reason to be "tool_results", got %v`, reloaded.Reason) + } + if reloaded.Removed == nil || *reloaded.Removed != 2 { + t.Errorf(`Expected Removed to be 2, got %v`, reloaded.Removed) + } +} + +// TestMessagesUpdatedPayloadToYAML tests that ToYAML produces valid YAML +func TestMessagesUpdatedPayloadToYAML(t *testing.T) { + jsonData := ` +{ + "reason": "tool_results", + "removed": 2 +} +` + var data map[string]interface{} + if err := json.Unmarshal([]byte(jsonData), &data); err != nil { + t.Fatalf("Failed to parse JSON: %v", err) + } + + ctx := prompty.NewLoadContext() + instance, err := prompty.LoadMessagesUpdatedPayload(data, ctx) + if err != nil { + t.Fatalf("Failed to load MessagesUpdatedPayload: %v", err) + } + yamlOutput, err := instance.ToYAML() + if err != nil { + t.Fatalf("Failed to convert to YAML: %v", err) + } + + var parsed map[string]interface{} + if err := yaml.Unmarshal([]byte(yamlOutput), &parsed); err != nil { + t.Fatalf("Failed to parse generated YAML: %v", err) + } + + reloaded, err := prompty.LoadMessagesUpdatedPayload(parsed, ctx) + if err != nil { + t.Fatalf("Failed to reload generated YAML: %v", err) + } + if reloaded.Reason == nil || *reloaded.Reason != "tool_results" { + t.Errorf(`Expected Reason to be "tool_results", got %v`, reloaded.Reason) + } + if reloaded.Removed == nil || *reloaded.Removed != 2 { + t.Errorf(`Expected Removed to be 2, got %v`, reloaded.Removed) + } +} + +// TestMessagesUpdatedPayloadFromJSONInvalid rejects malformed JSON instead of silently defaulting +func TestMessagesUpdatedPayloadFromJSONInvalid(t *testing.T) { + if _, err := prompty.MessagesUpdatedPayloadFromJSON("{"); err == nil { + t.Fatalf("Expected malformed JSON to fail") + } +} diff --git a/runtime/go/prompty/model/model.go b/runtime/go/prompty/model/model.go index 3cfd9e30..3b5d7289 100644 --- a/runtime/go/prompty/model/model.go +++ b/runtime/go/prompty/model/model.go @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. // Group: model package prompty @@ -74,7 +75,7 @@ func LoadModel(data interface{}, ctx *LoadContext) (Model, error) { } // Save serializes Model to map[string]interface{} -func (obj *Model) Save(ctx *SaveContext) map[string]interface{} { +func (obj Model) Save(ctx *SaveContext) map[string]interface{} { result := make(map[string]interface{}) result["id"] = obj.Id if obj.Provider != nil { @@ -118,11 +119,7 @@ func (obj *Model) ToJSON() (string, error) { func (obj *Model) ToYAML() (string, error) { ctx := NewSaveContext() data := obj.Save(ctx) - bytes, err := yaml.Marshal(data) - if err != nil { - return "", err - } - return string(bytes), nil + return marshalYAMLDocument(data) } // FromJSON creates Model from JSON string diff --git a/runtime/go/prompty/model/model_info.go b/runtime/go/prompty/model/model_info.go index a362e02b..fdc2b736 100644 --- a/runtime/go/prompty/model/model_info.go +++ b/runtime/go/prompty/model/model_info.go @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. // Group: model package prompty @@ -58,19 +59,25 @@ func LoadModelInfo(data interface{}, ctx *LoadContext) (ModelInfo, error) { result.ContextWindow = &v } if val, ok := m["inputModalities"]; ok && val != nil { - if arr, ok := val.([]interface{}); ok { + switch arr := val.(type) { + case []interface{}: result.InputModalities = make([]string, len(arr)) for i, v := range arr { result.InputModalities[i] = v.(string) } + case []string: + result.InputModalities = arr } } if val, ok := m["outputModalities"]; ok && val != nil { - if arr, ok := val.([]interface{}); ok { + switch arr := val.(type) { + case []interface{}: result.OutputModalities = make([]string, len(arr)) for i, v := range arr { result.OutputModalities[i] = v.(string) } + case []string: + result.OutputModalities = arr } } if val, ok := m["additionalProperties"]; ok && val != nil { @@ -84,7 +91,7 @@ func LoadModelInfo(data interface{}, ctx *LoadContext) (ModelInfo, error) { } // Save serializes ModelInfo to map[string]interface{} -func (obj *ModelInfo) Save(ctx *SaveContext) map[string]interface{} { +func (obj ModelInfo) Save(ctx *SaveContext) map[string]interface{} { result := make(map[string]interface{}) result["id"] = obj.Id if obj.DisplayName != nil { @@ -120,11 +127,7 @@ func (obj *ModelInfo) ToJSON() (string, error) { func (obj *ModelInfo) ToYAML() (string, error) { ctx := NewSaveContext() data := obj.Save(ctx) - bytes, err := yaml.Marshal(data) - if err != nil { - return "", err - } - return string(bytes), nil + return marshalYAMLDocument(data) } // FromJSON creates ModelInfo from JSON string diff --git a/runtime/go/prompty/model/model_info_test.go b/runtime/go/prompty/model/model_info_test.go index dec61dea..ab0dd060 100644 --- a/runtime/go/prompty/model/model_info_test.go +++ b/runtime/go/prompty/model/model_info_test.go @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. package prompty_test @@ -53,6 +54,24 @@ func TestModelInfoLoadJSON(t *testing.T) { if instance.ContextWindow == nil || *instance.ContextWindow != 128000 { t.Errorf(`Expected ContextWindow to be 128000, got %v`, instance.ContextWindow) } + if len(instance.InputModalities) != 2 { + t.Fatalf("Expected InputModalities length to be 2, got %d", len(instance.InputModalities)) + } + if instance.InputModalities[0] != "text" { + t.Errorf(`Expected InputModalities[0] to be "text", got %v`, instance.InputModalities[0]) + } + if instance.InputModalities[1] != "image" { + t.Errorf(`Expected InputModalities[1] to be "image", got %v`, instance.InputModalities[1]) + } + if len(instance.OutputModalities) != 1 { + t.Fatalf("Expected OutputModalities length to be 1, got %d", len(instance.OutputModalities)) + } + if instance.OutputModalities[0] != "text" { + t.Errorf(`Expected OutputModalities[0] to be "text", got %v`, instance.OutputModalities[0]) + } + if instance.AdditionalProperties == nil { + t.Fatalf("Expected AdditionalProperties to be populated") + } } // TestModelInfoLoadYAML tests loading ModelInfo from YAML @@ -93,6 +112,134 @@ additionalProperties: if instance.ContextWindow == nil || *instance.ContextWindow != 128000 { t.Errorf(`Expected ContextWindow to be 128000, got %v`, instance.ContextWindow) } + if len(instance.InputModalities) != 2 { + t.Fatalf("Expected InputModalities length to be 2, got %d", len(instance.InputModalities)) + } + if instance.InputModalities[0] != "text" { + t.Errorf(`Expected InputModalities[0] to be "text", got %v`, instance.InputModalities[0]) + } + if instance.InputModalities[1] != "image" { + t.Errorf(`Expected InputModalities[1] to be "image", got %v`, instance.InputModalities[1]) + } + if len(instance.OutputModalities) != 1 { + t.Fatalf("Expected OutputModalities length to be 1, got %d", len(instance.OutputModalities)) + } + if instance.OutputModalities[0] != "text" { + t.Errorf(`Expected OutputModalities[0] to be "text", got %v`, instance.OutputModalities[0]) + } + if instance.AdditionalProperties == nil { + t.Fatalf("Expected AdditionalProperties to be populated") + } +} + +// TestModelInfoFromJSON tests loading ModelInfo through the generated JSON helper +func TestModelInfoFromJSON(t *testing.T) { + jsonData := ` +{ + "id": "gpt-4o", + "displayName": "GPT-4o", + "ownedBy": "openai", + "contextWindow": 128000, + "inputModalities": [ + "text", + "image" + ], + "outputModalities": [ + "text" + ], + "additionalProperties": { + "supportsStreaming": true + } +} +` + + instance, err := prompty.ModelInfoFromJSON(jsonData) + if err != nil { + t.Fatalf("Failed to load ModelInfo from JSON helper: %v", err) + } + if instance.Id != "gpt-4o" { + t.Errorf(`Expected Id to be "gpt-4o", got %v`, instance.Id) + } + if instance.DisplayName == nil || *instance.DisplayName != "GPT-4o" { + t.Errorf(`Expected DisplayName to be "GPT-4o", got %v`, instance.DisplayName) + } + if instance.OwnedBy == nil || *instance.OwnedBy != "openai" { + t.Errorf(`Expected OwnedBy to be "openai", got %v`, instance.OwnedBy) + } + if instance.ContextWindow == nil || *instance.ContextWindow != 128000 { + t.Errorf(`Expected ContextWindow to be 128000, got %v`, instance.ContextWindow) + } + if len(instance.InputModalities) != 2 { + t.Fatalf("Expected InputModalities length to be 2, got %d", len(instance.InputModalities)) + } + if instance.InputModalities[0] != "text" { + t.Errorf(`Expected InputModalities[0] to be "text", got %v`, instance.InputModalities[0]) + } + if instance.InputModalities[1] != "image" { + t.Errorf(`Expected InputModalities[1] to be "image", got %v`, instance.InputModalities[1]) + } + if len(instance.OutputModalities) != 1 { + t.Fatalf("Expected OutputModalities length to be 1, got %d", len(instance.OutputModalities)) + } + if instance.OutputModalities[0] != "text" { + t.Errorf(`Expected OutputModalities[0] to be "text", got %v`, instance.OutputModalities[0]) + } + if instance.AdditionalProperties == nil { + t.Fatalf("Expected AdditionalProperties to be populated") + } +} + +// TestModelInfoFromYAML tests loading ModelInfo through the generated YAML helper +func TestModelInfoFromYAML(t *testing.T) { + yamlData := ` +id: gpt-4o +displayName: GPT-4o +ownedBy: openai +contextWindow: 128000 +inputModalities: + - text + - image +outputModalities: + - text +additionalProperties: + supportsStreaming: true + +` + + instance, err := prompty.ModelInfoFromYAML(yamlData) + if err != nil { + t.Fatalf("Failed to load ModelInfo from YAML helper: %v", err) + } + if instance.Id != "gpt-4o" { + t.Errorf(`Expected Id to be "gpt-4o", got %v`, instance.Id) + } + if instance.DisplayName == nil || *instance.DisplayName != "GPT-4o" { + t.Errorf(`Expected DisplayName to be "GPT-4o", got %v`, instance.DisplayName) + } + if instance.OwnedBy == nil || *instance.OwnedBy != "openai" { + t.Errorf(`Expected OwnedBy to be "openai", got %v`, instance.OwnedBy) + } + if instance.ContextWindow == nil || *instance.ContextWindow != 128000 { + t.Errorf(`Expected ContextWindow to be 128000, got %v`, instance.ContextWindow) + } + if len(instance.InputModalities) != 2 { + t.Fatalf("Expected InputModalities length to be 2, got %d", len(instance.InputModalities)) + } + if instance.InputModalities[0] != "text" { + t.Errorf(`Expected InputModalities[0] to be "text", got %v`, instance.InputModalities[0]) + } + if instance.InputModalities[1] != "image" { + t.Errorf(`Expected InputModalities[1] to be "image", got %v`, instance.InputModalities[1]) + } + if len(instance.OutputModalities) != 1 { + t.Fatalf("Expected OutputModalities length to be 1, got %d", len(instance.OutputModalities)) + } + if instance.OutputModalities[0] != "text" { + t.Errorf(`Expected OutputModalities[0] to be "text", got %v`, instance.OutputModalities[0]) + } + if instance.AdditionalProperties == nil { + t.Fatalf("Expected AdditionalProperties to be populated") + } } // TestModelInfoRoundtrip tests load -> save -> load produces equivalent data @@ -144,6 +291,24 @@ func TestModelInfoRoundtrip(t *testing.T) { if reloaded.ContextWindow == nil || *reloaded.ContextWindow != 128000 { t.Errorf(`Expected ContextWindow to be 128000, got %v`, reloaded.ContextWindow) } + if len(reloaded.InputModalities) != 2 { + t.Fatalf("Expected InputModalities length to be 2, got %d", len(reloaded.InputModalities)) + } + if reloaded.InputModalities[0] != "text" { + t.Errorf(`Expected InputModalities[0] to be "text", got %v`, reloaded.InputModalities[0]) + } + if reloaded.InputModalities[1] != "image" { + t.Errorf(`Expected InputModalities[1] to be "image", got %v`, reloaded.InputModalities[1]) + } + if len(reloaded.OutputModalities) != 1 { + t.Fatalf("Expected OutputModalities length to be 1, got %d", len(reloaded.OutputModalities)) + } + if reloaded.OutputModalities[0] != "text" { + t.Errorf(`Expected OutputModalities[0] to be "text", got %v`, reloaded.OutputModalities[0]) + } + if reloaded.AdditionalProperties == nil { + t.Fatalf("Expected AdditionalProperties to be populated") + } } // TestModelInfoToJSON tests that ToJSON produces valid JSON @@ -185,6 +350,41 @@ func TestModelInfoToJSON(t *testing.T) { if err := json.Unmarshal([]byte(jsonOutput), &parsed); err != nil { t.Fatalf("Failed to parse generated JSON: %v", err) } + + reloaded, err := prompty.LoadModelInfo(parsed, ctx) + if err != nil { + t.Fatalf("Failed to reload generated JSON: %v", err) + } + if reloaded.Id != "gpt-4o" { + t.Errorf(`Expected Id to be "gpt-4o", got %v`, reloaded.Id) + } + if reloaded.DisplayName == nil || *reloaded.DisplayName != "GPT-4o" { + t.Errorf(`Expected DisplayName to be "GPT-4o", got %v`, reloaded.DisplayName) + } + if reloaded.OwnedBy == nil || *reloaded.OwnedBy != "openai" { + t.Errorf(`Expected OwnedBy to be "openai", got %v`, reloaded.OwnedBy) + } + if reloaded.ContextWindow == nil || *reloaded.ContextWindow != 128000 { + t.Errorf(`Expected ContextWindow to be 128000, got %v`, reloaded.ContextWindow) + } + if len(reloaded.InputModalities) != 2 { + t.Fatalf("Expected InputModalities length to be 2, got %d", len(reloaded.InputModalities)) + } + if reloaded.InputModalities[0] != "text" { + t.Errorf(`Expected InputModalities[0] to be "text", got %v`, reloaded.InputModalities[0]) + } + if reloaded.InputModalities[1] != "image" { + t.Errorf(`Expected InputModalities[1] to be "image", got %v`, reloaded.InputModalities[1]) + } + if len(reloaded.OutputModalities) != 1 { + t.Fatalf("Expected OutputModalities length to be 1, got %d", len(reloaded.OutputModalities)) + } + if reloaded.OutputModalities[0] != "text" { + t.Errorf(`Expected OutputModalities[0] to be "text", got %v`, reloaded.OutputModalities[0]) + } + if reloaded.AdditionalProperties == nil { + t.Fatalf("Expected AdditionalProperties to be populated") + } } // TestModelInfoToYAML tests that ToYAML produces valid YAML @@ -226,4 +426,46 @@ func TestModelInfoToYAML(t *testing.T) { if err := yaml.Unmarshal([]byte(yamlOutput), &parsed); err != nil { t.Fatalf("Failed to parse generated YAML: %v", err) } + + reloaded, err := prompty.LoadModelInfo(parsed, ctx) + if err != nil { + t.Fatalf("Failed to reload generated YAML: %v", err) + } + if reloaded.Id != "gpt-4o" { + t.Errorf(`Expected Id to be "gpt-4o", got %v`, reloaded.Id) + } + if reloaded.DisplayName == nil || *reloaded.DisplayName != "GPT-4o" { + t.Errorf(`Expected DisplayName to be "GPT-4o", got %v`, reloaded.DisplayName) + } + if reloaded.OwnedBy == nil || *reloaded.OwnedBy != "openai" { + t.Errorf(`Expected OwnedBy to be "openai", got %v`, reloaded.OwnedBy) + } + if reloaded.ContextWindow == nil || *reloaded.ContextWindow != 128000 { + t.Errorf(`Expected ContextWindow to be 128000, got %v`, reloaded.ContextWindow) + } + if len(reloaded.InputModalities) != 2 { + t.Fatalf("Expected InputModalities length to be 2, got %d", len(reloaded.InputModalities)) + } + if reloaded.InputModalities[0] != "text" { + t.Errorf(`Expected InputModalities[0] to be "text", got %v`, reloaded.InputModalities[0]) + } + if reloaded.InputModalities[1] != "image" { + t.Errorf(`Expected InputModalities[1] to be "image", got %v`, reloaded.InputModalities[1]) + } + if len(reloaded.OutputModalities) != 1 { + t.Fatalf("Expected OutputModalities length to be 1, got %d", len(reloaded.OutputModalities)) + } + if reloaded.OutputModalities[0] != "text" { + t.Errorf(`Expected OutputModalities[0] to be "text", got %v`, reloaded.OutputModalities[0]) + } + if reloaded.AdditionalProperties == nil { + t.Fatalf("Expected AdditionalProperties to be populated") + } +} + +// TestModelInfoFromJSONInvalid rejects malformed JSON instead of silently defaulting +func TestModelInfoFromJSONInvalid(t *testing.T) { + if _, err := prompty.ModelInfoFromJSON("{"); err == nil { + t.Fatalf("Expected malformed JSON to fail") + } } diff --git a/runtime/go/prompty/model/model_options.go b/runtime/go/prompty/model/model_options.go index 5ef6f708..d416e9e5 100644 --- a/runtime/go/prompty/model/model_options.go +++ b/runtime/go/prompty/model/model_options.go @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. // Group: model package prompty @@ -137,11 +138,14 @@ func LoadModelOptions(data interface{}, ctx *LoadContext) (ModelOptions, error) result.TopP = &v } if val, ok := m["stopSequences"]; ok && val != nil { - if arr, ok := val.([]interface{}); ok { + switch arr := val.(type) { + case []interface{}: result.StopSequences = make([]string, len(arr)) for i, v := range arr { result.StopSequences[i] = v.(string) } + case []string: + result.StopSequences = arr } } if val, ok := m["allowMultipleToolCalls"]; ok && val != nil { @@ -159,7 +163,7 @@ func LoadModelOptions(data interface{}, ctx *LoadContext) (ModelOptions, error) } // Save serializes ModelOptions to map[string]interface{} -func (obj *ModelOptions) Save(ctx *SaveContext) map[string]interface{} { +func (obj ModelOptions) Save(ctx *SaveContext) map[string]interface{} { result := make(map[string]interface{}) if obj.FrequencyPenalty != nil { result["frequencyPenalty"] = *obj.FrequencyPenalty @@ -233,11 +237,7 @@ func (obj *ModelOptions) ToJSON() (string, error) { func (obj *ModelOptions) ToYAML() (string, error) { ctx := NewSaveContext() data := obj.Save(ctx) - bytes, err := yaml.Marshal(data) - if err != nil { - return "", err - } - return string(bytes), nil + return marshalYAMLDocument(data) } // FromJSON creates ModelOptions from JSON string diff --git a/runtime/go/prompty/model/model_options_test.go b/runtime/go/prompty/model/model_options_test.go index 36b7613a..01c2cac9 100644 --- a/runtime/go/prompty/model/model_options_test.go +++ b/runtime/go/prompty/model/model_options_test.go @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. package prompty_test @@ -67,6 +68,24 @@ func TestModelOptionsLoadJSON(t *testing.T) { if instance.AllowMultipleToolCalls == nil || *instance.AllowMultipleToolCalls != true { t.Errorf(`Expected AllowMultipleToolCalls to be true, got %v`, instance.AllowMultipleToolCalls) } + if len(instance.StopSequences) != 2 { + t.Fatalf("Expected StopSequences length to be 2, got %d", len(instance.StopSequences)) + } + if instance.StopSequences[0] != "\n" { + t.Errorf(`Expected StopSequences[0] to be "\n", got %v`, instance.StopSequences[0]) + } + if instance.StopSequences[1] != "###" { + t.Errorf(`Expected StopSequences[1] to be "###", got %v`, instance.StopSequences[1]) + } + if instance.AdditionalProperties == nil { + t.Fatalf("Expected AdditionalProperties to be populated") + } + if got := instance.AdditionalProperties["customProperty"]; got != "value" { + t.Errorf(`Expected AdditionalProperties["customProperty"] to be "value", got %v`, got) + } + if got := instance.AdditionalProperties["anotherProperty"]; got != "anotherValue" { + t.Errorf(`Expected AdditionalProperties["anotherProperty"] to be "anotherValue", got %v`, got) + } } // TestModelOptionsLoadYAML tests loading ModelOptions from YAML @@ -122,6 +141,163 @@ additionalProperties: if instance.AllowMultipleToolCalls == nil || *instance.AllowMultipleToolCalls != true { t.Errorf(`Expected AllowMultipleToolCalls to be true, got %v`, instance.AllowMultipleToolCalls) } + if len(instance.StopSequences) != 2 { + t.Fatalf("Expected StopSequences length to be 2, got %d", len(instance.StopSequences)) + } + if instance.StopSequences[0] != "\n" { + t.Errorf(`Expected StopSequences[0] to be "\n", got %v`, instance.StopSequences[0]) + } + if instance.StopSequences[1] != "###" { + t.Errorf(`Expected StopSequences[1] to be "###", got %v`, instance.StopSequences[1]) + } + if instance.AdditionalProperties == nil { + t.Fatalf("Expected AdditionalProperties to be populated") + } + if got := instance.AdditionalProperties["customProperty"]; got != "value" { + t.Errorf(`Expected AdditionalProperties["customProperty"] to be "value", got %v`, got) + } + if got := instance.AdditionalProperties["anotherProperty"]; got != "anotherValue" { + t.Errorf(`Expected AdditionalProperties["anotherProperty"] to be "anotherValue", got %v`, got) + } +} + +// TestModelOptionsFromJSON tests loading ModelOptions through the generated JSON helper +func TestModelOptionsFromJSON(t *testing.T) { + jsonData := ` +{ + "frequencyPenalty": 0.5, + "maxOutputTokens": 2048, + "presencePenalty": 0.3, + "seed": 42, + "temperature": 0.7, + "topK": 40, + "topP": 0.9, + "stopSequences": [ + "\n", + "###" + ], + "allowMultipleToolCalls": true, + "additionalProperties": { + "customProperty": "value", + "anotherProperty": "anotherValue" + } +} +` + + instance, err := prompty.ModelOptionsFromJSON(jsonData) + if err != nil { + t.Fatalf("Failed to load ModelOptions from JSON helper: %v", err) + } + if instance.FrequencyPenalty == nil || *instance.FrequencyPenalty != 0.5 { + t.Errorf(`Expected FrequencyPenalty to be 0.5, got %v`, instance.FrequencyPenalty) + } + if instance.MaxOutputTokens == nil || *instance.MaxOutputTokens != 2048 { + t.Errorf(`Expected MaxOutputTokens to be 2048, got %v`, instance.MaxOutputTokens) + } + if instance.PresencePenalty == nil || *instance.PresencePenalty != 0.3 { + t.Errorf(`Expected PresencePenalty to be 0.3, got %v`, instance.PresencePenalty) + } + if instance.Seed == nil || *instance.Seed != 42 { + t.Errorf(`Expected Seed to be 42, got %v`, instance.Seed) + } + if instance.Temperature == nil || *instance.Temperature != 0.7 { + t.Errorf(`Expected Temperature to be 0.7, got %v`, instance.Temperature) + } + if instance.TopK == nil || *instance.TopK != 40 { + t.Errorf(`Expected TopK to be 40, got %v`, instance.TopK) + } + if instance.TopP == nil || *instance.TopP != 0.9 { + t.Errorf(`Expected TopP to be 0.9, got %v`, instance.TopP) + } + if instance.AllowMultipleToolCalls == nil || *instance.AllowMultipleToolCalls != true { + t.Errorf(`Expected AllowMultipleToolCalls to be true, got %v`, instance.AllowMultipleToolCalls) + } + if len(instance.StopSequences) != 2 { + t.Fatalf("Expected StopSequences length to be 2, got %d", len(instance.StopSequences)) + } + if instance.StopSequences[0] != "\n" { + t.Errorf(`Expected StopSequences[0] to be "\n", got %v`, instance.StopSequences[0]) + } + if instance.StopSequences[1] != "###" { + t.Errorf(`Expected StopSequences[1] to be "###", got %v`, instance.StopSequences[1]) + } + if instance.AdditionalProperties == nil { + t.Fatalf("Expected AdditionalProperties to be populated") + } + if got := instance.AdditionalProperties["customProperty"]; got != "value" { + t.Errorf(`Expected AdditionalProperties["customProperty"] to be "value", got %v`, got) + } + if got := instance.AdditionalProperties["anotherProperty"]; got != "anotherValue" { + t.Errorf(`Expected AdditionalProperties["anotherProperty"] to be "anotherValue", got %v`, got) + } +} + +// TestModelOptionsFromYAML tests loading ModelOptions through the generated YAML helper +func TestModelOptionsFromYAML(t *testing.T) { + yamlData := ` +frequencyPenalty: 0.5 +maxOutputTokens: 2048 +presencePenalty: 0.3 +seed: 42 +temperature: 0.7 +topK: 40 +topP: 0.9 +stopSequences: + - "\n" + - "###" +allowMultipleToolCalls: true +additionalProperties: + customProperty: value + anotherProperty: anotherValue + +` + + instance, err := prompty.ModelOptionsFromYAML(yamlData) + if err != nil { + t.Fatalf("Failed to load ModelOptions from YAML helper: %v", err) + } + if instance.FrequencyPenalty == nil || *instance.FrequencyPenalty != 0.5 { + t.Errorf(`Expected FrequencyPenalty to be 0.5, got %v`, instance.FrequencyPenalty) + } + if instance.MaxOutputTokens == nil || *instance.MaxOutputTokens != 2048 { + t.Errorf(`Expected MaxOutputTokens to be 2048, got %v`, instance.MaxOutputTokens) + } + if instance.PresencePenalty == nil || *instance.PresencePenalty != 0.3 { + t.Errorf(`Expected PresencePenalty to be 0.3, got %v`, instance.PresencePenalty) + } + if instance.Seed == nil || *instance.Seed != 42 { + t.Errorf(`Expected Seed to be 42, got %v`, instance.Seed) + } + if instance.Temperature == nil || *instance.Temperature != 0.7 { + t.Errorf(`Expected Temperature to be 0.7, got %v`, instance.Temperature) + } + if instance.TopK == nil || *instance.TopK != 40 { + t.Errorf(`Expected TopK to be 40, got %v`, instance.TopK) + } + if instance.TopP == nil || *instance.TopP != 0.9 { + t.Errorf(`Expected TopP to be 0.9, got %v`, instance.TopP) + } + if instance.AllowMultipleToolCalls == nil || *instance.AllowMultipleToolCalls != true { + t.Errorf(`Expected AllowMultipleToolCalls to be true, got %v`, instance.AllowMultipleToolCalls) + } + if len(instance.StopSequences) != 2 { + t.Fatalf("Expected StopSequences length to be 2, got %d", len(instance.StopSequences)) + } + if instance.StopSequences[0] != "\n" { + t.Errorf(`Expected StopSequences[0] to be "\n", got %v`, instance.StopSequences[0]) + } + if instance.StopSequences[1] != "###" { + t.Errorf(`Expected StopSequences[1] to be "###", got %v`, instance.StopSequences[1]) + } + if instance.AdditionalProperties == nil { + t.Fatalf("Expected AdditionalProperties to be populated") + } + if got := instance.AdditionalProperties["customProperty"]; got != "value" { + t.Errorf(`Expected AdditionalProperties["customProperty"] to be "value", got %v`, got) + } + if got := instance.AdditionalProperties["anotherProperty"]; got != "anotherValue" { + t.Errorf(`Expected AdditionalProperties["anotherProperty"] to be "anotherValue", got %v`, got) + } } // TestModelOptionsRoundtrip tests load -> save -> load produces equivalent data @@ -187,6 +363,24 @@ func TestModelOptionsRoundtrip(t *testing.T) { if reloaded.AllowMultipleToolCalls == nil || *reloaded.AllowMultipleToolCalls != true { t.Errorf(`Expected AllowMultipleToolCalls to be true, got %v`, reloaded.AllowMultipleToolCalls) } + if len(reloaded.StopSequences) != 2 { + t.Fatalf("Expected StopSequences length to be 2, got %d", len(reloaded.StopSequences)) + } + if reloaded.StopSequences[0] != "\n" { + t.Errorf(`Expected StopSequences[0] to be "\n", got %v`, reloaded.StopSequences[0]) + } + if reloaded.StopSequences[1] != "###" { + t.Errorf(`Expected StopSequences[1] to be "###", got %v`, reloaded.StopSequences[1]) + } + if reloaded.AdditionalProperties == nil { + t.Fatalf("Expected AdditionalProperties to be populated") + } + if got := reloaded.AdditionalProperties["customProperty"]; got != "value" { + t.Errorf(`Expected AdditionalProperties["customProperty"] to be "value", got %v`, got) + } + if got := reloaded.AdditionalProperties["anotherProperty"]; got != "anotherValue" { + t.Errorf(`Expected AdditionalProperties["anotherProperty"] to be "anotherValue", got %v`, got) + } } // TestModelOptionsToJSON tests that ToJSON produces valid JSON @@ -230,6 +424,53 @@ func TestModelOptionsToJSON(t *testing.T) { if err := json.Unmarshal([]byte(jsonOutput), &parsed); err != nil { t.Fatalf("Failed to parse generated JSON: %v", err) } + + reloaded, err := prompty.LoadModelOptions(parsed, ctx) + if err != nil { + t.Fatalf("Failed to reload generated JSON: %v", err) + } + if reloaded.FrequencyPenalty == nil || *reloaded.FrequencyPenalty != 0.5 { + t.Errorf(`Expected FrequencyPenalty to be 0.5, got %v`, reloaded.FrequencyPenalty) + } + if reloaded.MaxOutputTokens == nil || *reloaded.MaxOutputTokens != 2048 { + t.Errorf(`Expected MaxOutputTokens to be 2048, got %v`, reloaded.MaxOutputTokens) + } + if reloaded.PresencePenalty == nil || *reloaded.PresencePenalty != 0.3 { + t.Errorf(`Expected PresencePenalty to be 0.3, got %v`, reloaded.PresencePenalty) + } + if reloaded.Seed == nil || *reloaded.Seed != 42 { + t.Errorf(`Expected Seed to be 42, got %v`, reloaded.Seed) + } + if reloaded.Temperature == nil || *reloaded.Temperature != 0.7 { + t.Errorf(`Expected Temperature to be 0.7, got %v`, reloaded.Temperature) + } + if reloaded.TopK == nil || *reloaded.TopK != 40 { + t.Errorf(`Expected TopK to be 40, got %v`, reloaded.TopK) + } + if reloaded.TopP == nil || *reloaded.TopP != 0.9 { + t.Errorf(`Expected TopP to be 0.9, got %v`, reloaded.TopP) + } + if reloaded.AllowMultipleToolCalls == nil || *reloaded.AllowMultipleToolCalls != true { + t.Errorf(`Expected AllowMultipleToolCalls to be true, got %v`, reloaded.AllowMultipleToolCalls) + } + if len(reloaded.StopSequences) != 2 { + t.Fatalf("Expected StopSequences length to be 2, got %d", len(reloaded.StopSequences)) + } + if reloaded.StopSequences[0] != "\n" { + t.Errorf(`Expected StopSequences[0] to be "\n", got %v`, reloaded.StopSequences[0]) + } + if reloaded.StopSequences[1] != "###" { + t.Errorf(`Expected StopSequences[1] to be "###", got %v`, reloaded.StopSequences[1]) + } + if reloaded.AdditionalProperties == nil { + t.Fatalf("Expected AdditionalProperties to be populated") + } + if got := reloaded.AdditionalProperties["customProperty"]; got != "value" { + t.Errorf(`Expected AdditionalProperties["customProperty"] to be "value", got %v`, got) + } + if got := reloaded.AdditionalProperties["anotherProperty"]; got != "anotherValue" { + t.Errorf(`Expected AdditionalProperties["anotherProperty"] to be "anotherValue", got %v`, got) + } } // TestModelOptionsToYAML tests that ToYAML produces valid YAML @@ -273,4 +514,188 @@ func TestModelOptionsToYAML(t *testing.T) { if err := yaml.Unmarshal([]byte(yamlOutput), &parsed); err != nil { t.Fatalf("Failed to parse generated YAML: %v", err) } + + reloaded, err := prompty.LoadModelOptions(parsed, ctx) + if err != nil { + t.Fatalf("Failed to reload generated YAML: %v", err) + } + if reloaded.FrequencyPenalty == nil || *reloaded.FrequencyPenalty != 0.5 { + t.Errorf(`Expected FrequencyPenalty to be 0.5, got %v`, reloaded.FrequencyPenalty) + } + if reloaded.MaxOutputTokens == nil || *reloaded.MaxOutputTokens != 2048 { + t.Errorf(`Expected MaxOutputTokens to be 2048, got %v`, reloaded.MaxOutputTokens) + } + if reloaded.PresencePenalty == nil || *reloaded.PresencePenalty != 0.3 { + t.Errorf(`Expected PresencePenalty to be 0.3, got %v`, reloaded.PresencePenalty) + } + if reloaded.Seed == nil || *reloaded.Seed != 42 { + t.Errorf(`Expected Seed to be 42, got %v`, reloaded.Seed) + } + if reloaded.Temperature == nil || *reloaded.Temperature != 0.7 { + t.Errorf(`Expected Temperature to be 0.7, got %v`, reloaded.Temperature) + } + if reloaded.TopK == nil || *reloaded.TopK != 40 { + t.Errorf(`Expected TopK to be 40, got %v`, reloaded.TopK) + } + if reloaded.TopP == nil || *reloaded.TopP != 0.9 { + t.Errorf(`Expected TopP to be 0.9, got %v`, reloaded.TopP) + } + if reloaded.AllowMultipleToolCalls == nil || *reloaded.AllowMultipleToolCalls != true { + t.Errorf(`Expected AllowMultipleToolCalls to be true, got %v`, reloaded.AllowMultipleToolCalls) + } + if len(reloaded.StopSequences) != 2 { + t.Fatalf("Expected StopSequences length to be 2, got %d", len(reloaded.StopSequences)) + } + if reloaded.StopSequences[0] != "\n" { + t.Errorf(`Expected StopSequences[0] to be "\n", got %v`, reloaded.StopSequences[0]) + } + if reloaded.StopSequences[1] != "###" { + t.Errorf(`Expected StopSequences[1] to be "###", got %v`, reloaded.StopSequences[1]) + } + if reloaded.AdditionalProperties == nil { + t.Fatalf("Expected AdditionalProperties to be populated") + } + if got := reloaded.AdditionalProperties["customProperty"]; got != "value" { + t.Errorf(`Expected AdditionalProperties["customProperty"] to be "value", got %v`, got) + } + if got := reloaded.AdditionalProperties["anotherProperty"]; got != "anotherValue" { + t.Errorf(`Expected AdditionalProperties["anotherProperty"] to be "anotherValue", got %v`, got) + } +} + +// TestModelOptionsFromJSONInvalid rejects malformed JSON instead of silently defaulting +func TestModelOptionsFromJSONInvalid(t *testing.T) { + if _, err := prompty.ModelOptionsFromJSON("{"); err == nil { + t.Fatalf("Expected malformed JSON to fail") + } +} + +// TestModelOptionsToWire tests provider-specific wire field names +func TestModelOptionsToWire(t *testing.T) { + jsonData := ` +{ + "frequencyPenalty": 0.5, + "maxOutputTokens": 2048, + "presencePenalty": 0.3, + "seed": 42, + "temperature": 0.7, + "topK": 40, + "topP": 0.9, + "stopSequences": [ + "\n", + "###" + ], + "allowMultipleToolCalls": true, + "additionalProperties": { + "customProperty": "value", + "anotherProperty": "anotherValue" + } +} +` + var data map[string]interface{} + if err := json.Unmarshal([]byte(jsonData), &data); err != nil { + t.Fatalf("Failed to parse JSON: %v", err) + } + + ctx := prompty.NewLoadContext() + instance, err := prompty.LoadModelOptions(data, ctx) + if err != nil { + t.Fatalf("Failed to load ModelOptions: %v", err) + } + + openaiWire := instance.ToWire("openai") + if _, ok := openaiWire["frequency_penalty"]; !ok { + t.Errorf("Expected openai wire output to include frequency_penalty") + } + if _, ok := openaiWire["frequencyPenalty"]; ok { + t.Errorf("Expected openai wire output to omit source field frequencyPenalty") + } + if _, ok := openaiWire["max_completion_tokens"]; !ok { + t.Errorf("Expected openai wire output to include max_completion_tokens") + } + if _, ok := openaiWire["maxOutputTokens"]; ok { + t.Errorf("Expected openai wire output to omit source field maxOutputTokens") + } + if _, ok := openaiWire["presence_penalty"]; !ok { + t.Errorf("Expected openai wire output to include presence_penalty") + } + if _, ok := openaiWire["presencePenalty"]; ok { + t.Errorf("Expected openai wire output to omit source field presencePenalty") + } + if _, ok := openaiWire["seed"]; !ok { + t.Errorf("Expected openai wire output to include seed") + } + if _, ok := openaiWire["temperature"]; !ok { + t.Errorf("Expected openai wire output to include temperature") + } + if _, ok := openaiWire["top_k"]; !ok { + t.Errorf("Expected openai wire output to include top_k") + } + if _, ok := openaiWire["topK"]; ok { + t.Errorf("Expected openai wire output to omit source field topK") + } + if _, ok := openaiWire["top_p"]; !ok { + t.Errorf("Expected openai wire output to include top_p") + } + if _, ok := openaiWire["topP"]; ok { + t.Errorf("Expected openai wire output to omit source field topP") + } + if _, ok := openaiWire["stop"]; !ok { + t.Errorf("Expected openai wire output to include stop") + } + if _, ok := openaiWire["stopSequences"]; ok { + t.Errorf("Expected openai wire output to omit source field stopSequences") + } + if _, ok := openaiWire["parallel_tool_calls"]; !ok { + t.Errorf("Expected openai wire output to include parallel_tool_calls") + } + if _, ok := openaiWire["allowMultipleToolCalls"]; ok { + t.Errorf("Expected openai wire output to omit source field allowMultipleToolCalls") + } + + responsesWire := instance.ToWire("responses") + if _, ok := responsesWire["max_output_tokens"]; !ok { + t.Errorf("Expected responses wire output to include max_output_tokens") + } + if _, ok := responsesWire["maxOutputTokens"]; ok { + t.Errorf("Expected responses wire output to omit source field maxOutputTokens") + } + if _, ok := responsesWire["temperature"]; !ok { + t.Errorf("Expected responses wire output to include temperature") + } + if _, ok := responsesWire["top_p"]; !ok { + t.Errorf("Expected responses wire output to include top_p") + } + if _, ok := responsesWire["topP"]; ok { + t.Errorf("Expected responses wire output to omit source field topP") + } + + anthropicWire := instance.ToWire("anthropic") + if _, ok := anthropicWire["max_tokens"]; !ok { + t.Errorf("Expected anthropic wire output to include max_tokens") + } + if _, ok := anthropicWire["maxOutputTokens"]; ok { + t.Errorf("Expected anthropic wire output to omit source field maxOutputTokens") + } + if _, ok := anthropicWire["temperature"]; !ok { + t.Errorf("Expected anthropic wire output to include temperature") + } + if _, ok := anthropicWire["top_k"]; !ok { + t.Errorf("Expected anthropic wire output to include top_k") + } + if _, ok := anthropicWire["topK"]; ok { + t.Errorf("Expected anthropic wire output to omit source field topK") + } + if _, ok := anthropicWire["top_p"]; !ok { + t.Errorf("Expected anthropic wire output to include top_p") + } + if _, ok := anthropicWire["topP"]; ok { + t.Errorf("Expected anthropic wire output to omit source field topP") + } + if _, ok := anthropicWire["stop_sequences"]; !ok { + t.Errorf("Expected anthropic wire output to include stop_sequences") + } + if _, ok := anthropicWire["stopSequences"]; ok { + t.Errorf("Expected anthropic wire output to omit source field stopSequences") + } } diff --git a/runtime/go/prompty/model/model_test.go b/runtime/go/prompty/model/model_test.go index debf8efd..1c530237 100644 --- a/runtime/go/prompty/model/model_test.go +++ b/runtime/go/prompty/model/model_test.go @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. package prompty_test @@ -49,6 +50,16 @@ func TestModelLoadJSON(t *testing.T) { if instance.ApiType == nil || *instance.ApiType != "chat" { t.Errorf(`Expected ApiType to be "chat", got %v`, instance.ApiType) } + connectionValue, ok := instance.Connection.(prompty.ApiKeyConnection) + if !ok { + t.Fatalf("Expected Connection to be prompty.ApiKeyConnection, got %T", instance.Connection) + } + if connectionValue.Kind != "key" { + t.Errorf(`Expected Kind to be "key", got %v`, connectionValue.Kind) + } + if connectionValue.Endpoint != "https://{your-custom-endpoint}.openai.azure.com/" { + t.Errorf(`Expected Endpoint to be "https://{your-custom-endpoint}.openai.azure.com/", got %v`, connectionValue.Endpoint) + } } // TestModelLoadYAML tests loading Model from YAML @@ -86,6 +97,103 @@ options: if instance.ApiType == nil || *instance.ApiType != "chat" { t.Errorf(`Expected ApiType to be "chat", got %v`, instance.ApiType) } + connectionValue, ok := instance.Connection.(prompty.ApiKeyConnection) + if !ok { + t.Fatalf("Expected Connection to be prompty.ApiKeyConnection, got %T", instance.Connection) + } + if connectionValue.Kind != "key" { + t.Errorf(`Expected Kind to be "key", got %v`, connectionValue.Kind) + } + if connectionValue.Endpoint != "https://{your-custom-endpoint}.openai.azure.com/" { + t.Errorf(`Expected Endpoint to be "https://{your-custom-endpoint}.openai.azure.com/", got %v`, connectionValue.Endpoint) + } +} + +// TestModelFromJSON tests loading Model through the generated JSON helper +func TestModelFromJSON(t *testing.T) { + jsonData := ` +{ + "id": "gpt-35-turbo", + "provider": "foundry", + "apiType": "chat", + "connection": { + "kind": "key", + "endpoint": "https://{your-custom-endpoint}.openai.azure.com/", + "key": "{your-api-key}" + }, + "options": { + "type": "chat", + "temperature": 0.7, + "maxOutputTokens": 1000 + } +} +` + + instance, err := prompty.ModelFromJSON(jsonData) + if err != nil { + t.Fatalf("Failed to load Model from JSON helper: %v", err) + } + if instance.Id != "gpt-35-turbo" { + t.Errorf(`Expected Id to be "gpt-35-turbo", got %v`, instance.Id) + } + if instance.Provider == nil || *instance.Provider != "foundry" { + t.Errorf(`Expected Provider to be "foundry", got %v`, instance.Provider) + } + if instance.ApiType == nil || *instance.ApiType != "chat" { + t.Errorf(`Expected ApiType to be "chat", got %v`, instance.ApiType) + } + connectionValue, ok := instance.Connection.(prompty.ApiKeyConnection) + if !ok { + t.Fatalf("Expected Connection to be prompty.ApiKeyConnection, got %T", instance.Connection) + } + if connectionValue.Kind != "key" { + t.Errorf(`Expected Kind to be "key", got %v`, connectionValue.Kind) + } + if connectionValue.Endpoint != "https://{your-custom-endpoint}.openai.azure.com/" { + t.Errorf(`Expected Endpoint to be "https://{your-custom-endpoint}.openai.azure.com/", got %v`, connectionValue.Endpoint) + } +} + +// TestModelFromYAML tests loading Model through the generated YAML helper +func TestModelFromYAML(t *testing.T) { + yamlData := ` +id: gpt-35-turbo +provider: foundry +apiType: chat +connection: + kind: key + endpoint: "https://{your-custom-endpoint}.openai.azure.com/" + key: "{your-api-key}" +options: + type: chat + temperature: 0.7 + maxOutputTokens: 1000 + +` + + instance, err := prompty.ModelFromYAML(yamlData) + if err != nil { + t.Fatalf("Failed to load Model from YAML helper: %v", err) + } + if instance.Id != "gpt-35-turbo" { + t.Errorf(`Expected Id to be "gpt-35-turbo", got %v`, instance.Id) + } + if instance.Provider == nil || *instance.Provider != "foundry" { + t.Errorf(`Expected Provider to be "foundry", got %v`, instance.Provider) + } + if instance.ApiType == nil || *instance.ApiType != "chat" { + t.Errorf(`Expected ApiType to be "chat", got %v`, instance.ApiType) + } + connectionValue, ok := instance.Connection.(prompty.ApiKeyConnection) + if !ok { + t.Fatalf("Expected Connection to be prompty.ApiKeyConnection, got %T", instance.Connection) + } + if connectionValue.Kind != "key" { + t.Errorf(`Expected Kind to be "key", got %v`, connectionValue.Kind) + } + if connectionValue.Endpoint != "https://{your-custom-endpoint}.openai.azure.com/" { + t.Errorf(`Expected Endpoint to be "https://{your-custom-endpoint}.openai.azure.com/", got %v`, connectionValue.Endpoint) + } } // TestModelRoundtrip tests load -> save -> load produces equivalent data @@ -133,6 +241,16 @@ func TestModelRoundtrip(t *testing.T) { if reloaded.ApiType == nil || *reloaded.ApiType != "chat" { t.Errorf(`Expected ApiType to be "chat", got %v`, reloaded.ApiType) } + connectionValue, ok := reloaded.Connection.(prompty.ApiKeyConnection) + if !ok { + t.Fatalf("Expected Connection to be prompty.ApiKeyConnection, got %T", reloaded.Connection) + } + if connectionValue.Kind != "key" { + t.Errorf(`Expected Kind to be "key", got %v`, connectionValue.Kind) + } + if connectionValue.Endpoint != "https://{your-custom-endpoint}.openai.azure.com/" { + t.Errorf(`Expected Endpoint to be "https://{your-custom-endpoint}.openai.azure.com/", got %v`, connectionValue.Endpoint) + } } // TestModelToJSON tests that ToJSON produces valid JSON @@ -173,6 +291,30 @@ func TestModelToJSON(t *testing.T) { if err := json.Unmarshal([]byte(jsonOutput), &parsed); err != nil { t.Fatalf("Failed to parse generated JSON: %v", err) } + + reloaded, err := prompty.LoadModel(parsed, ctx) + if err != nil { + t.Fatalf("Failed to reload generated JSON: %v", err) + } + if reloaded.Id != "gpt-35-turbo" { + t.Errorf(`Expected Id to be "gpt-35-turbo", got %v`, reloaded.Id) + } + if reloaded.Provider == nil || *reloaded.Provider != "foundry" { + t.Errorf(`Expected Provider to be "foundry", got %v`, reloaded.Provider) + } + if reloaded.ApiType == nil || *reloaded.ApiType != "chat" { + t.Errorf(`Expected ApiType to be "chat", got %v`, reloaded.ApiType) + } + connectionValue, ok := reloaded.Connection.(prompty.ApiKeyConnection) + if !ok { + t.Fatalf("Expected Connection to be prompty.ApiKeyConnection, got %T", reloaded.Connection) + } + if connectionValue.Kind != "key" { + t.Errorf(`Expected Kind to be "key", got %v`, connectionValue.Kind) + } + if connectionValue.Endpoint != "https://{your-custom-endpoint}.openai.azure.com/" { + t.Errorf(`Expected Endpoint to be "https://{your-custom-endpoint}.openai.azure.com/", got %v`, connectionValue.Endpoint) + } } // TestModelToYAML tests that ToYAML produces valid YAML @@ -213,6 +355,37 @@ func TestModelToYAML(t *testing.T) { if err := yaml.Unmarshal([]byte(yamlOutput), &parsed); err != nil { t.Fatalf("Failed to parse generated YAML: %v", err) } + + reloaded, err := prompty.LoadModel(parsed, ctx) + if err != nil { + t.Fatalf("Failed to reload generated YAML: %v", err) + } + if reloaded.Id != "gpt-35-turbo" { + t.Errorf(`Expected Id to be "gpt-35-turbo", got %v`, reloaded.Id) + } + if reloaded.Provider == nil || *reloaded.Provider != "foundry" { + t.Errorf(`Expected Provider to be "foundry", got %v`, reloaded.Provider) + } + if reloaded.ApiType == nil || *reloaded.ApiType != "chat" { + t.Errorf(`Expected ApiType to be "chat", got %v`, reloaded.ApiType) + } + connectionValue, ok := reloaded.Connection.(prompty.ApiKeyConnection) + if !ok { + t.Fatalf("Expected Connection to be prompty.ApiKeyConnection, got %T", reloaded.Connection) + } + if connectionValue.Kind != "key" { + t.Errorf(`Expected Kind to be "key", got %v`, connectionValue.Kind) + } + if connectionValue.Endpoint != "https://{your-custom-endpoint}.openai.azure.com/" { + t.Errorf(`Expected Endpoint to be "https://{your-custom-endpoint}.openai.azure.com/", got %v`, connectionValue.Endpoint) + } +} + +// TestModelFromJSONInvalid rejects malformed JSON instead of silently defaulting +func TestModelFromJSONInvalid(t *testing.T) { + if _, err := prompty.ModelFromJSON("{"); err == nil { + t.Fatalf("Expected malformed JSON to fail") + } } // TestModelFromModel tests loading Model from string diff --git a/runtime/go/prompty/model/o_auth_connection_test.go b/runtime/go/prompty/model/o_auth_connection_test.go index 4107d7d0..09b5ff15 100644 --- a/runtime/go/prompty/model/o_auth_connection_test.go +++ b/runtime/go/prompty/model/o_auth_connection_test.go @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. package prompty_test @@ -50,6 +51,12 @@ func TestOAuthConnectionLoadJSON(t *testing.T) { if instance.TokenUrl != "https://login.microsoftonline.com/{tenant}/oauth2/v2.0/token" { t.Errorf(`Expected TokenUrl to be "https://login.microsoftonline.com/{tenant}/oauth2/v2.0/token", got %v`, instance.TokenUrl) } + if len(instance.Scopes) != 1 { + t.Fatalf("Expected Scopes length to be 1, got %d", len(instance.Scopes)) + } + if instance.Scopes[0] != "https://cognitiveservices.azure.com/.default" { + t.Errorf(`Expected Scopes[0] to be "https://cognitiveservices.azure.com/.default", got %v`, instance.Scopes[0]) + } } // TestOAuthConnectionLoadYAML tests loading OAuthConnection from YAML @@ -89,6 +96,94 @@ scopes: if instance.TokenUrl != "https://login.microsoftonline.com/{tenant}/oauth2/v2.0/token" { t.Errorf(`Expected TokenUrl to be "https://login.microsoftonline.com/{tenant}/oauth2/v2.0/token", got %v`, instance.TokenUrl) } + if len(instance.Scopes) != 1 { + t.Fatalf("Expected Scopes length to be 1, got %d", len(instance.Scopes)) + } + if instance.Scopes[0] != "https://cognitiveservices.azure.com/.default" { + t.Errorf(`Expected Scopes[0] to be "https://cognitiveservices.azure.com/.default", got %v`, instance.Scopes[0]) + } +} + +// TestOAuthConnectionFromJSON tests loading OAuthConnection through the generated JSON helper +func TestOAuthConnectionFromJSON(t *testing.T) { + jsonData := ` +{ + "kind": "oauth", + "endpoint": "https://api.example.com", + "clientId": "your-client-id", + "clientSecret": "your-client-secret", + "tokenUrl": "https://login.microsoftonline.com/{tenant}/oauth2/v2.0/token", + "scopes": [ + "https://cognitiveservices.azure.com/.default" + ] +} +` + + instance, err := prompty.OAuthConnectionFromJSON(jsonData) + if err != nil { + t.Fatalf("Failed to load OAuthConnection from JSON helper: %v", err) + } + if instance.Kind != "oauth" { + t.Errorf(`Expected Kind to be "oauth", got %v`, instance.Kind) + } + if instance.Endpoint != "https://api.example.com" { + t.Errorf(`Expected Endpoint to be "https://api.example.com", got %v`, instance.Endpoint) + } + if instance.ClientId != "your-client-id" { + t.Errorf(`Expected ClientId to be "your-client-id", got %v`, instance.ClientId) + } + if instance.ClientSecret != "your-client-secret" { + t.Errorf(`Expected ClientSecret to be "your-client-secret", got %v`, instance.ClientSecret) + } + if instance.TokenUrl != "https://login.microsoftonline.com/{tenant}/oauth2/v2.0/token" { + t.Errorf(`Expected TokenUrl to be "https://login.microsoftonline.com/{tenant}/oauth2/v2.0/token", got %v`, instance.TokenUrl) + } + if len(instance.Scopes) != 1 { + t.Fatalf("Expected Scopes length to be 1, got %d", len(instance.Scopes)) + } + if instance.Scopes[0] != "https://cognitiveservices.azure.com/.default" { + t.Errorf(`Expected Scopes[0] to be "https://cognitiveservices.azure.com/.default", got %v`, instance.Scopes[0]) + } +} + +// TestOAuthConnectionFromYAML tests loading OAuthConnection through the generated YAML helper +func TestOAuthConnectionFromYAML(t *testing.T) { + yamlData := ` +kind: oauth +endpoint: "https://api.example.com" +clientId: your-client-id +clientSecret: your-client-secret +tokenUrl: "https://login.microsoftonline.com/{tenant}/oauth2/v2.0/token" +scopes: + - "https://cognitiveservices.azure.com/.default" + +` + + instance, err := prompty.OAuthConnectionFromYAML(yamlData) + if err != nil { + t.Fatalf("Failed to load OAuthConnection from YAML helper: %v", err) + } + if instance.Kind != "oauth" { + t.Errorf(`Expected Kind to be "oauth", got %v`, instance.Kind) + } + if instance.Endpoint != "https://api.example.com" { + t.Errorf(`Expected Endpoint to be "https://api.example.com", got %v`, instance.Endpoint) + } + if instance.ClientId != "your-client-id" { + t.Errorf(`Expected ClientId to be "your-client-id", got %v`, instance.ClientId) + } + if instance.ClientSecret != "your-client-secret" { + t.Errorf(`Expected ClientSecret to be "your-client-secret", got %v`, instance.ClientSecret) + } + if instance.TokenUrl != "https://login.microsoftonline.com/{tenant}/oauth2/v2.0/token" { + t.Errorf(`Expected TokenUrl to be "https://login.microsoftonline.com/{tenant}/oauth2/v2.0/token", got %v`, instance.TokenUrl) + } + if len(instance.Scopes) != 1 { + t.Fatalf("Expected Scopes length to be 1, got %d", len(instance.Scopes)) + } + if instance.Scopes[0] != "https://cognitiveservices.azure.com/.default" { + t.Errorf(`Expected Scopes[0] to be "https://cognitiveservices.azure.com/.default", got %v`, instance.Scopes[0]) + } } // TestOAuthConnectionRoundtrip tests load -> save -> load produces equivalent data @@ -137,6 +232,12 @@ func TestOAuthConnectionRoundtrip(t *testing.T) { if reloaded.TokenUrl != "https://login.microsoftonline.com/{tenant}/oauth2/v2.0/token" { t.Errorf(`Expected TokenUrl to be "https://login.microsoftonline.com/{tenant}/oauth2/v2.0/token", got %v`, reloaded.TokenUrl) } + if len(reloaded.Scopes) != 1 { + t.Fatalf("Expected Scopes length to be 1, got %d", len(reloaded.Scopes)) + } + if reloaded.Scopes[0] != "https://cognitiveservices.azure.com/.default" { + t.Errorf(`Expected Scopes[0] to be "https://cognitiveservices.azure.com/.default", got %v`, reloaded.Scopes[0]) + } } // TestOAuthConnectionToJSON tests that ToJSON produces valid JSON @@ -172,6 +273,32 @@ func TestOAuthConnectionToJSON(t *testing.T) { if err := json.Unmarshal([]byte(jsonOutput), &parsed); err != nil { t.Fatalf("Failed to parse generated JSON: %v", err) } + + reloaded, err := prompty.LoadOAuthConnection(parsed, ctx) + if err != nil { + t.Fatalf("Failed to reload generated JSON: %v", err) + } + if reloaded.Kind != "oauth" { + t.Errorf(`Expected Kind to be "oauth", got %v`, reloaded.Kind) + } + if reloaded.Endpoint != "https://api.example.com" { + t.Errorf(`Expected Endpoint to be "https://api.example.com", got %v`, reloaded.Endpoint) + } + if reloaded.ClientId != "your-client-id" { + t.Errorf(`Expected ClientId to be "your-client-id", got %v`, reloaded.ClientId) + } + if reloaded.ClientSecret != "your-client-secret" { + t.Errorf(`Expected ClientSecret to be "your-client-secret", got %v`, reloaded.ClientSecret) + } + if reloaded.TokenUrl != "https://login.microsoftonline.com/{tenant}/oauth2/v2.0/token" { + t.Errorf(`Expected TokenUrl to be "https://login.microsoftonline.com/{tenant}/oauth2/v2.0/token", got %v`, reloaded.TokenUrl) + } + if len(reloaded.Scopes) != 1 { + t.Fatalf("Expected Scopes length to be 1, got %d", len(reloaded.Scopes)) + } + if reloaded.Scopes[0] != "https://cognitiveservices.azure.com/.default" { + t.Errorf(`Expected Scopes[0] to be "https://cognitiveservices.azure.com/.default", got %v`, reloaded.Scopes[0]) + } } // TestOAuthConnectionToYAML tests that ToYAML produces valid YAML @@ -207,4 +334,37 @@ func TestOAuthConnectionToYAML(t *testing.T) { if err := yaml.Unmarshal([]byte(yamlOutput), &parsed); err != nil { t.Fatalf("Failed to parse generated YAML: %v", err) } + + reloaded, err := prompty.LoadOAuthConnection(parsed, ctx) + if err != nil { + t.Fatalf("Failed to reload generated YAML: %v", err) + } + if reloaded.Kind != "oauth" { + t.Errorf(`Expected Kind to be "oauth", got %v`, reloaded.Kind) + } + if reloaded.Endpoint != "https://api.example.com" { + t.Errorf(`Expected Endpoint to be "https://api.example.com", got %v`, reloaded.Endpoint) + } + if reloaded.ClientId != "your-client-id" { + t.Errorf(`Expected ClientId to be "your-client-id", got %v`, reloaded.ClientId) + } + if reloaded.ClientSecret != "your-client-secret" { + t.Errorf(`Expected ClientSecret to be "your-client-secret", got %v`, reloaded.ClientSecret) + } + if reloaded.TokenUrl != "https://login.microsoftonline.com/{tenant}/oauth2/v2.0/token" { + t.Errorf(`Expected TokenUrl to be "https://login.microsoftonline.com/{tenant}/oauth2/v2.0/token", got %v`, reloaded.TokenUrl) + } + if len(reloaded.Scopes) != 1 { + t.Fatalf("Expected Scopes length to be 1, got %d", len(reloaded.Scopes)) + } + if reloaded.Scopes[0] != "https://cognitiveservices.azure.com/.default" { + t.Errorf(`Expected Scopes[0] to be "https://cognitiveservices.azure.com/.default", got %v`, reloaded.Scopes[0]) + } +} + +// TestOAuthConnectionFromJSONInvalid rejects malformed JSON instead of silently defaulting +func TestOAuthConnectionFromJSONInvalid(t *testing.T) { + if _, err := prompty.OAuthConnectionFromJSON("{"); err == nil { + t.Fatalf("Expected malformed JSON to fail") + } } diff --git a/runtime/go/prompty/model/object_property_test.go b/runtime/go/prompty/model/object_property_test.go index 473a9518..16f8c975 100644 --- a/runtime/go/prompty/model/object_property_test.go +++ b/runtime/go/prompty/model/object_property_test.go @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. package prompty_test @@ -61,6 +62,46 @@ properties: _ = instance // No scalar properties to validate } +// TestObjectPropertyFromJSON tests loading ObjectProperty through the generated JSON helper +func TestObjectPropertyFromJSON(t *testing.T) { + jsonData := ` +{ + "properties": { + "property1": { + "kind": "string" + }, + "property2": { + "kind": "number" + } + } +} +` + + instance, err := prompty.ObjectPropertyFromJSON(jsonData) + if err != nil { + t.Fatalf("Failed to load ObjectProperty from JSON helper: %v", err) + } + _ = instance // No scalar properties to validate +} + +// TestObjectPropertyFromYAML tests loading ObjectProperty through the generated YAML helper +func TestObjectPropertyFromYAML(t *testing.T) { + yamlData := ` +properties: + property1: + kind: string + property2: + kind: number + +` + + instance, err := prompty.ObjectPropertyFromYAML(yamlData) + if err != nil { + t.Fatalf("Failed to load ObjectProperty from YAML helper: %v", err) + } + _ = instance // No scalar properties to validate +} + // TestObjectPropertyRoundtrip tests load -> save -> load produces equivalent data func TestObjectPropertyRoundtrip(t *testing.T) { jsonData := ` @@ -128,6 +169,12 @@ func TestObjectPropertyToJSON(t *testing.T) { if err := json.Unmarshal([]byte(jsonOutput), &parsed); err != nil { t.Fatalf("Failed to parse generated JSON: %v", err) } + + reloaded, err := prompty.LoadObjectProperty(parsed, ctx) + if err != nil { + t.Fatalf("Failed to reload generated JSON: %v", err) + } + _ = reloaded // No scalar properties to validate } // TestObjectPropertyToYAML tests that ToYAML produces valid YAML @@ -163,4 +210,17 @@ func TestObjectPropertyToYAML(t *testing.T) { if err := yaml.Unmarshal([]byte(yamlOutput), &parsed); err != nil { t.Fatalf("Failed to parse generated YAML: %v", err) } + + reloaded, err := prompty.LoadObjectProperty(parsed, ctx) + if err != nil { + t.Fatalf("Failed to reload generated YAML: %v", err) + } + _ = reloaded // No scalar properties to validate +} + +// TestObjectPropertyFromJSONInvalid rejects malformed JSON instead of silently defaulting +func TestObjectPropertyFromJSONInvalid(t *testing.T) { + if _, err := prompty.ObjectPropertyFromJSON("{"); err == nil { + t.Fatalf("Expected malformed JSON to fail") + } } diff --git a/runtime/go/prompty/model/open_api_tool_test.go b/runtime/go/prompty/model/open_api_tool_test.go index 75f6cd2e..25dfc691 100644 --- a/runtime/go/prompty/model/open_api_tool_test.go +++ b/runtime/go/prompty/model/open_api_tool_test.go @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. package prompty_test @@ -67,6 +68,52 @@ specification: ./openapi.json } } +// TestOpenApiToolFromJSON tests loading OpenApiTool through the generated JSON helper +func TestOpenApiToolFromJSON(t *testing.T) { + jsonData := ` +{ + "kind": "openapi", + "connection": { + "kind": "reference" + }, + "specification": "./openapi.json" +} +` + + instance, err := prompty.OpenApiToolFromJSON(jsonData) + if err != nil { + t.Fatalf("Failed to load OpenApiTool from JSON helper: %v", err) + } + if instance.Kind != "openapi" { + t.Errorf(`Expected Kind to be "openapi", got %v`, instance.Kind) + } + if instance.Specification != "./openapi.json" { + t.Errorf(`Expected Specification to be "./openapi.json", got %v`, instance.Specification) + } +} + +// TestOpenApiToolFromYAML tests loading OpenApiTool through the generated YAML helper +func TestOpenApiToolFromYAML(t *testing.T) { + yamlData := ` +kind: openapi +connection: + kind: reference +specification: ./openapi.json + +` + + instance, err := prompty.OpenApiToolFromYAML(yamlData) + if err != nil { + t.Fatalf("Failed to load OpenApiTool from YAML helper: %v", err) + } + if instance.Kind != "openapi" { + t.Errorf(`Expected Kind to be "openapi", got %v`, instance.Kind) + } + if instance.Specification != "./openapi.json" { + t.Errorf(`Expected Specification to be "./openapi.json", got %v`, instance.Specification) + } +} + // TestOpenApiToolRoundtrip tests load -> save -> load produces equivalent data func TestOpenApiToolRoundtrip(t *testing.T) { jsonData := ` @@ -133,6 +180,17 @@ func TestOpenApiToolToJSON(t *testing.T) { if err := json.Unmarshal([]byte(jsonOutput), &parsed); err != nil { t.Fatalf("Failed to parse generated JSON: %v", err) } + + reloaded, err := prompty.LoadOpenApiTool(parsed, ctx) + if err != nil { + t.Fatalf("Failed to reload generated JSON: %v", err) + } + if reloaded.Kind != "openapi" { + t.Errorf(`Expected Kind to be "openapi", got %v`, reloaded.Kind) + } + if reloaded.Specification != "./openapi.json" { + t.Errorf(`Expected Specification to be "./openapi.json", got %v`, reloaded.Specification) + } } // TestOpenApiToolToYAML tests that ToYAML produces valid YAML @@ -165,4 +223,22 @@ func TestOpenApiToolToYAML(t *testing.T) { if err := yaml.Unmarshal([]byte(yamlOutput), &parsed); err != nil { t.Fatalf("Failed to parse generated YAML: %v", err) } + + reloaded, err := prompty.LoadOpenApiTool(parsed, ctx) + if err != nil { + t.Fatalf("Failed to reload generated YAML: %v", err) + } + if reloaded.Kind != "openapi" { + t.Errorf(`Expected Kind to be "openapi", got %v`, reloaded.Kind) + } + if reloaded.Specification != "./openapi.json" { + t.Errorf(`Expected Specification to be "./openapi.json", got %v`, reloaded.Specification) + } +} + +// TestOpenApiToolFromJSONInvalid rejects malformed JSON instead of silently defaulting +func TestOpenApiToolFromJSONInvalid(t *testing.T) { + if _, err := prompty.OpenApiToolFromJSON("{"); err == nil { + t.Fatalf("Expected malformed JSON to fail") + } } diff --git a/runtime/go/prompty/model/parser.go b/runtime/go/prompty/model/parser.go index 07089fcd..894c2578 100644 --- a/runtime/go/prompty/model/parser.go +++ b/runtime/go/prompty/model/parser.go @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. // Group: pipeline package prompty diff --git a/runtime/go/prompty/model/parser_config.go b/runtime/go/prompty/model/parser_config.go index b95334ec..d100d23e 100644 --- a/runtime/go/prompty/model/parser_config.go +++ b/runtime/go/prompty/model/parser_config.go @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. // Group: template package prompty @@ -43,7 +44,7 @@ func LoadParserConfig(data interface{}, ctx *LoadContext) (ParserConfig, error) } // Save serializes ParserConfig to map[string]interface{} -func (obj *ParserConfig) Save(ctx *SaveContext) map[string]interface{} { +func (obj ParserConfig) Save(ctx *SaveContext) map[string]interface{} { result := make(map[string]interface{}) result["kind"] = obj.Kind if obj.Options != nil { @@ -68,11 +69,7 @@ func (obj *ParserConfig) ToJSON() (string, error) { func (obj *ParserConfig) ToYAML() (string, error) { ctx := NewSaveContext() data := obj.Save(ctx) - bytes, err := yaml.Marshal(data) - if err != nil { - return "", err - } - return string(bytes), nil + return marshalYAMLDocument(data) } // FromJSON creates ParserConfig from JSON string diff --git a/runtime/go/prompty/model/parser_config_test.go b/runtime/go/prompty/model/parser_config_test.go index 65160d3c..763a0184 100644 --- a/runtime/go/prompty/model/parser_config_test.go +++ b/runtime/go/prompty/model/parser_config_test.go @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. package prompty_test @@ -59,6 +60,44 @@ options: // Note: Validation skipped for polymorphic base types - test child types directly } +// TestParserConfigFromJSON tests loading ParserConfig through the generated JSON helper +func TestParserConfigFromJSON(t *testing.T) { + jsonData := ` +{ + "kind": "prompty", + "options": { + "key": "value" + } +} +` + + instance, err := prompty.ParserConfigFromJSON(jsonData) + if err != nil { + t.Fatalf("Failed to load ParserConfig from JSON helper: %v", err) + } + // Polymorphic types return interface{}, extract common fields via reflection or type-specific access + _ = instance // Load succeeded, exact type depends on discriminator + // Note: Validation skipped for polymorphic base types - test child types directly +} + +// TestParserConfigFromYAML tests loading ParserConfig through the generated YAML helper +func TestParserConfigFromYAML(t *testing.T) { + yamlData := ` +kind: prompty +options: + key: value + +` + + instance, err := prompty.ParserConfigFromYAML(yamlData) + if err != nil { + t.Fatalf("Failed to load ParserConfig from YAML helper: %v", err) + } + // Polymorphic types return interface{}, extract common fields via reflection or type-specific access + _ = instance // Load succeeded, exact type depends on discriminator + // Note: Validation skipped for polymorphic base types - test child types directly +} + // TestParserConfigRoundtrip tests load -> save -> load produces equivalent data func TestParserConfigRoundtrip(t *testing.T) { jsonData := ` @@ -134,6 +173,13 @@ func TestParserConfigToYAML(t *testing.T) { // Note: ToYAML test skipped for polymorphic base types - test child types directly } +// TestParserConfigFromJSONInvalid rejects malformed JSON instead of silently defaulting +func TestParserConfigFromJSONInvalid(t *testing.T) { + if _, err := prompty.ParserConfigFromJSON("{"); err == nil { + t.Fatalf("Expected malformed JSON to fail") + } +} + // TestParserConfigFromParser tests loading ParserConfig from string func TestParserConfigFromParser(t *testing.T) { ctx := prompty.NewLoadContext() diff --git a/runtime/go/prompty/model/permission_completed_payload.go b/runtime/go/prompty/model/permission_completed_payload.go new file mode 100644 index 00000000..ea08ce44 --- /dev/null +++ b/runtime/go/prompty/model/permission_completed_payload.go @@ -0,0 +1,125 @@ +// +// Code generated by Typra emitter; DO NOT EDIT. +// Group: events + +package prompty + +import ( + "encoding/json" + + "gopkg.in/yaml.v3" +) + +// PermissionCompletedPayload represents Payload for permission completion events — an approval decision was made. + +type PermissionCompletedPayload struct { + RequestId *string `json:"requestId,omitempty" yaml:"requestId,omitempty"` + ToolCallId *string `json:"toolCallId,omitempty" yaml:"toolCallId,omitempty"` + Permission string `json:"permission" yaml:"permission"` + Approved bool `json:"approved" yaml:"approved"` + Reason *string `json:"reason,omitempty" yaml:"reason,omitempty"` + Result map[string]interface{} `json:"result,omitempty" yaml:"result,omitempty"` + Redaction *RedactionMetadata `json:"redaction,omitempty" yaml:"redaction,omitempty"` +} + +// LoadPermissionCompletedPayload creates a PermissionCompletedPayload from a map[string]interface{} +func LoadPermissionCompletedPayload(data interface{}, ctx *LoadContext) (PermissionCompletedPayload, error) { + result := PermissionCompletedPayload{} + + // Load from map + if m, ok := data.(map[string]interface{}); ok { + if val, ok := m["requestId"]; ok && val != nil { + v := string(val.(string)) + result.RequestId = &v + } + if val, ok := m["toolCallId"]; ok && val != nil { + v := string(val.(string)) + result.ToolCallId = &v + } + if val, ok := m["permission"]; ok && val != nil { + result.Permission = string(val.(string)) + } + if val, ok := m["approved"]; ok && val != nil { + result.Approved = val.(bool) + } + if val, ok := m["reason"]; ok && val != nil { + v := string(val.(string)) + result.Reason = &v + } + if val, ok := m["result"]; ok && val != nil { + if m, ok := val.(map[string]interface{}); ok { + result.Result = m + } + } + if val, ok := m["redaction"]; ok && val != nil { + if m, ok := val.(map[string]interface{}); ok { + loaded, _ := LoadRedactionMetadata(m, ctx) + result.Redaction = &loaded + } + } + } + + return result, nil +} + +// Save serializes PermissionCompletedPayload to map[string]interface{} +func (obj PermissionCompletedPayload) Save(ctx *SaveContext) map[string]interface{} { + result := make(map[string]interface{}) + if obj.RequestId != nil { + result["requestId"] = *obj.RequestId + } + if obj.ToolCallId != nil { + result["toolCallId"] = *obj.ToolCallId + } + result["permission"] = obj.Permission + result["approved"] = obj.Approved + if obj.Reason != nil { + result["reason"] = *obj.Reason + } + if obj.Result != nil { + result["result"] = obj.Result + } + if obj.Redaction != nil { + result["redaction"] = obj.Redaction.Save(ctx) + } + + return result +} + +// ToJSON serializes PermissionCompletedPayload to JSON string +func (obj *PermissionCompletedPayload) ToJSON() (string, error) { + ctx := NewSaveContext() + data := obj.Save(ctx) + bytes, err := json.Marshal(data) + if err != nil { + return "", err + } + return string(bytes), nil +} + +// ToYAML serializes PermissionCompletedPayload to YAML string +func (obj *PermissionCompletedPayload) ToYAML() (string, error) { + ctx := NewSaveContext() + data := obj.Save(ctx) + return marshalYAMLDocument(data) +} + +// FromJSON creates PermissionCompletedPayload from JSON string +func PermissionCompletedPayloadFromJSON(jsonStr string) (PermissionCompletedPayload, error) { + var data map[string]interface{} + if err := json.Unmarshal([]byte(jsonStr), &data); err != nil { + return PermissionCompletedPayload{}, err + } + ctx := NewLoadContext() + return LoadPermissionCompletedPayload(data, ctx) +} + +// FromYAML creates PermissionCompletedPayload from YAML string +func PermissionCompletedPayloadFromYAML(yamlStr string) (PermissionCompletedPayload, error) { + var data map[string]interface{} + if err := yaml.Unmarshal([]byte(yamlStr), &data); err != nil { + return PermissionCompletedPayload{}, err + } + ctx := NewLoadContext() + return LoadPermissionCompletedPayload(data, ctx) +} diff --git a/runtime/go/prompty/model/permission_completed_payload_test.go b/runtime/go/prompty/model/permission_completed_payload_test.go new file mode 100644 index 00000000..7aee727b --- /dev/null +++ b/runtime/go/prompty/model/permission_completed_payload_test.go @@ -0,0 +1,309 @@ +// +// Code generated by Typra emitter; DO NOT EDIT. + +package prompty_test + +import ( + "encoding/json" + "testing" + + "gopkg.in/yaml.v3" + + "prompty/model" +) + +// TestPermissionCompletedPayloadLoadJSON tests loading PermissionCompletedPayload from JSON +func TestPermissionCompletedPayloadLoadJSON(t *testing.T) { + jsonData := ` +{ + "requestId": "perm_abc123", + "toolCallId": "call_abc123", + "permission": "tool.execute", + "approved": true, + "reason": "user_approved" +} +` + var data map[string]interface{} + if err := json.Unmarshal([]byte(jsonData), &data); err != nil { + t.Fatalf("Failed to parse JSON: %v", err) + } + + ctx := prompty.NewLoadContext() + instance, err := prompty.LoadPermissionCompletedPayload(data, ctx) + if err != nil { + t.Fatalf("Failed to load PermissionCompletedPayload: %v", err) + } + if instance.RequestId == nil || *instance.RequestId != "perm_abc123" { + t.Errorf(`Expected RequestId to be "perm_abc123", got %v`, instance.RequestId) + } + if instance.ToolCallId == nil || *instance.ToolCallId != "call_abc123" { + t.Errorf(`Expected ToolCallId to be "call_abc123", got %v`, instance.ToolCallId) + } + if instance.Permission != "tool.execute" { + t.Errorf(`Expected Permission to be "tool.execute", got %v`, instance.Permission) + } + if instance.Approved != true { + t.Errorf(`Expected Approved to be true, got %v`, instance.Approved) + } + if instance.Reason == nil || *instance.Reason != "user_approved" { + t.Errorf(`Expected Reason to be "user_approved", got %v`, instance.Reason) + } +} + +// TestPermissionCompletedPayloadLoadYAML tests loading PermissionCompletedPayload from YAML +func TestPermissionCompletedPayloadLoadYAML(t *testing.T) { + yamlData := ` +requestId: perm_abc123 +toolCallId: call_abc123 +permission: tool.execute +approved: true +reason: user_approved + +` + var data map[string]interface{} + if err := yaml.Unmarshal([]byte(yamlData), &data); err != nil { + t.Fatalf("Failed to parse YAML: %v", err) + } + + ctx := prompty.NewLoadContext() + instance, err := prompty.LoadPermissionCompletedPayload(data, ctx) + if err != nil { + t.Fatalf("Failed to load PermissionCompletedPayload: %v", err) + } + if instance.RequestId == nil || *instance.RequestId != "perm_abc123" { + t.Errorf(`Expected RequestId to be "perm_abc123", got %v`, instance.RequestId) + } + if instance.ToolCallId == nil || *instance.ToolCallId != "call_abc123" { + t.Errorf(`Expected ToolCallId to be "call_abc123", got %v`, instance.ToolCallId) + } + if instance.Permission != "tool.execute" { + t.Errorf(`Expected Permission to be "tool.execute", got %v`, instance.Permission) + } + if instance.Approved != true { + t.Errorf(`Expected Approved to be true, got %v`, instance.Approved) + } + if instance.Reason == nil || *instance.Reason != "user_approved" { + t.Errorf(`Expected Reason to be "user_approved", got %v`, instance.Reason) + } +} + +// TestPermissionCompletedPayloadFromJSON tests loading PermissionCompletedPayload through the generated JSON helper +func TestPermissionCompletedPayloadFromJSON(t *testing.T) { + jsonData := ` +{ + "requestId": "perm_abc123", + "toolCallId": "call_abc123", + "permission": "tool.execute", + "approved": true, + "reason": "user_approved" +} +` + + instance, err := prompty.PermissionCompletedPayloadFromJSON(jsonData) + if err != nil { + t.Fatalf("Failed to load PermissionCompletedPayload from JSON helper: %v", err) + } + if instance.RequestId == nil || *instance.RequestId != "perm_abc123" { + t.Errorf(`Expected RequestId to be "perm_abc123", got %v`, instance.RequestId) + } + if instance.ToolCallId == nil || *instance.ToolCallId != "call_abc123" { + t.Errorf(`Expected ToolCallId to be "call_abc123", got %v`, instance.ToolCallId) + } + if instance.Permission != "tool.execute" { + t.Errorf(`Expected Permission to be "tool.execute", got %v`, instance.Permission) + } + if instance.Approved != true { + t.Errorf(`Expected Approved to be true, got %v`, instance.Approved) + } + if instance.Reason == nil || *instance.Reason != "user_approved" { + t.Errorf(`Expected Reason to be "user_approved", got %v`, instance.Reason) + } +} + +// TestPermissionCompletedPayloadFromYAML tests loading PermissionCompletedPayload through the generated YAML helper +func TestPermissionCompletedPayloadFromYAML(t *testing.T) { + yamlData := ` +requestId: perm_abc123 +toolCallId: call_abc123 +permission: tool.execute +approved: true +reason: user_approved + +` + + instance, err := prompty.PermissionCompletedPayloadFromYAML(yamlData) + if err != nil { + t.Fatalf("Failed to load PermissionCompletedPayload from YAML helper: %v", err) + } + if instance.RequestId == nil || *instance.RequestId != "perm_abc123" { + t.Errorf(`Expected RequestId to be "perm_abc123", got %v`, instance.RequestId) + } + if instance.ToolCallId == nil || *instance.ToolCallId != "call_abc123" { + t.Errorf(`Expected ToolCallId to be "call_abc123", got %v`, instance.ToolCallId) + } + if instance.Permission != "tool.execute" { + t.Errorf(`Expected Permission to be "tool.execute", got %v`, instance.Permission) + } + if instance.Approved != true { + t.Errorf(`Expected Approved to be true, got %v`, instance.Approved) + } + if instance.Reason == nil || *instance.Reason != "user_approved" { + t.Errorf(`Expected Reason to be "user_approved", got %v`, instance.Reason) + } +} + +// TestPermissionCompletedPayloadRoundtrip tests load -> save -> load produces equivalent data +func TestPermissionCompletedPayloadRoundtrip(t *testing.T) { + jsonData := ` +{ + "requestId": "perm_abc123", + "toolCallId": "call_abc123", + "permission": "tool.execute", + "approved": true, + "reason": "user_approved" +} +` + var data map[string]interface{} + if err := json.Unmarshal([]byte(jsonData), &data); err != nil { + t.Fatalf("Failed to parse JSON: %v", err) + } + + loadCtx := prompty.NewLoadContext() + instance, err := prompty.LoadPermissionCompletedPayload(data, loadCtx) + if err != nil { + t.Fatalf("Failed to load PermissionCompletedPayload: %v", err) + } + saveCtx := prompty.NewSaveContext() + savedData := instance.Save(saveCtx) + + reloaded, err := prompty.LoadPermissionCompletedPayload(savedData, loadCtx) + if err != nil { + t.Fatalf("Failed to reload PermissionCompletedPayload: %v", err) + } + if reloaded.RequestId == nil || *reloaded.RequestId != "perm_abc123" { + t.Errorf(`Expected RequestId to be "perm_abc123", got %v`, reloaded.RequestId) + } + if reloaded.ToolCallId == nil || *reloaded.ToolCallId != "call_abc123" { + t.Errorf(`Expected ToolCallId to be "call_abc123", got %v`, reloaded.ToolCallId) + } + if reloaded.Permission != "tool.execute" { + t.Errorf(`Expected Permission to be "tool.execute", got %v`, reloaded.Permission) + } + if reloaded.Approved != true { + t.Errorf(`Expected Approved to be true, got %v`, reloaded.Approved) + } + if reloaded.Reason == nil || *reloaded.Reason != "user_approved" { + t.Errorf(`Expected Reason to be "user_approved", got %v`, reloaded.Reason) + } +} + +// TestPermissionCompletedPayloadToJSON tests that ToJSON produces valid JSON +func TestPermissionCompletedPayloadToJSON(t *testing.T) { + jsonData := ` +{ + "requestId": "perm_abc123", + "toolCallId": "call_abc123", + "permission": "tool.execute", + "approved": true, + "reason": "user_approved" +} +` + var data map[string]interface{} + if err := json.Unmarshal([]byte(jsonData), &data); err != nil { + t.Fatalf("Failed to parse JSON: %v", err) + } + + ctx := prompty.NewLoadContext() + instance, err := prompty.LoadPermissionCompletedPayload(data, ctx) + if err != nil { + t.Fatalf("Failed to load PermissionCompletedPayload: %v", err) + } + jsonOutput, err := instance.ToJSON() + if err != nil { + t.Fatalf("Failed to convert to JSON: %v", err) + } + + var parsed map[string]interface{} + if err := json.Unmarshal([]byte(jsonOutput), &parsed); err != nil { + t.Fatalf("Failed to parse generated JSON: %v", err) + } + + reloaded, err := prompty.LoadPermissionCompletedPayload(parsed, ctx) + if err != nil { + t.Fatalf("Failed to reload generated JSON: %v", err) + } + if reloaded.RequestId == nil || *reloaded.RequestId != "perm_abc123" { + t.Errorf(`Expected RequestId to be "perm_abc123", got %v`, reloaded.RequestId) + } + if reloaded.ToolCallId == nil || *reloaded.ToolCallId != "call_abc123" { + t.Errorf(`Expected ToolCallId to be "call_abc123", got %v`, reloaded.ToolCallId) + } + if reloaded.Permission != "tool.execute" { + t.Errorf(`Expected Permission to be "tool.execute", got %v`, reloaded.Permission) + } + if reloaded.Approved != true { + t.Errorf(`Expected Approved to be true, got %v`, reloaded.Approved) + } + if reloaded.Reason == nil || *reloaded.Reason != "user_approved" { + t.Errorf(`Expected Reason to be "user_approved", got %v`, reloaded.Reason) + } +} + +// TestPermissionCompletedPayloadToYAML tests that ToYAML produces valid YAML +func TestPermissionCompletedPayloadToYAML(t *testing.T) { + jsonData := ` +{ + "requestId": "perm_abc123", + "toolCallId": "call_abc123", + "permission": "tool.execute", + "approved": true, + "reason": "user_approved" +} +` + var data map[string]interface{} + if err := json.Unmarshal([]byte(jsonData), &data); err != nil { + t.Fatalf("Failed to parse JSON: %v", err) + } + + ctx := prompty.NewLoadContext() + instance, err := prompty.LoadPermissionCompletedPayload(data, ctx) + if err != nil { + t.Fatalf("Failed to load PermissionCompletedPayload: %v", err) + } + yamlOutput, err := instance.ToYAML() + if err != nil { + t.Fatalf("Failed to convert to YAML: %v", err) + } + + var parsed map[string]interface{} + if err := yaml.Unmarshal([]byte(yamlOutput), &parsed); err != nil { + t.Fatalf("Failed to parse generated YAML: %v", err) + } + + reloaded, err := prompty.LoadPermissionCompletedPayload(parsed, ctx) + if err != nil { + t.Fatalf("Failed to reload generated YAML: %v", err) + } + if reloaded.RequestId == nil || *reloaded.RequestId != "perm_abc123" { + t.Errorf(`Expected RequestId to be "perm_abc123", got %v`, reloaded.RequestId) + } + if reloaded.ToolCallId == nil || *reloaded.ToolCallId != "call_abc123" { + t.Errorf(`Expected ToolCallId to be "call_abc123", got %v`, reloaded.ToolCallId) + } + if reloaded.Permission != "tool.execute" { + t.Errorf(`Expected Permission to be "tool.execute", got %v`, reloaded.Permission) + } + if reloaded.Approved != true { + t.Errorf(`Expected Approved to be true, got %v`, reloaded.Approved) + } + if reloaded.Reason == nil || *reloaded.Reason != "user_approved" { + t.Errorf(`Expected Reason to be "user_approved", got %v`, reloaded.Reason) + } +} + +// TestPermissionCompletedPayloadFromJSONInvalid rejects malformed JSON instead of silently defaulting +func TestPermissionCompletedPayloadFromJSONInvalid(t *testing.T) { + if _, err := prompty.PermissionCompletedPayloadFromJSON("{"); err == nil { + t.Fatalf("Expected malformed JSON to fail") + } +} diff --git a/runtime/go/prompty/model/permission_decision.go b/runtime/go/prompty/model/permission_decision.go new file mode 100644 index 00000000..ef20fbf9 --- /dev/null +++ b/runtime/go/prompty/model/permission_decision.go @@ -0,0 +1,115 @@ +// +// Code generated by Typra emitter; DO NOT EDIT. +// Group: events + +package prompty + +import ( + "encoding/json" + + "gopkg.in/yaml.v3" +) + +// PermissionDecision represents Decision returned by a permission resolver. + +type PermissionDecision struct { + RequestId *string `json:"requestId,omitempty" yaml:"requestId,omitempty"` + ToolCallId *string `json:"toolCallId,omitempty" yaml:"toolCallId,omitempty"` + Permission string `json:"permission" yaml:"permission"` + Approved bool `json:"approved" yaml:"approved"` + Reason *string `json:"reason,omitempty" yaml:"reason,omitempty"` + Result map[string]interface{} `json:"result,omitempty" yaml:"result,omitempty"` +} + +// LoadPermissionDecision creates a PermissionDecision from a map[string]interface{} +func LoadPermissionDecision(data interface{}, ctx *LoadContext) (PermissionDecision, error) { + result := PermissionDecision{} + + // Load from map + if m, ok := data.(map[string]interface{}); ok { + if val, ok := m["requestId"]; ok && val != nil { + v := string(val.(string)) + result.RequestId = &v + } + if val, ok := m["toolCallId"]; ok && val != nil { + v := string(val.(string)) + result.ToolCallId = &v + } + if val, ok := m["permission"]; ok && val != nil { + result.Permission = string(val.(string)) + } + if val, ok := m["approved"]; ok && val != nil { + result.Approved = val.(bool) + } + if val, ok := m["reason"]; ok && val != nil { + v := string(val.(string)) + result.Reason = &v + } + if val, ok := m["result"]; ok && val != nil { + if m, ok := val.(map[string]interface{}); ok { + result.Result = m + } + } + } + + return result, nil +} + +// Save serializes PermissionDecision to map[string]interface{} +func (obj PermissionDecision) Save(ctx *SaveContext) map[string]interface{} { + result := make(map[string]interface{}) + if obj.RequestId != nil { + result["requestId"] = *obj.RequestId + } + if obj.ToolCallId != nil { + result["toolCallId"] = *obj.ToolCallId + } + result["permission"] = obj.Permission + result["approved"] = obj.Approved + if obj.Reason != nil { + result["reason"] = *obj.Reason + } + if obj.Result != nil { + result["result"] = obj.Result + } + + return result +} + +// ToJSON serializes PermissionDecision to JSON string +func (obj *PermissionDecision) ToJSON() (string, error) { + ctx := NewSaveContext() + data := obj.Save(ctx) + bytes, err := json.Marshal(data) + if err != nil { + return "", err + } + return string(bytes), nil +} + +// ToYAML serializes PermissionDecision to YAML string +func (obj *PermissionDecision) ToYAML() (string, error) { + ctx := NewSaveContext() + data := obj.Save(ctx) + return marshalYAMLDocument(data) +} + +// FromJSON creates PermissionDecision from JSON string +func PermissionDecisionFromJSON(jsonStr string) (PermissionDecision, error) { + var data map[string]interface{} + if err := json.Unmarshal([]byte(jsonStr), &data); err != nil { + return PermissionDecision{}, err + } + ctx := NewLoadContext() + return LoadPermissionDecision(data, ctx) +} + +// FromYAML creates PermissionDecision from YAML string +func PermissionDecisionFromYAML(yamlStr string) (PermissionDecision, error) { + var data map[string]interface{} + if err := yaml.Unmarshal([]byte(yamlStr), &data); err != nil { + return PermissionDecision{}, err + } + ctx := NewLoadContext() + return LoadPermissionDecision(data, ctx) +} diff --git a/runtime/go/prompty/model/permission_decision_test.go b/runtime/go/prompty/model/permission_decision_test.go new file mode 100644 index 00000000..d786da6d --- /dev/null +++ b/runtime/go/prompty/model/permission_decision_test.go @@ -0,0 +1,309 @@ +// +// Code generated by Typra emitter; DO NOT EDIT. + +package prompty_test + +import ( + "encoding/json" + "testing" + + "gopkg.in/yaml.v3" + + "prompty/model" +) + +// TestPermissionDecisionLoadJSON tests loading PermissionDecision from JSON +func TestPermissionDecisionLoadJSON(t *testing.T) { + jsonData := ` +{ + "requestId": "perm_abc123", + "toolCallId": "call_abc123", + "permission": "tool.execute", + "approved": true, + "reason": "user_approved" +} +` + var data map[string]interface{} + if err := json.Unmarshal([]byte(jsonData), &data); err != nil { + t.Fatalf("Failed to parse JSON: %v", err) + } + + ctx := prompty.NewLoadContext() + instance, err := prompty.LoadPermissionDecision(data, ctx) + if err != nil { + t.Fatalf("Failed to load PermissionDecision: %v", err) + } + if instance.RequestId == nil || *instance.RequestId != "perm_abc123" { + t.Errorf(`Expected RequestId to be "perm_abc123", got %v`, instance.RequestId) + } + if instance.ToolCallId == nil || *instance.ToolCallId != "call_abc123" { + t.Errorf(`Expected ToolCallId to be "call_abc123", got %v`, instance.ToolCallId) + } + if instance.Permission != "tool.execute" { + t.Errorf(`Expected Permission to be "tool.execute", got %v`, instance.Permission) + } + if instance.Approved != true { + t.Errorf(`Expected Approved to be true, got %v`, instance.Approved) + } + if instance.Reason == nil || *instance.Reason != "user_approved" { + t.Errorf(`Expected Reason to be "user_approved", got %v`, instance.Reason) + } +} + +// TestPermissionDecisionLoadYAML tests loading PermissionDecision from YAML +func TestPermissionDecisionLoadYAML(t *testing.T) { + yamlData := ` +requestId: perm_abc123 +toolCallId: call_abc123 +permission: tool.execute +approved: true +reason: user_approved + +` + var data map[string]interface{} + if err := yaml.Unmarshal([]byte(yamlData), &data); err != nil { + t.Fatalf("Failed to parse YAML: %v", err) + } + + ctx := prompty.NewLoadContext() + instance, err := prompty.LoadPermissionDecision(data, ctx) + if err != nil { + t.Fatalf("Failed to load PermissionDecision: %v", err) + } + if instance.RequestId == nil || *instance.RequestId != "perm_abc123" { + t.Errorf(`Expected RequestId to be "perm_abc123", got %v`, instance.RequestId) + } + if instance.ToolCallId == nil || *instance.ToolCallId != "call_abc123" { + t.Errorf(`Expected ToolCallId to be "call_abc123", got %v`, instance.ToolCallId) + } + if instance.Permission != "tool.execute" { + t.Errorf(`Expected Permission to be "tool.execute", got %v`, instance.Permission) + } + if instance.Approved != true { + t.Errorf(`Expected Approved to be true, got %v`, instance.Approved) + } + if instance.Reason == nil || *instance.Reason != "user_approved" { + t.Errorf(`Expected Reason to be "user_approved", got %v`, instance.Reason) + } +} + +// TestPermissionDecisionFromJSON tests loading PermissionDecision through the generated JSON helper +func TestPermissionDecisionFromJSON(t *testing.T) { + jsonData := ` +{ + "requestId": "perm_abc123", + "toolCallId": "call_abc123", + "permission": "tool.execute", + "approved": true, + "reason": "user_approved" +} +` + + instance, err := prompty.PermissionDecisionFromJSON(jsonData) + if err != nil { + t.Fatalf("Failed to load PermissionDecision from JSON helper: %v", err) + } + if instance.RequestId == nil || *instance.RequestId != "perm_abc123" { + t.Errorf(`Expected RequestId to be "perm_abc123", got %v`, instance.RequestId) + } + if instance.ToolCallId == nil || *instance.ToolCallId != "call_abc123" { + t.Errorf(`Expected ToolCallId to be "call_abc123", got %v`, instance.ToolCallId) + } + if instance.Permission != "tool.execute" { + t.Errorf(`Expected Permission to be "tool.execute", got %v`, instance.Permission) + } + if instance.Approved != true { + t.Errorf(`Expected Approved to be true, got %v`, instance.Approved) + } + if instance.Reason == nil || *instance.Reason != "user_approved" { + t.Errorf(`Expected Reason to be "user_approved", got %v`, instance.Reason) + } +} + +// TestPermissionDecisionFromYAML tests loading PermissionDecision through the generated YAML helper +func TestPermissionDecisionFromYAML(t *testing.T) { + yamlData := ` +requestId: perm_abc123 +toolCallId: call_abc123 +permission: tool.execute +approved: true +reason: user_approved + +` + + instance, err := prompty.PermissionDecisionFromYAML(yamlData) + if err != nil { + t.Fatalf("Failed to load PermissionDecision from YAML helper: %v", err) + } + if instance.RequestId == nil || *instance.RequestId != "perm_abc123" { + t.Errorf(`Expected RequestId to be "perm_abc123", got %v`, instance.RequestId) + } + if instance.ToolCallId == nil || *instance.ToolCallId != "call_abc123" { + t.Errorf(`Expected ToolCallId to be "call_abc123", got %v`, instance.ToolCallId) + } + if instance.Permission != "tool.execute" { + t.Errorf(`Expected Permission to be "tool.execute", got %v`, instance.Permission) + } + if instance.Approved != true { + t.Errorf(`Expected Approved to be true, got %v`, instance.Approved) + } + if instance.Reason == nil || *instance.Reason != "user_approved" { + t.Errorf(`Expected Reason to be "user_approved", got %v`, instance.Reason) + } +} + +// TestPermissionDecisionRoundtrip tests load -> save -> load produces equivalent data +func TestPermissionDecisionRoundtrip(t *testing.T) { + jsonData := ` +{ + "requestId": "perm_abc123", + "toolCallId": "call_abc123", + "permission": "tool.execute", + "approved": true, + "reason": "user_approved" +} +` + var data map[string]interface{} + if err := json.Unmarshal([]byte(jsonData), &data); err != nil { + t.Fatalf("Failed to parse JSON: %v", err) + } + + loadCtx := prompty.NewLoadContext() + instance, err := prompty.LoadPermissionDecision(data, loadCtx) + if err != nil { + t.Fatalf("Failed to load PermissionDecision: %v", err) + } + saveCtx := prompty.NewSaveContext() + savedData := instance.Save(saveCtx) + + reloaded, err := prompty.LoadPermissionDecision(savedData, loadCtx) + if err != nil { + t.Fatalf("Failed to reload PermissionDecision: %v", err) + } + if reloaded.RequestId == nil || *reloaded.RequestId != "perm_abc123" { + t.Errorf(`Expected RequestId to be "perm_abc123", got %v`, reloaded.RequestId) + } + if reloaded.ToolCallId == nil || *reloaded.ToolCallId != "call_abc123" { + t.Errorf(`Expected ToolCallId to be "call_abc123", got %v`, reloaded.ToolCallId) + } + if reloaded.Permission != "tool.execute" { + t.Errorf(`Expected Permission to be "tool.execute", got %v`, reloaded.Permission) + } + if reloaded.Approved != true { + t.Errorf(`Expected Approved to be true, got %v`, reloaded.Approved) + } + if reloaded.Reason == nil || *reloaded.Reason != "user_approved" { + t.Errorf(`Expected Reason to be "user_approved", got %v`, reloaded.Reason) + } +} + +// TestPermissionDecisionToJSON tests that ToJSON produces valid JSON +func TestPermissionDecisionToJSON(t *testing.T) { + jsonData := ` +{ + "requestId": "perm_abc123", + "toolCallId": "call_abc123", + "permission": "tool.execute", + "approved": true, + "reason": "user_approved" +} +` + var data map[string]interface{} + if err := json.Unmarshal([]byte(jsonData), &data); err != nil { + t.Fatalf("Failed to parse JSON: %v", err) + } + + ctx := prompty.NewLoadContext() + instance, err := prompty.LoadPermissionDecision(data, ctx) + if err != nil { + t.Fatalf("Failed to load PermissionDecision: %v", err) + } + jsonOutput, err := instance.ToJSON() + if err != nil { + t.Fatalf("Failed to convert to JSON: %v", err) + } + + var parsed map[string]interface{} + if err := json.Unmarshal([]byte(jsonOutput), &parsed); err != nil { + t.Fatalf("Failed to parse generated JSON: %v", err) + } + + reloaded, err := prompty.LoadPermissionDecision(parsed, ctx) + if err != nil { + t.Fatalf("Failed to reload generated JSON: %v", err) + } + if reloaded.RequestId == nil || *reloaded.RequestId != "perm_abc123" { + t.Errorf(`Expected RequestId to be "perm_abc123", got %v`, reloaded.RequestId) + } + if reloaded.ToolCallId == nil || *reloaded.ToolCallId != "call_abc123" { + t.Errorf(`Expected ToolCallId to be "call_abc123", got %v`, reloaded.ToolCallId) + } + if reloaded.Permission != "tool.execute" { + t.Errorf(`Expected Permission to be "tool.execute", got %v`, reloaded.Permission) + } + if reloaded.Approved != true { + t.Errorf(`Expected Approved to be true, got %v`, reloaded.Approved) + } + if reloaded.Reason == nil || *reloaded.Reason != "user_approved" { + t.Errorf(`Expected Reason to be "user_approved", got %v`, reloaded.Reason) + } +} + +// TestPermissionDecisionToYAML tests that ToYAML produces valid YAML +func TestPermissionDecisionToYAML(t *testing.T) { + jsonData := ` +{ + "requestId": "perm_abc123", + "toolCallId": "call_abc123", + "permission": "tool.execute", + "approved": true, + "reason": "user_approved" +} +` + var data map[string]interface{} + if err := json.Unmarshal([]byte(jsonData), &data); err != nil { + t.Fatalf("Failed to parse JSON: %v", err) + } + + ctx := prompty.NewLoadContext() + instance, err := prompty.LoadPermissionDecision(data, ctx) + if err != nil { + t.Fatalf("Failed to load PermissionDecision: %v", err) + } + yamlOutput, err := instance.ToYAML() + if err != nil { + t.Fatalf("Failed to convert to YAML: %v", err) + } + + var parsed map[string]interface{} + if err := yaml.Unmarshal([]byte(yamlOutput), &parsed); err != nil { + t.Fatalf("Failed to parse generated YAML: %v", err) + } + + reloaded, err := prompty.LoadPermissionDecision(parsed, ctx) + if err != nil { + t.Fatalf("Failed to reload generated YAML: %v", err) + } + if reloaded.RequestId == nil || *reloaded.RequestId != "perm_abc123" { + t.Errorf(`Expected RequestId to be "perm_abc123", got %v`, reloaded.RequestId) + } + if reloaded.ToolCallId == nil || *reloaded.ToolCallId != "call_abc123" { + t.Errorf(`Expected ToolCallId to be "call_abc123", got %v`, reloaded.ToolCallId) + } + if reloaded.Permission != "tool.execute" { + t.Errorf(`Expected Permission to be "tool.execute", got %v`, reloaded.Permission) + } + if reloaded.Approved != true { + t.Errorf(`Expected Approved to be true, got %v`, reloaded.Approved) + } + if reloaded.Reason == nil || *reloaded.Reason != "user_approved" { + t.Errorf(`Expected Reason to be "user_approved", got %v`, reloaded.Reason) + } +} + +// TestPermissionDecisionFromJSONInvalid rejects malformed JSON instead of silently defaulting +func TestPermissionDecisionFromJSONInvalid(t *testing.T) { + if _, err := prompty.PermissionDecisionFromJSON("{"); err == nil { + t.Fatalf("Expected malformed JSON to fail") + } +} diff --git a/runtime/go/prompty/model/permission_request.go b/runtime/go/prompty/model/permission_request.go new file mode 100644 index 00000000..414e95ff --- /dev/null +++ b/runtime/go/prompty/model/permission_request.go @@ -0,0 +1,128 @@ +// +// Code generated by Typra emitter; DO NOT EDIT. +// Group: events + +package prompty + +import ( + "encoding/json" + + "gopkg.in/yaml.v3" +) + +// PermissionRequest represents Request passed to a permission resolver. This is the live protocol shape; the +// event payloads above can include trace-only metadata such as redaction state. + +type PermissionRequest struct { + RequestId *string `json:"requestId,omitempty" yaml:"requestId,omitempty"` + ToolCallId *string `json:"toolCallId,omitempty" yaml:"toolCallId,omitempty"` + Permission string `json:"permission" yaml:"permission"` + Target *string `json:"target,omitempty" yaml:"target,omitempty"` + Details map[string]interface{} `json:"details,omitempty" yaml:"details,omitempty"` + PromptRequest *string `json:"promptRequest,omitempty" yaml:"promptRequest,omitempty"` + Policy map[string]interface{} `json:"policy,omitempty" yaml:"policy,omitempty"` +} + +// LoadPermissionRequest creates a PermissionRequest from a map[string]interface{} +func LoadPermissionRequest(data interface{}, ctx *LoadContext) (PermissionRequest, error) { + result := PermissionRequest{} + + // Load from map + if m, ok := data.(map[string]interface{}); ok { + if val, ok := m["requestId"]; ok && val != nil { + v := string(val.(string)) + result.RequestId = &v + } + if val, ok := m["toolCallId"]; ok && val != nil { + v := string(val.(string)) + result.ToolCallId = &v + } + if val, ok := m["permission"]; ok && val != nil { + result.Permission = string(val.(string)) + } + if val, ok := m["target"]; ok && val != nil { + v := string(val.(string)) + result.Target = &v + } + if val, ok := m["details"]; ok && val != nil { + if m, ok := val.(map[string]interface{}); ok { + result.Details = m + } + } + if val, ok := m["promptRequest"]; ok && val != nil { + v := string(val.(string)) + result.PromptRequest = &v + } + if val, ok := m["policy"]; ok && val != nil { + if m, ok := val.(map[string]interface{}); ok { + result.Policy = m + } + } + } + + return result, nil +} + +// Save serializes PermissionRequest to map[string]interface{} +func (obj PermissionRequest) Save(ctx *SaveContext) map[string]interface{} { + result := make(map[string]interface{}) + if obj.RequestId != nil { + result["requestId"] = *obj.RequestId + } + if obj.ToolCallId != nil { + result["toolCallId"] = *obj.ToolCallId + } + result["permission"] = obj.Permission + if obj.Target != nil { + result["target"] = *obj.Target + } + if obj.Details != nil { + result["details"] = obj.Details + } + if obj.PromptRequest != nil { + result["promptRequest"] = *obj.PromptRequest + } + if obj.Policy != nil { + result["policy"] = obj.Policy + } + + return result +} + +// ToJSON serializes PermissionRequest to JSON string +func (obj *PermissionRequest) ToJSON() (string, error) { + ctx := NewSaveContext() + data := obj.Save(ctx) + bytes, err := json.Marshal(data) + if err != nil { + return "", err + } + return string(bytes), nil +} + +// ToYAML serializes PermissionRequest to YAML string +func (obj *PermissionRequest) ToYAML() (string, error) { + ctx := NewSaveContext() + data := obj.Save(ctx) + return marshalYAMLDocument(data) +} + +// FromJSON creates PermissionRequest from JSON string +func PermissionRequestFromJSON(jsonStr string) (PermissionRequest, error) { + var data map[string]interface{} + if err := json.Unmarshal([]byte(jsonStr), &data); err != nil { + return PermissionRequest{}, err + } + ctx := NewLoadContext() + return LoadPermissionRequest(data, ctx) +} + +// FromYAML creates PermissionRequest from YAML string +func PermissionRequestFromYAML(yamlStr string) (PermissionRequest, error) { + var data map[string]interface{} + if err := yaml.Unmarshal([]byte(yamlStr), &data); err != nil { + return PermissionRequest{}, err + } + ctx := NewLoadContext() + return LoadPermissionRequest(data, ctx) +} diff --git a/runtime/go/prompty/model/permission_request_test.go b/runtime/go/prompty/model/permission_request_test.go new file mode 100644 index 00000000..091d046c --- /dev/null +++ b/runtime/go/prompty/model/permission_request_test.go @@ -0,0 +1,309 @@ +// +// Code generated by Typra emitter; DO NOT EDIT. + +package prompty_test + +import ( + "encoding/json" + "testing" + + "gopkg.in/yaml.v3" + + "prompty/model" +) + +// TestPermissionRequestLoadJSON tests loading PermissionRequest from JSON +func TestPermissionRequestLoadJSON(t *testing.T) { + jsonData := ` +{ + "requestId": "perm_abc123", + "toolCallId": "call_abc123", + "permission": "tool.execute", + "target": "shell", + "promptRequest": "Allow shell to run tests?" +} +` + var data map[string]interface{} + if err := json.Unmarshal([]byte(jsonData), &data); err != nil { + t.Fatalf("Failed to parse JSON: %v", err) + } + + ctx := prompty.NewLoadContext() + instance, err := prompty.LoadPermissionRequest(data, ctx) + if err != nil { + t.Fatalf("Failed to load PermissionRequest: %v", err) + } + if instance.RequestId == nil || *instance.RequestId != "perm_abc123" { + t.Errorf(`Expected RequestId to be "perm_abc123", got %v`, instance.RequestId) + } + if instance.ToolCallId == nil || *instance.ToolCallId != "call_abc123" { + t.Errorf(`Expected ToolCallId to be "call_abc123", got %v`, instance.ToolCallId) + } + if instance.Permission != "tool.execute" { + t.Errorf(`Expected Permission to be "tool.execute", got %v`, instance.Permission) + } + if instance.Target == nil || *instance.Target != "shell" { + t.Errorf(`Expected Target to be "shell", got %v`, instance.Target) + } + if instance.PromptRequest == nil || *instance.PromptRequest != "Allow shell to run tests?" { + t.Errorf(`Expected PromptRequest to be "Allow shell to run tests?", got %v`, instance.PromptRequest) + } +} + +// TestPermissionRequestLoadYAML tests loading PermissionRequest from YAML +func TestPermissionRequestLoadYAML(t *testing.T) { + yamlData := ` +requestId: perm_abc123 +toolCallId: call_abc123 +permission: tool.execute +target: shell +promptRequest: Allow shell to run tests? + +` + var data map[string]interface{} + if err := yaml.Unmarshal([]byte(yamlData), &data); err != nil { + t.Fatalf("Failed to parse YAML: %v", err) + } + + ctx := prompty.NewLoadContext() + instance, err := prompty.LoadPermissionRequest(data, ctx) + if err != nil { + t.Fatalf("Failed to load PermissionRequest: %v", err) + } + if instance.RequestId == nil || *instance.RequestId != "perm_abc123" { + t.Errorf(`Expected RequestId to be "perm_abc123", got %v`, instance.RequestId) + } + if instance.ToolCallId == nil || *instance.ToolCallId != "call_abc123" { + t.Errorf(`Expected ToolCallId to be "call_abc123", got %v`, instance.ToolCallId) + } + if instance.Permission != "tool.execute" { + t.Errorf(`Expected Permission to be "tool.execute", got %v`, instance.Permission) + } + if instance.Target == nil || *instance.Target != "shell" { + t.Errorf(`Expected Target to be "shell", got %v`, instance.Target) + } + if instance.PromptRequest == nil || *instance.PromptRequest != "Allow shell to run tests?" { + t.Errorf(`Expected PromptRequest to be "Allow shell to run tests?", got %v`, instance.PromptRequest) + } +} + +// TestPermissionRequestFromJSON tests loading PermissionRequest through the generated JSON helper +func TestPermissionRequestFromJSON(t *testing.T) { + jsonData := ` +{ + "requestId": "perm_abc123", + "toolCallId": "call_abc123", + "permission": "tool.execute", + "target": "shell", + "promptRequest": "Allow shell to run tests?" +} +` + + instance, err := prompty.PermissionRequestFromJSON(jsonData) + if err != nil { + t.Fatalf("Failed to load PermissionRequest from JSON helper: %v", err) + } + if instance.RequestId == nil || *instance.RequestId != "perm_abc123" { + t.Errorf(`Expected RequestId to be "perm_abc123", got %v`, instance.RequestId) + } + if instance.ToolCallId == nil || *instance.ToolCallId != "call_abc123" { + t.Errorf(`Expected ToolCallId to be "call_abc123", got %v`, instance.ToolCallId) + } + if instance.Permission != "tool.execute" { + t.Errorf(`Expected Permission to be "tool.execute", got %v`, instance.Permission) + } + if instance.Target == nil || *instance.Target != "shell" { + t.Errorf(`Expected Target to be "shell", got %v`, instance.Target) + } + if instance.PromptRequest == nil || *instance.PromptRequest != "Allow shell to run tests?" { + t.Errorf(`Expected PromptRequest to be "Allow shell to run tests?", got %v`, instance.PromptRequest) + } +} + +// TestPermissionRequestFromYAML tests loading PermissionRequest through the generated YAML helper +func TestPermissionRequestFromYAML(t *testing.T) { + yamlData := ` +requestId: perm_abc123 +toolCallId: call_abc123 +permission: tool.execute +target: shell +promptRequest: Allow shell to run tests? + +` + + instance, err := prompty.PermissionRequestFromYAML(yamlData) + if err != nil { + t.Fatalf("Failed to load PermissionRequest from YAML helper: %v", err) + } + if instance.RequestId == nil || *instance.RequestId != "perm_abc123" { + t.Errorf(`Expected RequestId to be "perm_abc123", got %v`, instance.RequestId) + } + if instance.ToolCallId == nil || *instance.ToolCallId != "call_abc123" { + t.Errorf(`Expected ToolCallId to be "call_abc123", got %v`, instance.ToolCallId) + } + if instance.Permission != "tool.execute" { + t.Errorf(`Expected Permission to be "tool.execute", got %v`, instance.Permission) + } + if instance.Target == nil || *instance.Target != "shell" { + t.Errorf(`Expected Target to be "shell", got %v`, instance.Target) + } + if instance.PromptRequest == nil || *instance.PromptRequest != "Allow shell to run tests?" { + t.Errorf(`Expected PromptRequest to be "Allow shell to run tests?", got %v`, instance.PromptRequest) + } +} + +// TestPermissionRequestRoundtrip tests load -> save -> load produces equivalent data +func TestPermissionRequestRoundtrip(t *testing.T) { + jsonData := ` +{ + "requestId": "perm_abc123", + "toolCallId": "call_abc123", + "permission": "tool.execute", + "target": "shell", + "promptRequest": "Allow shell to run tests?" +} +` + var data map[string]interface{} + if err := json.Unmarshal([]byte(jsonData), &data); err != nil { + t.Fatalf("Failed to parse JSON: %v", err) + } + + loadCtx := prompty.NewLoadContext() + instance, err := prompty.LoadPermissionRequest(data, loadCtx) + if err != nil { + t.Fatalf("Failed to load PermissionRequest: %v", err) + } + saveCtx := prompty.NewSaveContext() + savedData := instance.Save(saveCtx) + + reloaded, err := prompty.LoadPermissionRequest(savedData, loadCtx) + if err != nil { + t.Fatalf("Failed to reload PermissionRequest: %v", err) + } + if reloaded.RequestId == nil || *reloaded.RequestId != "perm_abc123" { + t.Errorf(`Expected RequestId to be "perm_abc123", got %v`, reloaded.RequestId) + } + if reloaded.ToolCallId == nil || *reloaded.ToolCallId != "call_abc123" { + t.Errorf(`Expected ToolCallId to be "call_abc123", got %v`, reloaded.ToolCallId) + } + if reloaded.Permission != "tool.execute" { + t.Errorf(`Expected Permission to be "tool.execute", got %v`, reloaded.Permission) + } + if reloaded.Target == nil || *reloaded.Target != "shell" { + t.Errorf(`Expected Target to be "shell", got %v`, reloaded.Target) + } + if reloaded.PromptRequest == nil || *reloaded.PromptRequest != "Allow shell to run tests?" { + t.Errorf(`Expected PromptRequest to be "Allow shell to run tests?", got %v`, reloaded.PromptRequest) + } +} + +// TestPermissionRequestToJSON tests that ToJSON produces valid JSON +func TestPermissionRequestToJSON(t *testing.T) { + jsonData := ` +{ + "requestId": "perm_abc123", + "toolCallId": "call_abc123", + "permission": "tool.execute", + "target": "shell", + "promptRequest": "Allow shell to run tests?" +} +` + var data map[string]interface{} + if err := json.Unmarshal([]byte(jsonData), &data); err != nil { + t.Fatalf("Failed to parse JSON: %v", err) + } + + ctx := prompty.NewLoadContext() + instance, err := prompty.LoadPermissionRequest(data, ctx) + if err != nil { + t.Fatalf("Failed to load PermissionRequest: %v", err) + } + jsonOutput, err := instance.ToJSON() + if err != nil { + t.Fatalf("Failed to convert to JSON: %v", err) + } + + var parsed map[string]interface{} + if err := json.Unmarshal([]byte(jsonOutput), &parsed); err != nil { + t.Fatalf("Failed to parse generated JSON: %v", err) + } + + reloaded, err := prompty.LoadPermissionRequest(parsed, ctx) + if err != nil { + t.Fatalf("Failed to reload generated JSON: %v", err) + } + if reloaded.RequestId == nil || *reloaded.RequestId != "perm_abc123" { + t.Errorf(`Expected RequestId to be "perm_abc123", got %v`, reloaded.RequestId) + } + if reloaded.ToolCallId == nil || *reloaded.ToolCallId != "call_abc123" { + t.Errorf(`Expected ToolCallId to be "call_abc123", got %v`, reloaded.ToolCallId) + } + if reloaded.Permission != "tool.execute" { + t.Errorf(`Expected Permission to be "tool.execute", got %v`, reloaded.Permission) + } + if reloaded.Target == nil || *reloaded.Target != "shell" { + t.Errorf(`Expected Target to be "shell", got %v`, reloaded.Target) + } + if reloaded.PromptRequest == nil || *reloaded.PromptRequest != "Allow shell to run tests?" { + t.Errorf(`Expected PromptRequest to be "Allow shell to run tests?", got %v`, reloaded.PromptRequest) + } +} + +// TestPermissionRequestToYAML tests that ToYAML produces valid YAML +func TestPermissionRequestToYAML(t *testing.T) { + jsonData := ` +{ + "requestId": "perm_abc123", + "toolCallId": "call_abc123", + "permission": "tool.execute", + "target": "shell", + "promptRequest": "Allow shell to run tests?" +} +` + var data map[string]interface{} + if err := json.Unmarshal([]byte(jsonData), &data); err != nil { + t.Fatalf("Failed to parse JSON: %v", err) + } + + ctx := prompty.NewLoadContext() + instance, err := prompty.LoadPermissionRequest(data, ctx) + if err != nil { + t.Fatalf("Failed to load PermissionRequest: %v", err) + } + yamlOutput, err := instance.ToYAML() + if err != nil { + t.Fatalf("Failed to convert to YAML: %v", err) + } + + var parsed map[string]interface{} + if err := yaml.Unmarshal([]byte(yamlOutput), &parsed); err != nil { + t.Fatalf("Failed to parse generated YAML: %v", err) + } + + reloaded, err := prompty.LoadPermissionRequest(parsed, ctx) + if err != nil { + t.Fatalf("Failed to reload generated YAML: %v", err) + } + if reloaded.RequestId == nil || *reloaded.RequestId != "perm_abc123" { + t.Errorf(`Expected RequestId to be "perm_abc123", got %v`, reloaded.RequestId) + } + if reloaded.ToolCallId == nil || *reloaded.ToolCallId != "call_abc123" { + t.Errorf(`Expected ToolCallId to be "call_abc123", got %v`, reloaded.ToolCallId) + } + if reloaded.Permission != "tool.execute" { + t.Errorf(`Expected Permission to be "tool.execute", got %v`, reloaded.Permission) + } + if reloaded.Target == nil || *reloaded.Target != "shell" { + t.Errorf(`Expected Target to be "shell", got %v`, reloaded.Target) + } + if reloaded.PromptRequest == nil || *reloaded.PromptRequest != "Allow shell to run tests?" { + t.Errorf(`Expected PromptRequest to be "Allow shell to run tests?", got %v`, reloaded.PromptRequest) + } +} + +// TestPermissionRequestFromJSONInvalid rejects malformed JSON instead of silently defaulting +func TestPermissionRequestFromJSONInvalid(t *testing.T) { + if _, err := prompty.PermissionRequestFromJSON("{"); err == nil { + t.Fatalf("Expected malformed JSON to fail") + } +} diff --git a/runtime/go/prompty/model/permission_requested_payload.go b/runtime/go/prompty/model/permission_requested_payload.go new file mode 100644 index 00000000..81969518 --- /dev/null +++ b/runtime/go/prompty/model/permission_requested_payload.go @@ -0,0 +1,137 @@ +// +// Code generated by Typra emitter; DO NOT EDIT. +// Group: events + +package prompty + +import ( + "encoding/json" + + "gopkg.in/yaml.v3" +) + +// PermissionRequestedPayload represents Payload for permission request events — a host is asked to approve an action. + +type PermissionRequestedPayload struct { + RequestId *string `json:"requestId,omitempty" yaml:"requestId,omitempty"` + ToolCallId *string `json:"toolCallId,omitempty" yaml:"toolCallId,omitempty"` + Permission string `json:"permission" yaml:"permission"` + Target *string `json:"target,omitempty" yaml:"target,omitempty"` + Details map[string]interface{} `json:"details,omitempty" yaml:"details,omitempty"` + PromptRequest *string `json:"promptRequest,omitempty" yaml:"promptRequest,omitempty"` + Policy map[string]interface{} `json:"policy,omitempty" yaml:"policy,omitempty"` + Redaction *RedactionMetadata `json:"redaction,omitempty" yaml:"redaction,omitempty"` +} + +// LoadPermissionRequestedPayload creates a PermissionRequestedPayload from a map[string]interface{} +func LoadPermissionRequestedPayload(data interface{}, ctx *LoadContext) (PermissionRequestedPayload, error) { + result := PermissionRequestedPayload{} + + // Load from map + if m, ok := data.(map[string]interface{}); ok { + if val, ok := m["requestId"]; ok && val != nil { + v := string(val.(string)) + result.RequestId = &v + } + if val, ok := m["toolCallId"]; ok && val != nil { + v := string(val.(string)) + result.ToolCallId = &v + } + if val, ok := m["permission"]; ok && val != nil { + result.Permission = string(val.(string)) + } + if val, ok := m["target"]; ok && val != nil { + v := string(val.(string)) + result.Target = &v + } + if val, ok := m["details"]; ok && val != nil { + if m, ok := val.(map[string]interface{}); ok { + result.Details = m + } + } + if val, ok := m["promptRequest"]; ok && val != nil { + v := string(val.(string)) + result.PromptRequest = &v + } + if val, ok := m["policy"]; ok && val != nil { + if m, ok := val.(map[string]interface{}); ok { + result.Policy = m + } + } + if val, ok := m["redaction"]; ok && val != nil { + if m, ok := val.(map[string]interface{}); ok { + loaded, _ := LoadRedactionMetadata(m, ctx) + result.Redaction = &loaded + } + } + } + + return result, nil +} + +// Save serializes PermissionRequestedPayload to map[string]interface{} +func (obj PermissionRequestedPayload) Save(ctx *SaveContext) map[string]interface{} { + result := make(map[string]interface{}) + if obj.RequestId != nil { + result["requestId"] = *obj.RequestId + } + if obj.ToolCallId != nil { + result["toolCallId"] = *obj.ToolCallId + } + result["permission"] = obj.Permission + if obj.Target != nil { + result["target"] = *obj.Target + } + if obj.Details != nil { + result["details"] = obj.Details + } + if obj.PromptRequest != nil { + result["promptRequest"] = *obj.PromptRequest + } + if obj.Policy != nil { + result["policy"] = obj.Policy + } + if obj.Redaction != nil { + result["redaction"] = obj.Redaction.Save(ctx) + } + + return result +} + +// ToJSON serializes PermissionRequestedPayload to JSON string +func (obj *PermissionRequestedPayload) ToJSON() (string, error) { + ctx := NewSaveContext() + data := obj.Save(ctx) + bytes, err := json.Marshal(data) + if err != nil { + return "", err + } + return string(bytes), nil +} + +// ToYAML serializes PermissionRequestedPayload to YAML string +func (obj *PermissionRequestedPayload) ToYAML() (string, error) { + ctx := NewSaveContext() + data := obj.Save(ctx) + return marshalYAMLDocument(data) +} + +// FromJSON creates PermissionRequestedPayload from JSON string +func PermissionRequestedPayloadFromJSON(jsonStr string) (PermissionRequestedPayload, error) { + var data map[string]interface{} + if err := json.Unmarshal([]byte(jsonStr), &data); err != nil { + return PermissionRequestedPayload{}, err + } + ctx := NewLoadContext() + return LoadPermissionRequestedPayload(data, ctx) +} + +// FromYAML creates PermissionRequestedPayload from YAML string +func PermissionRequestedPayloadFromYAML(yamlStr string) (PermissionRequestedPayload, error) { + var data map[string]interface{} + if err := yaml.Unmarshal([]byte(yamlStr), &data); err != nil { + return PermissionRequestedPayload{}, err + } + ctx := NewLoadContext() + return LoadPermissionRequestedPayload(data, ctx) +} diff --git a/runtime/go/prompty/model/permission_requested_payload_test.go b/runtime/go/prompty/model/permission_requested_payload_test.go new file mode 100644 index 00000000..9e6dfa35 --- /dev/null +++ b/runtime/go/prompty/model/permission_requested_payload_test.go @@ -0,0 +1,309 @@ +// +// Code generated by Typra emitter; DO NOT EDIT. + +package prompty_test + +import ( + "encoding/json" + "testing" + + "gopkg.in/yaml.v3" + + "prompty/model" +) + +// TestPermissionRequestedPayloadLoadJSON tests loading PermissionRequestedPayload from JSON +func TestPermissionRequestedPayloadLoadJSON(t *testing.T) { + jsonData := ` +{ + "requestId": "perm_abc123", + "toolCallId": "call_abc123", + "permission": "tool.execute", + "target": "shell", + "promptRequest": "Allow shell to run tests?" +} +` + var data map[string]interface{} + if err := json.Unmarshal([]byte(jsonData), &data); err != nil { + t.Fatalf("Failed to parse JSON: %v", err) + } + + ctx := prompty.NewLoadContext() + instance, err := prompty.LoadPermissionRequestedPayload(data, ctx) + if err != nil { + t.Fatalf("Failed to load PermissionRequestedPayload: %v", err) + } + if instance.RequestId == nil || *instance.RequestId != "perm_abc123" { + t.Errorf(`Expected RequestId to be "perm_abc123", got %v`, instance.RequestId) + } + if instance.ToolCallId == nil || *instance.ToolCallId != "call_abc123" { + t.Errorf(`Expected ToolCallId to be "call_abc123", got %v`, instance.ToolCallId) + } + if instance.Permission != "tool.execute" { + t.Errorf(`Expected Permission to be "tool.execute", got %v`, instance.Permission) + } + if instance.Target == nil || *instance.Target != "shell" { + t.Errorf(`Expected Target to be "shell", got %v`, instance.Target) + } + if instance.PromptRequest == nil || *instance.PromptRequest != "Allow shell to run tests?" { + t.Errorf(`Expected PromptRequest to be "Allow shell to run tests?", got %v`, instance.PromptRequest) + } +} + +// TestPermissionRequestedPayloadLoadYAML tests loading PermissionRequestedPayload from YAML +func TestPermissionRequestedPayloadLoadYAML(t *testing.T) { + yamlData := ` +requestId: perm_abc123 +toolCallId: call_abc123 +permission: tool.execute +target: shell +promptRequest: Allow shell to run tests? + +` + var data map[string]interface{} + if err := yaml.Unmarshal([]byte(yamlData), &data); err != nil { + t.Fatalf("Failed to parse YAML: %v", err) + } + + ctx := prompty.NewLoadContext() + instance, err := prompty.LoadPermissionRequestedPayload(data, ctx) + if err != nil { + t.Fatalf("Failed to load PermissionRequestedPayload: %v", err) + } + if instance.RequestId == nil || *instance.RequestId != "perm_abc123" { + t.Errorf(`Expected RequestId to be "perm_abc123", got %v`, instance.RequestId) + } + if instance.ToolCallId == nil || *instance.ToolCallId != "call_abc123" { + t.Errorf(`Expected ToolCallId to be "call_abc123", got %v`, instance.ToolCallId) + } + if instance.Permission != "tool.execute" { + t.Errorf(`Expected Permission to be "tool.execute", got %v`, instance.Permission) + } + if instance.Target == nil || *instance.Target != "shell" { + t.Errorf(`Expected Target to be "shell", got %v`, instance.Target) + } + if instance.PromptRequest == nil || *instance.PromptRequest != "Allow shell to run tests?" { + t.Errorf(`Expected PromptRequest to be "Allow shell to run tests?", got %v`, instance.PromptRequest) + } +} + +// TestPermissionRequestedPayloadFromJSON tests loading PermissionRequestedPayload through the generated JSON helper +func TestPermissionRequestedPayloadFromJSON(t *testing.T) { + jsonData := ` +{ + "requestId": "perm_abc123", + "toolCallId": "call_abc123", + "permission": "tool.execute", + "target": "shell", + "promptRequest": "Allow shell to run tests?" +} +` + + instance, err := prompty.PermissionRequestedPayloadFromJSON(jsonData) + if err != nil { + t.Fatalf("Failed to load PermissionRequestedPayload from JSON helper: %v", err) + } + if instance.RequestId == nil || *instance.RequestId != "perm_abc123" { + t.Errorf(`Expected RequestId to be "perm_abc123", got %v`, instance.RequestId) + } + if instance.ToolCallId == nil || *instance.ToolCallId != "call_abc123" { + t.Errorf(`Expected ToolCallId to be "call_abc123", got %v`, instance.ToolCallId) + } + if instance.Permission != "tool.execute" { + t.Errorf(`Expected Permission to be "tool.execute", got %v`, instance.Permission) + } + if instance.Target == nil || *instance.Target != "shell" { + t.Errorf(`Expected Target to be "shell", got %v`, instance.Target) + } + if instance.PromptRequest == nil || *instance.PromptRequest != "Allow shell to run tests?" { + t.Errorf(`Expected PromptRequest to be "Allow shell to run tests?", got %v`, instance.PromptRequest) + } +} + +// TestPermissionRequestedPayloadFromYAML tests loading PermissionRequestedPayload through the generated YAML helper +func TestPermissionRequestedPayloadFromYAML(t *testing.T) { + yamlData := ` +requestId: perm_abc123 +toolCallId: call_abc123 +permission: tool.execute +target: shell +promptRequest: Allow shell to run tests? + +` + + instance, err := prompty.PermissionRequestedPayloadFromYAML(yamlData) + if err != nil { + t.Fatalf("Failed to load PermissionRequestedPayload from YAML helper: %v", err) + } + if instance.RequestId == nil || *instance.RequestId != "perm_abc123" { + t.Errorf(`Expected RequestId to be "perm_abc123", got %v`, instance.RequestId) + } + if instance.ToolCallId == nil || *instance.ToolCallId != "call_abc123" { + t.Errorf(`Expected ToolCallId to be "call_abc123", got %v`, instance.ToolCallId) + } + if instance.Permission != "tool.execute" { + t.Errorf(`Expected Permission to be "tool.execute", got %v`, instance.Permission) + } + if instance.Target == nil || *instance.Target != "shell" { + t.Errorf(`Expected Target to be "shell", got %v`, instance.Target) + } + if instance.PromptRequest == nil || *instance.PromptRequest != "Allow shell to run tests?" { + t.Errorf(`Expected PromptRequest to be "Allow shell to run tests?", got %v`, instance.PromptRequest) + } +} + +// TestPermissionRequestedPayloadRoundtrip tests load -> save -> load produces equivalent data +func TestPermissionRequestedPayloadRoundtrip(t *testing.T) { + jsonData := ` +{ + "requestId": "perm_abc123", + "toolCallId": "call_abc123", + "permission": "tool.execute", + "target": "shell", + "promptRequest": "Allow shell to run tests?" +} +` + var data map[string]interface{} + if err := json.Unmarshal([]byte(jsonData), &data); err != nil { + t.Fatalf("Failed to parse JSON: %v", err) + } + + loadCtx := prompty.NewLoadContext() + instance, err := prompty.LoadPermissionRequestedPayload(data, loadCtx) + if err != nil { + t.Fatalf("Failed to load PermissionRequestedPayload: %v", err) + } + saveCtx := prompty.NewSaveContext() + savedData := instance.Save(saveCtx) + + reloaded, err := prompty.LoadPermissionRequestedPayload(savedData, loadCtx) + if err != nil { + t.Fatalf("Failed to reload PermissionRequestedPayload: %v", err) + } + if reloaded.RequestId == nil || *reloaded.RequestId != "perm_abc123" { + t.Errorf(`Expected RequestId to be "perm_abc123", got %v`, reloaded.RequestId) + } + if reloaded.ToolCallId == nil || *reloaded.ToolCallId != "call_abc123" { + t.Errorf(`Expected ToolCallId to be "call_abc123", got %v`, reloaded.ToolCallId) + } + if reloaded.Permission != "tool.execute" { + t.Errorf(`Expected Permission to be "tool.execute", got %v`, reloaded.Permission) + } + if reloaded.Target == nil || *reloaded.Target != "shell" { + t.Errorf(`Expected Target to be "shell", got %v`, reloaded.Target) + } + if reloaded.PromptRequest == nil || *reloaded.PromptRequest != "Allow shell to run tests?" { + t.Errorf(`Expected PromptRequest to be "Allow shell to run tests?", got %v`, reloaded.PromptRequest) + } +} + +// TestPermissionRequestedPayloadToJSON tests that ToJSON produces valid JSON +func TestPermissionRequestedPayloadToJSON(t *testing.T) { + jsonData := ` +{ + "requestId": "perm_abc123", + "toolCallId": "call_abc123", + "permission": "tool.execute", + "target": "shell", + "promptRequest": "Allow shell to run tests?" +} +` + var data map[string]interface{} + if err := json.Unmarshal([]byte(jsonData), &data); err != nil { + t.Fatalf("Failed to parse JSON: %v", err) + } + + ctx := prompty.NewLoadContext() + instance, err := prompty.LoadPermissionRequestedPayload(data, ctx) + if err != nil { + t.Fatalf("Failed to load PermissionRequestedPayload: %v", err) + } + jsonOutput, err := instance.ToJSON() + if err != nil { + t.Fatalf("Failed to convert to JSON: %v", err) + } + + var parsed map[string]interface{} + if err := json.Unmarshal([]byte(jsonOutput), &parsed); err != nil { + t.Fatalf("Failed to parse generated JSON: %v", err) + } + + reloaded, err := prompty.LoadPermissionRequestedPayload(parsed, ctx) + if err != nil { + t.Fatalf("Failed to reload generated JSON: %v", err) + } + if reloaded.RequestId == nil || *reloaded.RequestId != "perm_abc123" { + t.Errorf(`Expected RequestId to be "perm_abc123", got %v`, reloaded.RequestId) + } + if reloaded.ToolCallId == nil || *reloaded.ToolCallId != "call_abc123" { + t.Errorf(`Expected ToolCallId to be "call_abc123", got %v`, reloaded.ToolCallId) + } + if reloaded.Permission != "tool.execute" { + t.Errorf(`Expected Permission to be "tool.execute", got %v`, reloaded.Permission) + } + if reloaded.Target == nil || *reloaded.Target != "shell" { + t.Errorf(`Expected Target to be "shell", got %v`, reloaded.Target) + } + if reloaded.PromptRequest == nil || *reloaded.PromptRequest != "Allow shell to run tests?" { + t.Errorf(`Expected PromptRequest to be "Allow shell to run tests?", got %v`, reloaded.PromptRequest) + } +} + +// TestPermissionRequestedPayloadToYAML tests that ToYAML produces valid YAML +func TestPermissionRequestedPayloadToYAML(t *testing.T) { + jsonData := ` +{ + "requestId": "perm_abc123", + "toolCallId": "call_abc123", + "permission": "tool.execute", + "target": "shell", + "promptRequest": "Allow shell to run tests?" +} +` + var data map[string]interface{} + if err := json.Unmarshal([]byte(jsonData), &data); err != nil { + t.Fatalf("Failed to parse JSON: %v", err) + } + + ctx := prompty.NewLoadContext() + instance, err := prompty.LoadPermissionRequestedPayload(data, ctx) + if err != nil { + t.Fatalf("Failed to load PermissionRequestedPayload: %v", err) + } + yamlOutput, err := instance.ToYAML() + if err != nil { + t.Fatalf("Failed to convert to YAML: %v", err) + } + + var parsed map[string]interface{} + if err := yaml.Unmarshal([]byte(yamlOutput), &parsed); err != nil { + t.Fatalf("Failed to parse generated YAML: %v", err) + } + + reloaded, err := prompty.LoadPermissionRequestedPayload(parsed, ctx) + if err != nil { + t.Fatalf("Failed to reload generated YAML: %v", err) + } + if reloaded.RequestId == nil || *reloaded.RequestId != "perm_abc123" { + t.Errorf(`Expected RequestId to be "perm_abc123", got %v`, reloaded.RequestId) + } + if reloaded.ToolCallId == nil || *reloaded.ToolCallId != "call_abc123" { + t.Errorf(`Expected ToolCallId to be "call_abc123", got %v`, reloaded.ToolCallId) + } + if reloaded.Permission != "tool.execute" { + t.Errorf(`Expected Permission to be "tool.execute", got %v`, reloaded.Permission) + } + if reloaded.Target == nil || *reloaded.Target != "shell" { + t.Errorf(`Expected Target to be "shell", got %v`, reloaded.Target) + } + if reloaded.PromptRequest == nil || *reloaded.PromptRequest != "Allow shell to run tests?" { + t.Errorf(`Expected PromptRequest to be "Allow shell to run tests?", got %v`, reloaded.PromptRequest) + } +} + +// TestPermissionRequestedPayloadFromJSONInvalid rejects malformed JSON instead of silently defaulting +func TestPermissionRequestedPayloadFromJSONInvalid(t *testing.T) { + if _, err := prompty.PermissionRequestedPayloadFromJSON("{"); err == nil { + t.Fatalf("Expected malformed JSON to fail") + } +} diff --git a/runtime/go/prompty/model/permission_resolver.go b/runtime/go/prompty/model/permission_resolver.go new file mode 100644 index 00000000..8c5694ad --- /dev/null +++ b/runtime/go/prompty/model/permission_resolver.go @@ -0,0 +1,12 @@ +// +// Code generated by Typra emitter; DO NOT EDIT. +// Group: pipeline + +package prompty + +// PermissionResolver represents Resolves host permission requests for potentially sensitive actions. + +type PermissionResolver interface { + // Request — Resolve a host permission request + Request(request PermissionRequest) (PermissionDecision, error) +} diff --git a/runtime/go/prompty/model/processor.go b/runtime/go/prompty/model/processor.go index 484c49df..23a83d37 100644 --- a/runtime/go/prompty/model/processor.go +++ b/runtime/go/prompty/model/processor.go @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. // Group: pipeline package prompty diff --git a/runtime/go/prompty/model/prompty.go b/runtime/go/prompty/model/prompty.go index 5ea8484c..82ae8e98 100644 --- a/runtime/go/prompty/model/prompty.go +++ b/runtime/go/prompty/model/prompty.go @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. // Group: agent package prompty @@ -15,6 +16,12 @@ import ( // // This is the single root type for the Prompty schema — there is no abstract base // class or kind discriminator. A .prompty file always produces a Prompty instance. +// +// Runtime loaders may resolve frontmatter references such as `${env:VAR}` and +// `${file:relative/path}`. File references must be treated as a host-controlled +// capability: by default they are scoped to the containing .prompty file's +// directory tree after canonicalization, and any additional allowed roots must +// be supplied by the host application's load options rather than frontmatter. type Prompty struct { Name string `json:"name" yaml:"name"` @@ -79,6 +86,9 @@ func LoadPrompty(data interface{}, ctx *LoadContext) (Prompty, error) { if m, ok := val.(map[string]interface{}); ok { loaded, _ := LoadModel(m, ctx) result.Model = loaded + } else { + loaded, _ := LoadModel(val, ctx) + result.Model = loaded } } if val, ok := m["tools"]; ok && val != nil { @@ -109,7 +119,7 @@ func LoadPrompty(data interface{}, ctx *LoadContext) (Prompty, error) { } // Save serializes Prompty to map[string]interface{} -func (obj *Prompty) Save(ctx *SaveContext) map[string]interface{} { +func (obj Prompty) Save(ctx *SaveContext) map[string]interface{} { result := make(map[string]interface{}) result["name"] = obj.Name if obj.DisplayName != nil { @@ -193,11 +203,7 @@ func (obj *Prompty) ToJSON() (string, error) { func (obj *Prompty) ToYAML() (string, error) { ctx := NewSaveContext() data := obj.Save(ctx) - bytes, err := yaml.Marshal(data) - if err != nil { - return "", err - } - return string(bytes), nil + return marshalYAMLDocument(data) } // FromJSON creates Prompty from JSON string diff --git a/runtime/go/prompty/model/prompty_test.go b/runtime/go/prompty/model/prompty_test.go index 24da9bda..ab4a9094 100644 --- a/runtime/go/prompty/model/prompty_test.go +++ b/runtime/go/prompty/model/prompty_test.go @@ -1,9 +1,11 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. package prompty_test import ( "encoding/json" + "reflect" "testing" "gopkg.in/yaml.v3" @@ -102,6 +104,28 @@ func TestPromptyLoadJSON(t *testing.T) { if instance.Instructions == nil || *instance.Instructions != "system:\nYou are an AI assistant who helps people find information.\nAs the assistant, you answer questions briefly, succinctly,\nand in a personable manner using markdown and even add some \npersonal flair with appropriate emojis.\n\n# Customer\nYou are helping {{firstName}} {{lastName}} to find answers to \ntheir questions. Use their name to address them in your responses.\nuser:\n{{question}}" { t.Errorf(`Expected Instructions to be "system:\nYou are an AI assistant who helps people find information.\nAs the assistant, you answer questions briefly, succinctly,\nand in a personable manner using markdown and even add some \npersonal flair with appropriate emojis.\n\n# Customer\nYou are helping {{firstName}} {{lastName}} to find answers to \ntheir questions. Use their name to address them in your responses.\nuser:\n{{question}}", got %v`, instance.Instructions) } + if instance.Metadata == nil { + t.Fatalf("Expected Metadata to be populated") + } + if instance.Model.Id != "gpt-35-turbo" { + t.Errorf(`Expected Model.Id to be "gpt-35-turbo", got %v`, instance.Model.Id) + } + if len(instance.Tools) != 1 { + t.Fatalf("Expected Tools length to be 1, got %d", len(instance.Tools)) + } + tools0Value, ok := instance.Tools[0].(prompty.FunctionTool) + if !ok { + t.Fatalf("Expected Tools[0] to be prompty.FunctionTool, got %T", instance.Tools[0]) + } + if tools0Value.Kind != "function" { + t.Errorf(`Expected Kind to be "function", got %v`, tools0Value.Kind) + } + if instance.Template.Format.Kind != "mustache" { + t.Errorf(`Expected Template.Format.Kind to be "mustache", got %v`, instance.Template.Format.Kind) + } + if instance.Template.Parser.Kind != "prompty" { + t.Errorf(`Expected Template.Parser.Kind to be "prompty", got %v`, instance.Template.Parser.Kind) + } } // TestPromptyLoadYAML tests loading Prompty from YAML @@ -157,14 +181,14 @@ instructions: "system: As the assistant, you answer questions briefly, succinctly, - and in a personable manner using markdown and even add some\ + and in a personable manner using markdown and even add some\ personal flair with appropriate emojis. # Customer - You are helping {{firstName}} {{lastName}} to find answers to\ + You are helping {{firstName}} {{lastName}} to find answers to\ their questions. Use their name to address them in your responses. @@ -195,6 +219,248 @@ instructions: "system: if instance.Instructions == nil || *instance.Instructions != "system:\nYou are an AI assistant who helps people find information.\nAs the assistant, you answer questions briefly, succinctly,\nand in a personable manner using markdown and even add some \npersonal flair with appropriate emojis.\n\n# Customer\nYou are helping {{firstName}} {{lastName}} to find answers to \ntheir questions. Use their name to address them in your responses.\nuser:\n{{question}}" { t.Errorf(`Expected Instructions to be "system:\nYou are an AI assistant who helps people find information.\nAs the assistant, you answer questions briefly, succinctly,\nand in a personable manner using markdown and even add some \npersonal flair with appropriate emojis.\n\n# Customer\nYou are helping {{firstName}} {{lastName}} to find answers to \ntheir questions. Use their name to address them in your responses.\nuser:\n{{question}}", got %v`, instance.Instructions) } + if instance.Metadata == nil { + t.Fatalf("Expected Metadata to be populated") + } + if instance.Model.Id != "gpt-35-turbo" { + t.Errorf(`Expected Model.Id to be "gpt-35-turbo", got %v`, instance.Model.Id) + } + if len(instance.Tools) != 1 { + t.Fatalf("Expected Tools length to be 1, got %d", len(instance.Tools)) + } + tools0Value, ok := instance.Tools[0].(prompty.FunctionTool) + if !ok { + t.Fatalf("Expected Tools[0] to be prompty.FunctionTool, got %T", instance.Tools[0]) + } + if tools0Value.Kind != "function" { + t.Errorf(`Expected Kind to be "function", got %v`, tools0Value.Kind) + } + if instance.Template.Format.Kind != "mustache" { + t.Errorf(`Expected Template.Format.Kind to be "mustache", got %v`, instance.Template.Format.Kind) + } + if instance.Template.Parser.Kind != "prompty" { + t.Errorf(`Expected Template.Parser.Kind to be "prompty", got %v`, instance.Template.Parser.Kind) + } +} + +// TestPromptyFromJSON tests loading Prompty through the generated JSON helper +func TestPromptyFromJSON(t *testing.T) { + jsonData := ` +{ + "name": "basic-prompt", + "displayName": "Basic Prompt", + "description": "A basic prompt that uses the GPT-3 chat API to answer questions", + "metadata": { + "authors": [ + "sethjuarez", + "jietong" + ], + "tags": [ + "example", + "prompt" + ] + }, + "inputs": { + "firstName": { + "kind": "string", + "default": "Jane" + }, + "lastName": { + "kind": "string", + "default": "Doe" + }, + "question": { + "kind": "string", + "default": "What is the meaning of life?" + } + }, + "outputs": { + "answer": { + "kind": "string", + "description": "The answer to the user's question." + } + }, + "model": { + "id": "gpt-35-turbo", + "connection": { + "kind": "key", + "endpoint": "https://{your-custom-endpoint}.openai.azure.com/", + "apiKey": "{your-api-key}" + } + }, + "tools": [ + { + "name": "getCurrentWeather", + "kind": "function", + "description": "Get the current weather in a given location", + "parameters": { + "location": { + "kind": "string", + "description": "The city and state, e.g. San Francisco, CA" + }, + "unit": { + "kind": "string", + "description": "The unit of temperature, e.g. Celsius or Fahrenheit" + } + } + } + ], + "template": { + "format": "mustache", + "parser": "prompty" + }, + "instructions": "system:\nYou are an AI assistant who helps people find information.\nAs the assistant, you answer questions briefly, succinctly,\nand in a personable manner using markdown and even add some \npersonal flair with appropriate emojis.\n\n# Customer\nYou are helping {{firstName}} {{lastName}} to find answers to \ntheir questions. Use their name to address them in your responses.\nuser:\n{{question}}" +} +` + + instance, err := prompty.PromptyFromJSON(jsonData) + if err != nil { + t.Fatalf("Failed to load Prompty from JSON helper: %v", err) + } + if instance.Name != "basic-prompt" { + t.Errorf(`Expected Name to be "basic-prompt", got %v`, instance.Name) + } + if instance.DisplayName == nil || *instance.DisplayName != "Basic Prompt" { + t.Errorf(`Expected DisplayName to be "Basic Prompt", got %v`, instance.DisplayName) + } + if instance.Description == nil || *instance.Description != "A basic prompt that uses the GPT-3 chat API to answer questions" { + t.Errorf(`Expected Description to be "A basic prompt that uses the GPT-3 chat API to answer questions", got %v`, instance.Description) + } + if instance.Instructions == nil || *instance.Instructions != "system:\nYou are an AI assistant who helps people find information.\nAs the assistant, you answer questions briefly, succinctly,\nand in a personable manner using markdown and even add some \npersonal flair with appropriate emojis.\n\n# Customer\nYou are helping {{firstName}} {{lastName}} to find answers to \ntheir questions. Use their name to address them in your responses.\nuser:\n{{question}}" { + t.Errorf(`Expected Instructions to be "system:\nYou are an AI assistant who helps people find information.\nAs the assistant, you answer questions briefly, succinctly,\nand in a personable manner using markdown and even add some \npersonal flair with appropriate emojis.\n\n# Customer\nYou are helping {{firstName}} {{lastName}} to find answers to \ntheir questions. Use their name to address them in your responses.\nuser:\n{{question}}", got %v`, instance.Instructions) + } + if instance.Metadata == nil { + t.Fatalf("Expected Metadata to be populated") + } + if instance.Model.Id != "gpt-35-turbo" { + t.Errorf(`Expected Model.Id to be "gpt-35-turbo", got %v`, instance.Model.Id) + } + if len(instance.Tools) != 1 { + t.Fatalf("Expected Tools length to be 1, got %d", len(instance.Tools)) + } + tools0Value, ok := instance.Tools[0].(prompty.FunctionTool) + if !ok { + t.Fatalf("Expected Tools[0] to be prompty.FunctionTool, got %T", instance.Tools[0]) + } + if tools0Value.Kind != "function" { + t.Errorf(`Expected Kind to be "function", got %v`, tools0Value.Kind) + } + if instance.Template.Format.Kind != "mustache" { + t.Errorf(`Expected Template.Format.Kind to be "mustache", got %v`, instance.Template.Format.Kind) + } + if instance.Template.Parser.Kind != "prompty" { + t.Errorf(`Expected Template.Parser.Kind to be "prompty", got %v`, instance.Template.Parser.Kind) + } +} + +// TestPromptyFromYAML tests loading Prompty through the generated YAML helper +func TestPromptyFromYAML(t *testing.T) { + yamlData := ` +name: basic-prompt +displayName: Basic Prompt +description: A basic prompt that uses the GPT-3 chat API to answer questions +metadata: + authors: + - sethjuarez + - jietong + tags: + - example + - prompt +inputs: + firstName: + kind: string + default: Jane + lastName: + kind: string + default: Doe + question: + kind: string + default: What is the meaning of life? +outputs: + answer: + kind: string + description: The answer to the user's question. +model: + id: gpt-35-turbo + connection: + kind: key + endpoint: "https://{your-custom-endpoint}.openai.azure.com/" + apiKey: "{your-api-key}" +tools: + - name: getCurrentWeather + kind: function + description: Get the current weather in a given location + parameters: + location: + kind: string + description: The city and state, e.g. San Francisco, CA + unit: + kind: string + description: The unit of temperature, e.g. Celsius or Fahrenheit +template: + format: mustache + parser: prompty +instructions: "system: + + You are an AI assistant who helps people find information. + + As the assistant, you answer questions briefly, succinctly, + + and in a personable manner using markdown and even add some\ + + personal flair with appropriate emojis. + + + # Customer + + You are helping {{firstName}} {{lastName}} to find answers to\ + + their questions. Use their name to address them in your responses. + + user: + + {{question}}" + +` + + instance, err := prompty.PromptyFromYAML(yamlData) + if err != nil { + t.Fatalf("Failed to load Prompty from YAML helper: %v", err) + } + if instance.Name != "basic-prompt" { + t.Errorf(`Expected Name to be "basic-prompt", got %v`, instance.Name) + } + if instance.DisplayName == nil || *instance.DisplayName != "Basic Prompt" { + t.Errorf(`Expected DisplayName to be "Basic Prompt", got %v`, instance.DisplayName) + } + if instance.Description == nil || *instance.Description != "A basic prompt that uses the GPT-3 chat API to answer questions" { + t.Errorf(`Expected Description to be "A basic prompt that uses the GPT-3 chat API to answer questions", got %v`, instance.Description) + } + if instance.Instructions == nil || *instance.Instructions != "system:\nYou are an AI assistant who helps people find information.\nAs the assistant, you answer questions briefly, succinctly,\nand in a personable manner using markdown and even add some \npersonal flair with appropriate emojis.\n\n# Customer\nYou are helping {{firstName}} {{lastName}} to find answers to \ntheir questions. Use their name to address them in your responses.\nuser:\n{{question}}" { + t.Errorf(`Expected Instructions to be "system:\nYou are an AI assistant who helps people find information.\nAs the assistant, you answer questions briefly, succinctly,\nand in a personable manner using markdown and even add some \npersonal flair with appropriate emojis.\n\n# Customer\nYou are helping {{firstName}} {{lastName}} to find answers to \ntheir questions. Use their name to address them in your responses.\nuser:\n{{question}}", got %v`, instance.Instructions) + } + if instance.Metadata == nil { + t.Fatalf("Expected Metadata to be populated") + } + if instance.Model.Id != "gpt-35-turbo" { + t.Errorf(`Expected Model.Id to be "gpt-35-turbo", got %v`, instance.Model.Id) + } + if len(instance.Tools) != 1 { + t.Fatalf("Expected Tools length to be 1, got %d", len(instance.Tools)) + } + tools0Value, ok := instance.Tools[0].(prompty.FunctionTool) + if !ok { + t.Fatalf("Expected Tools[0] to be prompty.FunctionTool, got %T", instance.Tools[0]) + } + if tools0Value.Kind != "function" { + t.Errorf(`Expected Kind to be "function", got %v`, tools0Value.Kind) + } + if instance.Template.Format.Kind != "mustache" { + t.Errorf(`Expected Template.Format.Kind to be "mustache", got %v`, instance.Template.Format.Kind) + } + if instance.Template.Parser.Kind != "prompty" { + t.Errorf(`Expected Template.Parser.Kind to be "prompty", got %v`, instance.Template.Parser.Kind) + } } // TestPromptyRoundtrip tests load -> save -> load produces equivalent data @@ -295,6 +561,28 @@ func TestPromptyRoundtrip(t *testing.T) { if reloaded.Instructions == nil || *reloaded.Instructions != "system:\nYou are an AI assistant who helps people find information.\nAs the assistant, you answer questions briefly, succinctly,\nand in a personable manner using markdown and even add some \npersonal flair with appropriate emojis.\n\n# Customer\nYou are helping {{firstName}} {{lastName}} to find answers to \ntheir questions. Use their name to address them in your responses.\nuser:\n{{question}}" { t.Errorf(`Expected Instructions to be "system:\nYou are an AI assistant who helps people find information.\nAs the assistant, you answer questions briefly, succinctly,\nand in a personable manner using markdown and even add some \npersonal flair with appropriate emojis.\n\n# Customer\nYou are helping {{firstName}} {{lastName}} to find answers to \ntheir questions. Use their name to address them in your responses.\nuser:\n{{question}}", got %v`, reloaded.Instructions) } + if reloaded.Metadata == nil { + t.Fatalf("Expected Metadata to be populated") + } + if reloaded.Model.Id != "gpt-35-turbo" { + t.Errorf(`Expected Model.Id to be "gpt-35-turbo", got %v`, reloaded.Model.Id) + } + if len(reloaded.Tools) != 1 { + t.Fatalf("Expected Tools length to be 1, got %d", len(reloaded.Tools)) + } + tools0Value, ok := reloaded.Tools[0].(prompty.FunctionTool) + if !ok { + t.Fatalf("Expected Tools[0] to be prompty.FunctionTool, got %T", reloaded.Tools[0]) + } + if tools0Value.Kind != "function" { + t.Errorf(`Expected Kind to be "function", got %v`, tools0Value.Kind) + } + if reloaded.Template.Format.Kind != "mustache" { + t.Errorf(`Expected Template.Format.Kind to be "mustache", got %v`, reloaded.Template.Format.Kind) + } + if reloaded.Template.Parser.Kind != "prompty" { + t.Errorf(`Expected Template.Parser.Kind to be "prompty", got %v`, reloaded.Template.Parser.Kind) + } } // TestPromptyToJSON tests that ToJSON produces valid JSON @@ -385,6 +673,45 @@ func TestPromptyToJSON(t *testing.T) { if err := json.Unmarshal([]byte(jsonOutput), &parsed); err != nil { t.Fatalf("Failed to parse generated JSON: %v", err) } + + reloaded, err := prompty.LoadPrompty(parsed, ctx) + if err != nil { + t.Fatalf("Failed to reload generated JSON: %v", err) + } + if reloaded.Name != "basic-prompt" { + t.Errorf(`Expected Name to be "basic-prompt", got %v`, reloaded.Name) + } + if reloaded.DisplayName == nil || *reloaded.DisplayName != "Basic Prompt" { + t.Errorf(`Expected DisplayName to be "Basic Prompt", got %v`, reloaded.DisplayName) + } + if reloaded.Description == nil || *reloaded.Description != "A basic prompt that uses the GPT-3 chat API to answer questions" { + t.Errorf(`Expected Description to be "A basic prompt that uses the GPT-3 chat API to answer questions", got %v`, reloaded.Description) + } + if reloaded.Instructions == nil || *reloaded.Instructions != "system:\nYou are an AI assistant who helps people find information.\nAs the assistant, you answer questions briefly, succinctly,\nand in a personable manner using markdown and even add some \npersonal flair with appropriate emojis.\n\n# Customer\nYou are helping {{firstName}} {{lastName}} to find answers to \ntheir questions. Use their name to address them in your responses.\nuser:\n{{question}}" { + t.Errorf(`Expected Instructions to be "system:\nYou are an AI assistant who helps people find information.\nAs the assistant, you answer questions briefly, succinctly,\nand in a personable manner using markdown and even add some \npersonal flair with appropriate emojis.\n\n# Customer\nYou are helping {{firstName}} {{lastName}} to find answers to \ntheir questions. Use their name to address them in your responses.\nuser:\n{{question}}", got %v`, reloaded.Instructions) + } + if reloaded.Metadata == nil { + t.Fatalf("Expected Metadata to be populated") + } + if reloaded.Model.Id != "gpt-35-turbo" { + t.Errorf(`Expected Model.Id to be "gpt-35-turbo", got %v`, reloaded.Model.Id) + } + if len(reloaded.Tools) != 1 { + t.Fatalf("Expected Tools length to be 1, got %d", len(reloaded.Tools)) + } + tools0Value, ok := reloaded.Tools[0].(prompty.FunctionTool) + if !ok { + t.Fatalf("Expected Tools[0] to be prompty.FunctionTool, got %T", reloaded.Tools[0]) + } + if tools0Value.Kind != "function" { + t.Errorf(`Expected Kind to be "function", got %v`, tools0Value.Kind) + } + if reloaded.Template.Format.Kind != "mustache" { + t.Errorf(`Expected Template.Format.Kind to be "mustache", got %v`, reloaded.Template.Format.Kind) + } + if reloaded.Template.Parser.Kind != "prompty" { + t.Errorf(`Expected Template.Parser.Kind to be "prompty", got %v`, reloaded.Template.Parser.Kind) + } } // TestPromptyToYAML tests that ToYAML produces valid YAML @@ -475,6 +802,45 @@ func TestPromptyToYAML(t *testing.T) { if err := yaml.Unmarshal([]byte(yamlOutput), &parsed); err != nil { t.Fatalf("Failed to parse generated YAML: %v", err) } + + reloaded, err := prompty.LoadPrompty(parsed, ctx) + if err != nil { + t.Fatalf("Failed to reload generated YAML: %v", err) + } + if reloaded.Name != "basic-prompt" { + t.Errorf(`Expected Name to be "basic-prompt", got %v`, reloaded.Name) + } + if reloaded.DisplayName == nil || *reloaded.DisplayName != "Basic Prompt" { + t.Errorf(`Expected DisplayName to be "Basic Prompt", got %v`, reloaded.DisplayName) + } + if reloaded.Description == nil || *reloaded.Description != "A basic prompt that uses the GPT-3 chat API to answer questions" { + t.Errorf(`Expected Description to be "A basic prompt that uses the GPT-3 chat API to answer questions", got %v`, reloaded.Description) + } + if reloaded.Instructions == nil || *reloaded.Instructions != "system:\nYou are an AI assistant who helps people find information.\nAs the assistant, you answer questions briefly, succinctly,\nand in a personable manner using markdown and even add some \npersonal flair with appropriate emojis.\n\n# Customer\nYou are helping {{firstName}} {{lastName}} to find answers to \ntheir questions. Use their name to address them in your responses.\nuser:\n{{question}}" { + t.Errorf(`Expected Instructions to be "system:\nYou are an AI assistant who helps people find information.\nAs the assistant, you answer questions briefly, succinctly,\nand in a personable manner using markdown and even add some \npersonal flair with appropriate emojis.\n\n# Customer\nYou are helping {{firstName}} {{lastName}} to find answers to \ntheir questions. Use their name to address them in your responses.\nuser:\n{{question}}", got %v`, reloaded.Instructions) + } + if reloaded.Metadata == nil { + t.Fatalf("Expected Metadata to be populated") + } + if reloaded.Model.Id != "gpt-35-turbo" { + t.Errorf(`Expected Model.Id to be "gpt-35-turbo", got %v`, reloaded.Model.Id) + } + if len(reloaded.Tools) != 1 { + t.Fatalf("Expected Tools length to be 1, got %d", len(reloaded.Tools)) + } + tools0Value, ok := reloaded.Tools[0].(prompty.FunctionTool) + if !ok { + t.Fatalf("Expected Tools[0] to be prompty.FunctionTool, got %T", reloaded.Tools[0]) + } + if tools0Value.Kind != "function" { + t.Errorf(`Expected Kind to be "function", got %v`, tools0Value.Kind) + } + if reloaded.Template.Format.Kind != "mustache" { + t.Errorf(`Expected Template.Format.Kind to be "mustache", got %v`, reloaded.Template.Format.Kind) + } + if reloaded.Template.Parser.Kind != "prompty" { + t.Errorf(`Expected Template.Parser.Kind to be "prompty", got %v`, reloaded.Template.Parser.Kind) + } } // TestPromptyLoadJSON1 tests loading Prompty from JSON @@ -567,6 +933,18 @@ func TestPromptyLoadJSON1(t *testing.T) { if instance.Instructions == nil || *instance.Instructions != "system:\nYou are an AI assistant who helps people find information.\nAs the assistant, you answer questions briefly, succinctly,\nand in a personable manner using markdown and even add some \npersonal flair with appropriate emojis.\n\n# Customer\nYou are helping {{firstName}} {{lastName}} to find answers to \ntheir questions. Use their name to address them in your responses.\nuser:\n{{question}}" { t.Errorf(`Expected Instructions to be "system:\nYou are an AI assistant who helps people find information.\nAs the assistant, you answer questions briefly, succinctly,\nand in a personable manner using markdown and even add some \npersonal flair with appropriate emojis.\n\n# Customer\nYou are helping {{firstName}} {{lastName}} to find answers to \ntheir questions. Use their name to address them in your responses.\nuser:\n{{question}}", got %v`, instance.Instructions) } + if instance.Metadata == nil { + t.Fatalf("Expected Metadata to be populated") + } + if instance.Model.Id != "gpt-35-turbo" { + t.Errorf(`Expected Model.Id to be "gpt-35-turbo", got %v`, instance.Model.Id) + } + if instance.Template.Format.Kind != "mustache" { + t.Errorf(`Expected Template.Format.Kind to be "mustache", got %v`, instance.Template.Format.Kind) + } + if instance.Template.Parser.Kind != "prompty" { + t.Errorf(`Expected Template.Parser.Kind to be "prompty", got %v`, instance.Template.Parser.Kind) + } } // TestPromptyLoadYAML1 tests loading Prompty from YAML @@ -622,14 +1000,14 @@ instructions: "system: As the assistant, you answer questions briefly, succinctly, - and in a personable manner using markdown and even add some\ + and in a personable manner using markdown and even add some\ personal flair with appropriate emojis. # Customer - You are helping {{firstName}} {{lastName}} to find answers to\ + You are helping {{firstName}} {{lastName}} to find answers to\ their questions. Use their name to address them in your responses. @@ -660,10 +1038,22 @@ instructions: "system: if instance.Instructions == nil || *instance.Instructions != "system:\nYou are an AI assistant who helps people find information.\nAs the assistant, you answer questions briefly, succinctly,\nand in a personable manner using markdown and even add some \npersonal flair with appropriate emojis.\n\n# Customer\nYou are helping {{firstName}} {{lastName}} to find answers to \ntheir questions. Use their name to address them in your responses.\nuser:\n{{question}}" { t.Errorf(`Expected Instructions to be "system:\nYou are an AI assistant who helps people find information.\nAs the assistant, you answer questions briefly, succinctly,\nand in a personable manner using markdown and even add some \npersonal flair with appropriate emojis.\n\n# Customer\nYou are helping {{firstName}} {{lastName}} to find answers to \ntheir questions. Use their name to address them in your responses.\nuser:\n{{question}}", got %v`, instance.Instructions) } + if instance.Metadata == nil { + t.Fatalf("Expected Metadata to be populated") + } + if instance.Model.Id != "gpt-35-turbo" { + t.Errorf(`Expected Model.Id to be "gpt-35-turbo", got %v`, instance.Model.Id) + } + if instance.Template.Format.Kind != "mustache" { + t.Errorf(`Expected Template.Format.Kind to be "mustache", got %v`, instance.Template.Format.Kind) + } + if instance.Template.Parser.Kind != "prompty" { + t.Errorf(`Expected Template.Parser.Kind to be "prompty", got %v`, instance.Template.Parser.Kind) + } } -// TestPromptyRoundtrip1 tests load -> save -> load produces equivalent data -func TestPromptyRoundtrip1(t *testing.T) { +// TestPromptyFromJSON1 tests loading Prompty through the generated JSON helper +func TestPromptyFromJSON1(t *testing.T) { jsonData := ` { "name": "basic-prompt", @@ -730,7 +1120,206 @@ func TestPromptyRoundtrip1(t *testing.T) { "instructions": "system:\nYou are an AI assistant who helps people find information.\nAs the assistant, you answer questions briefly, succinctly,\nand in a personable manner using markdown and even add some \npersonal flair with appropriate emojis.\n\n# Customer\nYou are helping {{firstName}} {{lastName}} to find answers to \ntheir questions. Use their name to address them in your responses.\nuser:\n{{question}}" } ` - var data map[string]interface{} + + instance, err := prompty.PromptyFromJSON(jsonData) + if err != nil { + t.Fatalf("Failed to load Prompty from JSON helper: %v", err) + } + if instance.Name != "basic-prompt" { + t.Errorf(`Expected Name to be "basic-prompt", got %v`, instance.Name) + } + if instance.DisplayName == nil || *instance.DisplayName != "Basic Prompt" { + t.Errorf(`Expected DisplayName to be "Basic Prompt", got %v`, instance.DisplayName) + } + if instance.Description == nil || *instance.Description != "A basic prompt that uses the GPT-3 chat API to answer questions" { + t.Errorf(`Expected Description to be "A basic prompt that uses the GPT-3 chat API to answer questions", got %v`, instance.Description) + } + if instance.Instructions == nil || *instance.Instructions != "system:\nYou are an AI assistant who helps people find information.\nAs the assistant, you answer questions briefly, succinctly,\nand in a personable manner using markdown and even add some \npersonal flair with appropriate emojis.\n\n# Customer\nYou are helping {{firstName}} {{lastName}} to find answers to \ntheir questions. Use their name to address them in your responses.\nuser:\n{{question}}" { + t.Errorf(`Expected Instructions to be "system:\nYou are an AI assistant who helps people find information.\nAs the assistant, you answer questions briefly, succinctly,\nand in a personable manner using markdown and even add some \npersonal flair with appropriate emojis.\n\n# Customer\nYou are helping {{firstName}} {{lastName}} to find answers to \ntheir questions. Use their name to address them in your responses.\nuser:\n{{question}}", got %v`, instance.Instructions) + } + if instance.Metadata == nil { + t.Fatalf("Expected Metadata to be populated") + } + if instance.Model.Id != "gpt-35-turbo" { + t.Errorf(`Expected Model.Id to be "gpt-35-turbo", got %v`, instance.Model.Id) + } + if instance.Template.Format.Kind != "mustache" { + t.Errorf(`Expected Template.Format.Kind to be "mustache", got %v`, instance.Template.Format.Kind) + } + if instance.Template.Parser.Kind != "prompty" { + t.Errorf(`Expected Template.Parser.Kind to be "prompty", got %v`, instance.Template.Parser.Kind) + } +} + +// TestPromptyFromYAML1 tests loading Prompty through the generated YAML helper +func TestPromptyFromYAML1(t *testing.T) { + yamlData := ` +name: basic-prompt +displayName: Basic Prompt +description: A basic prompt that uses the GPT-3 chat API to answer questions +metadata: + authors: + - sethjuarez + - jietong + tags: + - example + - prompt +inputs: + firstName: + kind: string + default: Jane + lastName: + kind: string + default: Doe + question: + kind: string + default: What is the meaning of life? +outputs: + answer: + kind: string + description: The answer to the user's question. +model: + id: gpt-35-turbo + connection: + kind: key + endpoint: "https://{your-custom-endpoint}.openai.azure.com/" + apiKey: "{your-api-key}" +tools: + getCurrentWeather: + kind: function + description: Get the current weather in a given location + parameters: + location: + kind: string + description: The city and state, e.g. San Francisco, CA + unit: + kind: string + description: The unit of temperature, e.g. Celsius or Fahrenheit +template: + format: mustache + parser: prompty +instructions: "system: + + You are an AI assistant who helps people find information. + + As the assistant, you answer questions briefly, succinctly, + + and in a personable manner using markdown and even add some\ + + personal flair with appropriate emojis. + + + # Customer + + You are helping {{firstName}} {{lastName}} to find answers to\ + + their questions. Use their name to address them in your responses. + + user: + + {{question}}" + +` + + instance, err := prompty.PromptyFromYAML(yamlData) + if err != nil { + t.Fatalf("Failed to load Prompty from YAML helper: %v", err) + } + if instance.Name != "basic-prompt" { + t.Errorf(`Expected Name to be "basic-prompt", got %v`, instance.Name) + } + if instance.DisplayName == nil || *instance.DisplayName != "Basic Prompt" { + t.Errorf(`Expected DisplayName to be "Basic Prompt", got %v`, instance.DisplayName) + } + if instance.Description == nil || *instance.Description != "A basic prompt that uses the GPT-3 chat API to answer questions" { + t.Errorf(`Expected Description to be "A basic prompt that uses the GPT-3 chat API to answer questions", got %v`, instance.Description) + } + if instance.Instructions == nil || *instance.Instructions != "system:\nYou are an AI assistant who helps people find information.\nAs the assistant, you answer questions briefly, succinctly,\nand in a personable manner using markdown and even add some \npersonal flair with appropriate emojis.\n\n# Customer\nYou are helping {{firstName}} {{lastName}} to find answers to \ntheir questions. Use their name to address them in your responses.\nuser:\n{{question}}" { + t.Errorf(`Expected Instructions to be "system:\nYou are an AI assistant who helps people find information.\nAs the assistant, you answer questions briefly, succinctly,\nand in a personable manner using markdown and even add some \npersonal flair with appropriate emojis.\n\n# Customer\nYou are helping {{firstName}} {{lastName}} to find answers to \ntheir questions. Use their name to address them in your responses.\nuser:\n{{question}}", got %v`, instance.Instructions) + } + if instance.Metadata == nil { + t.Fatalf("Expected Metadata to be populated") + } + if instance.Model.Id != "gpt-35-turbo" { + t.Errorf(`Expected Model.Id to be "gpt-35-turbo", got %v`, instance.Model.Id) + } + if instance.Template.Format.Kind != "mustache" { + t.Errorf(`Expected Template.Format.Kind to be "mustache", got %v`, instance.Template.Format.Kind) + } + if instance.Template.Parser.Kind != "prompty" { + t.Errorf(`Expected Template.Parser.Kind to be "prompty", got %v`, instance.Template.Parser.Kind) + } +} + +// TestPromptyRoundtrip1 tests load -> save -> load produces equivalent data +func TestPromptyRoundtrip1(t *testing.T) { + jsonData := ` +{ + "name": "basic-prompt", + "displayName": "Basic Prompt", + "description": "A basic prompt that uses the GPT-3 chat API to answer questions", + "metadata": { + "authors": [ + "sethjuarez", + "jietong" + ], + "tags": [ + "example", + "prompt" + ] + }, + "inputs": { + "firstName": { + "kind": "string", + "default": "Jane" + }, + "lastName": { + "kind": "string", + "default": "Doe" + }, + "question": { + "kind": "string", + "default": "What is the meaning of life?" + } + }, + "outputs": { + "answer": { + "kind": "string", + "description": "The answer to the user's question." + } + }, + "model": { + "id": "gpt-35-turbo", + "connection": { + "kind": "key", + "endpoint": "https://{your-custom-endpoint}.openai.azure.com/", + "apiKey": "{your-api-key}" + } + }, + "tools": { + "getCurrentWeather": { + "kind": "function", + "description": "Get the current weather in a given location", + "parameters": { + "location": { + "kind": "string", + "description": "The city and state, e.g. San Francisco, CA" + }, + "unit": { + "kind": "string", + "description": "The unit of temperature, e.g. Celsius or Fahrenheit" + } + } + } + }, + "template": { + "format": "mustache", + "parser": "prompty" + }, + "instructions": "system:\nYou are an AI assistant who helps people find information.\nAs the assistant, you answer questions briefly, succinctly,\nand in a personable manner using markdown and even add some \npersonal flair with appropriate emojis.\n\n# Customer\nYou are helping {{firstName}} {{lastName}} to find answers to \ntheir questions. Use their name to address them in your responses.\nuser:\n{{question}}" +} +` + var data map[string]interface{} if err := json.Unmarshal([]byte(jsonData), &data); err != nil { t.Fatalf("Failed to parse JSON: %v", err) } @@ -759,6 +1348,18 @@ func TestPromptyRoundtrip1(t *testing.T) { if reloaded.Instructions == nil || *reloaded.Instructions != "system:\nYou are an AI assistant who helps people find information.\nAs the assistant, you answer questions briefly, succinctly,\nand in a personable manner using markdown and even add some \npersonal flair with appropriate emojis.\n\n# Customer\nYou are helping {{firstName}} {{lastName}} to find answers to \ntheir questions. Use their name to address them in your responses.\nuser:\n{{question}}" { t.Errorf(`Expected Instructions to be "system:\nYou are an AI assistant who helps people find information.\nAs the assistant, you answer questions briefly, succinctly,\nand in a personable manner using markdown and even add some \npersonal flair with appropriate emojis.\n\n# Customer\nYou are helping {{firstName}} {{lastName}} to find answers to \ntheir questions. Use their name to address them in your responses.\nuser:\n{{question}}", got %v`, reloaded.Instructions) } + if reloaded.Metadata == nil { + t.Fatalf("Expected Metadata to be populated") + } + if reloaded.Model.Id != "gpt-35-turbo" { + t.Errorf(`Expected Model.Id to be "gpt-35-turbo", got %v`, reloaded.Model.Id) + } + if reloaded.Template.Format.Kind != "mustache" { + t.Errorf(`Expected Template.Format.Kind to be "mustache", got %v`, reloaded.Template.Format.Kind) + } + if reloaded.Template.Parser.Kind != "prompty" { + t.Errorf(`Expected Template.Parser.Kind to be "prompty", got %v`, reloaded.Template.Parser.Kind) + } } // TestPromptyToJSON1 tests that ToJSON produces valid JSON @@ -848,6 +1449,35 @@ func TestPromptyToJSON1(t *testing.T) { if err := json.Unmarshal([]byte(jsonOutput), &parsed); err != nil { t.Fatalf("Failed to parse generated JSON: %v", err) } + + reloaded, err := prompty.LoadPrompty(parsed, ctx) + if err != nil { + t.Fatalf("Failed to reload generated JSON: %v", err) + } + if reloaded.Name != "basic-prompt" { + t.Errorf(`Expected Name to be "basic-prompt", got %v`, reloaded.Name) + } + if reloaded.DisplayName == nil || *reloaded.DisplayName != "Basic Prompt" { + t.Errorf(`Expected DisplayName to be "Basic Prompt", got %v`, reloaded.DisplayName) + } + if reloaded.Description == nil || *reloaded.Description != "A basic prompt that uses the GPT-3 chat API to answer questions" { + t.Errorf(`Expected Description to be "A basic prompt that uses the GPT-3 chat API to answer questions", got %v`, reloaded.Description) + } + if reloaded.Instructions == nil || *reloaded.Instructions != "system:\nYou are an AI assistant who helps people find information.\nAs the assistant, you answer questions briefly, succinctly,\nand in a personable manner using markdown and even add some \npersonal flair with appropriate emojis.\n\n# Customer\nYou are helping {{firstName}} {{lastName}} to find answers to \ntheir questions. Use their name to address them in your responses.\nuser:\n{{question}}" { + t.Errorf(`Expected Instructions to be "system:\nYou are an AI assistant who helps people find information.\nAs the assistant, you answer questions briefly, succinctly,\nand in a personable manner using markdown and even add some \npersonal flair with appropriate emojis.\n\n# Customer\nYou are helping {{firstName}} {{lastName}} to find answers to \ntheir questions. Use their name to address them in your responses.\nuser:\n{{question}}", got %v`, reloaded.Instructions) + } + if reloaded.Metadata == nil { + t.Fatalf("Expected Metadata to be populated") + } + if reloaded.Model.Id != "gpt-35-turbo" { + t.Errorf(`Expected Model.Id to be "gpt-35-turbo", got %v`, reloaded.Model.Id) + } + if reloaded.Template.Format.Kind != "mustache" { + t.Errorf(`Expected Template.Format.Kind to be "mustache", got %v`, reloaded.Template.Format.Kind) + } + if reloaded.Template.Parser.Kind != "prompty" { + t.Errorf(`Expected Template.Parser.Kind to be "prompty", got %v`, reloaded.Template.Parser.Kind) + } } // TestPromptyToYAML1 tests that ToYAML produces valid YAML @@ -937,6 +1567,35 @@ func TestPromptyToYAML1(t *testing.T) { if err := yaml.Unmarshal([]byte(yamlOutput), &parsed); err != nil { t.Fatalf("Failed to parse generated YAML: %v", err) } + + reloaded, err := prompty.LoadPrompty(parsed, ctx) + if err != nil { + t.Fatalf("Failed to reload generated YAML: %v", err) + } + if reloaded.Name != "basic-prompt" { + t.Errorf(`Expected Name to be "basic-prompt", got %v`, reloaded.Name) + } + if reloaded.DisplayName == nil || *reloaded.DisplayName != "Basic Prompt" { + t.Errorf(`Expected DisplayName to be "Basic Prompt", got %v`, reloaded.DisplayName) + } + if reloaded.Description == nil || *reloaded.Description != "A basic prompt that uses the GPT-3 chat API to answer questions" { + t.Errorf(`Expected Description to be "A basic prompt that uses the GPT-3 chat API to answer questions", got %v`, reloaded.Description) + } + if reloaded.Instructions == nil || *reloaded.Instructions != "system:\nYou are an AI assistant who helps people find information.\nAs the assistant, you answer questions briefly, succinctly,\nand in a personable manner using markdown and even add some \npersonal flair with appropriate emojis.\n\n# Customer\nYou are helping {{firstName}} {{lastName}} to find answers to \ntheir questions. Use their name to address them in your responses.\nuser:\n{{question}}" { + t.Errorf(`Expected Instructions to be "system:\nYou are an AI assistant who helps people find information.\nAs the assistant, you answer questions briefly, succinctly,\nand in a personable manner using markdown and even add some \npersonal flair with appropriate emojis.\n\n# Customer\nYou are helping {{firstName}} {{lastName}} to find answers to \ntheir questions. Use their name to address them in your responses.\nuser:\n{{question}}", got %v`, reloaded.Instructions) + } + if reloaded.Metadata == nil { + t.Fatalf("Expected Metadata to be populated") + } + if reloaded.Model.Id != "gpt-35-turbo" { + t.Errorf(`Expected Model.Id to be "gpt-35-turbo", got %v`, reloaded.Model.Id) + } + if reloaded.Template.Format.Kind != "mustache" { + t.Errorf(`Expected Template.Format.Kind to be "mustache", got %v`, reloaded.Template.Format.Kind) + } + if reloaded.Template.Parser.Kind != "prompty" { + t.Errorf(`Expected Template.Parser.Kind to be "prompty", got %v`, reloaded.Template.Parser.Kind) + } } // TestPromptyLoadJSON2 tests loading Prompty from JSON @@ -1031,6 +1690,31 @@ func TestPromptyLoadJSON2(t *testing.T) { if instance.Instructions == nil || *instance.Instructions != "system:\nYou are an AI assistant who helps people find information.\nAs the assistant, you answer questions briefly, succinctly,\nand in a personable manner using markdown and even add some \npersonal flair with appropriate emojis.\n\n# Customer\nYou are helping {{firstName}} {{lastName}} to find answers to \ntheir questions. Use their name to address them in your responses.\nuser:\n{{question}}" { t.Errorf(`Expected Instructions to be "system:\nYou are an AI assistant who helps people find information.\nAs the assistant, you answer questions briefly, succinctly,\nand in a personable manner using markdown and even add some \npersonal flair with appropriate emojis.\n\n# Customer\nYou are helping {{firstName}} {{lastName}} to find answers to \ntheir questions. Use their name to address them in your responses.\nuser:\n{{question}}", got %v`, instance.Instructions) } + if instance.Metadata == nil { + t.Fatalf("Expected Metadata to be populated") + } + if len(instance.Outputs) != 1 { + t.Fatalf("Expected Outputs length to be 1, got %d", len(instance.Outputs)) + } + if instance.Model.Id != "gpt-35-turbo" { + t.Errorf(`Expected Model.Id to be "gpt-35-turbo", got %v`, instance.Model.Id) + } + if len(instance.Tools) != 1 { + t.Fatalf("Expected Tools length to be 1, got %d", len(instance.Tools)) + } + tools0Value, ok := instance.Tools[0].(prompty.FunctionTool) + if !ok { + t.Fatalf("Expected Tools[0] to be prompty.FunctionTool, got %T", instance.Tools[0]) + } + if tools0Value.Kind != "function" { + t.Errorf(`Expected Kind to be "function", got %v`, tools0Value.Kind) + } + if instance.Template.Format.Kind != "mustache" { + t.Errorf(`Expected Template.Format.Kind to be "mustache", got %v`, instance.Template.Format.Kind) + } + if instance.Template.Parser.Kind != "prompty" { + t.Errorf(`Expected Template.Parser.Kind to be "prompty", got %v`, instance.Template.Parser.Kind) + } } // TestPromptyLoadYAML2 tests loading Prompty from YAML @@ -1086,14 +1770,14 @@ instructions: "system: As the assistant, you answer questions briefly, succinctly, - and in a personable manner using markdown and even add some\ + and in a personable manner using markdown and even add some\ personal flair with appropriate emojis. # Customer - You are helping {{firstName}} {{lastName}} to find answers to\ + You are helping {{firstName}} {{lastName}} to find answers to\ their questions. Use their name to address them in your responses. @@ -1124,10 +1808,35 @@ instructions: "system: if instance.Instructions == nil || *instance.Instructions != "system:\nYou are an AI assistant who helps people find information.\nAs the assistant, you answer questions briefly, succinctly,\nand in a personable manner using markdown and even add some \npersonal flair with appropriate emojis.\n\n# Customer\nYou are helping {{firstName}} {{lastName}} to find answers to \ntheir questions. Use their name to address them in your responses.\nuser:\n{{question}}" { t.Errorf(`Expected Instructions to be "system:\nYou are an AI assistant who helps people find information.\nAs the assistant, you answer questions briefly, succinctly,\nand in a personable manner using markdown and even add some \npersonal flair with appropriate emojis.\n\n# Customer\nYou are helping {{firstName}} {{lastName}} to find answers to \ntheir questions. Use their name to address them in your responses.\nuser:\n{{question}}", got %v`, instance.Instructions) } + if instance.Metadata == nil { + t.Fatalf("Expected Metadata to be populated") + } + if len(instance.Outputs) != 1 { + t.Fatalf("Expected Outputs length to be 1, got %d", len(instance.Outputs)) + } + if instance.Model.Id != "gpt-35-turbo" { + t.Errorf(`Expected Model.Id to be "gpt-35-turbo", got %v`, instance.Model.Id) + } + if len(instance.Tools) != 1 { + t.Fatalf("Expected Tools length to be 1, got %d", len(instance.Tools)) + } + tools0Value, ok := instance.Tools[0].(prompty.FunctionTool) + if !ok { + t.Fatalf("Expected Tools[0] to be prompty.FunctionTool, got %T", instance.Tools[0]) + } + if tools0Value.Kind != "function" { + t.Errorf(`Expected Kind to be "function", got %v`, tools0Value.Kind) + } + if instance.Template.Format.Kind != "mustache" { + t.Errorf(`Expected Template.Format.Kind to be "mustache", got %v`, instance.Template.Format.Kind) + } + if instance.Template.Parser.Kind != "prompty" { + t.Errorf(`Expected Template.Parser.Kind to be "prompty", got %v`, instance.Template.Parser.Kind) + } } -// TestPromptyRoundtrip2 tests load -> save -> load produces equivalent data -func TestPromptyRoundtrip2(t *testing.T) { +// TestPromptyFromJSON2 tests loading Prompty through the generated JSON helper +func TestPromptyFromJSON2(t *testing.T) { jsonData := ` { "name": "basic-prompt", @@ -1196,27 +1905,254 @@ func TestPromptyRoundtrip2(t *testing.T) { "instructions": "system:\nYou are an AI assistant who helps people find information.\nAs the assistant, you answer questions briefly, succinctly,\nand in a personable manner using markdown and even add some \npersonal flair with appropriate emojis.\n\n# Customer\nYou are helping {{firstName}} {{lastName}} to find answers to \ntheir questions. Use their name to address them in your responses.\nuser:\n{{question}}" } ` - var data map[string]interface{} - if err := json.Unmarshal([]byte(jsonData), &data); err != nil { - t.Fatalf("Failed to parse JSON: %v", err) - } - loadCtx := prompty.NewLoadContext() - instance, err := prompty.LoadPrompty(data, loadCtx) + instance, err := prompty.PromptyFromJSON(jsonData) if err != nil { - t.Fatalf("Failed to load Prompty: %v", err) + t.Fatalf("Failed to load Prompty from JSON helper: %v", err) } - saveCtx := prompty.NewSaveContext() - savedData := instance.Save(saveCtx) - - reloaded, err := prompty.LoadPrompty(savedData, loadCtx) - if err != nil { - t.Fatalf("Failed to reload Prompty: %v", err) + if instance.Name != "basic-prompt" { + t.Errorf(`Expected Name to be "basic-prompt", got %v`, instance.Name) } - if reloaded.Name != "basic-prompt" { - t.Errorf(`Expected Name to be "basic-prompt", got %v`, reloaded.Name) + if instance.DisplayName == nil || *instance.DisplayName != "Basic Prompt" { + t.Errorf(`Expected DisplayName to be "Basic Prompt", got %v`, instance.DisplayName) } - if reloaded.DisplayName == nil || *reloaded.DisplayName != "Basic Prompt" { + if instance.Description == nil || *instance.Description != "A basic prompt that uses the GPT-3 chat API to answer questions" { + t.Errorf(`Expected Description to be "A basic prompt that uses the GPT-3 chat API to answer questions", got %v`, instance.Description) + } + if instance.Instructions == nil || *instance.Instructions != "system:\nYou are an AI assistant who helps people find information.\nAs the assistant, you answer questions briefly, succinctly,\nand in a personable manner using markdown and even add some \npersonal flair with appropriate emojis.\n\n# Customer\nYou are helping {{firstName}} {{lastName}} to find answers to \ntheir questions. Use their name to address them in your responses.\nuser:\n{{question}}" { + t.Errorf(`Expected Instructions to be "system:\nYou are an AI assistant who helps people find information.\nAs the assistant, you answer questions briefly, succinctly,\nand in a personable manner using markdown and even add some \npersonal flair with appropriate emojis.\n\n# Customer\nYou are helping {{firstName}} {{lastName}} to find answers to \ntheir questions. Use their name to address them in your responses.\nuser:\n{{question}}", got %v`, instance.Instructions) + } + if instance.Metadata == nil { + t.Fatalf("Expected Metadata to be populated") + } + if len(instance.Outputs) != 1 { + t.Fatalf("Expected Outputs length to be 1, got %d", len(instance.Outputs)) + } + if instance.Model.Id != "gpt-35-turbo" { + t.Errorf(`Expected Model.Id to be "gpt-35-turbo", got %v`, instance.Model.Id) + } + if len(instance.Tools) != 1 { + t.Fatalf("Expected Tools length to be 1, got %d", len(instance.Tools)) + } + tools0Value, ok := instance.Tools[0].(prompty.FunctionTool) + if !ok { + t.Fatalf("Expected Tools[0] to be prompty.FunctionTool, got %T", instance.Tools[0]) + } + if tools0Value.Kind != "function" { + t.Errorf(`Expected Kind to be "function", got %v`, tools0Value.Kind) + } + if instance.Template.Format.Kind != "mustache" { + t.Errorf(`Expected Template.Format.Kind to be "mustache", got %v`, instance.Template.Format.Kind) + } + if instance.Template.Parser.Kind != "prompty" { + t.Errorf(`Expected Template.Parser.Kind to be "prompty", got %v`, instance.Template.Parser.Kind) + } +} + +// TestPromptyFromYAML2 tests loading Prompty through the generated YAML helper +func TestPromptyFromYAML2(t *testing.T) { + yamlData := ` +name: basic-prompt +displayName: Basic Prompt +description: A basic prompt that uses the GPT-3 chat API to answer questions +metadata: + authors: + - sethjuarez + - jietong + tags: + - example + - prompt +inputs: + firstName: + kind: string + default: Jane + lastName: + kind: string + default: Doe + question: + kind: string + default: What is the meaning of life? +outputs: + - name: answer + kind: string + description: The answer to the user's question. +model: + id: gpt-35-turbo + connection: + kind: key + endpoint: "https://{your-custom-endpoint}.openai.azure.com/" + apiKey: "{your-api-key}" +tools: + - name: getCurrentWeather + kind: function + description: Get the current weather in a given location + parameters: + location: + kind: string + description: The city and state, e.g. San Francisco, CA + unit: + kind: string + description: The unit of temperature, e.g. Celsius or Fahrenheit +template: + format: mustache + parser: prompty +instructions: "system: + + You are an AI assistant who helps people find information. + + As the assistant, you answer questions briefly, succinctly, + + and in a personable manner using markdown and even add some\ + + personal flair with appropriate emojis. + + + # Customer + + You are helping {{firstName}} {{lastName}} to find answers to\ + + their questions. Use their name to address them in your responses. + + user: + + {{question}}" + +` + + instance, err := prompty.PromptyFromYAML(yamlData) + if err != nil { + t.Fatalf("Failed to load Prompty from YAML helper: %v", err) + } + if instance.Name != "basic-prompt" { + t.Errorf(`Expected Name to be "basic-prompt", got %v`, instance.Name) + } + if instance.DisplayName == nil || *instance.DisplayName != "Basic Prompt" { + t.Errorf(`Expected DisplayName to be "Basic Prompt", got %v`, instance.DisplayName) + } + if instance.Description == nil || *instance.Description != "A basic prompt that uses the GPT-3 chat API to answer questions" { + t.Errorf(`Expected Description to be "A basic prompt that uses the GPT-3 chat API to answer questions", got %v`, instance.Description) + } + if instance.Instructions == nil || *instance.Instructions != "system:\nYou are an AI assistant who helps people find information.\nAs the assistant, you answer questions briefly, succinctly,\nand in a personable manner using markdown and even add some \npersonal flair with appropriate emojis.\n\n# Customer\nYou are helping {{firstName}} {{lastName}} to find answers to \ntheir questions. Use their name to address them in your responses.\nuser:\n{{question}}" { + t.Errorf(`Expected Instructions to be "system:\nYou are an AI assistant who helps people find information.\nAs the assistant, you answer questions briefly, succinctly,\nand in a personable manner using markdown and even add some \npersonal flair with appropriate emojis.\n\n# Customer\nYou are helping {{firstName}} {{lastName}} to find answers to \ntheir questions. Use their name to address them in your responses.\nuser:\n{{question}}", got %v`, instance.Instructions) + } + if instance.Metadata == nil { + t.Fatalf("Expected Metadata to be populated") + } + if len(instance.Outputs) != 1 { + t.Fatalf("Expected Outputs length to be 1, got %d", len(instance.Outputs)) + } + if instance.Model.Id != "gpt-35-turbo" { + t.Errorf(`Expected Model.Id to be "gpt-35-turbo", got %v`, instance.Model.Id) + } + if len(instance.Tools) != 1 { + t.Fatalf("Expected Tools length to be 1, got %d", len(instance.Tools)) + } + tools0Value, ok := instance.Tools[0].(prompty.FunctionTool) + if !ok { + t.Fatalf("Expected Tools[0] to be prompty.FunctionTool, got %T", instance.Tools[0]) + } + if tools0Value.Kind != "function" { + t.Errorf(`Expected Kind to be "function", got %v`, tools0Value.Kind) + } + if instance.Template.Format.Kind != "mustache" { + t.Errorf(`Expected Template.Format.Kind to be "mustache", got %v`, instance.Template.Format.Kind) + } + if instance.Template.Parser.Kind != "prompty" { + t.Errorf(`Expected Template.Parser.Kind to be "prompty", got %v`, instance.Template.Parser.Kind) + } +} + +// TestPromptyRoundtrip2 tests load -> save -> load produces equivalent data +func TestPromptyRoundtrip2(t *testing.T) { + jsonData := ` +{ + "name": "basic-prompt", + "displayName": "Basic Prompt", + "description": "A basic prompt that uses the GPT-3 chat API to answer questions", + "metadata": { + "authors": [ + "sethjuarez", + "jietong" + ], + "tags": [ + "example", + "prompt" + ] + }, + "inputs": { + "firstName": { + "kind": "string", + "default": "Jane" + }, + "lastName": { + "kind": "string", + "default": "Doe" + }, + "question": { + "kind": "string", + "default": "What is the meaning of life?" + } + }, + "outputs": [ + { + "name": "answer", + "kind": "string", + "description": "The answer to the user's question." + } + ], + "model": { + "id": "gpt-35-turbo", + "connection": { + "kind": "key", + "endpoint": "https://{your-custom-endpoint}.openai.azure.com/", + "apiKey": "{your-api-key}" + } + }, + "tools": [ + { + "name": "getCurrentWeather", + "kind": "function", + "description": "Get the current weather in a given location", + "parameters": { + "location": { + "kind": "string", + "description": "The city and state, e.g. San Francisco, CA" + }, + "unit": { + "kind": "string", + "description": "The unit of temperature, e.g. Celsius or Fahrenheit" + } + } + } + ], + "template": { + "format": "mustache", + "parser": "prompty" + }, + "instructions": "system:\nYou are an AI assistant who helps people find information.\nAs the assistant, you answer questions briefly, succinctly,\nand in a personable manner using markdown and even add some \npersonal flair with appropriate emojis.\n\n# Customer\nYou are helping {{firstName}} {{lastName}} to find answers to \ntheir questions. Use their name to address them in your responses.\nuser:\n{{question}}" +} +` + var data map[string]interface{} + if err := json.Unmarshal([]byte(jsonData), &data); err != nil { + t.Fatalf("Failed to parse JSON: %v", err) + } + + loadCtx := prompty.NewLoadContext() + instance, err := prompty.LoadPrompty(data, loadCtx) + if err != nil { + t.Fatalf("Failed to load Prompty: %v", err) + } + saveCtx := prompty.NewSaveContext() + savedData := instance.Save(saveCtx) + + reloaded, err := prompty.LoadPrompty(savedData, loadCtx) + if err != nil { + t.Fatalf("Failed to reload Prompty: %v", err) + } + if reloaded.Name != "basic-prompt" { + t.Errorf(`Expected Name to be "basic-prompt", got %v`, reloaded.Name) + } + if reloaded.DisplayName == nil || *reloaded.DisplayName != "Basic Prompt" { t.Errorf(`Expected DisplayName to be "Basic Prompt", got %v`, reloaded.DisplayName) } if reloaded.Description == nil || *reloaded.Description != "A basic prompt that uses the GPT-3 chat API to answer questions" { @@ -1225,6 +2161,31 @@ func TestPromptyRoundtrip2(t *testing.T) { if reloaded.Instructions == nil || *reloaded.Instructions != "system:\nYou are an AI assistant who helps people find information.\nAs the assistant, you answer questions briefly, succinctly,\nand in a personable manner using markdown and even add some \npersonal flair with appropriate emojis.\n\n# Customer\nYou are helping {{firstName}} {{lastName}} to find answers to \ntheir questions. Use their name to address them in your responses.\nuser:\n{{question}}" { t.Errorf(`Expected Instructions to be "system:\nYou are an AI assistant who helps people find information.\nAs the assistant, you answer questions briefly, succinctly,\nand in a personable manner using markdown and even add some \npersonal flair with appropriate emojis.\n\n# Customer\nYou are helping {{firstName}} {{lastName}} to find answers to \ntheir questions. Use their name to address them in your responses.\nuser:\n{{question}}", got %v`, reloaded.Instructions) } + if reloaded.Metadata == nil { + t.Fatalf("Expected Metadata to be populated") + } + if len(reloaded.Outputs) != 1 { + t.Fatalf("Expected Outputs length to be 1, got %d", len(reloaded.Outputs)) + } + if reloaded.Model.Id != "gpt-35-turbo" { + t.Errorf(`Expected Model.Id to be "gpt-35-turbo", got %v`, reloaded.Model.Id) + } + if len(reloaded.Tools) != 1 { + t.Fatalf("Expected Tools length to be 1, got %d", len(reloaded.Tools)) + } + tools0Value, ok := reloaded.Tools[0].(prompty.FunctionTool) + if !ok { + t.Fatalf("Expected Tools[0] to be prompty.FunctionTool, got %T", reloaded.Tools[0]) + } + if tools0Value.Kind != "function" { + t.Errorf(`Expected Kind to be "function", got %v`, tools0Value.Kind) + } + if reloaded.Template.Format.Kind != "mustache" { + t.Errorf(`Expected Template.Format.Kind to be "mustache", got %v`, reloaded.Template.Format.Kind) + } + if reloaded.Template.Parser.Kind != "prompty" { + t.Errorf(`Expected Template.Parser.Kind to be "prompty", got %v`, reloaded.Template.Parser.Kind) + } } // TestPromptyToJSON2 tests that ToJSON produces valid JSON @@ -1316,6 +2277,48 @@ func TestPromptyToJSON2(t *testing.T) { if err := json.Unmarshal([]byte(jsonOutput), &parsed); err != nil { t.Fatalf("Failed to parse generated JSON: %v", err) } + + reloaded, err := prompty.LoadPrompty(parsed, ctx) + if err != nil { + t.Fatalf("Failed to reload generated JSON: %v", err) + } + if reloaded.Name != "basic-prompt" { + t.Errorf(`Expected Name to be "basic-prompt", got %v`, reloaded.Name) + } + if reloaded.DisplayName == nil || *reloaded.DisplayName != "Basic Prompt" { + t.Errorf(`Expected DisplayName to be "Basic Prompt", got %v`, reloaded.DisplayName) + } + if reloaded.Description == nil || *reloaded.Description != "A basic prompt that uses the GPT-3 chat API to answer questions" { + t.Errorf(`Expected Description to be "A basic prompt that uses the GPT-3 chat API to answer questions", got %v`, reloaded.Description) + } + if reloaded.Instructions == nil || *reloaded.Instructions != "system:\nYou are an AI assistant who helps people find information.\nAs the assistant, you answer questions briefly, succinctly,\nand in a personable manner using markdown and even add some \npersonal flair with appropriate emojis.\n\n# Customer\nYou are helping {{firstName}} {{lastName}} to find answers to \ntheir questions. Use their name to address them in your responses.\nuser:\n{{question}}" { + t.Errorf(`Expected Instructions to be "system:\nYou are an AI assistant who helps people find information.\nAs the assistant, you answer questions briefly, succinctly,\nand in a personable manner using markdown and even add some \npersonal flair with appropriate emojis.\n\n# Customer\nYou are helping {{firstName}} {{lastName}} to find answers to \ntheir questions. Use their name to address them in your responses.\nuser:\n{{question}}", got %v`, reloaded.Instructions) + } + if reloaded.Metadata == nil { + t.Fatalf("Expected Metadata to be populated") + } + if len(reloaded.Outputs) != 1 { + t.Fatalf("Expected Outputs length to be 1, got %d", len(reloaded.Outputs)) + } + if reloaded.Model.Id != "gpt-35-turbo" { + t.Errorf(`Expected Model.Id to be "gpt-35-turbo", got %v`, reloaded.Model.Id) + } + if len(reloaded.Tools) != 1 { + t.Fatalf("Expected Tools length to be 1, got %d", len(reloaded.Tools)) + } + tools0Value, ok := reloaded.Tools[0].(prompty.FunctionTool) + if !ok { + t.Fatalf("Expected Tools[0] to be prompty.FunctionTool, got %T", reloaded.Tools[0]) + } + if tools0Value.Kind != "function" { + t.Errorf(`Expected Kind to be "function", got %v`, tools0Value.Kind) + } + if reloaded.Template.Format.Kind != "mustache" { + t.Errorf(`Expected Template.Format.Kind to be "mustache", got %v`, reloaded.Template.Format.Kind) + } + if reloaded.Template.Parser.Kind != "prompty" { + t.Errorf(`Expected Template.Parser.Kind to be "prompty", got %v`, reloaded.Template.Parser.Kind) + } } // TestPromptyToYAML2 tests that ToYAML produces valid YAML @@ -1407,6 +2410,48 @@ func TestPromptyToYAML2(t *testing.T) { if err := yaml.Unmarshal([]byte(yamlOutput), &parsed); err != nil { t.Fatalf("Failed to parse generated YAML: %v", err) } + + reloaded, err := prompty.LoadPrompty(parsed, ctx) + if err != nil { + t.Fatalf("Failed to reload generated YAML: %v", err) + } + if reloaded.Name != "basic-prompt" { + t.Errorf(`Expected Name to be "basic-prompt", got %v`, reloaded.Name) + } + if reloaded.DisplayName == nil || *reloaded.DisplayName != "Basic Prompt" { + t.Errorf(`Expected DisplayName to be "Basic Prompt", got %v`, reloaded.DisplayName) + } + if reloaded.Description == nil || *reloaded.Description != "A basic prompt that uses the GPT-3 chat API to answer questions" { + t.Errorf(`Expected Description to be "A basic prompt that uses the GPT-3 chat API to answer questions", got %v`, reloaded.Description) + } + if reloaded.Instructions == nil || *reloaded.Instructions != "system:\nYou are an AI assistant who helps people find information.\nAs the assistant, you answer questions briefly, succinctly,\nand in a personable manner using markdown and even add some \npersonal flair with appropriate emojis.\n\n# Customer\nYou are helping {{firstName}} {{lastName}} to find answers to \ntheir questions. Use their name to address them in your responses.\nuser:\n{{question}}" { + t.Errorf(`Expected Instructions to be "system:\nYou are an AI assistant who helps people find information.\nAs the assistant, you answer questions briefly, succinctly,\nand in a personable manner using markdown and even add some \npersonal flair with appropriate emojis.\n\n# Customer\nYou are helping {{firstName}} {{lastName}} to find answers to \ntheir questions. Use their name to address them in your responses.\nuser:\n{{question}}", got %v`, reloaded.Instructions) + } + if reloaded.Metadata == nil { + t.Fatalf("Expected Metadata to be populated") + } + if len(reloaded.Outputs) != 1 { + t.Fatalf("Expected Outputs length to be 1, got %d", len(reloaded.Outputs)) + } + if reloaded.Model.Id != "gpt-35-turbo" { + t.Errorf(`Expected Model.Id to be "gpt-35-turbo", got %v`, reloaded.Model.Id) + } + if len(reloaded.Tools) != 1 { + t.Fatalf("Expected Tools length to be 1, got %d", len(reloaded.Tools)) + } + tools0Value, ok := reloaded.Tools[0].(prompty.FunctionTool) + if !ok { + t.Fatalf("Expected Tools[0] to be prompty.FunctionTool, got %T", reloaded.Tools[0]) + } + if tools0Value.Kind != "function" { + t.Errorf(`Expected Kind to be "function", got %v`, tools0Value.Kind) + } + if reloaded.Template.Format.Kind != "mustache" { + t.Errorf(`Expected Template.Format.Kind to be "mustache", got %v`, reloaded.Template.Format.Kind) + } + if reloaded.Template.Parser.Kind != "prompty" { + t.Errorf(`Expected Template.Parser.Kind to be "prompty", got %v`, reloaded.Template.Parser.Kind) + } } // TestPromptyLoadJSON3 tests loading Prompty from JSON @@ -1500,6 +2545,21 @@ func TestPromptyLoadJSON3(t *testing.T) { if instance.Instructions == nil || *instance.Instructions != "system:\nYou are an AI assistant who helps people find information.\nAs the assistant, you answer questions briefly, succinctly,\nand in a personable manner using markdown and even add some \npersonal flair with appropriate emojis.\n\n# Customer\nYou are helping {{firstName}} {{lastName}} to find answers to \ntheir questions. Use their name to address them in your responses.\nuser:\n{{question}}" { t.Errorf(`Expected Instructions to be "system:\nYou are an AI assistant who helps people find information.\nAs the assistant, you answer questions briefly, succinctly,\nand in a personable manner using markdown and even add some \npersonal flair with appropriate emojis.\n\n# Customer\nYou are helping {{firstName}} {{lastName}} to find answers to \ntheir questions. Use their name to address them in your responses.\nuser:\n{{question}}", got %v`, instance.Instructions) } + if instance.Metadata == nil { + t.Fatalf("Expected Metadata to be populated") + } + if len(instance.Outputs) != 1 { + t.Fatalf("Expected Outputs length to be 1, got %d", len(instance.Outputs)) + } + if instance.Model.Id != "gpt-35-turbo" { + t.Errorf(`Expected Model.Id to be "gpt-35-turbo", got %v`, instance.Model.Id) + } + if instance.Template.Format.Kind != "mustache" { + t.Errorf(`Expected Template.Format.Kind to be "mustache", got %v`, instance.Template.Format.Kind) + } + if instance.Template.Parser.Kind != "prompty" { + t.Errorf(`Expected Template.Parser.Kind to be "prompty", got %v`, instance.Template.Parser.Kind) + } } // TestPromptyLoadYAML3 tests loading Prompty from YAML @@ -1555,14 +2615,14 @@ instructions: "system: As the assistant, you answer questions briefly, succinctly, - and in a personable manner using markdown and even add some\ + and in a personable manner using markdown and even add some\ personal flair with appropriate emojis. # Customer - You are helping {{firstName}} {{lastName}} to find answers to\ + You are helping {{firstName}} {{lastName}} to find answers to\ their questions. Use their name to address them in your responses. @@ -1593,6 +2653,227 @@ instructions: "system: if instance.Instructions == nil || *instance.Instructions != "system:\nYou are an AI assistant who helps people find information.\nAs the assistant, you answer questions briefly, succinctly,\nand in a personable manner using markdown and even add some \npersonal flair with appropriate emojis.\n\n# Customer\nYou are helping {{firstName}} {{lastName}} to find answers to \ntheir questions. Use their name to address them in your responses.\nuser:\n{{question}}" { t.Errorf(`Expected Instructions to be "system:\nYou are an AI assistant who helps people find information.\nAs the assistant, you answer questions briefly, succinctly,\nand in a personable manner using markdown and even add some \npersonal flair with appropriate emojis.\n\n# Customer\nYou are helping {{firstName}} {{lastName}} to find answers to \ntheir questions. Use their name to address them in your responses.\nuser:\n{{question}}", got %v`, instance.Instructions) } + if instance.Metadata == nil { + t.Fatalf("Expected Metadata to be populated") + } + if len(instance.Outputs) != 1 { + t.Fatalf("Expected Outputs length to be 1, got %d", len(instance.Outputs)) + } + if instance.Model.Id != "gpt-35-turbo" { + t.Errorf(`Expected Model.Id to be "gpt-35-turbo", got %v`, instance.Model.Id) + } + if instance.Template.Format.Kind != "mustache" { + t.Errorf(`Expected Template.Format.Kind to be "mustache", got %v`, instance.Template.Format.Kind) + } + if instance.Template.Parser.Kind != "prompty" { + t.Errorf(`Expected Template.Parser.Kind to be "prompty", got %v`, instance.Template.Parser.Kind) + } +} + +// TestPromptyFromJSON3 tests loading Prompty through the generated JSON helper +func TestPromptyFromJSON3(t *testing.T) { + jsonData := ` +{ + "name": "basic-prompt", + "displayName": "Basic Prompt", + "description": "A basic prompt that uses the GPT-3 chat API to answer questions", + "metadata": { + "authors": [ + "sethjuarez", + "jietong" + ], + "tags": [ + "example", + "prompt" + ] + }, + "inputs": { + "firstName": { + "kind": "string", + "default": "Jane" + }, + "lastName": { + "kind": "string", + "default": "Doe" + }, + "question": { + "kind": "string", + "default": "What is the meaning of life?" + } + }, + "outputs": [ + { + "name": "answer", + "kind": "string", + "description": "The answer to the user's question." + } + ], + "model": { + "id": "gpt-35-turbo", + "connection": { + "kind": "key", + "endpoint": "https://{your-custom-endpoint}.openai.azure.com/", + "apiKey": "{your-api-key}" + } + }, + "tools": { + "getCurrentWeather": { + "kind": "function", + "description": "Get the current weather in a given location", + "parameters": { + "location": { + "kind": "string", + "description": "The city and state, e.g. San Francisco, CA" + }, + "unit": { + "kind": "string", + "description": "The unit of temperature, e.g. Celsius or Fahrenheit" + } + } + } + }, + "template": { + "format": "mustache", + "parser": "prompty" + }, + "instructions": "system:\nYou are an AI assistant who helps people find information.\nAs the assistant, you answer questions briefly, succinctly,\nand in a personable manner using markdown and even add some \npersonal flair with appropriate emojis.\n\n# Customer\nYou are helping {{firstName}} {{lastName}} to find answers to \ntheir questions. Use their name to address them in your responses.\nuser:\n{{question}}" +} +` + + instance, err := prompty.PromptyFromJSON(jsonData) + if err != nil { + t.Fatalf("Failed to load Prompty from JSON helper: %v", err) + } + if instance.Name != "basic-prompt" { + t.Errorf(`Expected Name to be "basic-prompt", got %v`, instance.Name) + } + if instance.DisplayName == nil || *instance.DisplayName != "Basic Prompt" { + t.Errorf(`Expected DisplayName to be "Basic Prompt", got %v`, instance.DisplayName) + } + if instance.Description == nil || *instance.Description != "A basic prompt that uses the GPT-3 chat API to answer questions" { + t.Errorf(`Expected Description to be "A basic prompt that uses the GPT-3 chat API to answer questions", got %v`, instance.Description) + } + if instance.Instructions == nil || *instance.Instructions != "system:\nYou are an AI assistant who helps people find information.\nAs the assistant, you answer questions briefly, succinctly,\nand in a personable manner using markdown and even add some \npersonal flair with appropriate emojis.\n\n# Customer\nYou are helping {{firstName}} {{lastName}} to find answers to \ntheir questions. Use their name to address them in your responses.\nuser:\n{{question}}" { + t.Errorf(`Expected Instructions to be "system:\nYou are an AI assistant who helps people find information.\nAs the assistant, you answer questions briefly, succinctly,\nand in a personable manner using markdown and even add some \npersonal flair with appropriate emojis.\n\n# Customer\nYou are helping {{firstName}} {{lastName}} to find answers to \ntheir questions. Use their name to address them in your responses.\nuser:\n{{question}}", got %v`, instance.Instructions) + } + if instance.Metadata == nil { + t.Fatalf("Expected Metadata to be populated") + } + if len(instance.Outputs) != 1 { + t.Fatalf("Expected Outputs length to be 1, got %d", len(instance.Outputs)) + } + if instance.Model.Id != "gpt-35-turbo" { + t.Errorf(`Expected Model.Id to be "gpt-35-turbo", got %v`, instance.Model.Id) + } + if instance.Template.Format.Kind != "mustache" { + t.Errorf(`Expected Template.Format.Kind to be "mustache", got %v`, instance.Template.Format.Kind) + } + if instance.Template.Parser.Kind != "prompty" { + t.Errorf(`Expected Template.Parser.Kind to be "prompty", got %v`, instance.Template.Parser.Kind) + } +} + +// TestPromptyFromYAML3 tests loading Prompty through the generated YAML helper +func TestPromptyFromYAML3(t *testing.T) { + yamlData := ` +name: basic-prompt +displayName: Basic Prompt +description: A basic prompt that uses the GPT-3 chat API to answer questions +metadata: + authors: + - sethjuarez + - jietong + tags: + - example + - prompt +inputs: + firstName: + kind: string + default: Jane + lastName: + kind: string + default: Doe + question: + kind: string + default: What is the meaning of life? +outputs: + - name: answer + kind: string + description: The answer to the user's question. +model: + id: gpt-35-turbo + connection: + kind: key + endpoint: "https://{your-custom-endpoint}.openai.azure.com/" + apiKey: "{your-api-key}" +tools: + getCurrentWeather: + kind: function + description: Get the current weather in a given location + parameters: + location: + kind: string + description: The city and state, e.g. San Francisco, CA + unit: + kind: string + description: The unit of temperature, e.g. Celsius or Fahrenheit +template: + format: mustache + parser: prompty +instructions: "system: + + You are an AI assistant who helps people find information. + + As the assistant, you answer questions briefly, succinctly, + + and in a personable manner using markdown and even add some\ + + personal flair with appropriate emojis. + + + # Customer + + You are helping {{firstName}} {{lastName}} to find answers to\ + + their questions. Use their name to address them in your responses. + + user: + + {{question}}" + +` + + instance, err := prompty.PromptyFromYAML(yamlData) + if err != nil { + t.Fatalf("Failed to load Prompty from YAML helper: %v", err) + } + if instance.Name != "basic-prompt" { + t.Errorf(`Expected Name to be "basic-prompt", got %v`, instance.Name) + } + if instance.DisplayName == nil || *instance.DisplayName != "Basic Prompt" { + t.Errorf(`Expected DisplayName to be "Basic Prompt", got %v`, instance.DisplayName) + } + if instance.Description == nil || *instance.Description != "A basic prompt that uses the GPT-3 chat API to answer questions" { + t.Errorf(`Expected Description to be "A basic prompt that uses the GPT-3 chat API to answer questions", got %v`, instance.Description) + } + if instance.Instructions == nil || *instance.Instructions != "system:\nYou are an AI assistant who helps people find information.\nAs the assistant, you answer questions briefly, succinctly,\nand in a personable manner using markdown and even add some \npersonal flair with appropriate emojis.\n\n# Customer\nYou are helping {{firstName}} {{lastName}} to find answers to \ntheir questions. Use their name to address them in your responses.\nuser:\n{{question}}" { + t.Errorf(`Expected Instructions to be "system:\nYou are an AI assistant who helps people find information.\nAs the assistant, you answer questions briefly, succinctly,\nand in a personable manner using markdown and even add some \npersonal flair with appropriate emojis.\n\n# Customer\nYou are helping {{firstName}} {{lastName}} to find answers to \ntheir questions. Use their name to address them in your responses.\nuser:\n{{question}}", got %v`, instance.Instructions) + } + if instance.Metadata == nil { + t.Fatalf("Expected Metadata to be populated") + } + if len(instance.Outputs) != 1 { + t.Fatalf("Expected Outputs length to be 1, got %d", len(instance.Outputs)) + } + if instance.Model.Id != "gpt-35-turbo" { + t.Errorf(`Expected Model.Id to be "gpt-35-turbo", got %v`, instance.Model.Id) + } + if instance.Template.Format.Kind != "mustache" { + t.Errorf(`Expected Template.Format.Kind to be "mustache", got %v`, instance.Template.Format.Kind) + } + if instance.Template.Parser.Kind != "prompty" { + t.Errorf(`Expected Template.Parser.Kind to be "prompty", got %v`, instance.Template.Parser.Kind) + } } // TestPromptyRoundtrip3 tests load -> save -> load produces equivalent data @@ -1693,6 +2974,21 @@ func TestPromptyRoundtrip3(t *testing.T) { if reloaded.Instructions == nil || *reloaded.Instructions != "system:\nYou are an AI assistant who helps people find information.\nAs the assistant, you answer questions briefly, succinctly,\nand in a personable manner using markdown and even add some \npersonal flair with appropriate emojis.\n\n# Customer\nYou are helping {{firstName}} {{lastName}} to find answers to \ntheir questions. Use their name to address them in your responses.\nuser:\n{{question}}" { t.Errorf(`Expected Instructions to be "system:\nYou are an AI assistant who helps people find information.\nAs the assistant, you answer questions briefly, succinctly,\nand in a personable manner using markdown and even add some \npersonal flair with appropriate emojis.\n\n# Customer\nYou are helping {{firstName}} {{lastName}} to find answers to \ntheir questions. Use their name to address them in your responses.\nuser:\n{{question}}", got %v`, reloaded.Instructions) } + if reloaded.Metadata == nil { + t.Fatalf("Expected Metadata to be populated") + } + if len(reloaded.Outputs) != 1 { + t.Fatalf("Expected Outputs length to be 1, got %d", len(reloaded.Outputs)) + } + if reloaded.Model.Id != "gpt-35-turbo" { + t.Errorf(`Expected Model.Id to be "gpt-35-turbo", got %v`, reloaded.Model.Id) + } + if reloaded.Template.Format.Kind != "mustache" { + t.Errorf(`Expected Template.Format.Kind to be "mustache", got %v`, reloaded.Template.Format.Kind) + } + if reloaded.Template.Parser.Kind != "prompty" { + t.Errorf(`Expected Template.Parser.Kind to be "prompty", got %v`, reloaded.Template.Parser.Kind) + } } // TestPromptyToJSON3 tests that ToJSON produces valid JSON @@ -1783,6 +3079,38 @@ func TestPromptyToJSON3(t *testing.T) { if err := json.Unmarshal([]byte(jsonOutput), &parsed); err != nil { t.Fatalf("Failed to parse generated JSON: %v", err) } + + reloaded, err := prompty.LoadPrompty(parsed, ctx) + if err != nil { + t.Fatalf("Failed to reload generated JSON: %v", err) + } + if reloaded.Name != "basic-prompt" { + t.Errorf(`Expected Name to be "basic-prompt", got %v`, reloaded.Name) + } + if reloaded.DisplayName == nil || *reloaded.DisplayName != "Basic Prompt" { + t.Errorf(`Expected DisplayName to be "Basic Prompt", got %v`, reloaded.DisplayName) + } + if reloaded.Description == nil || *reloaded.Description != "A basic prompt that uses the GPT-3 chat API to answer questions" { + t.Errorf(`Expected Description to be "A basic prompt that uses the GPT-3 chat API to answer questions", got %v`, reloaded.Description) + } + if reloaded.Instructions == nil || *reloaded.Instructions != "system:\nYou are an AI assistant who helps people find information.\nAs the assistant, you answer questions briefly, succinctly,\nand in a personable manner using markdown and even add some \npersonal flair with appropriate emojis.\n\n# Customer\nYou are helping {{firstName}} {{lastName}} to find answers to \ntheir questions. Use their name to address them in your responses.\nuser:\n{{question}}" { + t.Errorf(`Expected Instructions to be "system:\nYou are an AI assistant who helps people find information.\nAs the assistant, you answer questions briefly, succinctly,\nand in a personable manner using markdown and even add some \npersonal flair with appropriate emojis.\n\n# Customer\nYou are helping {{firstName}} {{lastName}} to find answers to \ntheir questions. Use their name to address them in your responses.\nuser:\n{{question}}", got %v`, reloaded.Instructions) + } + if reloaded.Metadata == nil { + t.Fatalf("Expected Metadata to be populated") + } + if len(reloaded.Outputs) != 1 { + t.Fatalf("Expected Outputs length to be 1, got %d", len(reloaded.Outputs)) + } + if reloaded.Model.Id != "gpt-35-turbo" { + t.Errorf(`Expected Model.Id to be "gpt-35-turbo", got %v`, reloaded.Model.Id) + } + if reloaded.Template.Format.Kind != "mustache" { + t.Errorf(`Expected Template.Format.Kind to be "mustache", got %v`, reloaded.Template.Format.Kind) + } + if reloaded.Template.Parser.Kind != "prompty" { + t.Errorf(`Expected Template.Parser.Kind to be "prompty", got %v`, reloaded.Template.Parser.Kind) + } } // TestPromptyToYAML3 tests that ToYAML produces valid YAML @@ -1873,6 +3201,38 @@ func TestPromptyToYAML3(t *testing.T) { if err := yaml.Unmarshal([]byte(yamlOutput), &parsed); err != nil { t.Fatalf("Failed to parse generated YAML: %v", err) } + + reloaded, err := prompty.LoadPrompty(parsed, ctx) + if err != nil { + t.Fatalf("Failed to reload generated YAML: %v", err) + } + if reloaded.Name != "basic-prompt" { + t.Errorf(`Expected Name to be "basic-prompt", got %v`, reloaded.Name) + } + if reloaded.DisplayName == nil || *reloaded.DisplayName != "Basic Prompt" { + t.Errorf(`Expected DisplayName to be "Basic Prompt", got %v`, reloaded.DisplayName) + } + if reloaded.Description == nil || *reloaded.Description != "A basic prompt that uses the GPT-3 chat API to answer questions" { + t.Errorf(`Expected Description to be "A basic prompt that uses the GPT-3 chat API to answer questions", got %v`, reloaded.Description) + } + if reloaded.Instructions == nil || *reloaded.Instructions != "system:\nYou are an AI assistant who helps people find information.\nAs the assistant, you answer questions briefly, succinctly,\nand in a personable manner using markdown and even add some \npersonal flair with appropriate emojis.\n\n# Customer\nYou are helping {{firstName}} {{lastName}} to find answers to \ntheir questions. Use their name to address them in your responses.\nuser:\n{{question}}" { + t.Errorf(`Expected Instructions to be "system:\nYou are an AI assistant who helps people find information.\nAs the assistant, you answer questions briefly, succinctly,\nand in a personable manner using markdown and even add some \npersonal flair with appropriate emojis.\n\n# Customer\nYou are helping {{firstName}} {{lastName}} to find answers to \ntheir questions. Use their name to address them in your responses.\nuser:\n{{question}}", got %v`, reloaded.Instructions) + } + if reloaded.Metadata == nil { + t.Fatalf("Expected Metadata to be populated") + } + if len(reloaded.Outputs) != 1 { + t.Fatalf("Expected Outputs length to be 1, got %d", len(reloaded.Outputs)) + } + if reloaded.Model.Id != "gpt-35-turbo" { + t.Errorf(`Expected Model.Id to be "gpt-35-turbo", got %v`, reloaded.Model.Id) + } + if reloaded.Template.Format.Kind != "mustache" { + t.Errorf(`Expected Template.Format.Kind to be "mustache", got %v`, reloaded.Template.Format.Kind) + } + if reloaded.Template.Parser.Kind != "prompty" { + t.Errorf(`Expected Template.Parser.Kind to be "prompty", got %v`, reloaded.Template.Parser.Kind) + } } // TestPromptyLoadJSON4 tests loading Prompty from JSON @@ -1969,6 +3329,40 @@ func TestPromptyLoadJSON4(t *testing.T) { if instance.Instructions == nil || *instance.Instructions != "system:\nYou are an AI assistant who helps people find information.\nAs the assistant, you answer questions briefly, succinctly,\nand in a personable manner using markdown and even add some \npersonal flair with appropriate emojis.\n\n# Customer\nYou are helping {{firstName}} {{lastName}} to find answers to \ntheir questions. Use their name to address them in your responses.\nuser:\n{{question}}" { t.Errorf(`Expected Instructions to be "system:\nYou are an AI assistant who helps people find information.\nAs the assistant, you answer questions briefly, succinctly,\nand in a personable manner using markdown and even add some \npersonal flair with appropriate emojis.\n\n# Customer\nYou are helping {{firstName}} {{lastName}} to find answers to \ntheir questions. Use their name to address them in your responses.\nuser:\n{{question}}", got %v`, instance.Instructions) } + if instance.Metadata == nil { + t.Fatalf("Expected Metadata to be populated") + } + if len(instance.Inputs) != 3 { + t.Fatalf("Expected Inputs length to be 3, got %d", len(instance.Inputs)) + } + assertPromptyStringField(t, instance.Inputs[0], "Name", "firstName", "Inputs[0].Name") + assertPromptyStringField(t, instance.Inputs[0], "Kind", "string", "Inputs[0].Kind") + assertPromptyStringField(t, instance.Inputs[0], "Default", "Jane", "Inputs[0].Default") + assertPromptyStringField(t, instance.Inputs[1], "Name", "lastName", "Inputs[1].Name") + assertPromptyStringField(t, instance.Inputs[1], "Kind", "string", "Inputs[1].Kind") + assertPromptyStringField(t, instance.Inputs[1], "Default", "Doe", "Inputs[1].Default") + assertPromptyStringField(t, instance.Inputs[2], "Name", "question", "Inputs[2].Name") + assertPromptyStringField(t, instance.Inputs[2], "Kind", "string", "Inputs[2].Kind") + assertPromptyStringField(t, instance.Inputs[2], "Default", "What is the meaning of life?", "Inputs[2].Default") + if instance.Model.Id != "gpt-35-turbo" { + t.Errorf(`Expected Model.Id to be "gpt-35-turbo", got %v`, instance.Model.Id) + } + if len(instance.Tools) != 1 { + t.Fatalf("Expected Tools length to be 1, got %d", len(instance.Tools)) + } + tools0Value, ok := instance.Tools[0].(prompty.FunctionTool) + if !ok { + t.Fatalf("Expected Tools[0] to be prompty.FunctionTool, got %T", instance.Tools[0]) + } + if tools0Value.Kind != "function" { + t.Errorf(`Expected Kind to be "function", got %v`, tools0Value.Kind) + } + if instance.Template.Format.Kind != "mustache" { + t.Errorf(`Expected Template.Format.Kind to be "mustache", got %v`, instance.Template.Format.Kind) + } + if instance.Template.Parser.Kind != "prompty" { + t.Errorf(`Expected Template.Parser.Kind to be "prompty", got %v`, instance.Template.Parser.Kind) + } } // TestPromptyLoadYAML4 tests loading Prompty from YAML @@ -2024,14 +3418,14 @@ instructions: "system: As the assistant, you answer questions briefly, succinctly, - and in a personable manner using markdown and even add some\ + and in a personable manner using markdown and even add some\ personal flair with appropriate emojis. # Customer - You are helping {{firstName}} {{lastName}} to find answers to\ + You are helping {{firstName}} {{lastName}} to find answers to\ their questions. Use their name to address them in your responses. @@ -2062,10 +3456,44 @@ instructions: "system: if instance.Instructions == nil || *instance.Instructions != "system:\nYou are an AI assistant who helps people find information.\nAs the assistant, you answer questions briefly, succinctly,\nand in a personable manner using markdown and even add some \npersonal flair with appropriate emojis.\n\n# Customer\nYou are helping {{firstName}} {{lastName}} to find answers to \ntheir questions. Use their name to address them in your responses.\nuser:\n{{question}}" { t.Errorf(`Expected Instructions to be "system:\nYou are an AI assistant who helps people find information.\nAs the assistant, you answer questions briefly, succinctly,\nand in a personable manner using markdown and even add some \npersonal flair with appropriate emojis.\n\n# Customer\nYou are helping {{firstName}} {{lastName}} to find answers to \ntheir questions. Use their name to address them in your responses.\nuser:\n{{question}}", got %v`, instance.Instructions) } + if instance.Metadata == nil { + t.Fatalf("Expected Metadata to be populated") + } + if len(instance.Inputs) != 3 { + t.Fatalf("Expected Inputs length to be 3, got %d", len(instance.Inputs)) + } + assertPromptyStringField(t, instance.Inputs[0], "Name", "firstName", "Inputs[0].Name") + assertPromptyStringField(t, instance.Inputs[0], "Kind", "string", "Inputs[0].Kind") + assertPromptyStringField(t, instance.Inputs[0], "Default", "Jane", "Inputs[0].Default") + assertPromptyStringField(t, instance.Inputs[1], "Name", "lastName", "Inputs[1].Name") + assertPromptyStringField(t, instance.Inputs[1], "Kind", "string", "Inputs[1].Kind") + assertPromptyStringField(t, instance.Inputs[1], "Default", "Doe", "Inputs[1].Default") + assertPromptyStringField(t, instance.Inputs[2], "Name", "question", "Inputs[2].Name") + assertPromptyStringField(t, instance.Inputs[2], "Kind", "string", "Inputs[2].Kind") + assertPromptyStringField(t, instance.Inputs[2], "Default", "What is the meaning of life?", "Inputs[2].Default") + if instance.Model.Id != "gpt-35-turbo" { + t.Errorf(`Expected Model.Id to be "gpt-35-turbo", got %v`, instance.Model.Id) + } + if len(instance.Tools) != 1 { + t.Fatalf("Expected Tools length to be 1, got %d", len(instance.Tools)) + } + tools0Value, ok := instance.Tools[0].(prompty.FunctionTool) + if !ok { + t.Fatalf("Expected Tools[0] to be prompty.FunctionTool, got %T", instance.Tools[0]) + } + if tools0Value.Kind != "function" { + t.Errorf(`Expected Kind to be "function", got %v`, tools0Value.Kind) + } + if instance.Template.Format.Kind != "mustache" { + t.Errorf(`Expected Template.Format.Kind to be "mustache", got %v`, instance.Template.Format.Kind) + } + if instance.Template.Parser.Kind != "prompty" { + t.Errorf(`Expected Template.Parser.Kind to be "prompty", got %v`, instance.Template.Parser.Kind) + } } -// TestPromptyRoundtrip4 tests load -> save -> load produces equivalent data -func TestPromptyRoundtrip4(t *testing.T) { +// TestPromptyFromJSON4 tests loading Prompty through the generated JSON helper +func TestPromptyFromJSON4(t *testing.T) { jsonData := ` { "name": "basic-prompt", @@ -2136,21 +3564,268 @@ func TestPromptyRoundtrip4(t *testing.T) { "instructions": "system:\nYou are an AI assistant who helps people find information.\nAs the assistant, you answer questions briefly, succinctly,\nand in a personable manner using markdown and even add some \npersonal flair with appropriate emojis.\n\n# Customer\nYou are helping {{firstName}} {{lastName}} to find answers to \ntheir questions. Use their name to address them in your responses.\nuser:\n{{question}}" } ` - var data map[string]interface{} - if err := json.Unmarshal([]byte(jsonData), &data); err != nil { - t.Fatalf("Failed to parse JSON: %v", err) - } - loadCtx := prompty.NewLoadContext() - instance, err := prompty.LoadPrompty(data, loadCtx) + instance, err := prompty.PromptyFromJSON(jsonData) if err != nil { - t.Fatalf("Failed to load Prompty: %v", err) + t.Fatalf("Failed to load Prompty from JSON helper: %v", err) } - saveCtx := prompty.NewSaveContext() - savedData := instance.Save(saveCtx) - - reloaded, err := prompty.LoadPrompty(savedData, loadCtx) - if err != nil { + if instance.Name != "basic-prompt" { + t.Errorf(`Expected Name to be "basic-prompt", got %v`, instance.Name) + } + if instance.DisplayName == nil || *instance.DisplayName != "Basic Prompt" { + t.Errorf(`Expected DisplayName to be "Basic Prompt", got %v`, instance.DisplayName) + } + if instance.Description == nil || *instance.Description != "A basic prompt that uses the GPT-3 chat API to answer questions" { + t.Errorf(`Expected Description to be "A basic prompt that uses the GPT-3 chat API to answer questions", got %v`, instance.Description) + } + if instance.Instructions == nil || *instance.Instructions != "system:\nYou are an AI assistant who helps people find information.\nAs the assistant, you answer questions briefly, succinctly,\nand in a personable manner using markdown and even add some \npersonal flair with appropriate emojis.\n\n# Customer\nYou are helping {{firstName}} {{lastName}} to find answers to \ntheir questions. Use their name to address them in your responses.\nuser:\n{{question}}" { + t.Errorf(`Expected Instructions to be "system:\nYou are an AI assistant who helps people find information.\nAs the assistant, you answer questions briefly, succinctly,\nand in a personable manner using markdown and even add some \npersonal flair with appropriate emojis.\n\n# Customer\nYou are helping {{firstName}} {{lastName}} to find answers to \ntheir questions. Use their name to address them in your responses.\nuser:\n{{question}}", got %v`, instance.Instructions) + } + if instance.Metadata == nil { + t.Fatalf("Expected Metadata to be populated") + } + if len(instance.Inputs) != 3 { + t.Fatalf("Expected Inputs length to be 3, got %d", len(instance.Inputs)) + } + assertPromptyStringField(t, instance.Inputs[0], "Name", "firstName", "Inputs[0].Name") + assertPromptyStringField(t, instance.Inputs[0], "Kind", "string", "Inputs[0].Kind") + assertPromptyStringField(t, instance.Inputs[0], "Default", "Jane", "Inputs[0].Default") + assertPromptyStringField(t, instance.Inputs[1], "Name", "lastName", "Inputs[1].Name") + assertPromptyStringField(t, instance.Inputs[1], "Kind", "string", "Inputs[1].Kind") + assertPromptyStringField(t, instance.Inputs[1], "Default", "Doe", "Inputs[1].Default") + assertPromptyStringField(t, instance.Inputs[2], "Name", "question", "Inputs[2].Name") + assertPromptyStringField(t, instance.Inputs[2], "Kind", "string", "Inputs[2].Kind") + assertPromptyStringField(t, instance.Inputs[2], "Default", "What is the meaning of life?", "Inputs[2].Default") + if instance.Model.Id != "gpt-35-turbo" { + t.Errorf(`Expected Model.Id to be "gpt-35-turbo", got %v`, instance.Model.Id) + } + if len(instance.Tools) != 1 { + t.Fatalf("Expected Tools length to be 1, got %d", len(instance.Tools)) + } + tools0Value, ok := instance.Tools[0].(prompty.FunctionTool) + if !ok { + t.Fatalf("Expected Tools[0] to be prompty.FunctionTool, got %T", instance.Tools[0]) + } + if tools0Value.Kind != "function" { + t.Errorf(`Expected Kind to be "function", got %v`, tools0Value.Kind) + } + if instance.Template.Format.Kind != "mustache" { + t.Errorf(`Expected Template.Format.Kind to be "mustache", got %v`, instance.Template.Format.Kind) + } + if instance.Template.Parser.Kind != "prompty" { + t.Errorf(`Expected Template.Parser.Kind to be "prompty", got %v`, instance.Template.Parser.Kind) + } +} + +// TestPromptyFromYAML4 tests loading Prompty through the generated YAML helper +func TestPromptyFromYAML4(t *testing.T) { + yamlData := ` +name: basic-prompt +displayName: Basic Prompt +description: A basic prompt that uses the GPT-3 chat API to answer questions +metadata: + authors: + - sethjuarez + - jietong + tags: + - example + - prompt +inputs: + - name: firstName + kind: string + default: Jane + - name: lastName + kind: string + default: Doe + - name: question + kind: string + default: What is the meaning of life? +outputs: + answer: + kind: string + description: The answer to the user's question. +model: + id: gpt-35-turbo + connection: + kind: key + endpoint: "https://{your-custom-endpoint}.openai.azure.com/" + apiKey: "{your-api-key}" +tools: + - name: getCurrentWeather + kind: function + description: Get the current weather in a given location + parameters: + location: + kind: string + description: The city and state, e.g. San Francisco, CA + unit: + kind: string + description: The unit of temperature, e.g. Celsius or Fahrenheit +template: + format: mustache + parser: prompty +instructions: "system: + + You are an AI assistant who helps people find information. + + As the assistant, you answer questions briefly, succinctly, + + and in a personable manner using markdown and even add some\ + + personal flair with appropriate emojis. + + + # Customer + + You are helping {{firstName}} {{lastName}} to find answers to\ + + their questions. Use their name to address them in your responses. + + user: + + {{question}}" + +` + + instance, err := prompty.PromptyFromYAML(yamlData) + if err != nil { + t.Fatalf("Failed to load Prompty from YAML helper: %v", err) + } + if instance.Name != "basic-prompt" { + t.Errorf(`Expected Name to be "basic-prompt", got %v`, instance.Name) + } + if instance.DisplayName == nil || *instance.DisplayName != "Basic Prompt" { + t.Errorf(`Expected DisplayName to be "Basic Prompt", got %v`, instance.DisplayName) + } + if instance.Description == nil || *instance.Description != "A basic prompt that uses the GPT-3 chat API to answer questions" { + t.Errorf(`Expected Description to be "A basic prompt that uses the GPT-3 chat API to answer questions", got %v`, instance.Description) + } + if instance.Instructions == nil || *instance.Instructions != "system:\nYou are an AI assistant who helps people find information.\nAs the assistant, you answer questions briefly, succinctly,\nand in a personable manner using markdown and even add some \npersonal flair with appropriate emojis.\n\n# Customer\nYou are helping {{firstName}} {{lastName}} to find answers to \ntheir questions. Use their name to address them in your responses.\nuser:\n{{question}}" { + t.Errorf(`Expected Instructions to be "system:\nYou are an AI assistant who helps people find information.\nAs the assistant, you answer questions briefly, succinctly,\nand in a personable manner using markdown and even add some \npersonal flair with appropriate emojis.\n\n# Customer\nYou are helping {{firstName}} {{lastName}} to find answers to \ntheir questions. Use their name to address them in your responses.\nuser:\n{{question}}", got %v`, instance.Instructions) + } + if instance.Metadata == nil { + t.Fatalf("Expected Metadata to be populated") + } + if len(instance.Inputs) != 3 { + t.Fatalf("Expected Inputs length to be 3, got %d", len(instance.Inputs)) + } + assertPromptyStringField(t, instance.Inputs[0], "Name", "firstName", "Inputs[0].Name") + assertPromptyStringField(t, instance.Inputs[0], "Kind", "string", "Inputs[0].Kind") + assertPromptyStringField(t, instance.Inputs[0], "Default", "Jane", "Inputs[0].Default") + assertPromptyStringField(t, instance.Inputs[1], "Name", "lastName", "Inputs[1].Name") + assertPromptyStringField(t, instance.Inputs[1], "Kind", "string", "Inputs[1].Kind") + assertPromptyStringField(t, instance.Inputs[1], "Default", "Doe", "Inputs[1].Default") + assertPromptyStringField(t, instance.Inputs[2], "Name", "question", "Inputs[2].Name") + assertPromptyStringField(t, instance.Inputs[2], "Kind", "string", "Inputs[2].Kind") + assertPromptyStringField(t, instance.Inputs[2], "Default", "What is the meaning of life?", "Inputs[2].Default") + if instance.Model.Id != "gpt-35-turbo" { + t.Errorf(`Expected Model.Id to be "gpt-35-turbo", got %v`, instance.Model.Id) + } + if len(instance.Tools) != 1 { + t.Fatalf("Expected Tools length to be 1, got %d", len(instance.Tools)) + } + tools0Value, ok := instance.Tools[0].(prompty.FunctionTool) + if !ok { + t.Fatalf("Expected Tools[0] to be prompty.FunctionTool, got %T", instance.Tools[0]) + } + if tools0Value.Kind != "function" { + t.Errorf(`Expected Kind to be "function", got %v`, tools0Value.Kind) + } + if instance.Template.Format.Kind != "mustache" { + t.Errorf(`Expected Template.Format.Kind to be "mustache", got %v`, instance.Template.Format.Kind) + } + if instance.Template.Parser.Kind != "prompty" { + t.Errorf(`Expected Template.Parser.Kind to be "prompty", got %v`, instance.Template.Parser.Kind) + } +} + +// TestPromptyRoundtrip4 tests load -> save -> load produces equivalent data +func TestPromptyRoundtrip4(t *testing.T) { + jsonData := ` +{ + "name": "basic-prompt", + "displayName": "Basic Prompt", + "description": "A basic prompt that uses the GPT-3 chat API to answer questions", + "metadata": { + "authors": [ + "sethjuarez", + "jietong" + ], + "tags": [ + "example", + "prompt" + ] + }, + "inputs": [ + { + "name": "firstName", + "kind": "string", + "default": "Jane" + }, + { + "name": "lastName", + "kind": "string", + "default": "Doe" + }, + { + "name": "question", + "kind": "string", + "default": "What is the meaning of life?" + } + ], + "outputs": { + "answer": { + "kind": "string", + "description": "The answer to the user's question." + } + }, + "model": { + "id": "gpt-35-turbo", + "connection": { + "kind": "key", + "endpoint": "https://{your-custom-endpoint}.openai.azure.com/", + "apiKey": "{your-api-key}" + } + }, + "tools": [ + { + "name": "getCurrentWeather", + "kind": "function", + "description": "Get the current weather in a given location", + "parameters": { + "location": { + "kind": "string", + "description": "The city and state, e.g. San Francisco, CA" + }, + "unit": { + "kind": "string", + "description": "The unit of temperature, e.g. Celsius or Fahrenheit" + } + } + } + ], + "template": { + "format": "mustache", + "parser": "prompty" + }, + "instructions": "system:\nYou are an AI assistant who helps people find information.\nAs the assistant, you answer questions briefly, succinctly,\nand in a personable manner using markdown and even add some \npersonal flair with appropriate emojis.\n\n# Customer\nYou are helping {{firstName}} {{lastName}} to find answers to \ntheir questions. Use their name to address them in your responses.\nuser:\n{{question}}" +} +` + var data map[string]interface{} + if err := json.Unmarshal([]byte(jsonData), &data); err != nil { + t.Fatalf("Failed to parse JSON: %v", err) + } + + loadCtx := prompty.NewLoadContext() + instance, err := prompty.LoadPrompty(data, loadCtx) + if err != nil { + t.Fatalf("Failed to load Prompty: %v", err) + } + saveCtx := prompty.NewSaveContext() + savedData := instance.Save(saveCtx) + + reloaded, err := prompty.LoadPrompty(savedData, loadCtx) + if err != nil { t.Fatalf("Failed to reload Prompty: %v", err) } if reloaded.Name != "basic-prompt" { @@ -2165,6 +3840,40 @@ func TestPromptyRoundtrip4(t *testing.T) { if reloaded.Instructions == nil || *reloaded.Instructions != "system:\nYou are an AI assistant who helps people find information.\nAs the assistant, you answer questions briefly, succinctly,\nand in a personable manner using markdown and even add some \npersonal flair with appropriate emojis.\n\n# Customer\nYou are helping {{firstName}} {{lastName}} to find answers to \ntheir questions. Use their name to address them in your responses.\nuser:\n{{question}}" { t.Errorf(`Expected Instructions to be "system:\nYou are an AI assistant who helps people find information.\nAs the assistant, you answer questions briefly, succinctly,\nand in a personable manner using markdown and even add some \npersonal flair with appropriate emojis.\n\n# Customer\nYou are helping {{firstName}} {{lastName}} to find answers to \ntheir questions. Use their name to address them in your responses.\nuser:\n{{question}}", got %v`, reloaded.Instructions) } + if reloaded.Metadata == nil { + t.Fatalf("Expected Metadata to be populated") + } + if len(reloaded.Inputs) != 3 { + t.Fatalf("Expected Inputs length to be 3, got %d", len(reloaded.Inputs)) + } + assertPromptyStringField(t, reloaded.Inputs[0], "Name", "firstName", "Inputs[0].Name") + assertPromptyStringField(t, reloaded.Inputs[0], "Kind", "string", "Inputs[0].Kind") + assertPromptyStringField(t, reloaded.Inputs[0], "Default", "Jane", "Inputs[0].Default") + assertPromptyStringField(t, reloaded.Inputs[1], "Name", "lastName", "Inputs[1].Name") + assertPromptyStringField(t, reloaded.Inputs[1], "Kind", "string", "Inputs[1].Kind") + assertPromptyStringField(t, reloaded.Inputs[1], "Default", "Doe", "Inputs[1].Default") + assertPromptyStringField(t, reloaded.Inputs[2], "Name", "question", "Inputs[2].Name") + assertPromptyStringField(t, reloaded.Inputs[2], "Kind", "string", "Inputs[2].Kind") + assertPromptyStringField(t, reloaded.Inputs[2], "Default", "What is the meaning of life?", "Inputs[2].Default") + if reloaded.Model.Id != "gpt-35-turbo" { + t.Errorf(`Expected Model.Id to be "gpt-35-turbo", got %v`, reloaded.Model.Id) + } + if len(reloaded.Tools) != 1 { + t.Fatalf("Expected Tools length to be 1, got %d", len(reloaded.Tools)) + } + tools0Value, ok := reloaded.Tools[0].(prompty.FunctionTool) + if !ok { + t.Fatalf("Expected Tools[0] to be prompty.FunctionTool, got %T", reloaded.Tools[0]) + } + if tools0Value.Kind != "function" { + t.Errorf(`Expected Kind to be "function", got %v`, tools0Value.Kind) + } + if reloaded.Template.Format.Kind != "mustache" { + t.Errorf(`Expected Template.Format.Kind to be "mustache", got %v`, reloaded.Template.Format.Kind) + } + if reloaded.Template.Parser.Kind != "prompty" { + t.Errorf(`Expected Template.Parser.Kind to be "prompty", got %v`, reloaded.Template.Parser.Kind) + } } // TestPromptyToJSON4 tests that ToJSON produces valid JSON @@ -2258,6 +3967,57 @@ func TestPromptyToJSON4(t *testing.T) { if err := json.Unmarshal([]byte(jsonOutput), &parsed); err != nil { t.Fatalf("Failed to parse generated JSON: %v", err) } + + reloaded, err := prompty.LoadPrompty(parsed, ctx) + if err != nil { + t.Fatalf("Failed to reload generated JSON: %v", err) + } + if reloaded.Name != "basic-prompt" { + t.Errorf(`Expected Name to be "basic-prompt", got %v`, reloaded.Name) + } + if reloaded.DisplayName == nil || *reloaded.DisplayName != "Basic Prompt" { + t.Errorf(`Expected DisplayName to be "Basic Prompt", got %v`, reloaded.DisplayName) + } + if reloaded.Description == nil || *reloaded.Description != "A basic prompt that uses the GPT-3 chat API to answer questions" { + t.Errorf(`Expected Description to be "A basic prompt that uses the GPT-3 chat API to answer questions", got %v`, reloaded.Description) + } + if reloaded.Instructions == nil || *reloaded.Instructions != "system:\nYou are an AI assistant who helps people find information.\nAs the assistant, you answer questions briefly, succinctly,\nand in a personable manner using markdown and even add some \npersonal flair with appropriate emojis.\n\n# Customer\nYou are helping {{firstName}} {{lastName}} to find answers to \ntheir questions. Use their name to address them in your responses.\nuser:\n{{question}}" { + t.Errorf(`Expected Instructions to be "system:\nYou are an AI assistant who helps people find information.\nAs the assistant, you answer questions briefly, succinctly,\nand in a personable manner using markdown and even add some \npersonal flair with appropriate emojis.\n\n# Customer\nYou are helping {{firstName}} {{lastName}} to find answers to \ntheir questions. Use their name to address them in your responses.\nuser:\n{{question}}", got %v`, reloaded.Instructions) + } + if reloaded.Metadata == nil { + t.Fatalf("Expected Metadata to be populated") + } + if len(reloaded.Inputs) != 3 { + t.Fatalf("Expected Inputs length to be 3, got %d", len(reloaded.Inputs)) + } + assertPromptyStringField(t, reloaded.Inputs[0], "Name", "firstName", "Inputs[0].Name") + assertPromptyStringField(t, reloaded.Inputs[0], "Kind", "string", "Inputs[0].Kind") + assertPromptyStringField(t, reloaded.Inputs[0], "Default", "Jane", "Inputs[0].Default") + assertPromptyStringField(t, reloaded.Inputs[1], "Name", "lastName", "Inputs[1].Name") + assertPromptyStringField(t, reloaded.Inputs[1], "Kind", "string", "Inputs[1].Kind") + assertPromptyStringField(t, reloaded.Inputs[1], "Default", "Doe", "Inputs[1].Default") + assertPromptyStringField(t, reloaded.Inputs[2], "Name", "question", "Inputs[2].Name") + assertPromptyStringField(t, reloaded.Inputs[2], "Kind", "string", "Inputs[2].Kind") + assertPromptyStringField(t, reloaded.Inputs[2], "Default", "What is the meaning of life?", "Inputs[2].Default") + if reloaded.Model.Id != "gpt-35-turbo" { + t.Errorf(`Expected Model.Id to be "gpt-35-turbo", got %v`, reloaded.Model.Id) + } + if len(reloaded.Tools) != 1 { + t.Fatalf("Expected Tools length to be 1, got %d", len(reloaded.Tools)) + } + tools0Value, ok := reloaded.Tools[0].(prompty.FunctionTool) + if !ok { + t.Fatalf("Expected Tools[0] to be prompty.FunctionTool, got %T", reloaded.Tools[0]) + } + if tools0Value.Kind != "function" { + t.Errorf(`Expected Kind to be "function", got %v`, tools0Value.Kind) + } + if reloaded.Template.Format.Kind != "mustache" { + t.Errorf(`Expected Template.Format.Kind to be "mustache", got %v`, reloaded.Template.Format.Kind) + } + if reloaded.Template.Parser.Kind != "prompty" { + t.Errorf(`Expected Template.Parser.Kind to be "prompty", got %v`, reloaded.Template.Parser.Kind) + } } // TestPromptyToYAML4 tests that ToYAML produces valid YAML @@ -2351,6 +4111,57 @@ func TestPromptyToYAML4(t *testing.T) { if err := yaml.Unmarshal([]byte(yamlOutput), &parsed); err != nil { t.Fatalf("Failed to parse generated YAML: %v", err) } + + reloaded, err := prompty.LoadPrompty(parsed, ctx) + if err != nil { + t.Fatalf("Failed to reload generated YAML: %v", err) + } + if reloaded.Name != "basic-prompt" { + t.Errorf(`Expected Name to be "basic-prompt", got %v`, reloaded.Name) + } + if reloaded.DisplayName == nil || *reloaded.DisplayName != "Basic Prompt" { + t.Errorf(`Expected DisplayName to be "Basic Prompt", got %v`, reloaded.DisplayName) + } + if reloaded.Description == nil || *reloaded.Description != "A basic prompt that uses the GPT-3 chat API to answer questions" { + t.Errorf(`Expected Description to be "A basic prompt that uses the GPT-3 chat API to answer questions", got %v`, reloaded.Description) + } + if reloaded.Instructions == nil || *reloaded.Instructions != "system:\nYou are an AI assistant who helps people find information.\nAs the assistant, you answer questions briefly, succinctly,\nand in a personable manner using markdown and even add some \npersonal flair with appropriate emojis.\n\n# Customer\nYou are helping {{firstName}} {{lastName}} to find answers to \ntheir questions. Use their name to address them in your responses.\nuser:\n{{question}}" { + t.Errorf(`Expected Instructions to be "system:\nYou are an AI assistant who helps people find information.\nAs the assistant, you answer questions briefly, succinctly,\nand in a personable manner using markdown and even add some \npersonal flair with appropriate emojis.\n\n# Customer\nYou are helping {{firstName}} {{lastName}} to find answers to \ntheir questions. Use their name to address them in your responses.\nuser:\n{{question}}", got %v`, reloaded.Instructions) + } + if reloaded.Metadata == nil { + t.Fatalf("Expected Metadata to be populated") + } + if len(reloaded.Inputs) != 3 { + t.Fatalf("Expected Inputs length to be 3, got %d", len(reloaded.Inputs)) + } + assertPromptyStringField(t, reloaded.Inputs[0], "Name", "firstName", "Inputs[0].Name") + assertPromptyStringField(t, reloaded.Inputs[0], "Kind", "string", "Inputs[0].Kind") + assertPromptyStringField(t, reloaded.Inputs[0], "Default", "Jane", "Inputs[0].Default") + assertPromptyStringField(t, reloaded.Inputs[1], "Name", "lastName", "Inputs[1].Name") + assertPromptyStringField(t, reloaded.Inputs[1], "Kind", "string", "Inputs[1].Kind") + assertPromptyStringField(t, reloaded.Inputs[1], "Default", "Doe", "Inputs[1].Default") + assertPromptyStringField(t, reloaded.Inputs[2], "Name", "question", "Inputs[2].Name") + assertPromptyStringField(t, reloaded.Inputs[2], "Kind", "string", "Inputs[2].Kind") + assertPromptyStringField(t, reloaded.Inputs[2], "Default", "What is the meaning of life?", "Inputs[2].Default") + if reloaded.Model.Id != "gpt-35-turbo" { + t.Errorf(`Expected Model.Id to be "gpt-35-turbo", got %v`, reloaded.Model.Id) + } + if len(reloaded.Tools) != 1 { + t.Fatalf("Expected Tools length to be 1, got %d", len(reloaded.Tools)) + } + tools0Value, ok := reloaded.Tools[0].(prompty.FunctionTool) + if !ok { + t.Fatalf("Expected Tools[0] to be prompty.FunctionTool, got %T", reloaded.Tools[0]) + } + if tools0Value.Kind != "function" { + t.Errorf(`Expected Kind to be "function", got %v`, tools0Value.Kind) + } + if reloaded.Template.Format.Kind != "mustache" { + t.Errorf(`Expected Template.Format.Kind to be "mustache", got %v`, reloaded.Template.Format.Kind) + } + if reloaded.Template.Parser.Kind != "prompty" { + t.Errorf(`Expected Template.Parser.Kind to be "prompty", got %v`, reloaded.Template.Parser.Kind) + } } // TestPromptyLoadJSON5 tests loading Prompty from JSON @@ -2446,9 +4257,33 @@ func TestPromptyLoadJSON5(t *testing.T) { if instance.Instructions == nil || *instance.Instructions != "system:\nYou are an AI assistant who helps people find information.\nAs the assistant, you answer questions briefly, succinctly,\nand in a personable manner using markdown and even add some \npersonal flair with appropriate emojis.\n\n# Customer\nYou are helping {{firstName}} {{lastName}} to find answers to \ntheir questions. Use their name to address them in your responses.\nuser:\n{{question}}" { t.Errorf(`Expected Instructions to be "system:\nYou are an AI assistant who helps people find information.\nAs the assistant, you answer questions briefly, succinctly,\nand in a personable manner using markdown and even add some \npersonal flair with appropriate emojis.\n\n# Customer\nYou are helping {{firstName}} {{lastName}} to find answers to \ntheir questions. Use their name to address them in your responses.\nuser:\n{{question}}", got %v`, instance.Instructions) } -} - -// TestPromptyLoadYAML5 tests loading Prompty from YAML + if instance.Metadata == nil { + t.Fatalf("Expected Metadata to be populated") + } + if len(instance.Inputs) != 3 { + t.Fatalf("Expected Inputs length to be 3, got %d", len(instance.Inputs)) + } + assertPromptyStringField(t, instance.Inputs[0], "Name", "firstName", "Inputs[0].Name") + assertPromptyStringField(t, instance.Inputs[0], "Kind", "string", "Inputs[0].Kind") + assertPromptyStringField(t, instance.Inputs[0], "Default", "Jane", "Inputs[0].Default") + assertPromptyStringField(t, instance.Inputs[1], "Name", "lastName", "Inputs[1].Name") + assertPromptyStringField(t, instance.Inputs[1], "Kind", "string", "Inputs[1].Kind") + assertPromptyStringField(t, instance.Inputs[1], "Default", "Doe", "Inputs[1].Default") + assertPromptyStringField(t, instance.Inputs[2], "Name", "question", "Inputs[2].Name") + assertPromptyStringField(t, instance.Inputs[2], "Kind", "string", "Inputs[2].Kind") + assertPromptyStringField(t, instance.Inputs[2], "Default", "What is the meaning of life?", "Inputs[2].Default") + if instance.Model.Id != "gpt-35-turbo" { + t.Errorf(`Expected Model.Id to be "gpt-35-turbo", got %v`, instance.Model.Id) + } + if instance.Template.Format.Kind != "mustache" { + t.Errorf(`Expected Template.Format.Kind to be "mustache", got %v`, instance.Template.Format.Kind) + } + if instance.Template.Parser.Kind != "prompty" { + t.Errorf(`Expected Template.Parser.Kind to be "prompty", got %v`, instance.Template.Parser.Kind) + } +} + +// TestPromptyLoadYAML5 tests loading Prompty from YAML func TestPromptyLoadYAML5(t *testing.T) { yamlData := ` name: basic-prompt @@ -2501,14 +4336,14 @@ instructions: "system: As the assistant, you answer questions briefly, succinctly, - and in a personable manner using markdown and even add some\ + and in a personable manner using markdown and even add some\ personal flair with appropriate emojis. # Customer - You are helping {{firstName}} {{lastName}} to find answers to\ + You are helping {{firstName}} {{lastName}} to find answers to\ their questions. Use their name to address them in your responses. @@ -2539,6 +4374,256 @@ instructions: "system: if instance.Instructions == nil || *instance.Instructions != "system:\nYou are an AI assistant who helps people find information.\nAs the assistant, you answer questions briefly, succinctly,\nand in a personable manner using markdown and even add some \npersonal flair with appropriate emojis.\n\n# Customer\nYou are helping {{firstName}} {{lastName}} to find answers to \ntheir questions. Use their name to address them in your responses.\nuser:\n{{question}}" { t.Errorf(`Expected Instructions to be "system:\nYou are an AI assistant who helps people find information.\nAs the assistant, you answer questions briefly, succinctly,\nand in a personable manner using markdown and even add some \npersonal flair with appropriate emojis.\n\n# Customer\nYou are helping {{firstName}} {{lastName}} to find answers to \ntheir questions. Use their name to address them in your responses.\nuser:\n{{question}}", got %v`, instance.Instructions) } + if instance.Metadata == nil { + t.Fatalf("Expected Metadata to be populated") + } + if len(instance.Inputs) != 3 { + t.Fatalf("Expected Inputs length to be 3, got %d", len(instance.Inputs)) + } + assertPromptyStringField(t, instance.Inputs[0], "Name", "firstName", "Inputs[0].Name") + assertPromptyStringField(t, instance.Inputs[0], "Kind", "string", "Inputs[0].Kind") + assertPromptyStringField(t, instance.Inputs[0], "Default", "Jane", "Inputs[0].Default") + assertPromptyStringField(t, instance.Inputs[1], "Name", "lastName", "Inputs[1].Name") + assertPromptyStringField(t, instance.Inputs[1], "Kind", "string", "Inputs[1].Kind") + assertPromptyStringField(t, instance.Inputs[1], "Default", "Doe", "Inputs[1].Default") + assertPromptyStringField(t, instance.Inputs[2], "Name", "question", "Inputs[2].Name") + assertPromptyStringField(t, instance.Inputs[2], "Kind", "string", "Inputs[2].Kind") + assertPromptyStringField(t, instance.Inputs[2], "Default", "What is the meaning of life?", "Inputs[2].Default") + if instance.Model.Id != "gpt-35-turbo" { + t.Errorf(`Expected Model.Id to be "gpt-35-turbo", got %v`, instance.Model.Id) + } + if instance.Template.Format.Kind != "mustache" { + t.Errorf(`Expected Template.Format.Kind to be "mustache", got %v`, instance.Template.Format.Kind) + } + if instance.Template.Parser.Kind != "prompty" { + t.Errorf(`Expected Template.Parser.Kind to be "prompty", got %v`, instance.Template.Parser.Kind) + } +} + +// TestPromptyFromJSON5 tests loading Prompty through the generated JSON helper +func TestPromptyFromJSON5(t *testing.T) { + jsonData := ` +{ + "name": "basic-prompt", + "displayName": "Basic Prompt", + "description": "A basic prompt that uses the GPT-3 chat API to answer questions", + "metadata": { + "authors": [ + "sethjuarez", + "jietong" + ], + "tags": [ + "example", + "prompt" + ] + }, + "inputs": [ + { + "name": "firstName", + "kind": "string", + "default": "Jane" + }, + { + "name": "lastName", + "kind": "string", + "default": "Doe" + }, + { + "name": "question", + "kind": "string", + "default": "What is the meaning of life?" + } + ], + "outputs": { + "answer": { + "kind": "string", + "description": "The answer to the user's question." + } + }, + "model": { + "id": "gpt-35-turbo", + "connection": { + "kind": "key", + "endpoint": "https://{your-custom-endpoint}.openai.azure.com/", + "apiKey": "{your-api-key}" + } + }, + "tools": { + "getCurrentWeather": { + "kind": "function", + "description": "Get the current weather in a given location", + "parameters": { + "location": { + "kind": "string", + "description": "The city and state, e.g. San Francisco, CA" + }, + "unit": { + "kind": "string", + "description": "The unit of temperature, e.g. Celsius or Fahrenheit" + } + } + } + }, + "template": { + "format": "mustache", + "parser": "prompty" + }, + "instructions": "system:\nYou are an AI assistant who helps people find information.\nAs the assistant, you answer questions briefly, succinctly,\nand in a personable manner using markdown and even add some \npersonal flair with appropriate emojis.\n\n# Customer\nYou are helping {{firstName}} {{lastName}} to find answers to \ntheir questions. Use their name to address them in your responses.\nuser:\n{{question}}" +} +` + + instance, err := prompty.PromptyFromJSON(jsonData) + if err != nil { + t.Fatalf("Failed to load Prompty from JSON helper: %v", err) + } + if instance.Name != "basic-prompt" { + t.Errorf(`Expected Name to be "basic-prompt", got %v`, instance.Name) + } + if instance.DisplayName == nil || *instance.DisplayName != "Basic Prompt" { + t.Errorf(`Expected DisplayName to be "Basic Prompt", got %v`, instance.DisplayName) + } + if instance.Description == nil || *instance.Description != "A basic prompt that uses the GPT-3 chat API to answer questions" { + t.Errorf(`Expected Description to be "A basic prompt that uses the GPT-3 chat API to answer questions", got %v`, instance.Description) + } + if instance.Instructions == nil || *instance.Instructions != "system:\nYou are an AI assistant who helps people find information.\nAs the assistant, you answer questions briefly, succinctly,\nand in a personable manner using markdown and even add some \npersonal flair with appropriate emojis.\n\n# Customer\nYou are helping {{firstName}} {{lastName}} to find answers to \ntheir questions. Use their name to address them in your responses.\nuser:\n{{question}}" { + t.Errorf(`Expected Instructions to be "system:\nYou are an AI assistant who helps people find information.\nAs the assistant, you answer questions briefly, succinctly,\nand in a personable manner using markdown and even add some \npersonal flair with appropriate emojis.\n\n# Customer\nYou are helping {{firstName}} {{lastName}} to find answers to \ntheir questions. Use their name to address them in your responses.\nuser:\n{{question}}", got %v`, instance.Instructions) + } + if instance.Metadata == nil { + t.Fatalf("Expected Metadata to be populated") + } + if len(instance.Inputs) != 3 { + t.Fatalf("Expected Inputs length to be 3, got %d", len(instance.Inputs)) + } + assertPromptyStringField(t, instance.Inputs[0], "Name", "firstName", "Inputs[0].Name") + assertPromptyStringField(t, instance.Inputs[0], "Kind", "string", "Inputs[0].Kind") + assertPromptyStringField(t, instance.Inputs[0], "Default", "Jane", "Inputs[0].Default") + assertPromptyStringField(t, instance.Inputs[1], "Name", "lastName", "Inputs[1].Name") + assertPromptyStringField(t, instance.Inputs[1], "Kind", "string", "Inputs[1].Kind") + assertPromptyStringField(t, instance.Inputs[1], "Default", "Doe", "Inputs[1].Default") + assertPromptyStringField(t, instance.Inputs[2], "Name", "question", "Inputs[2].Name") + assertPromptyStringField(t, instance.Inputs[2], "Kind", "string", "Inputs[2].Kind") + assertPromptyStringField(t, instance.Inputs[2], "Default", "What is the meaning of life?", "Inputs[2].Default") + if instance.Model.Id != "gpt-35-turbo" { + t.Errorf(`Expected Model.Id to be "gpt-35-turbo", got %v`, instance.Model.Id) + } + if instance.Template.Format.Kind != "mustache" { + t.Errorf(`Expected Template.Format.Kind to be "mustache", got %v`, instance.Template.Format.Kind) + } + if instance.Template.Parser.Kind != "prompty" { + t.Errorf(`Expected Template.Parser.Kind to be "prompty", got %v`, instance.Template.Parser.Kind) + } +} + +// TestPromptyFromYAML5 tests loading Prompty through the generated YAML helper +func TestPromptyFromYAML5(t *testing.T) { + yamlData := ` +name: basic-prompt +displayName: Basic Prompt +description: A basic prompt that uses the GPT-3 chat API to answer questions +metadata: + authors: + - sethjuarez + - jietong + tags: + - example + - prompt +inputs: + - name: firstName + kind: string + default: Jane + - name: lastName + kind: string + default: Doe + - name: question + kind: string + default: What is the meaning of life? +outputs: + answer: + kind: string + description: The answer to the user's question. +model: + id: gpt-35-turbo + connection: + kind: key + endpoint: "https://{your-custom-endpoint}.openai.azure.com/" + apiKey: "{your-api-key}" +tools: + getCurrentWeather: + kind: function + description: Get the current weather in a given location + parameters: + location: + kind: string + description: The city and state, e.g. San Francisco, CA + unit: + kind: string + description: The unit of temperature, e.g. Celsius or Fahrenheit +template: + format: mustache + parser: prompty +instructions: "system: + + You are an AI assistant who helps people find information. + + As the assistant, you answer questions briefly, succinctly, + + and in a personable manner using markdown and even add some\ + + personal flair with appropriate emojis. + + + # Customer + + You are helping {{firstName}} {{lastName}} to find answers to\ + + their questions. Use their name to address them in your responses. + + user: + + {{question}}" + +` + + instance, err := prompty.PromptyFromYAML(yamlData) + if err != nil { + t.Fatalf("Failed to load Prompty from YAML helper: %v", err) + } + if instance.Name != "basic-prompt" { + t.Errorf(`Expected Name to be "basic-prompt", got %v`, instance.Name) + } + if instance.DisplayName == nil || *instance.DisplayName != "Basic Prompt" { + t.Errorf(`Expected DisplayName to be "Basic Prompt", got %v`, instance.DisplayName) + } + if instance.Description == nil || *instance.Description != "A basic prompt that uses the GPT-3 chat API to answer questions" { + t.Errorf(`Expected Description to be "A basic prompt that uses the GPT-3 chat API to answer questions", got %v`, instance.Description) + } + if instance.Instructions == nil || *instance.Instructions != "system:\nYou are an AI assistant who helps people find information.\nAs the assistant, you answer questions briefly, succinctly,\nand in a personable manner using markdown and even add some \npersonal flair with appropriate emojis.\n\n# Customer\nYou are helping {{firstName}} {{lastName}} to find answers to \ntheir questions. Use their name to address them in your responses.\nuser:\n{{question}}" { + t.Errorf(`Expected Instructions to be "system:\nYou are an AI assistant who helps people find information.\nAs the assistant, you answer questions briefly, succinctly,\nand in a personable manner using markdown and even add some \npersonal flair with appropriate emojis.\n\n# Customer\nYou are helping {{firstName}} {{lastName}} to find answers to \ntheir questions. Use their name to address them in your responses.\nuser:\n{{question}}", got %v`, instance.Instructions) + } + if instance.Metadata == nil { + t.Fatalf("Expected Metadata to be populated") + } + if len(instance.Inputs) != 3 { + t.Fatalf("Expected Inputs length to be 3, got %d", len(instance.Inputs)) + } + assertPromptyStringField(t, instance.Inputs[0], "Name", "firstName", "Inputs[0].Name") + assertPromptyStringField(t, instance.Inputs[0], "Kind", "string", "Inputs[0].Kind") + assertPromptyStringField(t, instance.Inputs[0], "Default", "Jane", "Inputs[0].Default") + assertPromptyStringField(t, instance.Inputs[1], "Name", "lastName", "Inputs[1].Name") + assertPromptyStringField(t, instance.Inputs[1], "Kind", "string", "Inputs[1].Kind") + assertPromptyStringField(t, instance.Inputs[1], "Default", "Doe", "Inputs[1].Default") + assertPromptyStringField(t, instance.Inputs[2], "Name", "question", "Inputs[2].Name") + assertPromptyStringField(t, instance.Inputs[2], "Kind", "string", "Inputs[2].Kind") + assertPromptyStringField(t, instance.Inputs[2], "Default", "What is the meaning of life?", "Inputs[2].Default") + if instance.Model.Id != "gpt-35-turbo" { + t.Errorf(`Expected Model.Id to be "gpt-35-turbo", got %v`, instance.Model.Id) + } + if instance.Template.Format.Kind != "mustache" { + t.Errorf(`Expected Template.Format.Kind to be "mustache", got %v`, instance.Template.Format.Kind) + } + if instance.Template.Parser.Kind != "prompty" { + t.Errorf(`Expected Template.Parser.Kind to be "prompty", got %v`, instance.Template.Parser.Kind) + } } // TestPromptyRoundtrip5 tests load -> save -> load produces equivalent data @@ -2641,6 +4726,30 @@ func TestPromptyRoundtrip5(t *testing.T) { if reloaded.Instructions == nil || *reloaded.Instructions != "system:\nYou are an AI assistant who helps people find information.\nAs the assistant, you answer questions briefly, succinctly,\nand in a personable manner using markdown and even add some \npersonal flair with appropriate emojis.\n\n# Customer\nYou are helping {{firstName}} {{lastName}} to find answers to \ntheir questions. Use their name to address them in your responses.\nuser:\n{{question}}" { t.Errorf(`Expected Instructions to be "system:\nYou are an AI assistant who helps people find information.\nAs the assistant, you answer questions briefly, succinctly,\nand in a personable manner using markdown and even add some \npersonal flair with appropriate emojis.\n\n# Customer\nYou are helping {{firstName}} {{lastName}} to find answers to \ntheir questions. Use their name to address them in your responses.\nuser:\n{{question}}", got %v`, reloaded.Instructions) } + if reloaded.Metadata == nil { + t.Fatalf("Expected Metadata to be populated") + } + if len(reloaded.Inputs) != 3 { + t.Fatalf("Expected Inputs length to be 3, got %d", len(reloaded.Inputs)) + } + assertPromptyStringField(t, reloaded.Inputs[0], "Name", "firstName", "Inputs[0].Name") + assertPromptyStringField(t, reloaded.Inputs[0], "Kind", "string", "Inputs[0].Kind") + assertPromptyStringField(t, reloaded.Inputs[0], "Default", "Jane", "Inputs[0].Default") + assertPromptyStringField(t, reloaded.Inputs[1], "Name", "lastName", "Inputs[1].Name") + assertPromptyStringField(t, reloaded.Inputs[1], "Kind", "string", "Inputs[1].Kind") + assertPromptyStringField(t, reloaded.Inputs[1], "Default", "Doe", "Inputs[1].Default") + assertPromptyStringField(t, reloaded.Inputs[2], "Name", "question", "Inputs[2].Name") + assertPromptyStringField(t, reloaded.Inputs[2], "Kind", "string", "Inputs[2].Kind") + assertPromptyStringField(t, reloaded.Inputs[2], "Default", "What is the meaning of life?", "Inputs[2].Default") + if reloaded.Model.Id != "gpt-35-turbo" { + t.Errorf(`Expected Model.Id to be "gpt-35-turbo", got %v`, reloaded.Model.Id) + } + if reloaded.Template.Format.Kind != "mustache" { + t.Errorf(`Expected Template.Format.Kind to be "mustache", got %v`, reloaded.Template.Format.Kind) + } + if reloaded.Template.Parser.Kind != "prompty" { + t.Errorf(`Expected Template.Parser.Kind to be "prompty", got %v`, reloaded.Template.Parser.Kind) + } } // TestPromptyToJSON5 tests that ToJSON produces valid JSON @@ -2733,6 +4842,47 @@ func TestPromptyToJSON5(t *testing.T) { if err := json.Unmarshal([]byte(jsonOutput), &parsed); err != nil { t.Fatalf("Failed to parse generated JSON: %v", err) } + + reloaded, err := prompty.LoadPrompty(parsed, ctx) + if err != nil { + t.Fatalf("Failed to reload generated JSON: %v", err) + } + if reloaded.Name != "basic-prompt" { + t.Errorf(`Expected Name to be "basic-prompt", got %v`, reloaded.Name) + } + if reloaded.DisplayName == nil || *reloaded.DisplayName != "Basic Prompt" { + t.Errorf(`Expected DisplayName to be "Basic Prompt", got %v`, reloaded.DisplayName) + } + if reloaded.Description == nil || *reloaded.Description != "A basic prompt that uses the GPT-3 chat API to answer questions" { + t.Errorf(`Expected Description to be "A basic prompt that uses the GPT-3 chat API to answer questions", got %v`, reloaded.Description) + } + if reloaded.Instructions == nil || *reloaded.Instructions != "system:\nYou are an AI assistant who helps people find information.\nAs the assistant, you answer questions briefly, succinctly,\nand in a personable manner using markdown and even add some \npersonal flair with appropriate emojis.\n\n# Customer\nYou are helping {{firstName}} {{lastName}} to find answers to \ntheir questions. Use their name to address them in your responses.\nuser:\n{{question}}" { + t.Errorf(`Expected Instructions to be "system:\nYou are an AI assistant who helps people find information.\nAs the assistant, you answer questions briefly, succinctly,\nand in a personable manner using markdown and even add some \npersonal flair with appropriate emojis.\n\n# Customer\nYou are helping {{firstName}} {{lastName}} to find answers to \ntheir questions. Use their name to address them in your responses.\nuser:\n{{question}}", got %v`, reloaded.Instructions) + } + if reloaded.Metadata == nil { + t.Fatalf("Expected Metadata to be populated") + } + if len(reloaded.Inputs) != 3 { + t.Fatalf("Expected Inputs length to be 3, got %d", len(reloaded.Inputs)) + } + assertPromptyStringField(t, reloaded.Inputs[0], "Name", "firstName", "Inputs[0].Name") + assertPromptyStringField(t, reloaded.Inputs[0], "Kind", "string", "Inputs[0].Kind") + assertPromptyStringField(t, reloaded.Inputs[0], "Default", "Jane", "Inputs[0].Default") + assertPromptyStringField(t, reloaded.Inputs[1], "Name", "lastName", "Inputs[1].Name") + assertPromptyStringField(t, reloaded.Inputs[1], "Kind", "string", "Inputs[1].Kind") + assertPromptyStringField(t, reloaded.Inputs[1], "Default", "Doe", "Inputs[1].Default") + assertPromptyStringField(t, reloaded.Inputs[2], "Name", "question", "Inputs[2].Name") + assertPromptyStringField(t, reloaded.Inputs[2], "Kind", "string", "Inputs[2].Kind") + assertPromptyStringField(t, reloaded.Inputs[2], "Default", "What is the meaning of life?", "Inputs[2].Default") + if reloaded.Model.Id != "gpt-35-turbo" { + t.Errorf(`Expected Model.Id to be "gpt-35-turbo", got %v`, reloaded.Model.Id) + } + if reloaded.Template.Format.Kind != "mustache" { + t.Errorf(`Expected Template.Format.Kind to be "mustache", got %v`, reloaded.Template.Format.Kind) + } + if reloaded.Template.Parser.Kind != "prompty" { + t.Errorf(`Expected Template.Parser.Kind to be "prompty", got %v`, reloaded.Template.Parser.Kind) + } } // TestPromptyToYAML5 tests that ToYAML produces valid YAML @@ -2825,6 +4975,47 @@ func TestPromptyToYAML5(t *testing.T) { if err := yaml.Unmarshal([]byte(yamlOutput), &parsed); err != nil { t.Fatalf("Failed to parse generated YAML: %v", err) } + + reloaded, err := prompty.LoadPrompty(parsed, ctx) + if err != nil { + t.Fatalf("Failed to reload generated YAML: %v", err) + } + if reloaded.Name != "basic-prompt" { + t.Errorf(`Expected Name to be "basic-prompt", got %v`, reloaded.Name) + } + if reloaded.DisplayName == nil || *reloaded.DisplayName != "Basic Prompt" { + t.Errorf(`Expected DisplayName to be "Basic Prompt", got %v`, reloaded.DisplayName) + } + if reloaded.Description == nil || *reloaded.Description != "A basic prompt that uses the GPT-3 chat API to answer questions" { + t.Errorf(`Expected Description to be "A basic prompt that uses the GPT-3 chat API to answer questions", got %v`, reloaded.Description) + } + if reloaded.Instructions == nil || *reloaded.Instructions != "system:\nYou are an AI assistant who helps people find information.\nAs the assistant, you answer questions briefly, succinctly,\nand in a personable manner using markdown and even add some \npersonal flair with appropriate emojis.\n\n# Customer\nYou are helping {{firstName}} {{lastName}} to find answers to \ntheir questions. Use their name to address them in your responses.\nuser:\n{{question}}" { + t.Errorf(`Expected Instructions to be "system:\nYou are an AI assistant who helps people find information.\nAs the assistant, you answer questions briefly, succinctly,\nand in a personable manner using markdown and even add some \npersonal flair with appropriate emojis.\n\n# Customer\nYou are helping {{firstName}} {{lastName}} to find answers to \ntheir questions. Use their name to address them in your responses.\nuser:\n{{question}}", got %v`, reloaded.Instructions) + } + if reloaded.Metadata == nil { + t.Fatalf("Expected Metadata to be populated") + } + if len(reloaded.Inputs) != 3 { + t.Fatalf("Expected Inputs length to be 3, got %d", len(reloaded.Inputs)) + } + assertPromptyStringField(t, reloaded.Inputs[0], "Name", "firstName", "Inputs[0].Name") + assertPromptyStringField(t, reloaded.Inputs[0], "Kind", "string", "Inputs[0].Kind") + assertPromptyStringField(t, reloaded.Inputs[0], "Default", "Jane", "Inputs[0].Default") + assertPromptyStringField(t, reloaded.Inputs[1], "Name", "lastName", "Inputs[1].Name") + assertPromptyStringField(t, reloaded.Inputs[1], "Kind", "string", "Inputs[1].Kind") + assertPromptyStringField(t, reloaded.Inputs[1], "Default", "Doe", "Inputs[1].Default") + assertPromptyStringField(t, reloaded.Inputs[2], "Name", "question", "Inputs[2].Name") + assertPromptyStringField(t, reloaded.Inputs[2], "Kind", "string", "Inputs[2].Kind") + assertPromptyStringField(t, reloaded.Inputs[2], "Default", "What is the meaning of life?", "Inputs[2].Default") + if reloaded.Model.Id != "gpt-35-turbo" { + t.Errorf(`Expected Model.Id to be "gpt-35-turbo", got %v`, reloaded.Model.Id) + } + if reloaded.Template.Format.Kind != "mustache" { + t.Errorf(`Expected Template.Format.Kind to be "mustache", got %v`, reloaded.Template.Format.Kind) + } + if reloaded.Template.Parser.Kind != "prompty" { + t.Errorf(`Expected Template.Parser.Kind to be "prompty", got %v`, reloaded.Template.Parser.Kind) + } } // TestPromptyLoadJSON6 tests loading Prompty from JSON @@ -2922,6 +5113,43 @@ func TestPromptyLoadJSON6(t *testing.T) { if instance.Instructions == nil || *instance.Instructions != "system:\nYou are an AI assistant who helps people find information.\nAs the assistant, you answer questions briefly, succinctly,\nand in a personable manner using markdown and even add some \npersonal flair with appropriate emojis.\n\n# Customer\nYou are helping {{firstName}} {{lastName}} to find answers to \ntheir questions. Use their name to address them in your responses.\nuser:\n{{question}}" { t.Errorf(`Expected Instructions to be "system:\nYou are an AI assistant who helps people find information.\nAs the assistant, you answer questions briefly, succinctly,\nand in a personable manner using markdown and even add some \npersonal flair with appropriate emojis.\n\n# Customer\nYou are helping {{firstName}} {{lastName}} to find answers to \ntheir questions. Use their name to address them in your responses.\nuser:\n{{question}}", got %v`, instance.Instructions) } + if instance.Metadata == nil { + t.Fatalf("Expected Metadata to be populated") + } + if len(instance.Inputs) != 3 { + t.Fatalf("Expected Inputs length to be 3, got %d", len(instance.Inputs)) + } + assertPromptyStringField(t, instance.Inputs[0], "Name", "firstName", "Inputs[0].Name") + assertPromptyStringField(t, instance.Inputs[0], "Kind", "string", "Inputs[0].Kind") + assertPromptyStringField(t, instance.Inputs[0], "Default", "Jane", "Inputs[0].Default") + assertPromptyStringField(t, instance.Inputs[1], "Name", "lastName", "Inputs[1].Name") + assertPromptyStringField(t, instance.Inputs[1], "Kind", "string", "Inputs[1].Kind") + assertPromptyStringField(t, instance.Inputs[1], "Default", "Doe", "Inputs[1].Default") + assertPromptyStringField(t, instance.Inputs[2], "Name", "question", "Inputs[2].Name") + assertPromptyStringField(t, instance.Inputs[2], "Kind", "string", "Inputs[2].Kind") + assertPromptyStringField(t, instance.Inputs[2], "Default", "What is the meaning of life?", "Inputs[2].Default") + if len(instance.Outputs) != 1 { + t.Fatalf("Expected Outputs length to be 1, got %d", len(instance.Outputs)) + } + if instance.Model.Id != "gpt-35-turbo" { + t.Errorf(`Expected Model.Id to be "gpt-35-turbo", got %v`, instance.Model.Id) + } + if len(instance.Tools) != 1 { + t.Fatalf("Expected Tools length to be 1, got %d", len(instance.Tools)) + } + tools0Value, ok := instance.Tools[0].(prompty.FunctionTool) + if !ok { + t.Fatalf("Expected Tools[0] to be prompty.FunctionTool, got %T", instance.Tools[0]) + } + if tools0Value.Kind != "function" { + t.Errorf(`Expected Kind to be "function", got %v`, tools0Value.Kind) + } + if instance.Template.Format.Kind != "mustache" { + t.Errorf(`Expected Template.Format.Kind to be "mustache", got %v`, instance.Template.Format.Kind) + } + if instance.Template.Parser.Kind != "prompty" { + t.Errorf(`Expected Template.Parser.Kind to be "prompty", got %v`, instance.Template.Parser.Kind) + } } // TestPromptyLoadYAML6 tests loading Prompty from YAML @@ -2977,14 +5205,14 @@ instructions: "system: As the assistant, you answer questions briefly, succinctly, - and in a personable manner using markdown and even add some\ + and in a personable manner using markdown and even add some\ personal flair with appropriate emojis. # Customer - You are helping {{firstName}} {{lastName}} to find answers to\ + You are helping {{firstName}} {{lastName}} to find answers to\ their questions. Use their name to address them in your responses. @@ -3015,10 +5243,301 @@ instructions: "system: if instance.Instructions == nil || *instance.Instructions != "system:\nYou are an AI assistant who helps people find information.\nAs the assistant, you answer questions briefly, succinctly,\nand in a personable manner using markdown and even add some \npersonal flair with appropriate emojis.\n\n# Customer\nYou are helping {{firstName}} {{lastName}} to find answers to \ntheir questions. Use their name to address them in your responses.\nuser:\n{{question}}" { t.Errorf(`Expected Instructions to be "system:\nYou are an AI assistant who helps people find information.\nAs the assistant, you answer questions briefly, succinctly,\nand in a personable manner using markdown and even add some \npersonal flair with appropriate emojis.\n\n# Customer\nYou are helping {{firstName}} {{lastName}} to find answers to \ntheir questions. Use their name to address them in your responses.\nuser:\n{{question}}", got %v`, instance.Instructions) } + if instance.Metadata == nil { + t.Fatalf("Expected Metadata to be populated") + } + if len(instance.Inputs) != 3 { + t.Fatalf("Expected Inputs length to be 3, got %d", len(instance.Inputs)) + } + assertPromptyStringField(t, instance.Inputs[0], "Name", "firstName", "Inputs[0].Name") + assertPromptyStringField(t, instance.Inputs[0], "Kind", "string", "Inputs[0].Kind") + assertPromptyStringField(t, instance.Inputs[0], "Default", "Jane", "Inputs[0].Default") + assertPromptyStringField(t, instance.Inputs[1], "Name", "lastName", "Inputs[1].Name") + assertPromptyStringField(t, instance.Inputs[1], "Kind", "string", "Inputs[1].Kind") + assertPromptyStringField(t, instance.Inputs[1], "Default", "Doe", "Inputs[1].Default") + assertPromptyStringField(t, instance.Inputs[2], "Name", "question", "Inputs[2].Name") + assertPromptyStringField(t, instance.Inputs[2], "Kind", "string", "Inputs[2].Kind") + assertPromptyStringField(t, instance.Inputs[2], "Default", "What is the meaning of life?", "Inputs[2].Default") + if len(instance.Outputs) != 1 { + t.Fatalf("Expected Outputs length to be 1, got %d", len(instance.Outputs)) + } + if instance.Model.Id != "gpt-35-turbo" { + t.Errorf(`Expected Model.Id to be "gpt-35-turbo", got %v`, instance.Model.Id) + } + if len(instance.Tools) != 1 { + t.Fatalf("Expected Tools length to be 1, got %d", len(instance.Tools)) + } + tools0Value, ok := instance.Tools[0].(prompty.FunctionTool) + if !ok { + t.Fatalf("Expected Tools[0] to be prompty.FunctionTool, got %T", instance.Tools[0]) + } + if tools0Value.Kind != "function" { + t.Errorf(`Expected Kind to be "function", got %v`, tools0Value.Kind) + } + if instance.Template.Format.Kind != "mustache" { + t.Errorf(`Expected Template.Format.Kind to be "mustache", got %v`, instance.Template.Format.Kind) + } + if instance.Template.Parser.Kind != "prompty" { + t.Errorf(`Expected Template.Parser.Kind to be "prompty", got %v`, instance.Template.Parser.Kind) + } } -// TestPromptyRoundtrip6 tests load -> save -> load produces equivalent data -func TestPromptyRoundtrip6(t *testing.T) { +// TestPromptyFromJSON6 tests loading Prompty through the generated JSON helper +func TestPromptyFromJSON6(t *testing.T) { + jsonData := ` +{ + "name": "basic-prompt", + "displayName": "Basic Prompt", + "description": "A basic prompt that uses the GPT-3 chat API to answer questions", + "metadata": { + "authors": [ + "sethjuarez", + "jietong" + ], + "tags": [ + "example", + "prompt" + ] + }, + "inputs": [ + { + "name": "firstName", + "kind": "string", + "default": "Jane" + }, + { + "name": "lastName", + "kind": "string", + "default": "Doe" + }, + { + "name": "question", + "kind": "string", + "default": "What is the meaning of life?" + } + ], + "outputs": [ + { + "name": "answer", + "kind": "string", + "description": "The answer to the user's question." + } + ], + "model": { + "id": "gpt-35-turbo", + "connection": { + "kind": "key", + "endpoint": "https://{your-custom-endpoint}.openai.azure.com/", + "apiKey": "{your-api-key}" + } + }, + "tools": [ + { + "name": "getCurrentWeather", + "kind": "function", + "description": "Get the current weather in a given location", + "parameters": { + "location": { + "kind": "string", + "description": "The city and state, e.g. San Francisco, CA" + }, + "unit": { + "kind": "string", + "description": "The unit of temperature, e.g. Celsius or Fahrenheit" + } + } + } + ], + "template": { + "format": "mustache", + "parser": "prompty" + }, + "instructions": "system:\nYou are an AI assistant who helps people find information.\nAs the assistant, you answer questions briefly, succinctly,\nand in a personable manner using markdown and even add some \npersonal flair with appropriate emojis.\n\n# Customer\nYou are helping {{firstName}} {{lastName}} to find answers to \ntheir questions. Use their name to address them in your responses.\nuser:\n{{question}}" +} +` + + instance, err := prompty.PromptyFromJSON(jsonData) + if err != nil { + t.Fatalf("Failed to load Prompty from JSON helper: %v", err) + } + if instance.Name != "basic-prompt" { + t.Errorf(`Expected Name to be "basic-prompt", got %v`, instance.Name) + } + if instance.DisplayName == nil || *instance.DisplayName != "Basic Prompt" { + t.Errorf(`Expected DisplayName to be "Basic Prompt", got %v`, instance.DisplayName) + } + if instance.Description == nil || *instance.Description != "A basic prompt that uses the GPT-3 chat API to answer questions" { + t.Errorf(`Expected Description to be "A basic prompt that uses the GPT-3 chat API to answer questions", got %v`, instance.Description) + } + if instance.Instructions == nil || *instance.Instructions != "system:\nYou are an AI assistant who helps people find information.\nAs the assistant, you answer questions briefly, succinctly,\nand in a personable manner using markdown and even add some \npersonal flair with appropriate emojis.\n\n# Customer\nYou are helping {{firstName}} {{lastName}} to find answers to \ntheir questions. Use their name to address them in your responses.\nuser:\n{{question}}" { + t.Errorf(`Expected Instructions to be "system:\nYou are an AI assistant who helps people find information.\nAs the assistant, you answer questions briefly, succinctly,\nand in a personable manner using markdown and even add some \npersonal flair with appropriate emojis.\n\n# Customer\nYou are helping {{firstName}} {{lastName}} to find answers to \ntheir questions. Use their name to address them in your responses.\nuser:\n{{question}}", got %v`, instance.Instructions) + } + if instance.Metadata == nil { + t.Fatalf("Expected Metadata to be populated") + } + if len(instance.Inputs) != 3 { + t.Fatalf("Expected Inputs length to be 3, got %d", len(instance.Inputs)) + } + assertPromptyStringField(t, instance.Inputs[0], "Name", "firstName", "Inputs[0].Name") + assertPromptyStringField(t, instance.Inputs[0], "Kind", "string", "Inputs[0].Kind") + assertPromptyStringField(t, instance.Inputs[0], "Default", "Jane", "Inputs[0].Default") + assertPromptyStringField(t, instance.Inputs[1], "Name", "lastName", "Inputs[1].Name") + assertPromptyStringField(t, instance.Inputs[1], "Kind", "string", "Inputs[1].Kind") + assertPromptyStringField(t, instance.Inputs[1], "Default", "Doe", "Inputs[1].Default") + assertPromptyStringField(t, instance.Inputs[2], "Name", "question", "Inputs[2].Name") + assertPromptyStringField(t, instance.Inputs[2], "Kind", "string", "Inputs[2].Kind") + assertPromptyStringField(t, instance.Inputs[2], "Default", "What is the meaning of life?", "Inputs[2].Default") + if len(instance.Outputs) != 1 { + t.Fatalf("Expected Outputs length to be 1, got %d", len(instance.Outputs)) + } + if instance.Model.Id != "gpt-35-turbo" { + t.Errorf(`Expected Model.Id to be "gpt-35-turbo", got %v`, instance.Model.Id) + } + if len(instance.Tools) != 1 { + t.Fatalf("Expected Tools length to be 1, got %d", len(instance.Tools)) + } + tools0Value, ok := instance.Tools[0].(prompty.FunctionTool) + if !ok { + t.Fatalf("Expected Tools[0] to be prompty.FunctionTool, got %T", instance.Tools[0]) + } + if tools0Value.Kind != "function" { + t.Errorf(`Expected Kind to be "function", got %v`, tools0Value.Kind) + } + if instance.Template.Format.Kind != "mustache" { + t.Errorf(`Expected Template.Format.Kind to be "mustache", got %v`, instance.Template.Format.Kind) + } + if instance.Template.Parser.Kind != "prompty" { + t.Errorf(`Expected Template.Parser.Kind to be "prompty", got %v`, instance.Template.Parser.Kind) + } +} + +// TestPromptyFromYAML6 tests loading Prompty through the generated YAML helper +func TestPromptyFromYAML6(t *testing.T) { + yamlData := ` +name: basic-prompt +displayName: Basic Prompt +description: A basic prompt that uses the GPT-3 chat API to answer questions +metadata: + authors: + - sethjuarez + - jietong + tags: + - example + - prompt +inputs: + - name: firstName + kind: string + default: Jane + - name: lastName + kind: string + default: Doe + - name: question + kind: string + default: What is the meaning of life? +outputs: + - name: answer + kind: string + description: The answer to the user's question. +model: + id: gpt-35-turbo + connection: + kind: key + endpoint: "https://{your-custom-endpoint}.openai.azure.com/" + apiKey: "{your-api-key}" +tools: + - name: getCurrentWeather + kind: function + description: Get the current weather in a given location + parameters: + location: + kind: string + description: The city and state, e.g. San Francisco, CA + unit: + kind: string + description: The unit of temperature, e.g. Celsius or Fahrenheit +template: + format: mustache + parser: prompty +instructions: "system: + + You are an AI assistant who helps people find information. + + As the assistant, you answer questions briefly, succinctly, + + and in a personable manner using markdown and even add some\ + + personal flair with appropriate emojis. + + + # Customer + + You are helping {{firstName}} {{lastName}} to find answers to\ + + their questions. Use their name to address them in your responses. + + user: + + {{question}}" + +` + + instance, err := prompty.PromptyFromYAML(yamlData) + if err != nil { + t.Fatalf("Failed to load Prompty from YAML helper: %v", err) + } + if instance.Name != "basic-prompt" { + t.Errorf(`Expected Name to be "basic-prompt", got %v`, instance.Name) + } + if instance.DisplayName == nil || *instance.DisplayName != "Basic Prompt" { + t.Errorf(`Expected DisplayName to be "Basic Prompt", got %v`, instance.DisplayName) + } + if instance.Description == nil || *instance.Description != "A basic prompt that uses the GPT-3 chat API to answer questions" { + t.Errorf(`Expected Description to be "A basic prompt that uses the GPT-3 chat API to answer questions", got %v`, instance.Description) + } + if instance.Instructions == nil || *instance.Instructions != "system:\nYou are an AI assistant who helps people find information.\nAs the assistant, you answer questions briefly, succinctly,\nand in a personable manner using markdown and even add some \npersonal flair with appropriate emojis.\n\n# Customer\nYou are helping {{firstName}} {{lastName}} to find answers to \ntheir questions. Use their name to address them in your responses.\nuser:\n{{question}}" { + t.Errorf(`Expected Instructions to be "system:\nYou are an AI assistant who helps people find information.\nAs the assistant, you answer questions briefly, succinctly,\nand in a personable manner using markdown and even add some \npersonal flair with appropriate emojis.\n\n# Customer\nYou are helping {{firstName}} {{lastName}} to find answers to \ntheir questions. Use their name to address them in your responses.\nuser:\n{{question}}", got %v`, instance.Instructions) + } + if instance.Metadata == nil { + t.Fatalf("Expected Metadata to be populated") + } + if len(instance.Inputs) != 3 { + t.Fatalf("Expected Inputs length to be 3, got %d", len(instance.Inputs)) + } + assertPromptyStringField(t, instance.Inputs[0], "Name", "firstName", "Inputs[0].Name") + assertPromptyStringField(t, instance.Inputs[0], "Kind", "string", "Inputs[0].Kind") + assertPromptyStringField(t, instance.Inputs[0], "Default", "Jane", "Inputs[0].Default") + assertPromptyStringField(t, instance.Inputs[1], "Name", "lastName", "Inputs[1].Name") + assertPromptyStringField(t, instance.Inputs[1], "Kind", "string", "Inputs[1].Kind") + assertPromptyStringField(t, instance.Inputs[1], "Default", "Doe", "Inputs[1].Default") + assertPromptyStringField(t, instance.Inputs[2], "Name", "question", "Inputs[2].Name") + assertPromptyStringField(t, instance.Inputs[2], "Kind", "string", "Inputs[2].Kind") + assertPromptyStringField(t, instance.Inputs[2], "Default", "What is the meaning of life?", "Inputs[2].Default") + if len(instance.Outputs) != 1 { + t.Fatalf("Expected Outputs length to be 1, got %d", len(instance.Outputs)) + } + if instance.Model.Id != "gpt-35-turbo" { + t.Errorf(`Expected Model.Id to be "gpt-35-turbo", got %v`, instance.Model.Id) + } + if len(instance.Tools) != 1 { + t.Fatalf("Expected Tools length to be 1, got %d", len(instance.Tools)) + } + tools0Value, ok := instance.Tools[0].(prompty.FunctionTool) + if !ok { + t.Fatalf("Expected Tools[0] to be prompty.FunctionTool, got %T", instance.Tools[0]) + } + if tools0Value.Kind != "function" { + t.Errorf(`Expected Kind to be "function", got %v`, tools0Value.Kind) + } + if instance.Template.Format.Kind != "mustache" { + t.Errorf(`Expected Template.Format.Kind to be "mustache", got %v`, instance.Template.Format.Kind) + } + if instance.Template.Parser.Kind != "prompty" { + t.Errorf(`Expected Template.Parser.Kind to be "prompty", got %v`, instance.Template.Parser.Kind) + } +} + +// TestPromptyRoundtrip6 tests load -> save -> load produces equivalent data +func TestPromptyRoundtrip6(t *testing.T) { jsonData := ` { "name": "basic-prompt", @@ -3110,19 +5629,204 @@ func TestPromptyRoundtrip6(t *testing.T) { if reloaded.Name != "basic-prompt" { t.Errorf(`Expected Name to be "basic-prompt", got %v`, reloaded.Name) } - if reloaded.DisplayName == nil || *reloaded.DisplayName != "Basic Prompt" { - t.Errorf(`Expected DisplayName to be "Basic Prompt", got %v`, reloaded.DisplayName) + if reloaded.DisplayName == nil || *reloaded.DisplayName != "Basic Prompt" { + t.Errorf(`Expected DisplayName to be "Basic Prompt", got %v`, reloaded.DisplayName) + } + if reloaded.Description == nil || *reloaded.Description != "A basic prompt that uses the GPT-3 chat API to answer questions" { + t.Errorf(`Expected Description to be "A basic prompt that uses the GPT-3 chat API to answer questions", got %v`, reloaded.Description) + } + if reloaded.Instructions == nil || *reloaded.Instructions != "system:\nYou are an AI assistant who helps people find information.\nAs the assistant, you answer questions briefly, succinctly,\nand in a personable manner using markdown and even add some \npersonal flair with appropriate emojis.\n\n# Customer\nYou are helping {{firstName}} {{lastName}} to find answers to \ntheir questions. Use their name to address them in your responses.\nuser:\n{{question}}" { + t.Errorf(`Expected Instructions to be "system:\nYou are an AI assistant who helps people find information.\nAs the assistant, you answer questions briefly, succinctly,\nand in a personable manner using markdown and even add some \npersonal flair with appropriate emojis.\n\n# Customer\nYou are helping {{firstName}} {{lastName}} to find answers to \ntheir questions. Use their name to address them in your responses.\nuser:\n{{question}}", got %v`, reloaded.Instructions) + } + if reloaded.Metadata == nil { + t.Fatalf("Expected Metadata to be populated") + } + if len(reloaded.Inputs) != 3 { + t.Fatalf("Expected Inputs length to be 3, got %d", len(reloaded.Inputs)) + } + assertPromptyStringField(t, reloaded.Inputs[0], "Name", "firstName", "Inputs[0].Name") + assertPromptyStringField(t, reloaded.Inputs[0], "Kind", "string", "Inputs[0].Kind") + assertPromptyStringField(t, reloaded.Inputs[0], "Default", "Jane", "Inputs[0].Default") + assertPromptyStringField(t, reloaded.Inputs[1], "Name", "lastName", "Inputs[1].Name") + assertPromptyStringField(t, reloaded.Inputs[1], "Kind", "string", "Inputs[1].Kind") + assertPromptyStringField(t, reloaded.Inputs[1], "Default", "Doe", "Inputs[1].Default") + assertPromptyStringField(t, reloaded.Inputs[2], "Name", "question", "Inputs[2].Name") + assertPromptyStringField(t, reloaded.Inputs[2], "Kind", "string", "Inputs[2].Kind") + assertPromptyStringField(t, reloaded.Inputs[2], "Default", "What is the meaning of life?", "Inputs[2].Default") + if len(reloaded.Outputs) != 1 { + t.Fatalf("Expected Outputs length to be 1, got %d", len(reloaded.Outputs)) + } + if reloaded.Model.Id != "gpt-35-turbo" { + t.Errorf(`Expected Model.Id to be "gpt-35-turbo", got %v`, reloaded.Model.Id) + } + if len(reloaded.Tools) != 1 { + t.Fatalf("Expected Tools length to be 1, got %d", len(reloaded.Tools)) + } + tools0Value, ok := reloaded.Tools[0].(prompty.FunctionTool) + if !ok { + t.Fatalf("Expected Tools[0] to be prompty.FunctionTool, got %T", reloaded.Tools[0]) + } + if tools0Value.Kind != "function" { + t.Errorf(`Expected Kind to be "function", got %v`, tools0Value.Kind) + } + if reloaded.Template.Format.Kind != "mustache" { + t.Errorf(`Expected Template.Format.Kind to be "mustache", got %v`, reloaded.Template.Format.Kind) + } + if reloaded.Template.Parser.Kind != "prompty" { + t.Errorf(`Expected Template.Parser.Kind to be "prompty", got %v`, reloaded.Template.Parser.Kind) + } +} + +// TestPromptyToJSON6 tests that ToJSON produces valid JSON +func TestPromptyToJSON6(t *testing.T) { + jsonData := ` +{ + "name": "basic-prompt", + "displayName": "Basic Prompt", + "description": "A basic prompt that uses the GPT-3 chat API to answer questions", + "metadata": { + "authors": [ + "sethjuarez", + "jietong" + ], + "tags": [ + "example", + "prompt" + ] + }, + "inputs": [ + { + "name": "firstName", + "kind": "string", + "default": "Jane" + }, + { + "name": "lastName", + "kind": "string", + "default": "Doe" + }, + { + "name": "question", + "kind": "string", + "default": "What is the meaning of life?" + } + ], + "outputs": [ + { + "name": "answer", + "kind": "string", + "description": "The answer to the user's question." + } + ], + "model": { + "id": "gpt-35-turbo", + "connection": { + "kind": "key", + "endpoint": "https://{your-custom-endpoint}.openai.azure.com/", + "apiKey": "{your-api-key}" + } + }, + "tools": [ + { + "name": "getCurrentWeather", + "kind": "function", + "description": "Get the current weather in a given location", + "parameters": { + "location": { + "kind": "string", + "description": "The city and state, e.g. San Francisco, CA" + }, + "unit": { + "kind": "string", + "description": "The unit of temperature, e.g. Celsius or Fahrenheit" + } + } + } + ], + "template": { + "format": "mustache", + "parser": "prompty" + }, + "instructions": "system:\nYou are an AI assistant who helps people find information.\nAs the assistant, you answer questions briefly, succinctly,\nand in a personable manner using markdown and even add some \npersonal flair with appropriate emojis.\n\n# Customer\nYou are helping {{firstName}} {{lastName}} to find answers to \ntheir questions. Use their name to address them in your responses.\nuser:\n{{question}}" +} +` + var data map[string]interface{} + if err := json.Unmarshal([]byte(jsonData), &data); err != nil { + t.Fatalf("Failed to parse JSON: %v", err) + } + + ctx := prompty.NewLoadContext() + instance, err := prompty.LoadPrompty(data, ctx) + if err != nil { + t.Fatalf("Failed to load Prompty: %v", err) + } + jsonOutput, err := instance.ToJSON() + if err != nil { + t.Fatalf("Failed to convert to JSON: %v", err) + } + + var parsed map[string]interface{} + if err := json.Unmarshal([]byte(jsonOutput), &parsed); err != nil { + t.Fatalf("Failed to parse generated JSON: %v", err) + } + + reloaded, err := prompty.LoadPrompty(parsed, ctx) + if err != nil { + t.Fatalf("Failed to reload generated JSON: %v", err) + } + if reloaded.Name != "basic-prompt" { + t.Errorf(`Expected Name to be "basic-prompt", got %v`, reloaded.Name) + } + if reloaded.DisplayName == nil || *reloaded.DisplayName != "Basic Prompt" { + t.Errorf(`Expected DisplayName to be "Basic Prompt", got %v`, reloaded.DisplayName) + } + if reloaded.Description == nil || *reloaded.Description != "A basic prompt that uses the GPT-3 chat API to answer questions" { + t.Errorf(`Expected Description to be "A basic prompt that uses the GPT-3 chat API to answer questions", got %v`, reloaded.Description) + } + if reloaded.Instructions == nil || *reloaded.Instructions != "system:\nYou are an AI assistant who helps people find information.\nAs the assistant, you answer questions briefly, succinctly,\nand in a personable manner using markdown and even add some \npersonal flair with appropriate emojis.\n\n# Customer\nYou are helping {{firstName}} {{lastName}} to find answers to \ntheir questions. Use their name to address them in your responses.\nuser:\n{{question}}" { + t.Errorf(`Expected Instructions to be "system:\nYou are an AI assistant who helps people find information.\nAs the assistant, you answer questions briefly, succinctly,\nand in a personable manner using markdown and even add some \npersonal flair with appropriate emojis.\n\n# Customer\nYou are helping {{firstName}} {{lastName}} to find answers to \ntheir questions. Use their name to address them in your responses.\nuser:\n{{question}}", got %v`, reloaded.Instructions) + } + if reloaded.Metadata == nil { + t.Fatalf("Expected Metadata to be populated") + } + if len(reloaded.Inputs) != 3 { + t.Fatalf("Expected Inputs length to be 3, got %d", len(reloaded.Inputs)) + } + assertPromptyStringField(t, reloaded.Inputs[0], "Name", "firstName", "Inputs[0].Name") + assertPromptyStringField(t, reloaded.Inputs[0], "Kind", "string", "Inputs[0].Kind") + assertPromptyStringField(t, reloaded.Inputs[0], "Default", "Jane", "Inputs[0].Default") + assertPromptyStringField(t, reloaded.Inputs[1], "Name", "lastName", "Inputs[1].Name") + assertPromptyStringField(t, reloaded.Inputs[1], "Kind", "string", "Inputs[1].Kind") + assertPromptyStringField(t, reloaded.Inputs[1], "Default", "Doe", "Inputs[1].Default") + assertPromptyStringField(t, reloaded.Inputs[2], "Name", "question", "Inputs[2].Name") + assertPromptyStringField(t, reloaded.Inputs[2], "Kind", "string", "Inputs[2].Kind") + assertPromptyStringField(t, reloaded.Inputs[2], "Default", "What is the meaning of life?", "Inputs[2].Default") + if len(reloaded.Outputs) != 1 { + t.Fatalf("Expected Outputs length to be 1, got %d", len(reloaded.Outputs)) + } + if reloaded.Model.Id != "gpt-35-turbo" { + t.Errorf(`Expected Model.Id to be "gpt-35-turbo", got %v`, reloaded.Model.Id) + } + if len(reloaded.Tools) != 1 { + t.Fatalf("Expected Tools length to be 1, got %d", len(reloaded.Tools)) + } + tools0Value, ok := reloaded.Tools[0].(prompty.FunctionTool) + if !ok { + t.Fatalf("Expected Tools[0] to be prompty.FunctionTool, got %T", reloaded.Tools[0]) } - if reloaded.Description == nil || *reloaded.Description != "A basic prompt that uses the GPT-3 chat API to answer questions" { - t.Errorf(`Expected Description to be "A basic prompt that uses the GPT-3 chat API to answer questions", got %v`, reloaded.Description) + if tools0Value.Kind != "function" { + t.Errorf(`Expected Kind to be "function", got %v`, tools0Value.Kind) } - if reloaded.Instructions == nil || *reloaded.Instructions != "system:\nYou are an AI assistant who helps people find information.\nAs the assistant, you answer questions briefly, succinctly,\nand in a personable manner using markdown and even add some \npersonal flair with appropriate emojis.\n\n# Customer\nYou are helping {{firstName}} {{lastName}} to find answers to \ntheir questions. Use their name to address them in your responses.\nuser:\n{{question}}" { - t.Errorf(`Expected Instructions to be "system:\nYou are an AI assistant who helps people find information.\nAs the assistant, you answer questions briefly, succinctly,\nand in a personable manner using markdown and even add some \npersonal flair with appropriate emojis.\n\n# Customer\nYou are helping {{firstName}} {{lastName}} to find answers to \ntheir questions. Use their name to address them in your responses.\nuser:\n{{question}}", got %v`, reloaded.Instructions) + if reloaded.Template.Format.Kind != "mustache" { + t.Errorf(`Expected Template.Format.Kind to be "mustache", got %v`, reloaded.Template.Format.Kind) + } + if reloaded.Template.Parser.Kind != "prompty" { + t.Errorf(`Expected Template.Parser.Kind to be "prompty", got %v`, reloaded.Template.Parser.Kind) } } -// TestPromptyToJSON6 tests that ToJSON produces valid JSON -func TestPromptyToJSON6(t *testing.T) { +// TestPromptyToYAML6 tests that ToYAML produces valid YAML +func TestPromptyToYAML6(t *testing.T) { jsonData := ` { "name": "basic-prompt", @@ -3204,19 +5908,73 @@ func TestPromptyToJSON6(t *testing.T) { if err != nil { t.Fatalf("Failed to load Prompty: %v", err) } - jsonOutput, err := instance.ToJSON() + yamlOutput, err := instance.ToYAML() if err != nil { - t.Fatalf("Failed to convert to JSON: %v", err) + t.Fatalf("Failed to convert to YAML: %v", err) } var parsed map[string]interface{} - if err := json.Unmarshal([]byte(jsonOutput), &parsed); err != nil { - t.Fatalf("Failed to parse generated JSON: %v", err) + if err := yaml.Unmarshal([]byte(yamlOutput), &parsed); err != nil { + t.Fatalf("Failed to parse generated YAML: %v", err) + } + + reloaded, err := prompty.LoadPrompty(parsed, ctx) + if err != nil { + t.Fatalf("Failed to reload generated YAML: %v", err) + } + if reloaded.Name != "basic-prompt" { + t.Errorf(`Expected Name to be "basic-prompt", got %v`, reloaded.Name) + } + if reloaded.DisplayName == nil || *reloaded.DisplayName != "Basic Prompt" { + t.Errorf(`Expected DisplayName to be "Basic Prompt", got %v`, reloaded.DisplayName) + } + if reloaded.Description == nil || *reloaded.Description != "A basic prompt that uses the GPT-3 chat API to answer questions" { + t.Errorf(`Expected Description to be "A basic prompt that uses the GPT-3 chat API to answer questions", got %v`, reloaded.Description) + } + if reloaded.Instructions == nil || *reloaded.Instructions != "system:\nYou are an AI assistant who helps people find information.\nAs the assistant, you answer questions briefly, succinctly,\nand in a personable manner using markdown and even add some \npersonal flair with appropriate emojis.\n\n# Customer\nYou are helping {{firstName}} {{lastName}} to find answers to \ntheir questions. Use their name to address them in your responses.\nuser:\n{{question}}" { + t.Errorf(`Expected Instructions to be "system:\nYou are an AI assistant who helps people find information.\nAs the assistant, you answer questions briefly, succinctly,\nand in a personable manner using markdown and even add some \npersonal flair with appropriate emojis.\n\n# Customer\nYou are helping {{firstName}} {{lastName}} to find answers to \ntheir questions. Use their name to address them in your responses.\nuser:\n{{question}}", got %v`, reloaded.Instructions) + } + if reloaded.Metadata == nil { + t.Fatalf("Expected Metadata to be populated") + } + if len(reloaded.Inputs) != 3 { + t.Fatalf("Expected Inputs length to be 3, got %d", len(reloaded.Inputs)) + } + assertPromptyStringField(t, reloaded.Inputs[0], "Name", "firstName", "Inputs[0].Name") + assertPromptyStringField(t, reloaded.Inputs[0], "Kind", "string", "Inputs[0].Kind") + assertPromptyStringField(t, reloaded.Inputs[0], "Default", "Jane", "Inputs[0].Default") + assertPromptyStringField(t, reloaded.Inputs[1], "Name", "lastName", "Inputs[1].Name") + assertPromptyStringField(t, reloaded.Inputs[1], "Kind", "string", "Inputs[1].Kind") + assertPromptyStringField(t, reloaded.Inputs[1], "Default", "Doe", "Inputs[1].Default") + assertPromptyStringField(t, reloaded.Inputs[2], "Name", "question", "Inputs[2].Name") + assertPromptyStringField(t, reloaded.Inputs[2], "Kind", "string", "Inputs[2].Kind") + assertPromptyStringField(t, reloaded.Inputs[2], "Default", "What is the meaning of life?", "Inputs[2].Default") + if len(reloaded.Outputs) != 1 { + t.Fatalf("Expected Outputs length to be 1, got %d", len(reloaded.Outputs)) + } + if reloaded.Model.Id != "gpt-35-turbo" { + t.Errorf(`Expected Model.Id to be "gpt-35-turbo", got %v`, reloaded.Model.Id) + } + if len(reloaded.Tools) != 1 { + t.Fatalf("Expected Tools length to be 1, got %d", len(reloaded.Tools)) + } + tools0Value, ok := reloaded.Tools[0].(prompty.FunctionTool) + if !ok { + t.Fatalf("Expected Tools[0] to be prompty.FunctionTool, got %T", reloaded.Tools[0]) + } + if tools0Value.Kind != "function" { + t.Errorf(`Expected Kind to be "function", got %v`, tools0Value.Kind) + } + if reloaded.Template.Format.Kind != "mustache" { + t.Errorf(`Expected Template.Format.Kind to be "mustache", got %v`, reloaded.Template.Format.Kind) + } + if reloaded.Template.Parser.Kind != "prompty" { + t.Errorf(`Expected Template.Parser.Kind to be "prompty", got %v`, reloaded.Template.Parser.Kind) } } -// TestPromptyToYAML6 tests that ToYAML produces valid YAML -func TestPromptyToYAML6(t *testing.T) { +// TestPromptyLoadJSON7 tests loading Prompty from JSON +func TestPromptyLoadJSON7(t *testing.T) { jsonData := ` { "name": "basic-prompt", @@ -3264,9 +6022,8 @@ func TestPromptyToYAML6(t *testing.T) { "apiKey": "{your-api-key}" } }, - "tools": [ - { - "name": "getCurrentWeather", + "tools": { + "getCurrentWeather": { "kind": "function", "description": "Get the current weather in a given location", "parameters": { @@ -3280,7 +6037,7 @@ func TestPromptyToYAML6(t *testing.T) { } } } - ], + }, "template": { "format": "mustache", "parser": "prompty" @@ -3298,19 +6055,169 @@ func TestPromptyToYAML6(t *testing.T) { if err != nil { t.Fatalf("Failed to load Prompty: %v", err) } - yamlOutput, err := instance.ToYAML() - if err != nil { - t.Fatalf("Failed to convert to YAML: %v", err) + if instance.Name != "basic-prompt" { + t.Errorf(`Expected Name to be "basic-prompt", got %v`, instance.Name) + } + if instance.DisplayName == nil || *instance.DisplayName != "Basic Prompt" { + t.Errorf(`Expected DisplayName to be "Basic Prompt", got %v`, instance.DisplayName) + } + if instance.Description == nil || *instance.Description != "A basic prompt that uses the GPT-3 chat API to answer questions" { + t.Errorf(`Expected Description to be "A basic prompt that uses the GPT-3 chat API to answer questions", got %v`, instance.Description) + } + if instance.Instructions == nil || *instance.Instructions != "system:\nYou are an AI assistant who helps people find information.\nAs the assistant, you answer questions briefly, succinctly,\nand in a personable manner using markdown and even add some \npersonal flair with appropriate emojis.\n\n# Customer\nYou are helping {{firstName}} {{lastName}} to find answers to \ntheir questions. Use their name to address them in your responses.\nuser:\n{{question}}" { + t.Errorf(`Expected Instructions to be "system:\nYou are an AI assistant who helps people find information.\nAs the assistant, you answer questions briefly, succinctly,\nand in a personable manner using markdown and even add some \npersonal flair with appropriate emojis.\n\n# Customer\nYou are helping {{firstName}} {{lastName}} to find answers to \ntheir questions. Use their name to address them in your responses.\nuser:\n{{question}}", got %v`, instance.Instructions) + } + if instance.Metadata == nil { + t.Fatalf("Expected Metadata to be populated") + } + if len(instance.Inputs) != 3 { + t.Fatalf("Expected Inputs length to be 3, got %d", len(instance.Inputs)) + } + assertPromptyStringField(t, instance.Inputs[0], "Name", "firstName", "Inputs[0].Name") + assertPromptyStringField(t, instance.Inputs[0], "Kind", "string", "Inputs[0].Kind") + assertPromptyStringField(t, instance.Inputs[0], "Default", "Jane", "Inputs[0].Default") + assertPromptyStringField(t, instance.Inputs[1], "Name", "lastName", "Inputs[1].Name") + assertPromptyStringField(t, instance.Inputs[1], "Kind", "string", "Inputs[1].Kind") + assertPromptyStringField(t, instance.Inputs[1], "Default", "Doe", "Inputs[1].Default") + assertPromptyStringField(t, instance.Inputs[2], "Name", "question", "Inputs[2].Name") + assertPromptyStringField(t, instance.Inputs[2], "Kind", "string", "Inputs[2].Kind") + assertPromptyStringField(t, instance.Inputs[2], "Default", "What is the meaning of life?", "Inputs[2].Default") + if len(instance.Outputs) != 1 { + t.Fatalf("Expected Outputs length to be 1, got %d", len(instance.Outputs)) + } + if instance.Model.Id != "gpt-35-turbo" { + t.Errorf(`Expected Model.Id to be "gpt-35-turbo", got %v`, instance.Model.Id) } + if instance.Template.Format.Kind != "mustache" { + t.Errorf(`Expected Template.Format.Kind to be "mustache", got %v`, instance.Template.Format.Kind) + } + if instance.Template.Parser.Kind != "prompty" { + t.Errorf(`Expected Template.Parser.Kind to be "prompty", got %v`, instance.Template.Parser.Kind) + } +} - var parsed map[string]interface{} - if err := yaml.Unmarshal([]byte(yamlOutput), &parsed); err != nil { - t.Fatalf("Failed to parse generated YAML: %v", err) +// TestPromptyLoadYAML7 tests loading Prompty from YAML +func TestPromptyLoadYAML7(t *testing.T) { + yamlData := ` +name: basic-prompt +displayName: Basic Prompt +description: A basic prompt that uses the GPT-3 chat API to answer questions +metadata: + authors: + - sethjuarez + - jietong + tags: + - example + - prompt +inputs: + - name: firstName + kind: string + default: Jane + - name: lastName + kind: string + default: Doe + - name: question + kind: string + default: What is the meaning of life? +outputs: + - name: answer + kind: string + description: The answer to the user's question. +model: + id: gpt-35-turbo + connection: + kind: key + endpoint: "https://{your-custom-endpoint}.openai.azure.com/" + apiKey: "{your-api-key}" +tools: + getCurrentWeather: + kind: function + description: Get the current weather in a given location + parameters: + location: + kind: string + description: The city and state, e.g. San Francisco, CA + unit: + kind: string + description: The unit of temperature, e.g. Celsius or Fahrenheit +template: + format: mustache + parser: prompty +instructions: "system: + + You are an AI assistant who helps people find information. + + As the assistant, you answer questions briefly, succinctly, + + and in a personable manner using markdown and even add some\ + + personal flair with appropriate emojis. + + + # Customer + + You are helping {{firstName}} {{lastName}} to find answers to\ + + their questions. Use their name to address them in your responses. + + user: + + {{question}}" + +` + var data map[string]interface{} + if err := yaml.Unmarshal([]byte(yamlData), &data); err != nil { + t.Fatalf("Failed to parse YAML: %v", err) + } + + ctx := prompty.NewLoadContext() + instance, err := prompty.LoadPrompty(data, ctx) + if err != nil { + t.Fatalf("Failed to load Prompty: %v", err) + } + if instance.Name != "basic-prompt" { + t.Errorf(`Expected Name to be "basic-prompt", got %v`, instance.Name) + } + if instance.DisplayName == nil || *instance.DisplayName != "Basic Prompt" { + t.Errorf(`Expected DisplayName to be "Basic Prompt", got %v`, instance.DisplayName) + } + if instance.Description == nil || *instance.Description != "A basic prompt that uses the GPT-3 chat API to answer questions" { + t.Errorf(`Expected Description to be "A basic prompt that uses the GPT-3 chat API to answer questions", got %v`, instance.Description) + } + if instance.Instructions == nil || *instance.Instructions != "system:\nYou are an AI assistant who helps people find information.\nAs the assistant, you answer questions briefly, succinctly,\nand in a personable manner using markdown and even add some \npersonal flair with appropriate emojis.\n\n# Customer\nYou are helping {{firstName}} {{lastName}} to find answers to \ntheir questions. Use their name to address them in your responses.\nuser:\n{{question}}" { + t.Errorf(`Expected Instructions to be "system:\nYou are an AI assistant who helps people find information.\nAs the assistant, you answer questions briefly, succinctly,\nand in a personable manner using markdown and even add some \npersonal flair with appropriate emojis.\n\n# Customer\nYou are helping {{firstName}} {{lastName}} to find answers to \ntheir questions. Use their name to address them in your responses.\nuser:\n{{question}}", got %v`, instance.Instructions) + } + if instance.Metadata == nil { + t.Fatalf("Expected Metadata to be populated") + } + if len(instance.Inputs) != 3 { + t.Fatalf("Expected Inputs length to be 3, got %d", len(instance.Inputs)) + } + assertPromptyStringField(t, instance.Inputs[0], "Name", "firstName", "Inputs[0].Name") + assertPromptyStringField(t, instance.Inputs[0], "Kind", "string", "Inputs[0].Kind") + assertPromptyStringField(t, instance.Inputs[0], "Default", "Jane", "Inputs[0].Default") + assertPromptyStringField(t, instance.Inputs[1], "Name", "lastName", "Inputs[1].Name") + assertPromptyStringField(t, instance.Inputs[1], "Kind", "string", "Inputs[1].Kind") + assertPromptyStringField(t, instance.Inputs[1], "Default", "Doe", "Inputs[1].Default") + assertPromptyStringField(t, instance.Inputs[2], "Name", "question", "Inputs[2].Name") + assertPromptyStringField(t, instance.Inputs[2], "Kind", "string", "Inputs[2].Kind") + assertPromptyStringField(t, instance.Inputs[2], "Default", "What is the meaning of life?", "Inputs[2].Default") + if len(instance.Outputs) != 1 { + t.Fatalf("Expected Outputs length to be 1, got %d", len(instance.Outputs)) + } + if instance.Model.Id != "gpt-35-turbo" { + t.Errorf(`Expected Model.Id to be "gpt-35-turbo", got %v`, instance.Model.Id) + } + if instance.Template.Format.Kind != "mustache" { + t.Errorf(`Expected Template.Format.Kind to be "mustache", got %v`, instance.Template.Format.Kind) + } + if instance.Template.Parser.Kind != "prompty" { + t.Errorf(`Expected Template.Parser.Kind to be "prompty", got %v`, instance.Template.Parser.Kind) } } -// TestPromptyLoadJSON7 tests loading Prompty from JSON -func TestPromptyLoadJSON7(t *testing.T) { +// TestPromptyFromJSON7 tests loading Prompty through the generated JSON helper +func TestPromptyFromJSON7(t *testing.T) { jsonData := ` { "name": "basic-prompt", @@ -3381,15 +6288,10 @@ func TestPromptyLoadJSON7(t *testing.T) { "instructions": "system:\nYou are an AI assistant who helps people find information.\nAs the assistant, you answer questions briefly, succinctly,\nand in a personable manner using markdown and even add some \npersonal flair with appropriate emojis.\n\n# Customer\nYou are helping {{firstName}} {{lastName}} to find answers to \ntheir questions. Use their name to address them in your responses.\nuser:\n{{question}}" } ` - var data map[string]interface{} - if err := json.Unmarshal([]byte(jsonData), &data); err != nil { - t.Fatalf("Failed to parse JSON: %v", err) - } - ctx := prompty.NewLoadContext() - instance, err := prompty.LoadPrompty(data, ctx) + instance, err := prompty.PromptyFromJSON(jsonData) if err != nil { - t.Fatalf("Failed to load Prompty: %v", err) + t.Fatalf("Failed to load Prompty from JSON helper: %v", err) } if instance.Name != "basic-prompt" { t.Errorf(`Expected Name to be "basic-prompt", got %v`, instance.Name) @@ -3403,10 +6305,37 @@ func TestPromptyLoadJSON7(t *testing.T) { if instance.Instructions == nil || *instance.Instructions != "system:\nYou are an AI assistant who helps people find information.\nAs the assistant, you answer questions briefly, succinctly,\nand in a personable manner using markdown and even add some \npersonal flair with appropriate emojis.\n\n# Customer\nYou are helping {{firstName}} {{lastName}} to find answers to \ntheir questions. Use their name to address them in your responses.\nuser:\n{{question}}" { t.Errorf(`Expected Instructions to be "system:\nYou are an AI assistant who helps people find information.\nAs the assistant, you answer questions briefly, succinctly,\nand in a personable manner using markdown and even add some \npersonal flair with appropriate emojis.\n\n# Customer\nYou are helping {{firstName}} {{lastName}} to find answers to \ntheir questions. Use their name to address them in your responses.\nuser:\n{{question}}", got %v`, instance.Instructions) } + if instance.Metadata == nil { + t.Fatalf("Expected Metadata to be populated") + } + if len(instance.Inputs) != 3 { + t.Fatalf("Expected Inputs length to be 3, got %d", len(instance.Inputs)) + } + assertPromptyStringField(t, instance.Inputs[0], "Name", "firstName", "Inputs[0].Name") + assertPromptyStringField(t, instance.Inputs[0], "Kind", "string", "Inputs[0].Kind") + assertPromptyStringField(t, instance.Inputs[0], "Default", "Jane", "Inputs[0].Default") + assertPromptyStringField(t, instance.Inputs[1], "Name", "lastName", "Inputs[1].Name") + assertPromptyStringField(t, instance.Inputs[1], "Kind", "string", "Inputs[1].Kind") + assertPromptyStringField(t, instance.Inputs[1], "Default", "Doe", "Inputs[1].Default") + assertPromptyStringField(t, instance.Inputs[2], "Name", "question", "Inputs[2].Name") + assertPromptyStringField(t, instance.Inputs[2], "Kind", "string", "Inputs[2].Kind") + assertPromptyStringField(t, instance.Inputs[2], "Default", "What is the meaning of life?", "Inputs[2].Default") + if len(instance.Outputs) != 1 { + t.Fatalf("Expected Outputs length to be 1, got %d", len(instance.Outputs)) + } + if instance.Model.Id != "gpt-35-turbo" { + t.Errorf(`Expected Model.Id to be "gpt-35-turbo", got %v`, instance.Model.Id) + } + if instance.Template.Format.Kind != "mustache" { + t.Errorf(`Expected Template.Format.Kind to be "mustache", got %v`, instance.Template.Format.Kind) + } + if instance.Template.Parser.Kind != "prompty" { + t.Errorf(`Expected Template.Parser.Kind to be "prompty", got %v`, instance.Template.Parser.Kind) + } } -// TestPromptyLoadYAML7 tests loading Prompty from YAML -func TestPromptyLoadYAML7(t *testing.T) { +// TestPromptyFromYAML7 tests loading Prompty through the generated YAML helper +func TestPromptyFromYAML7(t *testing.T) { yamlData := ` name: basic-prompt displayName: Basic Prompt @@ -3458,14 +6387,14 @@ instructions: "system: As the assistant, you answer questions briefly, succinctly, - and in a personable manner using markdown and even add some\ + and in a personable manner using markdown and even add some\ personal flair with appropriate emojis. # Customer - You are helping {{firstName}} {{lastName}} to find answers to\ + You are helping {{firstName}} {{lastName}} to find answers to\ their questions. Use their name to address them in your responses. @@ -3474,15 +6403,10 @@ instructions: "system: {{question}}" ` - var data map[string]interface{} - if err := yaml.Unmarshal([]byte(yamlData), &data); err != nil { - t.Fatalf("Failed to parse YAML: %v", err) - } - ctx := prompty.NewLoadContext() - instance, err := prompty.LoadPrompty(data, ctx) + instance, err := prompty.PromptyFromYAML(yamlData) if err != nil { - t.Fatalf("Failed to load Prompty: %v", err) + t.Fatalf("Failed to load Prompty from YAML helper: %v", err) } if instance.Name != "basic-prompt" { t.Errorf(`Expected Name to be "basic-prompt", got %v`, instance.Name) @@ -3496,6 +6420,33 @@ instructions: "system: if instance.Instructions == nil || *instance.Instructions != "system:\nYou are an AI assistant who helps people find information.\nAs the assistant, you answer questions briefly, succinctly,\nand in a personable manner using markdown and even add some \npersonal flair with appropriate emojis.\n\n# Customer\nYou are helping {{firstName}} {{lastName}} to find answers to \ntheir questions. Use their name to address them in your responses.\nuser:\n{{question}}" { t.Errorf(`Expected Instructions to be "system:\nYou are an AI assistant who helps people find information.\nAs the assistant, you answer questions briefly, succinctly,\nand in a personable manner using markdown and even add some \npersonal flair with appropriate emojis.\n\n# Customer\nYou are helping {{firstName}} {{lastName}} to find answers to \ntheir questions. Use their name to address them in your responses.\nuser:\n{{question}}", got %v`, instance.Instructions) } + if instance.Metadata == nil { + t.Fatalf("Expected Metadata to be populated") + } + if len(instance.Inputs) != 3 { + t.Fatalf("Expected Inputs length to be 3, got %d", len(instance.Inputs)) + } + assertPromptyStringField(t, instance.Inputs[0], "Name", "firstName", "Inputs[0].Name") + assertPromptyStringField(t, instance.Inputs[0], "Kind", "string", "Inputs[0].Kind") + assertPromptyStringField(t, instance.Inputs[0], "Default", "Jane", "Inputs[0].Default") + assertPromptyStringField(t, instance.Inputs[1], "Name", "lastName", "Inputs[1].Name") + assertPromptyStringField(t, instance.Inputs[1], "Kind", "string", "Inputs[1].Kind") + assertPromptyStringField(t, instance.Inputs[1], "Default", "Doe", "Inputs[1].Default") + assertPromptyStringField(t, instance.Inputs[2], "Name", "question", "Inputs[2].Name") + assertPromptyStringField(t, instance.Inputs[2], "Kind", "string", "Inputs[2].Kind") + assertPromptyStringField(t, instance.Inputs[2], "Default", "What is the meaning of life?", "Inputs[2].Default") + if len(instance.Outputs) != 1 { + t.Fatalf("Expected Outputs length to be 1, got %d", len(instance.Outputs)) + } + if instance.Model.Id != "gpt-35-turbo" { + t.Errorf(`Expected Model.Id to be "gpt-35-turbo", got %v`, instance.Model.Id) + } + if instance.Template.Format.Kind != "mustache" { + t.Errorf(`Expected Template.Format.Kind to be "mustache", got %v`, instance.Template.Format.Kind) + } + if instance.Template.Parser.Kind != "prompty" { + t.Errorf(`Expected Template.Parser.Kind to be "prompty", got %v`, instance.Template.Parser.Kind) + } } // TestPromptyRoundtrip7 tests load -> save -> load produces equivalent data @@ -3599,6 +6550,33 @@ func TestPromptyRoundtrip7(t *testing.T) { if reloaded.Instructions == nil || *reloaded.Instructions != "system:\nYou are an AI assistant who helps people find information.\nAs the assistant, you answer questions briefly, succinctly,\nand in a personable manner using markdown and even add some \npersonal flair with appropriate emojis.\n\n# Customer\nYou are helping {{firstName}} {{lastName}} to find answers to \ntheir questions. Use their name to address them in your responses.\nuser:\n{{question}}" { t.Errorf(`Expected Instructions to be "system:\nYou are an AI assistant who helps people find information.\nAs the assistant, you answer questions briefly, succinctly,\nand in a personable manner using markdown and even add some \npersonal flair with appropriate emojis.\n\n# Customer\nYou are helping {{firstName}} {{lastName}} to find answers to \ntheir questions. Use their name to address them in your responses.\nuser:\n{{question}}", got %v`, reloaded.Instructions) } + if reloaded.Metadata == nil { + t.Fatalf("Expected Metadata to be populated") + } + if len(reloaded.Inputs) != 3 { + t.Fatalf("Expected Inputs length to be 3, got %d", len(reloaded.Inputs)) + } + assertPromptyStringField(t, reloaded.Inputs[0], "Name", "firstName", "Inputs[0].Name") + assertPromptyStringField(t, reloaded.Inputs[0], "Kind", "string", "Inputs[0].Kind") + assertPromptyStringField(t, reloaded.Inputs[0], "Default", "Jane", "Inputs[0].Default") + assertPromptyStringField(t, reloaded.Inputs[1], "Name", "lastName", "Inputs[1].Name") + assertPromptyStringField(t, reloaded.Inputs[1], "Kind", "string", "Inputs[1].Kind") + assertPromptyStringField(t, reloaded.Inputs[1], "Default", "Doe", "Inputs[1].Default") + assertPromptyStringField(t, reloaded.Inputs[2], "Name", "question", "Inputs[2].Name") + assertPromptyStringField(t, reloaded.Inputs[2], "Kind", "string", "Inputs[2].Kind") + assertPromptyStringField(t, reloaded.Inputs[2], "Default", "What is the meaning of life?", "Inputs[2].Default") + if len(reloaded.Outputs) != 1 { + t.Fatalf("Expected Outputs length to be 1, got %d", len(reloaded.Outputs)) + } + if reloaded.Model.Id != "gpt-35-turbo" { + t.Errorf(`Expected Model.Id to be "gpt-35-turbo", got %v`, reloaded.Model.Id) + } + if reloaded.Template.Format.Kind != "mustache" { + t.Errorf(`Expected Template.Format.Kind to be "mustache", got %v`, reloaded.Template.Format.Kind) + } + if reloaded.Template.Parser.Kind != "prompty" { + t.Errorf(`Expected Template.Parser.Kind to be "prompty", got %v`, reloaded.Template.Parser.Kind) + } } // TestPromptyToJSON7 tests that ToJSON produces valid JSON @@ -3692,6 +6670,50 @@ func TestPromptyToJSON7(t *testing.T) { if err := json.Unmarshal([]byte(jsonOutput), &parsed); err != nil { t.Fatalf("Failed to parse generated JSON: %v", err) } + + reloaded, err := prompty.LoadPrompty(parsed, ctx) + if err != nil { + t.Fatalf("Failed to reload generated JSON: %v", err) + } + if reloaded.Name != "basic-prompt" { + t.Errorf(`Expected Name to be "basic-prompt", got %v`, reloaded.Name) + } + if reloaded.DisplayName == nil || *reloaded.DisplayName != "Basic Prompt" { + t.Errorf(`Expected DisplayName to be "Basic Prompt", got %v`, reloaded.DisplayName) + } + if reloaded.Description == nil || *reloaded.Description != "A basic prompt that uses the GPT-3 chat API to answer questions" { + t.Errorf(`Expected Description to be "A basic prompt that uses the GPT-3 chat API to answer questions", got %v`, reloaded.Description) + } + if reloaded.Instructions == nil || *reloaded.Instructions != "system:\nYou are an AI assistant who helps people find information.\nAs the assistant, you answer questions briefly, succinctly,\nand in a personable manner using markdown and even add some \npersonal flair with appropriate emojis.\n\n# Customer\nYou are helping {{firstName}} {{lastName}} to find answers to \ntheir questions. Use their name to address them in your responses.\nuser:\n{{question}}" { + t.Errorf(`Expected Instructions to be "system:\nYou are an AI assistant who helps people find information.\nAs the assistant, you answer questions briefly, succinctly,\nand in a personable manner using markdown and even add some \npersonal flair with appropriate emojis.\n\n# Customer\nYou are helping {{firstName}} {{lastName}} to find answers to \ntheir questions. Use their name to address them in your responses.\nuser:\n{{question}}", got %v`, reloaded.Instructions) + } + if reloaded.Metadata == nil { + t.Fatalf("Expected Metadata to be populated") + } + if len(reloaded.Inputs) != 3 { + t.Fatalf("Expected Inputs length to be 3, got %d", len(reloaded.Inputs)) + } + assertPromptyStringField(t, reloaded.Inputs[0], "Name", "firstName", "Inputs[0].Name") + assertPromptyStringField(t, reloaded.Inputs[0], "Kind", "string", "Inputs[0].Kind") + assertPromptyStringField(t, reloaded.Inputs[0], "Default", "Jane", "Inputs[0].Default") + assertPromptyStringField(t, reloaded.Inputs[1], "Name", "lastName", "Inputs[1].Name") + assertPromptyStringField(t, reloaded.Inputs[1], "Kind", "string", "Inputs[1].Kind") + assertPromptyStringField(t, reloaded.Inputs[1], "Default", "Doe", "Inputs[1].Default") + assertPromptyStringField(t, reloaded.Inputs[2], "Name", "question", "Inputs[2].Name") + assertPromptyStringField(t, reloaded.Inputs[2], "Kind", "string", "Inputs[2].Kind") + assertPromptyStringField(t, reloaded.Inputs[2], "Default", "What is the meaning of life?", "Inputs[2].Default") + if len(reloaded.Outputs) != 1 { + t.Fatalf("Expected Outputs length to be 1, got %d", len(reloaded.Outputs)) + } + if reloaded.Model.Id != "gpt-35-turbo" { + t.Errorf(`Expected Model.Id to be "gpt-35-turbo", got %v`, reloaded.Model.Id) + } + if reloaded.Template.Format.Kind != "mustache" { + t.Errorf(`Expected Template.Format.Kind to be "mustache", got %v`, reloaded.Template.Format.Kind) + } + if reloaded.Template.Parser.Kind != "prompty" { + t.Errorf(`Expected Template.Parser.Kind to be "prompty", got %v`, reloaded.Template.Parser.Kind) + } } // TestPromptyToYAML7 tests that ToYAML produces valid YAML @@ -3785,4 +6807,91 @@ func TestPromptyToYAML7(t *testing.T) { if err := yaml.Unmarshal([]byte(yamlOutput), &parsed); err != nil { t.Fatalf("Failed to parse generated YAML: %v", err) } + + reloaded, err := prompty.LoadPrompty(parsed, ctx) + if err != nil { + t.Fatalf("Failed to reload generated YAML: %v", err) + } + if reloaded.Name != "basic-prompt" { + t.Errorf(`Expected Name to be "basic-prompt", got %v`, reloaded.Name) + } + if reloaded.DisplayName == nil || *reloaded.DisplayName != "Basic Prompt" { + t.Errorf(`Expected DisplayName to be "Basic Prompt", got %v`, reloaded.DisplayName) + } + if reloaded.Description == nil || *reloaded.Description != "A basic prompt that uses the GPT-3 chat API to answer questions" { + t.Errorf(`Expected Description to be "A basic prompt that uses the GPT-3 chat API to answer questions", got %v`, reloaded.Description) + } + if reloaded.Instructions == nil || *reloaded.Instructions != "system:\nYou are an AI assistant who helps people find information.\nAs the assistant, you answer questions briefly, succinctly,\nand in a personable manner using markdown and even add some \npersonal flair with appropriate emojis.\n\n# Customer\nYou are helping {{firstName}} {{lastName}} to find answers to \ntheir questions. Use their name to address them in your responses.\nuser:\n{{question}}" { + t.Errorf(`Expected Instructions to be "system:\nYou are an AI assistant who helps people find information.\nAs the assistant, you answer questions briefly, succinctly,\nand in a personable manner using markdown and even add some \npersonal flair with appropriate emojis.\n\n# Customer\nYou are helping {{firstName}} {{lastName}} to find answers to \ntheir questions. Use their name to address them in your responses.\nuser:\n{{question}}", got %v`, reloaded.Instructions) + } + if reloaded.Metadata == nil { + t.Fatalf("Expected Metadata to be populated") + } + if len(reloaded.Inputs) != 3 { + t.Fatalf("Expected Inputs length to be 3, got %d", len(reloaded.Inputs)) + } + assertPromptyStringField(t, reloaded.Inputs[0], "Name", "firstName", "Inputs[0].Name") + assertPromptyStringField(t, reloaded.Inputs[0], "Kind", "string", "Inputs[0].Kind") + assertPromptyStringField(t, reloaded.Inputs[0], "Default", "Jane", "Inputs[0].Default") + assertPromptyStringField(t, reloaded.Inputs[1], "Name", "lastName", "Inputs[1].Name") + assertPromptyStringField(t, reloaded.Inputs[1], "Kind", "string", "Inputs[1].Kind") + assertPromptyStringField(t, reloaded.Inputs[1], "Default", "Doe", "Inputs[1].Default") + assertPromptyStringField(t, reloaded.Inputs[2], "Name", "question", "Inputs[2].Name") + assertPromptyStringField(t, reloaded.Inputs[2], "Kind", "string", "Inputs[2].Kind") + assertPromptyStringField(t, reloaded.Inputs[2], "Default", "What is the meaning of life?", "Inputs[2].Default") + if len(reloaded.Outputs) != 1 { + t.Fatalf("Expected Outputs length to be 1, got %d", len(reloaded.Outputs)) + } + if reloaded.Model.Id != "gpt-35-turbo" { + t.Errorf(`Expected Model.Id to be "gpt-35-turbo", got %v`, reloaded.Model.Id) + } + if reloaded.Template.Format.Kind != "mustache" { + t.Errorf(`Expected Template.Format.Kind to be "mustache", got %v`, reloaded.Template.Format.Kind) + } + if reloaded.Template.Parser.Kind != "prompty" { + t.Errorf(`Expected Template.Parser.Kind to be "prompty", got %v`, reloaded.Template.Parser.Kind) + } +} + +// TestPromptyFromJSONInvalid rejects malformed JSON instead of silently defaulting +func TestPromptyFromJSONInvalid(t *testing.T) { + if _, err := prompty.PromptyFromJSON("{"); err == nil { + t.Fatalf("Expected malformed JSON to fail") + } +} + +func assertPromptyStringField(t *testing.T, value interface{}, fieldName string, expected string, displayName string) { + t.Helper() + field := reflect.ValueOf(value) + if field.Kind() == reflect.Pointer { + if field.IsNil() { + t.Fatalf("Expected %s to be populated", displayName) + } + field = field.Elem() + } + if field.Kind() != reflect.Struct { + t.Fatalf("Expected %s receiver to be a struct, got %T", displayName, value) + } + member := field.FieldByName(fieldName) + if !member.IsValid() { + t.Fatalf("Expected %s to have field %s, got %T", displayName, fieldName, value) + } + if member.Kind() == reflect.Pointer { + if member.IsNil() { + t.Fatalf("Expected %s to be populated", displayName) + } + member = member.Elem() + } + if member.Kind() == reflect.Interface { + if member.IsNil() { + t.Fatalf("Expected %s to be populated", displayName) + } + member = member.Elem() + } + if member.Kind() != reflect.String { + t.Fatalf("Expected %s to be a string field, got %s", displayName, member.Kind()) + } + if got := member.String(); got != expected { + t.Errorf("Expected %s to be %q, got %q", displayName, expected, got) + } } diff --git a/runtime/go/prompty/model/prompty_tool_test.go b/runtime/go/prompty/model/prompty_tool_test.go index 74751407..719489be 100644 --- a/runtime/go/prompty/model/prompty_tool_test.go +++ b/runtime/go/prompty/model/prompty_tool_test.go @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. package prompty_test @@ -70,6 +71,55 @@ mode: single } } +// TestPromptyToolFromJSON tests loading PromptyTool through the generated JSON helper +func TestPromptyToolFromJSON(t *testing.T) { + jsonData := ` +{ + "kind": "prompty", + "path": "./summarize.prompty", + "mode": "single" +} +` + + instance, err := prompty.PromptyToolFromJSON(jsonData) + if err != nil { + t.Fatalf("Failed to load PromptyTool from JSON helper: %v", err) + } + if instance.Kind != "prompty" { + t.Errorf(`Expected Kind to be "prompty", got %v`, instance.Kind) + } + if instance.Path != "./summarize.prompty" { + t.Errorf(`Expected Path to be "./summarize.prompty", got %v`, instance.Path) + } + if instance.Mode != "single" { + t.Errorf(`Expected Mode to be "single", got %v`, instance.Mode) + } +} + +// TestPromptyToolFromYAML tests loading PromptyTool through the generated YAML helper +func TestPromptyToolFromYAML(t *testing.T) { + yamlData := ` +kind: prompty +path: ./summarize.prompty +mode: single + +` + + instance, err := prompty.PromptyToolFromYAML(yamlData) + if err != nil { + t.Fatalf("Failed to load PromptyTool from YAML helper: %v", err) + } + if instance.Kind != "prompty" { + t.Errorf(`Expected Kind to be "prompty", got %v`, instance.Kind) + } + if instance.Path != "./summarize.prompty" { + t.Errorf(`Expected Path to be "./summarize.prompty", got %v`, instance.Path) + } + if instance.Mode != "single" { + t.Errorf(`Expected Mode to be "single", got %v`, instance.Mode) + } +} + // TestPromptyToolRoundtrip tests load -> save -> load produces equivalent data func TestPromptyToolRoundtrip(t *testing.T) { jsonData := ` @@ -135,6 +185,20 @@ func TestPromptyToolToJSON(t *testing.T) { if err := json.Unmarshal([]byte(jsonOutput), &parsed); err != nil { t.Fatalf("Failed to parse generated JSON: %v", err) } + + reloaded, err := prompty.LoadPromptyTool(parsed, ctx) + if err != nil { + t.Fatalf("Failed to reload generated JSON: %v", err) + } + if reloaded.Kind != "prompty" { + t.Errorf(`Expected Kind to be "prompty", got %v`, reloaded.Kind) + } + if reloaded.Path != "./summarize.prompty" { + t.Errorf(`Expected Path to be "./summarize.prompty", got %v`, reloaded.Path) + } + if reloaded.Mode != "single" { + t.Errorf(`Expected Mode to be "single", got %v`, reloaded.Mode) + } } // TestPromptyToolToYAML tests that ToYAML produces valid YAML @@ -165,4 +229,25 @@ func TestPromptyToolToYAML(t *testing.T) { if err := yaml.Unmarshal([]byte(yamlOutput), &parsed); err != nil { t.Fatalf("Failed to parse generated YAML: %v", err) } + + reloaded, err := prompty.LoadPromptyTool(parsed, ctx) + if err != nil { + t.Fatalf("Failed to reload generated YAML: %v", err) + } + if reloaded.Kind != "prompty" { + t.Errorf(`Expected Kind to be "prompty", got %v`, reloaded.Kind) + } + if reloaded.Path != "./summarize.prompty" { + t.Errorf(`Expected Path to be "./summarize.prompty", got %v`, reloaded.Path) + } + if reloaded.Mode != "single" { + t.Errorf(`Expected Mode to be "single", got %v`, reloaded.Mode) + } +} + +// TestPromptyToolFromJSONInvalid rejects malformed JSON instead of silently defaulting +func TestPromptyToolFromJSONInvalid(t *testing.T) { + if _, err := prompty.PromptyToolFromJSON("{"); err == nil { + t.Fatalf("Expected malformed JSON to fail") + } } diff --git a/runtime/go/prompty/model/property.go b/runtime/go/prompty/model/property.go index 570ae1ac..c6701621 100644 --- a/runtime/go/prompty/model/property.go +++ b/runtime/go/prompty/model/property.go @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. // Group: core package prompty @@ -84,7 +85,8 @@ func LoadProperty(data interface{}, ctx *LoadContext) (interface{}, error) { result.Example = &val } if val, ok := m["enumValues"]; ok && val != nil { - if arr, ok := val.([]interface{}); ok { + switch arr := val.(type) { + case []interface{}: result.EnumValues = arr } } @@ -94,7 +96,7 @@ func LoadProperty(data interface{}, ctx *LoadContext) (interface{}, error) { } // Save serializes Property to map[string]interface{} -func (obj *Property) Save(ctx *SaveContext) map[string]interface{} { +func (obj Property) Save(ctx *SaveContext) map[string]interface{} { result := make(map[string]interface{}) result["name"] = obj.Name result["kind"] = obj.Kind @@ -130,11 +132,7 @@ func (obj *Property) ToJSON() (string, error) { func (obj *Property) ToYAML() (string, error) { ctx := NewSaveContext() data := obj.Save(ctx) - bytes, err := yaml.Marshal(data) - if err != nil { - return "", err - } - return string(bytes), nil + return marshalYAMLDocument(data) } // FromJSON creates Property from JSON string @@ -181,6 +179,9 @@ func LoadArrayProperty(data interface{}, ctx *LoadContext) (ArrayProperty, error loaded, _ := LoadProperty(m, ctx) // Polymorphic type - keep as interface{} result.Items = loaded + } else { + loaded, _ := LoadProperty(val, ctx) + result.Items = loaded } } } @@ -189,7 +190,7 @@ func LoadArrayProperty(data interface{}, ctx *LoadContext) (ArrayProperty, error } // Save serializes ArrayProperty to map[string]interface{} -func (obj *ArrayProperty) Save(ctx *SaveContext) map[string]interface{} { +func (obj ArrayProperty) Save(ctx *SaveContext) map[string]interface{} { result := make(map[string]interface{}) result["kind"] = obj.Kind @@ -221,11 +222,7 @@ func (obj *ArrayProperty) ToJSON() (string, error) { func (obj *ArrayProperty) ToYAML() (string, error) { ctx := NewSaveContext() data := obj.Save(ctx) - bytes, err := yaml.Marshal(data) - if err != nil { - return "", err - } - return string(bytes), nil + return marshalYAMLDocument(data) } // FromJSON creates ArrayProperty from JSON string @@ -283,7 +280,7 @@ func LoadObjectProperty(data interface{}, ctx *LoadContext) (ObjectProperty, err } // Save serializes ObjectProperty to map[string]interface{} -func (obj *ObjectProperty) Save(ctx *SaveContext) map[string]interface{} { +func (obj ObjectProperty) Save(ctx *SaveContext) map[string]interface{} { result := make(map[string]interface{}) result["kind"] = obj.Kind if obj.Properties != nil { @@ -320,11 +317,7 @@ func (obj *ObjectProperty) ToJSON() (string, error) { func (obj *ObjectProperty) ToYAML() (string, error) { ctx := NewSaveContext() data := obj.Save(ctx) - bytes, err := yaml.Marshal(data) - if err != nil { - return "", err - } - return string(bytes), nil + return marshalYAMLDocument(data) } // FromJSON creates ObjectProperty from JSON string diff --git a/runtime/go/prompty/model/property_test.go b/runtime/go/prompty/model/property_test.go index 33f3cfe1..306569bd 100644 --- a/runtime/go/prompty/model/property_test.go +++ b/runtime/go/prompty/model/property_test.go @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. package prompty_test @@ -73,6 +74,58 @@ enumValues: // Note: Validation skipped for polymorphic base types - test child types directly } +// TestPropertyFromJSON tests loading Property through the generated JSON helper +func TestPropertyFromJSON(t *testing.T) { + jsonData := ` +{ + "name": "my-input", + "kind": "string", + "description": "A description of the input property", + "required": true, + "default": "default value", + "example": "example value", + "enumValues": [ + "value1", + "value2", + "value3" + ] +} +` + + instance, err := prompty.PropertyFromJSON(jsonData) + if err != nil { + t.Fatalf("Failed to load Property from JSON helper: %v", err) + } + // Polymorphic types return interface{}, extract common fields via reflection or type-specific access + _ = instance // Load succeeded, exact type depends on discriminator + // Note: Validation skipped for polymorphic base types - test child types directly +} + +// TestPropertyFromYAML tests loading Property through the generated YAML helper +func TestPropertyFromYAML(t *testing.T) { + yamlData := ` +name: my-input +kind: string +description: A description of the input property +required: true +default: default value +example: example value +enumValues: + - value1 + - value2 + - value3 + +` + + instance, err := prompty.PropertyFromYAML(yamlData) + if err != nil { + t.Fatalf("Failed to load Property from YAML helper: %v", err) + } + // Polymorphic types return interface{}, extract common fields via reflection or type-specific access + _ = instance // Load succeeded, exact type depends on discriminator + // Note: Validation skipped for polymorphic base types - test child types directly +} + // TestPropertyRoundtrip tests load -> save -> load produces equivalent data func TestPropertyRoundtrip(t *testing.T) { jsonData := ` @@ -169,6 +222,13 @@ func TestPropertyToYAML(t *testing.T) { // Note: ToYAML test skipped for polymorphic base types - test child types directly } +// TestPropertyFromJSONInvalid rejects malformed JSON instead of silently defaulting +func TestPropertyFromJSONInvalid(t *testing.T) { + if _, err := prompty.PropertyFromJSON("{"); err == nil { + t.Fatalf("Expected malformed JSON to fail") + } +} + // TestPropertyFromInput tests loading Property from bool func TestPropertyFromInput(t *testing.T) { ctx := prompty.NewLoadContext() diff --git a/runtime/go/prompty/model/redacted_field.go b/runtime/go/prompty/model/redacted_field.go new file mode 100644 index 00000000..1c2821a0 --- /dev/null +++ b/runtime/go/prompty/model/redacted_field.go @@ -0,0 +1,101 @@ +// +// Code generated by Typra emitter; DO NOT EDIT. +// Group: events + +package prompty + +import ( + "encoding/json" + + "gopkg.in/yaml.v3" +) + +// RedactionMode represents the allowed values for RedactionMode. +type RedactionMode string + +const ( + RedactionModeNone RedactionMode = "none" + RedactionModeRedacted RedactionMode = "redacted" + RedactionModeHashed RedactionMode = "hashed" + RedactionModeSummary RedactionMode = "summary" + RedactionModeReference RedactionMode = "reference" +) + +// RedactedField represents Redaction handling for one JSON-shaped field path. + +type RedactedField struct { + Path string `json:"path" yaml:"path"` + Mode RedactionMode `json:"mode" yaml:"mode"` + Reason *string `json:"reason,omitempty" yaml:"reason,omitempty"` +} + +// LoadRedactedField creates a RedactedField from a map[string]interface{} +func LoadRedactedField(data interface{}, ctx *LoadContext) (RedactedField, error) { + result := RedactedField{} + + // Load from map + if m, ok := data.(map[string]interface{}); ok { + if val, ok := m["path"]; ok && val != nil { + result.Path = string(val.(string)) + } + if val, ok := m["mode"]; ok && val != nil { + result.Mode = RedactionMode(val.(string)) + } + if val, ok := m["reason"]; ok && val != nil { + v := string(val.(string)) + result.Reason = &v + } + } + + return result, nil +} + +// Save serializes RedactedField to map[string]interface{} +func (obj RedactedField) Save(ctx *SaveContext) map[string]interface{} { + result := make(map[string]interface{}) + result["path"] = obj.Path + result["mode"] = string(obj.Mode) + if obj.Reason != nil { + result["reason"] = *obj.Reason + } + + return result +} + +// ToJSON serializes RedactedField to JSON string +func (obj *RedactedField) ToJSON() (string, error) { + ctx := NewSaveContext() + data := obj.Save(ctx) + bytes, err := json.Marshal(data) + if err != nil { + return "", err + } + return string(bytes), nil +} + +// ToYAML serializes RedactedField to YAML string +func (obj *RedactedField) ToYAML() (string, error) { + ctx := NewSaveContext() + data := obj.Save(ctx) + return marshalYAMLDocument(data) +} + +// FromJSON creates RedactedField from JSON string +func RedactedFieldFromJSON(jsonStr string) (RedactedField, error) { + var data map[string]interface{} + if err := json.Unmarshal([]byte(jsonStr), &data); err != nil { + return RedactedField{}, err + } + ctx := NewLoadContext() + return LoadRedactedField(data, ctx) +} + +// FromYAML creates RedactedField from YAML string +func RedactedFieldFromYAML(yamlStr string) (RedactedField, error) { + var data map[string]interface{} + if err := yaml.Unmarshal([]byte(yamlStr), &data); err != nil { + return RedactedField{}, err + } + ctx := NewLoadContext() + return LoadRedactedField(data, ctx) +} diff --git a/runtime/go/prompty/model/redacted_field_test.go b/runtime/go/prompty/model/redacted_field_test.go new file mode 100644 index 00000000..e628566b --- /dev/null +++ b/runtime/go/prompty/model/redacted_field_test.go @@ -0,0 +1,253 @@ +// +// Code generated by Typra emitter; DO NOT EDIT. + +package prompty_test + +import ( + "encoding/json" + "testing" + + "gopkg.in/yaml.v3" + + "prompty/model" +) + +// TestRedactedFieldLoadJSON tests loading RedactedField from JSON +func TestRedactedFieldLoadJSON(t *testing.T) { + jsonData := ` +{ + "path": "$.arguments.apiKey", + "mode": "redacted", + "reason": "secret" +} +` + var data map[string]interface{} + if err := json.Unmarshal([]byte(jsonData), &data); err != nil { + t.Fatalf("Failed to parse JSON: %v", err) + } + + ctx := prompty.NewLoadContext() + instance, err := prompty.LoadRedactedField(data, ctx) + if err != nil { + t.Fatalf("Failed to load RedactedField: %v", err) + } + if instance.Path != "$.arguments.apiKey" { + t.Errorf(`Expected Path to be "$.arguments.apiKey", got %v`, instance.Path) + } + if instance.Mode != "redacted" { + t.Errorf(`Expected Mode to be "redacted", got %v`, instance.Mode) + } + if instance.Reason == nil || *instance.Reason != "secret" { + t.Errorf(`Expected Reason to be "secret", got %v`, instance.Reason) + } +} + +// TestRedactedFieldLoadYAML tests loading RedactedField from YAML +func TestRedactedFieldLoadYAML(t *testing.T) { + yamlData := ` +path: $.arguments.apiKey +mode: redacted +reason: secret + +` + var data map[string]interface{} + if err := yaml.Unmarshal([]byte(yamlData), &data); err != nil { + t.Fatalf("Failed to parse YAML: %v", err) + } + + ctx := prompty.NewLoadContext() + instance, err := prompty.LoadRedactedField(data, ctx) + if err != nil { + t.Fatalf("Failed to load RedactedField: %v", err) + } + if instance.Path != "$.arguments.apiKey" { + t.Errorf(`Expected Path to be "$.arguments.apiKey", got %v`, instance.Path) + } + if instance.Mode != "redacted" { + t.Errorf(`Expected Mode to be "redacted", got %v`, instance.Mode) + } + if instance.Reason == nil || *instance.Reason != "secret" { + t.Errorf(`Expected Reason to be "secret", got %v`, instance.Reason) + } +} + +// TestRedactedFieldFromJSON tests loading RedactedField through the generated JSON helper +func TestRedactedFieldFromJSON(t *testing.T) { + jsonData := ` +{ + "path": "$.arguments.apiKey", + "mode": "redacted", + "reason": "secret" +} +` + + instance, err := prompty.RedactedFieldFromJSON(jsonData) + if err != nil { + t.Fatalf("Failed to load RedactedField from JSON helper: %v", err) + } + if instance.Path != "$.arguments.apiKey" { + t.Errorf(`Expected Path to be "$.arguments.apiKey", got %v`, instance.Path) + } + if instance.Mode != "redacted" { + t.Errorf(`Expected Mode to be "redacted", got %v`, instance.Mode) + } + if instance.Reason == nil || *instance.Reason != "secret" { + t.Errorf(`Expected Reason to be "secret", got %v`, instance.Reason) + } +} + +// TestRedactedFieldFromYAML tests loading RedactedField through the generated YAML helper +func TestRedactedFieldFromYAML(t *testing.T) { + yamlData := ` +path: $.arguments.apiKey +mode: redacted +reason: secret + +` + + instance, err := prompty.RedactedFieldFromYAML(yamlData) + if err != nil { + t.Fatalf("Failed to load RedactedField from YAML helper: %v", err) + } + if instance.Path != "$.arguments.apiKey" { + t.Errorf(`Expected Path to be "$.arguments.apiKey", got %v`, instance.Path) + } + if instance.Mode != "redacted" { + t.Errorf(`Expected Mode to be "redacted", got %v`, instance.Mode) + } + if instance.Reason == nil || *instance.Reason != "secret" { + t.Errorf(`Expected Reason to be "secret", got %v`, instance.Reason) + } +} + +// TestRedactedFieldRoundtrip tests load -> save -> load produces equivalent data +func TestRedactedFieldRoundtrip(t *testing.T) { + jsonData := ` +{ + "path": "$.arguments.apiKey", + "mode": "redacted", + "reason": "secret" +} +` + var data map[string]interface{} + if err := json.Unmarshal([]byte(jsonData), &data); err != nil { + t.Fatalf("Failed to parse JSON: %v", err) + } + + loadCtx := prompty.NewLoadContext() + instance, err := prompty.LoadRedactedField(data, loadCtx) + if err != nil { + t.Fatalf("Failed to load RedactedField: %v", err) + } + saveCtx := prompty.NewSaveContext() + savedData := instance.Save(saveCtx) + + reloaded, err := prompty.LoadRedactedField(savedData, loadCtx) + if err != nil { + t.Fatalf("Failed to reload RedactedField: %v", err) + } + if reloaded.Path != "$.arguments.apiKey" { + t.Errorf(`Expected Path to be "$.arguments.apiKey", got %v`, reloaded.Path) + } + if reloaded.Mode != "redacted" { + t.Errorf(`Expected Mode to be "redacted", got %v`, reloaded.Mode) + } + if reloaded.Reason == nil || *reloaded.Reason != "secret" { + t.Errorf(`Expected Reason to be "secret", got %v`, reloaded.Reason) + } +} + +// TestRedactedFieldToJSON tests that ToJSON produces valid JSON +func TestRedactedFieldToJSON(t *testing.T) { + jsonData := ` +{ + "path": "$.arguments.apiKey", + "mode": "redacted", + "reason": "secret" +} +` + var data map[string]interface{} + if err := json.Unmarshal([]byte(jsonData), &data); err != nil { + t.Fatalf("Failed to parse JSON: %v", err) + } + + ctx := prompty.NewLoadContext() + instance, err := prompty.LoadRedactedField(data, ctx) + if err != nil { + t.Fatalf("Failed to load RedactedField: %v", err) + } + jsonOutput, err := instance.ToJSON() + if err != nil { + t.Fatalf("Failed to convert to JSON: %v", err) + } + + var parsed map[string]interface{} + if err := json.Unmarshal([]byte(jsonOutput), &parsed); err != nil { + t.Fatalf("Failed to parse generated JSON: %v", err) + } + + reloaded, err := prompty.LoadRedactedField(parsed, ctx) + if err != nil { + t.Fatalf("Failed to reload generated JSON: %v", err) + } + if reloaded.Path != "$.arguments.apiKey" { + t.Errorf(`Expected Path to be "$.arguments.apiKey", got %v`, reloaded.Path) + } + if reloaded.Mode != "redacted" { + t.Errorf(`Expected Mode to be "redacted", got %v`, reloaded.Mode) + } + if reloaded.Reason == nil || *reloaded.Reason != "secret" { + t.Errorf(`Expected Reason to be "secret", got %v`, reloaded.Reason) + } +} + +// TestRedactedFieldToYAML tests that ToYAML produces valid YAML +func TestRedactedFieldToYAML(t *testing.T) { + jsonData := ` +{ + "path": "$.arguments.apiKey", + "mode": "redacted", + "reason": "secret" +} +` + var data map[string]interface{} + if err := json.Unmarshal([]byte(jsonData), &data); err != nil { + t.Fatalf("Failed to parse JSON: %v", err) + } + + ctx := prompty.NewLoadContext() + instance, err := prompty.LoadRedactedField(data, ctx) + if err != nil { + t.Fatalf("Failed to load RedactedField: %v", err) + } + yamlOutput, err := instance.ToYAML() + if err != nil { + t.Fatalf("Failed to convert to YAML: %v", err) + } + + var parsed map[string]interface{} + if err := yaml.Unmarshal([]byte(yamlOutput), &parsed); err != nil { + t.Fatalf("Failed to parse generated YAML: %v", err) + } + + reloaded, err := prompty.LoadRedactedField(parsed, ctx) + if err != nil { + t.Fatalf("Failed to reload generated YAML: %v", err) + } + if reloaded.Path != "$.arguments.apiKey" { + t.Errorf(`Expected Path to be "$.arguments.apiKey", got %v`, reloaded.Path) + } + if reloaded.Mode != "redacted" { + t.Errorf(`Expected Mode to be "redacted", got %v`, reloaded.Mode) + } + if reloaded.Reason == nil || *reloaded.Reason != "secret" { + t.Errorf(`Expected Reason to be "secret", got %v`, reloaded.Reason) + } +} + +// TestRedactedFieldFromJSONInvalid rejects malformed JSON instead of silently defaulting +func TestRedactedFieldFromJSONInvalid(t *testing.T) { + if _, err := prompty.RedactedFieldFromJSON("{"); err == nil { + t.Fatalf("Expected malformed JSON to fail") + } +} diff --git a/runtime/go/prompty/model/redaction_metadata.go b/runtime/go/prompty/model/redaction_metadata.go new file mode 100644 index 00000000..c4fb9da4 --- /dev/null +++ b/runtime/go/prompty/model/redaction_metadata.go @@ -0,0 +1,107 @@ +// +// Code generated by Typra emitter; DO NOT EDIT. +// Group: events + +package prompty + +import ( + "encoding/json" + + "gopkg.in/yaml.v3" +) + +// RedactionMetadata represents Metadata describing whether and how a payload was sanitized. + +type RedactionMetadata struct { + Sanitized *bool `json:"sanitized,omitempty" yaml:"sanitized,omitempty"` + Fields []RedactedField `json:"fields,omitempty" yaml:"fields,omitempty"` + Policy *string `json:"policy,omitempty" yaml:"policy,omitempty"` +} + +// LoadRedactionMetadata creates a RedactionMetadata from a map[string]interface{} +func LoadRedactionMetadata(data interface{}, ctx *LoadContext) (RedactionMetadata, error) { + result := RedactionMetadata{} + + // Load from map + if m, ok := data.(map[string]interface{}); ok { + if val, ok := m["sanitized"]; ok && val != nil { + v := val.(bool) + result.Sanitized = &v + } + if val, ok := m["fields"]; ok && val != nil { + if arr, ok := val.([]interface{}); ok { + result.Fields = make([]RedactedField, len(arr)) + for i, v := range arr { + if item, ok := v.(map[string]interface{}); ok { + loaded, _ := LoadRedactedField(item, ctx) + result.Fields[i] = loaded + } + } + } + } + if val, ok := m["policy"]; ok && val != nil { + v := string(val.(string)) + result.Policy = &v + } + } + + return result, nil +} + +// Save serializes RedactionMetadata to map[string]interface{} +func (obj RedactionMetadata) Save(ctx *SaveContext) map[string]interface{} { + result := make(map[string]interface{}) + if obj.Sanitized != nil { + result["sanitized"] = *obj.Sanitized + } + if obj.Fields != nil { + arr := make([]interface{}, len(obj.Fields)) + for i, item := range obj.Fields { + arr[i] = item.Save(ctx) + } + result["fields"] = arr + } + if obj.Policy != nil { + result["policy"] = *obj.Policy + } + + return result +} + +// ToJSON serializes RedactionMetadata to JSON string +func (obj *RedactionMetadata) ToJSON() (string, error) { + ctx := NewSaveContext() + data := obj.Save(ctx) + bytes, err := json.Marshal(data) + if err != nil { + return "", err + } + return string(bytes), nil +} + +// ToYAML serializes RedactionMetadata to YAML string +func (obj *RedactionMetadata) ToYAML() (string, error) { + ctx := NewSaveContext() + data := obj.Save(ctx) + return marshalYAMLDocument(data) +} + +// FromJSON creates RedactionMetadata from JSON string +func RedactionMetadataFromJSON(jsonStr string) (RedactionMetadata, error) { + var data map[string]interface{} + if err := json.Unmarshal([]byte(jsonStr), &data); err != nil { + return RedactionMetadata{}, err + } + ctx := NewLoadContext() + return LoadRedactionMetadata(data, ctx) +} + +// FromYAML creates RedactionMetadata from YAML string +func RedactionMetadataFromYAML(yamlStr string) (RedactionMetadata, error) { + var data map[string]interface{} + if err := yaml.Unmarshal([]byte(yamlStr), &data); err != nil { + return RedactionMetadata{}, err + } + ctx := NewLoadContext() + return LoadRedactionMetadata(data, ctx) +} diff --git a/runtime/go/prompty/model/redaction_metadata_test.go b/runtime/go/prompty/model/redaction_metadata_test.go new file mode 100644 index 00000000..d09d5c7d --- /dev/null +++ b/runtime/go/prompty/model/redaction_metadata_test.go @@ -0,0 +1,225 @@ +// +// Code generated by Typra emitter; DO NOT EDIT. + +package prompty_test + +import ( + "encoding/json" + "testing" + + "gopkg.in/yaml.v3" + + "prompty/model" +) + +// TestRedactionMetadataLoadJSON tests loading RedactionMetadata from JSON +func TestRedactionMetadataLoadJSON(t *testing.T) { + jsonData := ` +{ + "sanitized": true, + "policy": "default-v1" +} +` + var data map[string]interface{} + if err := json.Unmarshal([]byte(jsonData), &data); err != nil { + t.Fatalf("Failed to parse JSON: %v", err) + } + + ctx := prompty.NewLoadContext() + instance, err := prompty.LoadRedactionMetadata(data, ctx) + if err != nil { + t.Fatalf("Failed to load RedactionMetadata: %v", err) + } + if instance.Sanitized == nil || *instance.Sanitized != true { + t.Errorf(`Expected Sanitized to be true, got %v`, instance.Sanitized) + } + if instance.Policy == nil || *instance.Policy != "default-v1" { + t.Errorf(`Expected Policy to be "default-v1", got %v`, instance.Policy) + } +} + +// TestRedactionMetadataLoadYAML tests loading RedactionMetadata from YAML +func TestRedactionMetadataLoadYAML(t *testing.T) { + yamlData := ` +sanitized: true +policy: default-v1 + +` + var data map[string]interface{} + if err := yaml.Unmarshal([]byte(yamlData), &data); err != nil { + t.Fatalf("Failed to parse YAML: %v", err) + } + + ctx := prompty.NewLoadContext() + instance, err := prompty.LoadRedactionMetadata(data, ctx) + if err != nil { + t.Fatalf("Failed to load RedactionMetadata: %v", err) + } + if instance.Sanitized == nil || *instance.Sanitized != true { + t.Errorf(`Expected Sanitized to be true, got %v`, instance.Sanitized) + } + if instance.Policy == nil || *instance.Policy != "default-v1" { + t.Errorf(`Expected Policy to be "default-v1", got %v`, instance.Policy) + } +} + +// TestRedactionMetadataFromJSON tests loading RedactionMetadata through the generated JSON helper +func TestRedactionMetadataFromJSON(t *testing.T) { + jsonData := ` +{ + "sanitized": true, + "policy": "default-v1" +} +` + + instance, err := prompty.RedactionMetadataFromJSON(jsonData) + if err != nil { + t.Fatalf("Failed to load RedactionMetadata from JSON helper: %v", err) + } + if instance.Sanitized == nil || *instance.Sanitized != true { + t.Errorf(`Expected Sanitized to be true, got %v`, instance.Sanitized) + } + if instance.Policy == nil || *instance.Policy != "default-v1" { + t.Errorf(`Expected Policy to be "default-v1", got %v`, instance.Policy) + } +} + +// TestRedactionMetadataFromYAML tests loading RedactionMetadata through the generated YAML helper +func TestRedactionMetadataFromYAML(t *testing.T) { + yamlData := ` +sanitized: true +policy: default-v1 + +` + + instance, err := prompty.RedactionMetadataFromYAML(yamlData) + if err != nil { + t.Fatalf("Failed to load RedactionMetadata from YAML helper: %v", err) + } + if instance.Sanitized == nil || *instance.Sanitized != true { + t.Errorf(`Expected Sanitized to be true, got %v`, instance.Sanitized) + } + if instance.Policy == nil || *instance.Policy != "default-v1" { + t.Errorf(`Expected Policy to be "default-v1", got %v`, instance.Policy) + } +} + +// TestRedactionMetadataRoundtrip tests load -> save -> load produces equivalent data +func TestRedactionMetadataRoundtrip(t *testing.T) { + jsonData := ` +{ + "sanitized": true, + "policy": "default-v1" +} +` + var data map[string]interface{} + if err := json.Unmarshal([]byte(jsonData), &data); err != nil { + t.Fatalf("Failed to parse JSON: %v", err) + } + + loadCtx := prompty.NewLoadContext() + instance, err := prompty.LoadRedactionMetadata(data, loadCtx) + if err != nil { + t.Fatalf("Failed to load RedactionMetadata: %v", err) + } + saveCtx := prompty.NewSaveContext() + savedData := instance.Save(saveCtx) + + reloaded, err := prompty.LoadRedactionMetadata(savedData, loadCtx) + if err != nil { + t.Fatalf("Failed to reload RedactionMetadata: %v", err) + } + if reloaded.Sanitized == nil || *reloaded.Sanitized != true { + t.Errorf(`Expected Sanitized to be true, got %v`, reloaded.Sanitized) + } + if reloaded.Policy == nil || *reloaded.Policy != "default-v1" { + t.Errorf(`Expected Policy to be "default-v1", got %v`, reloaded.Policy) + } +} + +// TestRedactionMetadataToJSON tests that ToJSON produces valid JSON +func TestRedactionMetadataToJSON(t *testing.T) { + jsonData := ` +{ + "sanitized": true, + "policy": "default-v1" +} +` + var data map[string]interface{} + if err := json.Unmarshal([]byte(jsonData), &data); err != nil { + t.Fatalf("Failed to parse JSON: %v", err) + } + + ctx := prompty.NewLoadContext() + instance, err := prompty.LoadRedactionMetadata(data, ctx) + if err != nil { + t.Fatalf("Failed to load RedactionMetadata: %v", err) + } + jsonOutput, err := instance.ToJSON() + if err != nil { + t.Fatalf("Failed to convert to JSON: %v", err) + } + + var parsed map[string]interface{} + if err := json.Unmarshal([]byte(jsonOutput), &parsed); err != nil { + t.Fatalf("Failed to parse generated JSON: %v", err) + } + + reloaded, err := prompty.LoadRedactionMetadata(parsed, ctx) + if err != nil { + t.Fatalf("Failed to reload generated JSON: %v", err) + } + if reloaded.Sanitized == nil || *reloaded.Sanitized != true { + t.Errorf(`Expected Sanitized to be true, got %v`, reloaded.Sanitized) + } + if reloaded.Policy == nil || *reloaded.Policy != "default-v1" { + t.Errorf(`Expected Policy to be "default-v1", got %v`, reloaded.Policy) + } +} + +// TestRedactionMetadataToYAML tests that ToYAML produces valid YAML +func TestRedactionMetadataToYAML(t *testing.T) { + jsonData := ` +{ + "sanitized": true, + "policy": "default-v1" +} +` + var data map[string]interface{} + if err := json.Unmarshal([]byte(jsonData), &data); err != nil { + t.Fatalf("Failed to parse JSON: %v", err) + } + + ctx := prompty.NewLoadContext() + instance, err := prompty.LoadRedactionMetadata(data, ctx) + if err != nil { + t.Fatalf("Failed to load RedactionMetadata: %v", err) + } + yamlOutput, err := instance.ToYAML() + if err != nil { + t.Fatalf("Failed to convert to YAML: %v", err) + } + + var parsed map[string]interface{} + if err := yaml.Unmarshal([]byte(yamlOutput), &parsed); err != nil { + t.Fatalf("Failed to parse generated YAML: %v", err) + } + + reloaded, err := prompty.LoadRedactionMetadata(parsed, ctx) + if err != nil { + t.Fatalf("Failed to reload generated YAML: %v", err) + } + if reloaded.Sanitized == nil || *reloaded.Sanitized != true { + t.Errorf(`Expected Sanitized to be true, got %v`, reloaded.Sanitized) + } + if reloaded.Policy == nil || *reloaded.Policy != "default-v1" { + t.Errorf(`Expected Policy to be "default-v1", got %v`, reloaded.Policy) + } +} + +// TestRedactionMetadataFromJSONInvalid rejects malformed JSON instead of silently defaulting +func TestRedactionMetadataFromJSONInvalid(t *testing.T) { + if _, err := prompty.RedactionMetadataFromJSON("{"); err == nil { + t.Fatalf("Expected malformed JSON to fail") + } +} diff --git a/runtime/go/prompty/model/reference_connection_test.go b/runtime/go/prompty/model/reference_connection_test.go index eb94de54..0e3ff071 100644 --- a/runtime/go/prompty/model/reference_connection_test.go +++ b/runtime/go/prompty/model/reference_connection_test.go @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. package prompty_test @@ -70,6 +71,55 @@ target: my-target-resource } } +// TestReferenceConnectionFromJSON tests loading ReferenceConnection through the generated JSON helper +func TestReferenceConnectionFromJSON(t *testing.T) { + jsonData := ` +{ + "kind": "reference", + "name": "my-reference-connection", + "target": "my-target-resource" +} +` + + instance, err := prompty.ReferenceConnectionFromJSON(jsonData) + if err != nil { + t.Fatalf("Failed to load ReferenceConnection from JSON helper: %v", err) + } + if instance.Kind != "reference" { + t.Errorf(`Expected Kind to be "reference", got %v`, instance.Kind) + } + if instance.Name != "my-reference-connection" { + t.Errorf(`Expected Name to be "my-reference-connection", got %v`, instance.Name) + } + if instance.Target == nil || *instance.Target != "my-target-resource" { + t.Errorf(`Expected Target to be "my-target-resource", got %v`, instance.Target) + } +} + +// TestReferenceConnectionFromYAML tests loading ReferenceConnection through the generated YAML helper +func TestReferenceConnectionFromYAML(t *testing.T) { + yamlData := ` +kind: reference +name: my-reference-connection +target: my-target-resource + +` + + instance, err := prompty.ReferenceConnectionFromYAML(yamlData) + if err != nil { + t.Fatalf("Failed to load ReferenceConnection from YAML helper: %v", err) + } + if instance.Kind != "reference" { + t.Errorf(`Expected Kind to be "reference", got %v`, instance.Kind) + } + if instance.Name != "my-reference-connection" { + t.Errorf(`Expected Name to be "my-reference-connection", got %v`, instance.Name) + } + if instance.Target == nil || *instance.Target != "my-target-resource" { + t.Errorf(`Expected Target to be "my-target-resource", got %v`, instance.Target) + } +} + // TestReferenceConnectionRoundtrip tests load -> save -> load produces equivalent data func TestReferenceConnectionRoundtrip(t *testing.T) { jsonData := ` @@ -135,6 +185,20 @@ func TestReferenceConnectionToJSON(t *testing.T) { if err := json.Unmarshal([]byte(jsonOutput), &parsed); err != nil { t.Fatalf("Failed to parse generated JSON: %v", err) } + + reloaded, err := prompty.LoadReferenceConnection(parsed, ctx) + if err != nil { + t.Fatalf("Failed to reload generated JSON: %v", err) + } + if reloaded.Kind != "reference" { + t.Errorf(`Expected Kind to be "reference", got %v`, reloaded.Kind) + } + if reloaded.Name != "my-reference-connection" { + t.Errorf(`Expected Name to be "my-reference-connection", got %v`, reloaded.Name) + } + if reloaded.Target == nil || *reloaded.Target != "my-target-resource" { + t.Errorf(`Expected Target to be "my-target-resource", got %v`, reloaded.Target) + } } // TestReferenceConnectionToYAML tests that ToYAML produces valid YAML @@ -165,4 +229,25 @@ func TestReferenceConnectionToYAML(t *testing.T) { if err := yaml.Unmarshal([]byte(yamlOutput), &parsed); err != nil { t.Fatalf("Failed to parse generated YAML: %v", err) } + + reloaded, err := prompty.LoadReferenceConnection(parsed, ctx) + if err != nil { + t.Fatalf("Failed to reload generated YAML: %v", err) + } + if reloaded.Kind != "reference" { + t.Errorf(`Expected Kind to be "reference", got %v`, reloaded.Kind) + } + if reloaded.Name != "my-reference-connection" { + t.Errorf(`Expected Name to be "my-reference-connection", got %v`, reloaded.Name) + } + if reloaded.Target == nil || *reloaded.Target != "my-target-resource" { + t.Errorf(`Expected Target to be "my-target-resource", got %v`, reloaded.Target) + } +} + +// TestReferenceConnectionFromJSONInvalid rejects malformed JSON instead of silently defaulting +func TestReferenceConnectionFromJSONInvalid(t *testing.T) { + if _, err := prompty.ReferenceConnectionFromJSON("{"); err == nil { + t.Fatalf("Expected malformed JSON to fail") + } } diff --git a/runtime/go/prompty/model/remote_connection_test.go b/runtime/go/prompty/model/remote_connection_test.go index 91155772..2cb55b1c 100644 --- a/runtime/go/prompty/model/remote_connection_test.go +++ b/runtime/go/prompty/model/remote_connection_test.go @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. package prompty_test @@ -70,6 +71,55 @@ endpoint: "https://{your-custom-endpoint}.openai.azure.com/" } } +// TestRemoteConnectionFromJSON tests loading RemoteConnection through the generated JSON helper +func TestRemoteConnectionFromJSON(t *testing.T) { + jsonData := ` +{ + "kind": "remote", + "name": "my-reference-connection", + "endpoint": "https://{your-custom-endpoint}.openai.azure.com/" +} +` + + instance, err := prompty.RemoteConnectionFromJSON(jsonData) + if err != nil { + t.Fatalf("Failed to load RemoteConnection from JSON helper: %v", err) + } + if instance.Kind != "remote" { + t.Errorf(`Expected Kind to be "remote", got %v`, instance.Kind) + } + if instance.Name != "my-reference-connection" { + t.Errorf(`Expected Name to be "my-reference-connection", got %v`, instance.Name) + } + if instance.Endpoint != "https://{your-custom-endpoint}.openai.azure.com/" { + t.Errorf(`Expected Endpoint to be "https://{your-custom-endpoint}.openai.azure.com/", got %v`, instance.Endpoint) + } +} + +// TestRemoteConnectionFromYAML tests loading RemoteConnection through the generated YAML helper +func TestRemoteConnectionFromYAML(t *testing.T) { + yamlData := ` +kind: remote +name: my-reference-connection +endpoint: "https://{your-custom-endpoint}.openai.azure.com/" + +` + + instance, err := prompty.RemoteConnectionFromYAML(yamlData) + if err != nil { + t.Fatalf("Failed to load RemoteConnection from YAML helper: %v", err) + } + if instance.Kind != "remote" { + t.Errorf(`Expected Kind to be "remote", got %v`, instance.Kind) + } + if instance.Name != "my-reference-connection" { + t.Errorf(`Expected Name to be "my-reference-connection", got %v`, instance.Name) + } + if instance.Endpoint != "https://{your-custom-endpoint}.openai.azure.com/" { + t.Errorf(`Expected Endpoint to be "https://{your-custom-endpoint}.openai.azure.com/", got %v`, instance.Endpoint) + } +} + // TestRemoteConnectionRoundtrip tests load -> save -> load produces equivalent data func TestRemoteConnectionRoundtrip(t *testing.T) { jsonData := ` @@ -135,6 +185,20 @@ func TestRemoteConnectionToJSON(t *testing.T) { if err := json.Unmarshal([]byte(jsonOutput), &parsed); err != nil { t.Fatalf("Failed to parse generated JSON: %v", err) } + + reloaded, err := prompty.LoadRemoteConnection(parsed, ctx) + if err != nil { + t.Fatalf("Failed to reload generated JSON: %v", err) + } + if reloaded.Kind != "remote" { + t.Errorf(`Expected Kind to be "remote", got %v`, reloaded.Kind) + } + if reloaded.Name != "my-reference-connection" { + t.Errorf(`Expected Name to be "my-reference-connection", got %v`, reloaded.Name) + } + if reloaded.Endpoint != "https://{your-custom-endpoint}.openai.azure.com/" { + t.Errorf(`Expected Endpoint to be "https://{your-custom-endpoint}.openai.azure.com/", got %v`, reloaded.Endpoint) + } } // TestRemoteConnectionToYAML tests that ToYAML produces valid YAML @@ -165,4 +229,25 @@ func TestRemoteConnectionToYAML(t *testing.T) { if err := yaml.Unmarshal([]byte(yamlOutput), &parsed); err != nil { t.Fatalf("Failed to parse generated YAML: %v", err) } + + reloaded, err := prompty.LoadRemoteConnection(parsed, ctx) + if err != nil { + t.Fatalf("Failed to reload generated YAML: %v", err) + } + if reloaded.Kind != "remote" { + t.Errorf(`Expected Kind to be "remote", got %v`, reloaded.Kind) + } + if reloaded.Name != "my-reference-connection" { + t.Errorf(`Expected Name to be "my-reference-connection", got %v`, reloaded.Name) + } + if reloaded.Endpoint != "https://{your-custom-endpoint}.openai.azure.com/" { + t.Errorf(`Expected Endpoint to be "https://{your-custom-endpoint}.openai.azure.com/", got %v`, reloaded.Endpoint) + } +} + +// TestRemoteConnectionFromJSONInvalid rejects malformed JSON instead of silently defaulting +func TestRemoteConnectionFromJSONInvalid(t *testing.T) { + if _, err := prompty.RemoteConnectionFromJSON("{"); err == nil { + t.Fatalf("Expected malformed JSON to fail") + } } diff --git a/runtime/go/prompty/model/renderer.go b/runtime/go/prompty/model/renderer.go index a500ca5d..76510e88 100644 --- a/runtime/go/prompty/model/renderer.go +++ b/runtime/go/prompty/model/renderer.go @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. // Group: pipeline package prompty diff --git a/runtime/go/prompty/model/replay_journal_record.go b/runtime/go/prompty/model/replay_journal_record.go new file mode 100644 index 00000000..e06c7617 --- /dev/null +++ b/runtime/go/prompty/model/replay_journal_record.go @@ -0,0 +1,217 @@ +// +// Code generated by Typra emitter; DO NOT EDIT. +// Group: pipeline + +package prompty + +import ( + "encoding/json" + + "gopkg.in/yaml.v3" +) + +// ReplayRecordKind represents the allowed values for ReplayRecordKind. +type ReplayRecordKind string + +const ( + ReplayRecordKindSession ReplayRecordKind = "session" + ReplayRecordKindTurn ReplayRecordKind = "turn" + ReplayRecordKindSummary ReplayRecordKind = "summary" +) + +// ReplayRecordStatus represents the allowed values for ReplayRecordStatus. +type ReplayRecordStatus string + +const ( + ReplayRecordStatusSuccess ReplayRecordStatus = "success" + ReplayRecordStatusError ReplayRecordStatus = "error" + ReplayRecordStatusCancelled ReplayRecordStatus = "cancelled" +) + +// ReplayJournalRecord represents Stable, replay-comparable projection of a journal record. +// +// Runtime journal records may carry additional payload fields, durations, telemetry, +// or provider-specific data. Replay verification compares this normalized shape so +// deterministic orchestration semantics are mechanically shared across runtimes. + +type ReplayJournalRecord struct { + Kind ReplayRecordKind `json:"kind" yaml:"kind"` + Type *string `json:"type,omitempty" yaml:"type,omitempty"` + SessionId *string `json:"sessionId,omitempty" yaml:"sessionId,omitempty"` + TurnId *string `json:"turnId,omitempty" yaml:"turnId,omitempty"` + Iteration *int32 `json:"iteration,omitempty" yaml:"iteration,omitempty"` + Status *ReplayRecordStatus `json:"status,omitempty" yaml:"status,omitempty"` + RequestId *string `json:"requestId,omitempty" yaml:"requestId,omitempty"` + ToolName *string `json:"toolName,omitempty" yaml:"toolName,omitempty"` + Success *bool `json:"success,omitempty" yaml:"success,omitempty"` + ErrorKind *string `json:"errorKind,omitempty" yaml:"errorKind,omitempty"` + Turns *int32 `json:"turns,omitempty" yaml:"turns,omitempty"` + Checkpoints *int32 `json:"checkpoints,omitempty" yaml:"checkpoints,omitempty"` +} + +// LoadReplayJournalRecord creates a ReplayJournalRecord from a map[string]interface{} +func LoadReplayJournalRecord(data interface{}, ctx *LoadContext) (ReplayJournalRecord, error) { + result := ReplayJournalRecord{} + + // Load from map + if m, ok := data.(map[string]interface{}); ok { + if val, ok := m["kind"]; ok && val != nil { + result.Kind = ReplayRecordKind(val.(string)) + } + if val, ok := m["type"]; ok && val != nil { + v := string(val.(string)) + result.Type = &v + } + if val, ok := m["sessionId"]; ok && val != nil { + v := string(val.(string)) + result.SessionId = &v + } + if val, ok := m["turnId"]; ok && val != nil { + v := string(val.(string)) + result.TurnId = &v + } + if val, ok := m["iteration"]; ok && val != nil { // Handle various numeric types from JSON/YAML/roundtrip + var v int32 + switch n := val.(type) { + case int: + v = int32(n) + case int32: + v = int32(n) + case int64: + v = int32(n) + case float64: + v = int32(n) + } + result.Iteration = &v + } + if val, ok := m["status"]; ok && val != nil { + v := ReplayRecordStatus(val.(string)) + result.Status = &v + } + if val, ok := m["requestId"]; ok && val != nil { + v := string(val.(string)) + result.RequestId = &v + } + if val, ok := m["toolName"]; ok && val != nil { + v := string(val.(string)) + result.ToolName = &v + } + if val, ok := m["success"]; ok && val != nil { + v := val.(bool) + result.Success = &v + } + if val, ok := m["errorKind"]; ok && val != nil { + v := string(val.(string)) + result.ErrorKind = &v + } + if val, ok := m["turns"]; ok && val != nil { // Handle various numeric types from JSON/YAML/roundtrip + var v int32 + switch n := val.(type) { + case int: + v = int32(n) + case int32: + v = int32(n) + case int64: + v = int32(n) + case float64: + v = int32(n) + } + result.Turns = &v + } + if val, ok := m["checkpoints"]; ok && val != nil { // Handle various numeric types from JSON/YAML/roundtrip + var v int32 + switch n := val.(type) { + case int: + v = int32(n) + case int32: + v = int32(n) + case int64: + v = int32(n) + case float64: + v = int32(n) + } + result.Checkpoints = &v + } + } + + return result, nil +} + +// Save serializes ReplayJournalRecord to map[string]interface{} +func (obj ReplayJournalRecord) Save(ctx *SaveContext) map[string]interface{} { + result := make(map[string]interface{}) + result["kind"] = string(obj.Kind) + if obj.Type != nil { + result["type"] = *obj.Type + } + if obj.SessionId != nil { + result["sessionId"] = *obj.SessionId + } + if obj.TurnId != nil { + result["turnId"] = *obj.TurnId + } + if obj.Iteration != nil { + result["iteration"] = *obj.Iteration + } + if obj.Status != nil { + result["status"] = string(*obj.Status) + } + if obj.RequestId != nil { + result["requestId"] = *obj.RequestId + } + if obj.ToolName != nil { + result["toolName"] = *obj.ToolName + } + if obj.Success != nil { + result["success"] = *obj.Success + } + if obj.ErrorKind != nil { + result["errorKind"] = *obj.ErrorKind + } + if obj.Turns != nil { + result["turns"] = *obj.Turns + } + if obj.Checkpoints != nil { + result["checkpoints"] = *obj.Checkpoints + } + + return result +} + +// ToJSON serializes ReplayJournalRecord to JSON string +func (obj *ReplayJournalRecord) ToJSON() (string, error) { + ctx := NewSaveContext() + data := obj.Save(ctx) + bytes, err := json.Marshal(data) + if err != nil { + return "", err + } + return string(bytes), nil +} + +// ToYAML serializes ReplayJournalRecord to YAML string +func (obj *ReplayJournalRecord) ToYAML() (string, error) { + ctx := NewSaveContext() + data := obj.Save(ctx) + return marshalYAMLDocument(data) +} + +// FromJSON creates ReplayJournalRecord from JSON string +func ReplayJournalRecordFromJSON(jsonStr string) (ReplayJournalRecord, error) { + var data map[string]interface{} + if err := json.Unmarshal([]byte(jsonStr), &data); err != nil { + return ReplayJournalRecord{}, err + } + ctx := NewLoadContext() + return LoadReplayJournalRecord(data, ctx) +} + +// FromYAML creates ReplayJournalRecord from YAML string +func ReplayJournalRecordFromYAML(yamlStr string) (ReplayJournalRecord, error) { + var data map[string]interface{} + if err := yaml.Unmarshal([]byte(yamlStr), &data); err != nil { + return ReplayJournalRecord{}, err + } + ctx := NewLoadContext() + return LoadReplayJournalRecord(data, ctx) +} diff --git a/runtime/go/prompty/model/replay_journal_record_test.go b/runtime/go/prompty/model/replay_journal_record_test.go new file mode 100644 index 00000000..9cc5440c --- /dev/null +++ b/runtime/go/prompty/model/replay_journal_record_test.go @@ -0,0 +1,4 @@ +// +// Code generated by Typra emitter; DO NOT EDIT. + +package prompty_test diff --git a/runtime/go/prompty/model/replay_mismatch.go b/runtime/go/prompty/model/replay_mismatch.go new file mode 100644 index 00000000..914fa98d --- /dev/null +++ b/runtime/go/prompty/model/replay_mismatch.go @@ -0,0 +1,113 @@ +// +// Code generated by Typra emitter; DO NOT EDIT. +// Group: pipeline + +package prompty + +import ( + "encoding/json" + + "gopkg.in/yaml.v3" +) + +// ReplayMismatch represents A single mismatch produced by replay verification. + +type ReplayMismatch struct { + Index int32 `json:"index" yaml:"index"` + Expected *ReplayJournalRecord `json:"expected,omitempty" yaml:"expected,omitempty"` + Actual *ReplayJournalRecord `json:"actual,omitempty" yaml:"actual,omitempty"` + Message string `json:"message" yaml:"message"` +} + +// LoadReplayMismatch creates a ReplayMismatch from a map[string]interface{} +func LoadReplayMismatch(data interface{}, ctx *LoadContext) (ReplayMismatch, error) { + result := ReplayMismatch{} + + // Load from map + if m, ok := data.(map[string]interface{}); ok { + if val, ok := m["index"]; ok && val != nil { // Handle various numeric types from JSON/YAML/roundtrip + var v int32 + switch n := val.(type) { + case int: + v = int32(n) + case int32: + v = int32(n) + case int64: + v = int32(n) + case float64: + v = int32(n) + } + result.Index = v + } + if val, ok := m["expected"]; ok && val != nil { + if m, ok := val.(map[string]interface{}); ok { + loaded, _ := LoadReplayJournalRecord(m, ctx) + result.Expected = &loaded + } + } + if val, ok := m["actual"]; ok && val != nil { + if m, ok := val.(map[string]interface{}); ok { + loaded, _ := LoadReplayJournalRecord(m, ctx) + result.Actual = &loaded + } + } + if val, ok := m["message"]; ok && val != nil { + result.Message = string(val.(string)) + } + } + + return result, nil +} + +// Save serializes ReplayMismatch to map[string]interface{} +func (obj ReplayMismatch) Save(ctx *SaveContext) map[string]interface{} { + result := make(map[string]interface{}) + result["index"] = obj.Index + if obj.Expected != nil { + result["expected"] = obj.Expected.Save(ctx) + } + if obj.Actual != nil { + result["actual"] = obj.Actual.Save(ctx) + } + result["message"] = obj.Message + + return result +} + +// ToJSON serializes ReplayMismatch to JSON string +func (obj *ReplayMismatch) ToJSON() (string, error) { + ctx := NewSaveContext() + data := obj.Save(ctx) + bytes, err := json.Marshal(data) + if err != nil { + return "", err + } + return string(bytes), nil +} + +// ToYAML serializes ReplayMismatch to YAML string +func (obj *ReplayMismatch) ToYAML() (string, error) { + ctx := NewSaveContext() + data := obj.Save(ctx) + return marshalYAMLDocument(data) +} + +// FromJSON creates ReplayMismatch from JSON string +func ReplayMismatchFromJSON(jsonStr string) (ReplayMismatch, error) { + var data map[string]interface{} + if err := json.Unmarshal([]byte(jsonStr), &data); err != nil { + return ReplayMismatch{}, err + } + ctx := NewLoadContext() + return LoadReplayMismatch(data, ctx) +} + +// FromYAML creates ReplayMismatch from YAML string +func ReplayMismatchFromYAML(yamlStr string) (ReplayMismatch, error) { + var data map[string]interface{} + if err := yaml.Unmarshal([]byte(yamlStr), &data); err != nil { + return ReplayMismatch{}, err + } + ctx := NewLoadContext() + return LoadReplayMismatch(data, ctx) +} diff --git a/runtime/go/prompty/model/replay_mismatch_test.go b/runtime/go/prompty/model/replay_mismatch_test.go new file mode 100644 index 00000000..9cc5440c --- /dev/null +++ b/runtime/go/prompty/model/replay_mismatch_test.go @@ -0,0 +1,4 @@ +// +// Code generated by Typra emitter; DO NOT EDIT. + +package prompty_test diff --git a/runtime/go/prompty/model/replay_verification_request.go b/runtime/go/prompty/model/replay_verification_request.go new file mode 100644 index 00000000..e4a506f6 --- /dev/null +++ b/runtime/go/prompty/model/replay_verification_request.go @@ -0,0 +1,110 @@ +// +// Code generated by Typra emitter; DO NOT EDIT. +// Group: pipeline + +package prompty + +import ( + "encoding/json" + + "gopkg.in/yaml.v3" +) + +// ReplayVerificationRequest represents Request accepted by a replay verifier implementation. + +type ReplayVerificationRequest struct { + Expected []ReplayJournalRecord `json:"expected" yaml:"expected"` + Actual []ReplayJournalRecord `json:"actual" yaml:"actual"` +} + +// LoadReplayVerificationRequest creates a ReplayVerificationRequest from a map[string]interface{} +func LoadReplayVerificationRequest(data interface{}, ctx *LoadContext) (ReplayVerificationRequest, error) { + result := ReplayVerificationRequest{} + + // Load from map + if m, ok := data.(map[string]interface{}); ok { + if val, ok := m["expected"]; ok && val != nil { + if arr, ok := val.([]interface{}); ok { + result.Expected = make([]ReplayJournalRecord, len(arr)) + for i, v := range arr { + if item, ok := v.(map[string]interface{}); ok { + loaded, _ := LoadReplayJournalRecord(item, ctx) + result.Expected[i] = loaded + } + } + } + } + if val, ok := m["actual"]; ok && val != nil { + if arr, ok := val.([]interface{}); ok { + result.Actual = make([]ReplayJournalRecord, len(arr)) + for i, v := range arr { + if item, ok := v.(map[string]interface{}); ok { + loaded, _ := LoadReplayJournalRecord(item, ctx) + result.Actual[i] = loaded + } + } + } + } + } + + return result, nil +} + +// Save serializes ReplayVerificationRequest to map[string]interface{} +func (obj ReplayVerificationRequest) Save(ctx *SaveContext) map[string]interface{} { + result := make(map[string]interface{}) + if obj.Expected != nil { + arr := make([]interface{}, len(obj.Expected)) + for i, item := range obj.Expected { + arr[i] = item.Save(ctx) + } + result["expected"] = arr + } + if obj.Actual != nil { + arr := make([]interface{}, len(obj.Actual)) + for i, item := range obj.Actual { + arr[i] = item.Save(ctx) + } + result["actual"] = arr + } + + return result +} + +// ToJSON serializes ReplayVerificationRequest to JSON string +func (obj *ReplayVerificationRequest) ToJSON() (string, error) { + ctx := NewSaveContext() + data := obj.Save(ctx) + bytes, err := json.Marshal(data) + if err != nil { + return "", err + } + return string(bytes), nil +} + +// ToYAML serializes ReplayVerificationRequest to YAML string +func (obj *ReplayVerificationRequest) ToYAML() (string, error) { + ctx := NewSaveContext() + data := obj.Save(ctx) + return marshalYAMLDocument(data) +} + +// FromJSON creates ReplayVerificationRequest from JSON string +func ReplayVerificationRequestFromJSON(jsonStr string) (ReplayVerificationRequest, error) { + var data map[string]interface{} + if err := json.Unmarshal([]byte(jsonStr), &data); err != nil { + return ReplayVerificationRequest{}, err + } + ctx := NewLoadContext() + return LoadReplayVerificationRequest(data, ctx) +} + +// FromYAML creates ReplayVerificationRequest from YAML string +func ReplayVerificationRequestFromYAML(yamlStr string) (ReplayVerificationRequest, error) { + var data map[string]interface{} + if err := yaml.Unmarshal([]byte(yamlStr), &data); err != nil { + return ReplayVerificationRequest{}, err + } + ctx := NewLoadContext() + return LoadReplayVerificationRequest(data, ctx) +} diff --git a/runtime/go/prompty/model/replay_verification_request_test.go b/runtime/go/prompty/model/replay_verification_request_test.go new file mode 100644 index 00000000..9cc5440c --- /dev/null +++ b/runtime/go/prompty/model/replay_verification_request_test.go @@ -0,0 +1,4 @@ +// +// Code generated by Typra emitter; DO NOT EDIT. + +package prompty_test diff --git a/runtime/go/prompty/model/replay_verification_result.go b/runtime/go/prompty/model/replay_verification_result.go new file mode 100644 index 00000000..f64d80b1 --- /dev/null +++ b/runtime/go/prompty/model/replay_verification_result.go @@ -0,0 +1,136 @@ +// +// Code generated by Typra emitter; DO NOT EDIT. +// Group: pipeline + +package prompty + +import ( + "encoding/json" + + "gopkg.in/yaml.v3" +) + +// ReplayVerificationStatus represents the allowed values for ReplayVerificationStatus. +type ReplayVerificationStatus string + +const ( + ReplayVerificationStatusPassed ReplayVerificationStatus = "passed" + ReplayVerificationStatusFailed ReplayVerificationStatus = "failed" +) + +// ReplayVerificationResult represents Result returned by a replay verifier implementation. + +type ReplayVerificationResult struct { + Status ReplayVerificationStatus `json:"status" yaml:"status"` + Mismatches []ReplayMismatch `json:"mismatches,omitempty" yaml:"mismatches,omitempty"` + ExpectedCount int32 `json:"expectedCount" yaml:"expectedCount"` + ActualCount int32 `json:"actualCount" yaml:"actualCount"` +} + +// LoadReplayVerificationResult creates a ReplayVerificationResult from a map[string]interface{} +func LoadReplayVerificationResult(data interface{}, ctx *LoadContext) (ReplayVerificationResult, error) { + result := ReplayVerificationResult{} + + // Load from map + if m, ok := data.(map[string]interface{}); ok { + if val, ok := m["status"]; ok && val != nil { + result.Status = ReplayVerificationStatus(val.(string)) + } + if val, ok := m["mismatches"]; ok && val != nil { + if arr, ok := val.([]interface{}); ok { + result.Mismatches = make([]ReplayMismatch, len(arr)) + for i, v := range arr { + if item, ok := v.(map[string]interface{}); ok { + loaded, _ := LoadReplayMismatch(item, ctx) + result.Mismatches[i] = loaded + } + } + } + } + if val, ok := m["expectedCount"]; ok && val != nil { // Handle various numeric types from JSON/YAML/roundtrip + var v int32 + switch n := val.(type) { + case int: + v = int32(n) + case int32: + v = int32(n) + case int64: + v = int32(n) + case float64: + v = int32(n) + } + result.ExpectedCount = v + } + if val, ok := m["actualCount"]; ok && val != nil { // Handle various numeric types from JSON/YAML/roundtrip + var v int32 + switch n := val.(type) { + case int: + v = int32(n) + case int32: + v = int32(n) + case int64: + v = int32(n) + case float64: + v = int32(n) + } + result.ActualCount = v + } + } + + return result, nil +} + +// Save serializes ReplayVerificationResult to map[string]interface{} +func (obj ReplayVerificationResult) Save(ctx *SaveContext) map[string]interface{} { + result := make(map[string]interface{}) + result["status"] = string(obj.Status) + if obj.Mismatches != nil { + arr := make([]interface{}, len(obj.Mismatches)) + for i, item := range obj.Mismatches { + arr[i] = item.Save(ctx) + } + result["mismatches"] = arr + } + result["expectedCount"] = obj.ExpectedCount + result["actualCount"] = obj.ActualCount + + return result +} + +// ToJSON serializes ReplayVerificationResult to JSON string +func (obj *ReplayVerificationResult) ToJSON() (string, error) { + ctx := NewSaveContext() + data := obj.Save(ctx) + bytes, err := json.Marshal(data) + if err != nil { + return "", err + } + return string(bytes), nil +} + +// ToYAML serializes ReplayVerificationResult to YAML string +func (obj *ReplayVerificationResult) ToYAML() (string, error) { + ctx := NewSaveContext() + data := obj.Save(ctx) + return marshalYAMLDocument(data) +} + +// FromJSON creates ReplayVerificationResult from JSON string +func ReplayVerificationResultFromJSON(jsonStr string) (ReplayVerificationResult, error) { + var data map[string]interface{} + if err := json.Unmarshal([]byte(jsonStr), &data); err != nil { + return ReplayVerificationResult{}, err + } + ctx := NewLoadContext() + return LoadReplayVerificationResult(data, ctx) +} + +// FromYAML creates ReplayVerificationResult from YAML string +func ReplayVerificationResultFromYAML(yamlStr string) (ReplayVerificationResult, error) { + var data map[string]interface{} + if err := yaml.Unmarshal([]byte(yamlStr), &data); err != nil { + return ReplayVerificationResult{}, err + } + ctx := NewLoadContext() + return LoadReplayVerificationResult(data, ctx) +} diff --git a/runtime/go/prompty/model/replay_verification_result_test.go b/runtime/go/prompty/model/replay_verification_result_test.go new file mode 100644 index 00000000..9cc5440c --- /dev/null +++ b/runtime/go/prompty/model/replay_verification_result_test.go @@ -0,0 +1,4 @@ +// +// Code generated by Typra emitter; DO NOT EDIT. + +package prompty_test diff --git a/runtime/go/prompty/model/replay_verifier.go b/runtime/go/prompty/model/replay_verifier.go new file mode 100644 index 00000000..2d44e812 --- /dev/null +++ b/runtime/go/prompty/model/replay_verifier.go @@ -0,0 +1,67 @@ +package prompty + +import ( + "encoding/json" + "fmt" +) + +// ReferenceReplayVerifier compares normalized replay journal records. +type ReferenceReplayVerifier struct{} + +// Verify returns a generated replay verification result. +func (ReferenceReplayVerifier) Verify(request ReplayVerificationRequest) ReplayVerificationResult { + expected := request.Expected + actual := request.Actual + max := len(expected) + if len(actual) > max { + max = len(actual) + } + mismatches := []ReplayMismatch{} + + for index := 0; index < max; index++ { + var expectedRecord *ReplayJournalRecord + var actualRecord *ReplayJournalRecord + if index < len(expected) { + expectedRecord = &expected[index] + } + if index < len(actual) { + actualRecord = &actual[index] + } + if comparableReplayRecord(expectedRecord) != comparableReplayRecord(actualRecord) { + message := "Replay record mismatch" + if expectedRecord == nil { + message = "Unexpected extra replay record" + } else if actualRecord == nil { + message = "Missing replay record" + } + mismatches = append(mismatches, ReplayMismatch{ + Index: int32(index), + Expected: expectedRecord, + Actual: actualRecord, + Message: message, + }) + } + } + + status := ReplayVerificationStatusPassed + if len(mismatches) > 0 { + status = ReplayVerificationStatusFailed + } + return ReplayVerificationResult{ + Status: status, + Mismatches: mismatches, + ExpectedCount: int32(len(expected)), + ActualCount: int32(len(actual)), + } +} + +func comparableReplayRecord(record *ReplayJournalRecord) string { + if record == nil { + return "" + } + bytes, err := json.Marshal(record.Save(NewSaveContext())) + if err != nil { + return fmt.Sprintf("", err) + } + return string(bytes) +} diff --git a/runtime/go/prompty/model/replay_verifier_test.go b/runtime/go/prompty/model/replay_verifier_test.go new file mode 100644 index 00000000..d6e108a3 --- /dev/null +++ b/runtime/go/prompty/model/replay_verifier_test.go @@ -0,0 +1,51 @@ +package prompty + +import "testing" + +func TestReferenceReplayVerifierPassesIdenticalRecords(t *testing.T) { + record := ReplayJournalRecord{ + Kind: ReplayRecordKindTurn, + Type: stringPtr("turn_end"), + TurnId: stringPtr("turn-1"), + Iteration: int32Ptr(1), + Status: ptrReplayRecordStatus(ReplayRecordStatusSuccess), + } + + result := ReferenceReplayVerifier{}.Verify(ReplayVerificationRequest{ + Expected: []ReplayJournalRecord{record}, + Actual: []ReplayJournalRecord{record}, + }) + + if result.Status != ReplayVerificationStatusPassed || len(result.Mismatches) != 0 { + t.Fatalf("unexpected result: %#v", result) + } + if result.ExpectedCount != 1 || result.ActualCount != 1 { + t.Fatalf("unexpected counts: %#v", result) + } +} + +func TestReferenceReplayVerifierReportsMismatches(t *testing.T) { + result := ReferenceReplayVerifier{}.Verify(ReplayVerificationRequest{ + Expected: []ReplayJournalRecord{{ + Kind: ReplayRecordKindSummary, + SessionId: stringPtr("session-1"), + Status: ptrReplayRecordStatus(ReplayRecordStatusSuccess), + }}, + Actual: []ReplayJournalRecord{{ + Kind: ReplayRecordKindSummary, + SessionId: stringPtr("session-1"), + Status: ptrReplayRecordStatus(ReplayRecordStatusError), + }}, + }) + + if result.Status != ReplayVerificationStatusFailed { + t.Fatalf("expected failure: %#v", result) + } + if result.Mismatches[0].Index != 0 || result.Mismatches[0].Message != "Replay record mismatch" { + t.Fatalf("unexpected mismatch: %#v", result.Mismatches[0]) + } +} + +func ptrReplayRecordStatus(value ReplayRecordStatus) *ReplayRecordStatus { + return &value +} diff --git a/runtime/go/prompty/model/retry_payload.go b/runtime/go/prompty/model/retry_payload.go new file mode 100644 index 00000000..f87b974e --- /dev/null +++ b/runtime/go/prompty/model/retry_payload.go @@ -0,0 +1,139 @@ +// +// Code generated by Typra emitter; DO NOT EDIT. +// Group: events + +package prompty + +import ( + "encoding/json" + + "gopkg.in/yaml.v3" +) + +// RetryPayload represents Payload for "retry" events — a transient operation will be retried. + +type RetryPayload struct { + Operation string `json:"operation" yaml:"operation"` + Attempt int32 `json:"attempt" yaml:"attempt"` + MaxAttempts *int32 `json:"maxAttempts,omitempty" yaml:"maxAttempts,omitempty"` + DelayMs *float64 `json:"delayMs,omitempty" yaml:"delayMs,omitempty"` + Reason *string `json:"reason,omitempty" yaml:"reason,omitempty"` +} + +// LoadRetryPayload creates a RetryPayload from a map[string]interface{} +func LoadRetryPayload(data interface{}, ctx *LoadContext) (RetryPayload, error) { + result := RetryPayload{} + + // Load from map + if m, ok := data.(map[string]interface{}); ok { + if val, ok := m["operation"]; ok && val != nil { + result.Operation = string(val.(string)) + } + if val, ok := m["attempt"]; ok && val != nil { // Handle various numeric types from JSON/YAML/roundtrip + var v int32 + switch n := val.(type) { + case int: + v = int32(n) + case int32: + v = int32(n) + case int64: + v = int32(n) + case float64: + v = int32(n) + } + result.Attempt = v + } + if val, ok := m["maxAttempts"]; ok && val != nil { // Handle various numeric types from JSON/YAML/roundtrip + var v int32 + switch n := val.(type) { + case int: + v = int32(n) + case int32: + v = int32(n) + case int64: + v = int32(n) + case float64: + v = int32(n) + } + result.MaxAttempts = &v + } + if val, ok := m["delayMs"]; ok && val != nil { // Handle various numeric types from JSON/YAML/roundtrip + var v float64 + switch n := val.(type) { + case int: + v = float64(n) + case int32: + v = float64(n) + case int64: + v = float64(n) + case float32: + v = float64(n) + case float64: + v = n + } + result.DelayMs = &v + } + if val, ok := m["reason"]; ok && val != nil { + v := string(val.(string)) + result.Reason = &v + } + } + + return result, nil +} + +// Save serializes RetryPayload to map[string]interface{} +func (obj RetryPayload) Save(ctx *SaveContext) map[string]interface{} { + result := make(map[string]interface{}) + result["operation"] = obj.Operation + result["attempt"] = obj.Attempt + if obj.MaxAttempts != nil { + result["maxAttempts"] = *obj.MaxAttempts + } + if obj.DelayMs != nil { + result["delayMs"] = *obj.DelayMs + } + if obj.Reason != nil { + result["reason"] = *obj.Reason + } + + return result +} + +// ToJSON serializes RetryPayload to JSON string +func (obj *RetryPayload) ToJSON() (string, error) { + ctx := NewSaveContext() + data := obj.Save(ctx) + bytes, err := json.Marshal(data) + if err != nil { + return "", err + } + return string(bytes), nil +} + +// ToYAML serializes RetryPayload to YAML string +func (obj *RetryPayload) ToYAML() (string, error) { + ctx := NewSaveContext() + data := obj.Save(ctx) + return marshalYAMLDocument(data) +} + +// FromJSON creates RetryPayload from JSON string +func RetryPayloadFromJSON(jsonStr string) (RetryPayload, error) { + var data map[string]interface{} + if err := json.Unmarshal([]byte(jsonStr), &data); err != nil { + return RetryPayload{}, err + } + ctx := NewLoadContext() + return LoadRetryPayload(data, ctx) +} + +// FromYAML creates RetryPayload from YAML string +func RetryPayloadFromYAML(yamlStr string) (RetryPayload, error) { + var data map[string]interface{} + if err := yaml.Unmarshal([]byte(yamlStr), &data); err != nil { + return RetryPayload{}, err + } + ctx := NewLoadContext() + return LoadRetryPayload(data, ctx) +} diff --git a/runtime/go/prompty/model/retry_payload_test.go b/runtime/go/prompty/model/retry_payload_test.go new file mode 100644 index 00000000..c652d28d --- /dev/null +++ b/runtime/go/prompty/model/retry_payload_test.go @@ -0,0 +1,309 @@ +// +// Code generated by Typra emitter; DO NOT EDIT. + +package prompty_test + +import ( + "encoding/json" + "testing" + + "gopkg.in/yaml.v3" + + "prompty/model" +) + +// TestRetryPayloadLoadJSON tests loading RetryPayload from JSON +func TestRetryPayloadLoadJSON(t *testing.T) { + jsonData := ` +{ + "operation": "llm", + "attempt": 2, + "maxAttempts": 3, + "delayMs": 1250, + "reason": "rate_limit" +} +` + var data map[string]interface{} + if err := json.Unmarshal([]byte(jsonData), &data); err != nil { + t.Fatalf("Failed to parse JSON: %v", err) + } + + ctx := prompty.NewLoadContext() + instance, err := prompty.LoadRetryPayload(data, ctx) + if err != nil { + t.Fatalf("Failed to load RetryPayload: %v", err) + } + if instance.Operation != "llm" { + t.Errorf(`Expected Operation to be "llm", got %v`, instance.Operation) + } + if instance.Attempt != 2 { + t.Errorf(`Expected Attempt to be 2, got %v`, instance.Attempt) + } + if instance.MaxAttempts == nil || *instance.MaxAttempts != 3 { + t.Errorf(`Expected MaxAttempts to be 3, got %v`, instance.MaxAttempts) + } + if instance.DelayMs == nil || *instance.DelayMs != 1250 { + t.Errorf(`Expected DelayMs to be 1250, got %v`, instance.DelayMs) + } + if instance.Reason == nil || *instance.Reason != "rate_limit" { + t.Errorf(`Expected Reason to be "rate_limit", got %v`, instance.Reason) + } +} + +// TestRetryPayloadLoadYAML tests loading RetryPayload from YAML +func TestRetryPayloadLoadYAML(t *testing.T) { + yamlData := ` +operation: llm +attempt: 2 +maxAttempts: 3 +delayMs: 1250 +reason: rate_limit + +` + var data map[string]interface{} + if err := yaml.Unmarshal([]byte(yamlData), &data); err != nil { + t.Fatalf("Failed to parse YAML: %v", err) + } + + ctx := prompty.NewLoadContext() + instance, err := prompty.LoadRetryPayload(data, ctx) + if err != nil { + t.Fatalf("Failed to load RetryPayload: %v", err) + } + if instance.Operation != "llm" { + t.Errorf(`Expected Operation to be "llm", got %v`, instance.Operation) + } + if instance.Attempt != 2 { + t.Errorf(`Expected Attempt to be 2, got %v`, instance.Attempt) + } + if instance.MaxAttempts == nil || *instance.MaxAttempts != 3 { + t.Errorf(`Expected MaxAttempts to be 3, got %v`, instance.MaxAttempts) + } + if instance.DelayMs == nil || *instance.DelayMs != 1250 { + t.Errorf(`Expected DelayMs to be 1250, got %v`, instance.DelayMs) + } + if instance.Reason == nil || *instance.Reason != "rate_limit" { + t.Errorf(`Expected Reason to be "rate_limit", got %v`, instance.Reason) + } +} + +// TestRetryPayloadFromJSON tests loading RetryPayload through the generated JSON helper +func TestRetryPayloadFromJSON(t *testing.T) { + jsonData := ` +{ + "operation": "llm", + "attempt": 2, + "maxAttempts": 3, + "delayMs": 1250, + "reason": "rate_limit" +} +` + + instance, err := prompty.RetryPayloadFromJSON(jsonData) + if err != nil { + t.Fatalf("Failed to load RetryPayload from JSON helper: %v", err) + } + if instance.Operation != "llm" { + t.Errorf(`Expected Operation to be "llm", got %v`, instance.Operation) + } + if instance.Attempt != 2 { + t.Errorf(`Expected Attempt to be 2, got %v`, instance.Attempt) + } + if instance.MaxAttempts == nil || *instance.MaxAttempts != 3 { + t.Errorf(`Expected MaxAttempts to be 3, got %v`, instance.MaxAttempts) + } + if instance.DelayMs == nil || *instance.DelayMs != 1250 { + t.Errorf(`Expected DelayMs to be 1250, got %v`, instance.DelayMs) + } + if instance.Reason == nil || *instance.Reason != "rate_limit" { + t.Errorf(`Expected Reason to be "rate_limit", got %v`, instance.Reason) + } +} + +// TestRetryPayloadFromYAML tests loading RetryPayload through the generated YAML helper +func TestRetryPayloadFromYAML(t *testing.T) { + yamlData := ` +operation: llm +attempt: 2 +maxAttempts: 3 +delayMs: 1250 +reason: rate_limit + +` + + instance, err := prompty.RetryPayloadFromYAML(yamlData) + if err != nil { + t.Fatalf("Failed to load RetryPayload from YAML helper: %v", err) + } + if instance.Operation != "llm" { + t.Errorf(`Expected Operation to be "llm", got %v`, instance.Operation) + } + if instance.Attempt != 2 { + t.Errorf(`Expected Attempt to be 2, got %v`, instance.Attempt) + } + if instance.MaxAttempts == nil || *instance.MaxAttempts != 3 { + t.Errorf(`Expected MaxAttempts to be 3, got %v`, instance.MaxAttempts) + } + if instance.DelayMs == nil || *instance.DelayMs != 1250 { + t.Errorf(`Expected DelayMs to be 1250, got %v`, instance.DelayMs) + } + if instance.Reason == nil || *instance.Reason != "rate_limit" { + t.Errorf(`Expected Reason to be "rate_limit", got %v`, instance.Reason) + } +} + +// TestRetryPayloadRoundtrip tests load -> save -> load produces equivalent data +func TestRetryPayloadRoundtrip(t *testing.T) { + jsonData := ` +{ + "operation": "llm", + "attempt": 2, + "maxAttempts": 3, + "delayMs": 1250, + "reason": "rate_limit" +} +` + var data map[string]interface{} + if err := json.Unmarshal([]byte(jsonData), &data); err != nil { + t.Fatalf("Failed to parse JSON: %v", err) + } + + loadCtx := prompty.NewLoadContext() + instance, err := prompty.LoadRetryPayload(data, loadCtx) + if err != nil { + t.Fatalf("Failed to load RetryPayload: %v", err) + } + saveCtx := prompty.NewSaveContext() + savedData := instance.Save(saveCtx) + + reloaded, err := prompty.LoadRetryPayload(savedData, loadCtx) + if err != nil { + t.Fatalf("Failed to reload RetryPayload: %v", err) + } + if reloaded.Operation != "llm" { + t.Errorf(`Expected Operation to be "llm", got %v`, reloaded.Operation) + } + if reloaded.Attempt != 2 { + t.Errorf(`Expected Attempt to be 2, got %v`, reloaded.Attempt) + } + if reloaded.MaxAttempts == nil || *reloaded.MaxAttempts != 3 { + t.Errorf(`Expected MaxAttempts to be 3, got %v`, reloaded.MaxAttempts) + } + if reloaded.DelayMs == nil || *reloaded.DelayMs != 1250 { + t.Errorf(`Expected DelayMs to be 1250, got %v`, reloaded.DelayMs) + } + if reloaded.Reason == nil || *reloaded.Reason != "rate_limit" { + t.Errorf(`Expected Reason to be "rate_limit", got %v`, reloaded.Reason) + } +} + +// TestRetryPayloadToJSON tests that ToJSON produces valid JSON +func TestRetryPayloadToJSON(t *testing.T) { + jsonData := ` +{ + "operation": "llm", + "attempt": 2, + "maxAttempts": 3, + "delayMs": 1250, + "reason": "rate_limit" +} +` + var data map[string]interface{} + if err := json.Unmarshal([]byte(jsonData), &data); err != nil { + t.Fatalf("Failed to parse JSON: %v", err) + } + + ctx := prompty.NewLoadContext() + instance, err := prompty.LoadRetryPayload(data, ctx) + if err != nil { + t.Fatalf("Failed to load RetryPayload: %v", err) + } + jsonOutput, err := instance.ToJSON() + if err != nil { + t.Fatalf("Failed to convert to JSON: %v", err) + } + + var parsed map[string]interface{} + if err := json.Unmarshal([]byte(jsonOutput), &parsed); err != nil { + t.Fatalf("Failed to parse generated JSON: %v", err) + } + + reloaded, err := prompty.LoadRetryPayload(parsed, ctx) + if err != nil { + t.Fatalf("Failed to reload generated JSON: %v", err) + } + if reloaded.Operation != "llm" { + t.Errorf(`Expected Operation to be "llm", got %v`, reloaded.Operation) + } + if reloaded.Attempt != 2 { + t.Errorf(`Expected Attempt to be 2, got %v`, reloaded.Attempt) + } + if reloaded.MaxAttempts == nil || *reloaded.MaxAttempts != 3 { + t.Errorf(`Expected MaxAttempts to be 3, got %v`, reloaded.MaxAttempts) + } + if reloaded.DelayMs == nil || *reloaded.DelayMs != 1250 { + t.Errorf(`Expected DelayMs to be 1250, got %v`, reloaded.DelayMs) + } + if reloaded.Reason == nil || *reloaded.Reason != "rate_limit" { + t.Errorf(`Expected Reason to be "rate_limit", got %v`, reloaded.Reason) + } +} + +// TestRetryPayloadToYAML tests that ToYAML produces valid YAML +func TestRetryPayloadToYAML(t *testing.T) { + jsonData := ` +{ + "operation": "llm", + "attempt": 2, + "maxAttempts": 3, + "delayMs": 1250, + "reason": "rate_limit" +} +` + var data map[string]interface{} + if err := json.Unmarshal([]byte(jsonData), &data); err != nil { + t.Fatalf("Failed to parse JSON: %v", err) + } + + ctx := prompty.NewLoadContext() + instance, err := prompty.LoadRetryPayload(data, ctx) + if err != nil { + t.Fatalf("Failed to load RetryPayload: %v", err) + } + yamlOutput, err := instance.ToYAML() + if err != nil { + t.Fatalf("Failed to convert to YAML: %v", err) + } + + var parsed map[string]interface{} + if err := yaml.Unmarshal([]byte(yamlOutput), &parsed); err != nil { + t.Fatalf("Failed to parse generated YAML: %v", err) + } + + reloaded, err := prompty.LoadRetryPayload(parsed, ctx) + if err != nil { + t.Fatalf("Failed to reload generated YAML: %v", err) + } + if reloaded.Operation != "llm" { + t.Errorf(`Expected Operation to be "llm", got %v`, reloaded.Operation) + } + if reloaded.Attempt != 2 { + t.Errorf(`Expected Attempt to be 2, got %v`, reloaded.Attempt) + } + if reloaded.MaxAttempts == nil || *reloaded.MaxAttempts != 3 { + t.Errorf(`Expected MaxAttempts to be 3, got %v`, reloaded.MaxAttempts) + } + if reloaded.DelayMs == nil || *reloaded.DelayMs != 1250 { + t.Errorf(`Expected DelayMs to be 1250, got %v`, reloaded.DelayMs) + } + if reloaded.Reason == nil || *reloaded.Reason != "rate_limit" { + t.Errorf(`Expected Reason to be "rate_limit", got %v`, reloaded.Reason) + } +} + +// TestRetryPayloadFromJSONInvalid rejects malformed JSON instead of silently defaulting +func TestRetryPayloadFromJSONInvalid(t *testing.T) { + if _, err := prompty.RetryPayloadFromJSON("{"); err == nil { + t.Fatalf("Expected malformed JSON to fail") + } +} diff --git a/runtime/go/prompty/model/run_turn_request.go b/runtime/go/prompty/model/run_turn_request.go new file mode 100644 index 00000000..61e0a323 --- /dev/null +++ b/runtime/go/prompty/model/run_turn_request.go @@ -0,0 +1,101 @@ +// +// Code generated by Typra emitter; DO NOT EDIT. +// Group: pipeline + +package prompty + +import ( + "encoding/json" + + "gopkg.in/yaml.v3" +) + +// RunTurnRequest represents Request accepted by a reference turn runner implementation. + +type RunTurnRequest struct { + SessionId string `json:"sessionId" yaml:"sessionId"` + TurnId string `json:"turnId" yaml:"turnId"` + Inputs map[string]interface{} `json:"inputs,omitempty" yaml:"inputs,omitempty"` + Options *TurnOptions `json:"options,omitempty" yaml:"options,omitempty"` +} + +// LoadRunTurnRequest creates a RunTurnRequest from a map[string]interface{} +func LoadRunTurnRequest(data interface{}, ctx *LoadContext) (RunTurnRequest, error) { + result := RunTurnRequest{} + + // Load from map + if m, ok := data.(map[string]interface{}); ok { + if val, ok := m["sessionId"]; ok && val != nil { + result.SessionId = string(val.(string)) + } + if val, ok := m["turnId"]; ok && val != nil { + result.TurnId = string(val.(string)) + } + if val, ok := m["inputs"]; ok && val != nil { + if m, ok := val.(map[string]interface{}); ok { + result.Inputs = m + } + } + if val, ok := m["options"]; ok && val != nil { + if m, ok := val.(map[string]interface{}); ok { + loaded, _ := LoadTurnOptions(m, ctx) + result.Options = &loaded + } + } + } + + return result, nil +} + +// Save serializes RunTurnRequest to map[string]interface{} +func (obj RunTurnRequest) Save(ctx *SaveContext) map[string]interface{} { + result := make(map[string]interface{}) + result["sessionId"] = obj.SessionId + result["turnId"] = obj.TurnId + if obj.Inputs != nil { + result["inputs"] = obj.Inputs + } + if obj.Options != nil { + result["options"] = obj.Options.Save(ctx) + } + + return result +} + +// ToJSON serializes RunTurnRequest to JSON string +func (obj *RunTurnRequest) ToJSON() (string, error) { + ctx := NewSaveContext() + data := obj.Save(ctx) + bytes, err := json.Marshal(data) + if err != nil { + return "", err + } + return string(bytes), nil +} + +// ToYAML serializes RunTurnRequest to YAML string +func (obj *RunTurnRequest) ToYAML() (string, error) { + ctx := NewSaveContext() + data := obj.Save(ctx) + return marshalYAMLDocument(data) +} + +// FromJSON creates RunTurnRequest from JSON string +func RunTurnRequestFromJSON(jsonStr string) (RunTurnRequest, error) { + var data map[string]interface{} + if err := json.Unmarshal([]byte(jsonStr), &data); err != nil { + return RunTurnRequest{}, err + } + ctx := NewLoadContext() + return LoadRunTurnRequest(data, ctx) +} + +// FromYAML creates RunTurnRequest from YAML string +func RunTurnRequestFromYAML(yamlStr string) (RunTurnRequest, error) { + var data map[string]interface{} + if err := yaml.Unmarshal([]byte(yamlStr), &data); err != nil { + return RunTurnRequest{}, err + } + ctx := NewLoadContext() + return LoadRunTurnRequest(data, ctx) +} diff --git a/runtime/go/prompty/model/run_turn_request_test.go b/runtime/go/prompty/model/run_turn_request_test.go new file mode 100644 index 00000000..b6b2f355 --- /dev/null +++ b/runtime/go/prompty/model/run_turn_request_test.go @@ -0,0 +1,225 @@ +// +// Code generated by Typra emitter; DO NOT EDIT. + +package prompty_test + +import ( + "encoding/json" + "testing" + + "gopkg.in/yaml.v3" + + "prompty/model" +) + +// TestRunTurnRequestLoadJSON tests loading RunTurnRequest from JSON +func TestRunTurnRequestLoadJSON(t *testing.T) { + jsonData := ` +{ + "sessionId": "sess_abc123", + "turnId": "turn_abc123" +} +` + var data map[string]interface{} + if err := json.Unmarshal([]byte(jsonData), &data); err != nil { + t.Fatalf("Failed to parse JSON: %v", err) + } + + ctx := prompty.NewLoadContext() + instance, err := prompty.LoadRunTurnRequest(data, ctx) + if err != nil { + t.Fatalf("Failed to load RunTurnRequest: %v", err) + } + if instance.SessionId != "sess_abc123" { + t.Errorf(`Expected SessionId to be "sess_abc123", got %v`, instance.SessionId) + } + if instance.TurnId != "turn_abc123" { + t.Errorf(`Expected TurnId to be "turn_abc123", got %v`, instance.TurnId) + } +} + +// TestRunTurnRequestLoadYAML tests loading RunTurnRequest from YAML +func TestRunTurnRequestLoadYAML(t *testing.T) { + yamlData := ` +sessionId: sess_abc123 +turnId: turn_abc123 + +` + var data map[string]interface{} + if err := yaml.Unmarshal([]byte(yamlData), &data); err != nil { + t.Fatalf("Failed to parse YAML: %v", err) + } + + ctx := prompty.NewLoadContext() + instance, err := prompty.LoadRunTurnRequest(data, ctx) + if err != nil { + t.Fatalf("Failed to load RunTurnRequest: %v", err) + } + if instance.SessionId != "sess_abc123" { + t.Errorf(`Expected SessionId to be "sess_abc123", got %v`, instance.SessionId) + } + if instance.TurnId != "turn_abc123" { + t.Errorf(`Expected TurnId to be "turn_abc123", got %v`, instance.TurnId) + } +} + +// TestRunTurnRequestFromJSON tests loading RunTurnRequest through the generated JSON helper +func TestRunTurnRequestFromJSON(t *testing.T) { + jsonData := ` +{ + "sessionId": "sess_abc123", + "turnId": "turn_abc123" +} +` + + instance, err := prompty.RunTurnRequestFromJSON(jsonData) + if err != nil { + t.Fatalf("Failed to load RunTurnRequest from JSON helper: %v", err) + } + if instance.SessionId != "sess_abc123" { + t.Errorf(`Expected SessionId to be "sess_abc123", got %v`, instance.SessionId) + } + if instance.TurnId != "turn_abc123" { + t.Errorf(`Expected TurnId to be "turn_abc123", got %v`, instance.TurnId) + } +} + +// TestRunTurnRequestFromYAML tests loading RunTurnRequest through the generated YAML helper +func TestRunTurnRequestFromYAML(t *testing.T) { + yamlData := ` +sessionId: sess_abc123 +turnId: turn_abc123 + +` + + instance, err := prompty.RunTurnRequestFromYAML(yamlData) + if err != nil { + t.Fatalf("Failed to load RunTurnRequest from YAML helper: %v", err) + } + if instance.SessionId != "sess_abc123" { + t.Errorf(`Expected SessionId to be "sess_abc123", got %v`, instance.SessionId) + } + if instance.TurnId != "turn_abc123" { + t.Errorf(`Expected TurnId to be "turn_abc123", got %v`, instance.TurnId) + } +} + +// TestRunTurnRequestRoundtrip tests load -> save -> load produces equivalent data +func TestRunTurnRequestRoundtrip(t *testing.T) { + jsonData := ` +{ + "sessionId": "sess_abc123", + "turnId": "turn_abc123" +} +` + var data map[string]interface{} + if err := json.Unmarshal([]byte(jsonData), &data); err != nil { + t.Fatalf("Failed to parse JSON: %v", err) + } + + loadCtx := prompty.NewLoadContext() + instance, err := prompty.LoadRunTurnRequest(data, loadCtx) + if err != nil { + t.Fatalf("Failed to load RunTurnRequest: %v", err) + } + saveCtx := prompty.NewSaveContext() + savedData := instance.Save(saveCtx) + + reloaded, err := prompty.LoadRunTurnRequest(savedData, loadCtx) + if err != nil { + t.Fatalf("Failed to reload RunTurnRequest: %v", err) + } + if reloaded.SessionId != "sess_abc123" { + t.Errorf(`Expected SessionId to be "sess_abc123", got %v`, reloaded.SessionId) + } + if reloaded.TurnId != "turn_abc123" { + t.Errorf(`Expected TurnId to be "turn_abc123", got %v`, reloaded.TurnId) + } +} + +// TestRunTurnRequestToJSON tests that ToJSON produces valid JSON +func TestRunTurnRequestToJSON(t *testing.T) { + jsonData := ` +{ + "sessionId": "sess_abc123", + "turnId": "turn_abc123" +} +` + var data map[string]interface{} + if err := json.Unmarshal([]byte(jsonData), &data); err != nil { + t.Fatalf("Failed to parse JSON: %v", err) + } + + ctx := prompty.NewLoadContext() + instance, err := prompty.LoadRunTurnRequest(data, ctx) + if err != nil { + t.Fatalf("Failed to load RunTurnRequest: %v", err) + } + jsonOutput, err := instance.ToJSON() + if err != nil { + t.Fatalf("Failed to convert to JSON: %v", err) + } + + var parsed map[string]interface{} + if err := json.Unmarshal([]byte(jsonOutput), &parsed); err != nil { + t.Fatalf("Failed to parse generated JSON: %v", err) + } + + reloaded, err := prompty.LoadRunTurnRequest(parsed, ctx) + if err != nil { + t.Fatalf("Failed to reload generated JSON: %v", err) + } + if reloaded.SessionId != "sess_abc123" { + t.Errorf(`Expected SessionId to be "sess_abc123", got %v`, reloaded.SessionId) + } + if reloaded.TurnId != "turn_abc123" { + t.Errorf(`Expected TurnId to be "turn_abc123", got %v`, reloaded.TurnId) + } +} + +// TestRunTurnRequestToYAML tests that ToYAML produces valid YAML +func TestRunTurnRequestToYAML(t *testing.T) { + jsonData := ` +{ + "sessionId": "sess_abc123", + "turnId": "turn_abc123" +} +` + var data map[string]interface{} + if err := json.Unmarshal([]byte(jsonData), &data); err != nil { + t.Fatalf("Failed to parse JSON: %v", err) + } + + ctx := prompty.NewLoadContext() + instance, err := prompty.LoadRunTurnRequest(data, ctx) + if err != nil { + t.Fatalf("Failed to load RunTurnRequest: %v", err) + } + yamlOutput, err := instance.ToYAML() + if err != nil { + t.Fatalf("Failed to convert to YAML: %v", err) + } + + var parsed map[string]interface{} + if err := yaml.Unmarshal([]byte(yamlOutput), &parsed); err != nil { + t.Fatalf("Failed to parse generated YAML: %v", err) + } + + reloaded, err := prompty.LoadRunTurnRequest(parsed, ctx) + if err != nil { + t.Fatalf("Failed to reload generated YAML: %v", err) + } + if reloaded.SessionId != "sess_abc123" { + t.Errorf(`Expected SessionId to be "sess_abc123", got %v`, reloaded.SessionId) + } + if reloaded.TurnId != "turn_abc123" { + t.Errorf(`Expected TurnId to be "turn_abc123", got %v`, reloaded.TurnId) + } +} + +// TestRunTurnRequestFromJSONInvalid rejects malformed JSON instead of silently defaulting +func TestRunTurnRequestFromJSONInvalid(t *testing.T) { + if _, err := prompty.RunTurnRequestFromJSON("{"); err == nil { + t.Fatalf("Expected malformed JSON to fail") + } +} diff --git a/runtime/go/prompty/model/run_turn_result.go b/runtime/go/prompty/model/run_turn_result.go new file mode 100644 index 00000000..4a99bf2d --- /dev/null +++ b/runtime/go/prompty/model/run_turn_result.go @@ -0,0 +1,157 @@ +// +// Code generated by Typra emitter; DO NOT EDIT. +// Group: pipeline + +package prompty + +import ( + "encoding/json" + + "gopkg.in/yaml.v3" +) + +// RunTurnStatus represents the allowed values for RunTurnStatus. +type RunTurnStatus string + +const ( + RunTurnStatusSuccess RunTurnStatus = "success" + RunTurnStatusError RunTurnStatus = "error" + RunTurnStatusCancelled RunTurnStatus = "cancelled" +) + +// RunTurnResult represents Result returned by a reference turn runner implementation. + +type RunTurnResult struct { + SessionId string `json:"sessionId" yaml:"sessionId"` + TurnId string `json:"turnId" yaml:"turnId"` + Status RunTurnStatus `json:"status" yaml:"status"` + Output *interface{} `json:"output,omitempty" yaml:"output,omitempty"` + Iterations int32 `json:"iterations" yaml:"iterations"` + ToolResults []HostToolResult `json:"toolResults,omitempty" yaml:"toolResults,omitempty"` + Checkpoints []Checkpoint `json:"checkpoints,omitempty" yaml:"checkpoints,omitempty"` +} + +// LoadRunTurnResult creates a RunTurnResult from a map[string]interface{} +func LoadRunTurnResult(data interface{}, ctx *LoadContext) (RunTurnResult, error) { + result := RunTurnResult{} + + // Load from map + if m, ok := data.(map[string]interface{}); ok { + if val, ok := m["sessionId"]; ok && val != nil { + result.SessionId = string(val.(string)) + } + if val, ok := m["turnId"]; ok && val != nil { + result.TurnId = string(val.(string)) + } + if val, ok := m["status"]; ok && val != nil { + result.Status = RunTurnStatus(val.(string)) + } + if val, ok := m["output"]; ok && val != nil { + result.Output = &val + } + if val, ok := m["iterations"]; ok && val != nil { // Handle various numeric types from JSON/YAML/roundtrip + var v int32 + switch n := val.(type) { + case int: + v = int32(n) + case int32: + v = int32(n) + case int64: + v = int32(n) + case float64: + v = int32(n) + } + result.Iterations = v + } + if val, ok := m["toolResults"]; ok && val != nil { + if arr, ok := val.([]interface{}); ok { + result.ToolResults = make([]HostToolResult, len(arr)) + for i, v := range arr { + if item, ok := v.(map[string]interface{}); ok { + loaded, _ := LoadHostToolResult(item, ctx) + result.ToolResults[i] = loaded + } + } + } + } + if val, ok := m["checkpoints"]; ok && val != nil { + if arr, ok := val.([]interface{}); ok { + result.Checkpoints = make([]Checkpoint, len(arr)) + for i, v := range arr { + if item, ok := v.(map[string]interface{}); ok { + loaded, _ := LoadCheckpoint(item, ctx) + result.Checkpoints[i] = loaded + } + } + } + } + } + + return result, nil +} + +// Save serializes RunTurnResult to map[string]interface{} +func (obj RunTurnResult) Save(ctx *SaveContext) map[string]interface{} { + result := make(map[string]interface{}) + result["sessionId"] = obj.SessionId + result["turnId"] = obj.TurnId + result["status"] = string(obj.Status) + if obj.Output != nil { + result["output"] = *obj.Output + } + result["iterations"] = obj.Iterations + if obj.ToolResults != nil { + arr := make([]interface{}, len(obj.ToolResults)) + for i, item := range obj.ToolResults { + arr[i] = item.Save(ctx) + } + result["toolResults"] = arr + } + if obj.Checkpoints != nil { + arr := make([]interface{}, len(obj.Checkpoints)) + for i, item := range obj.Checkpoints { + arr[i] = item.Save(ctx) + } + result["checkpoints"] = arr + } + + return result +} + +// ToJSON serializes RunTurnResult to JSON string +func (obj *RunTurnResult) ToJSON() (string, error) { + ctx := NewSaveContext() + data := obj.Save(ctx) + bytes, err := json.Marshal(data) + if err != nil { + return "", err + } + return string(bytes), nil +} + +// ToYAML serializes RunTurnResult to YAML string +func (obj *RunTurnResult) ToYAML() (string, error) { + ctx := NewSaveContext() + data := obj.Save(ctx) + return marshalYAMLDocument(data) +} + +// FromJSON creates RunTurnResult from JSON string +func RunTurnResultFromJSON(jsonStr string) (RunTurnResult, error) { + var data map[string]interface{} + if err := json.Unmarshal([]byte(jsonStr), &data); err != nil { + return RunTurnResult{}, err + } + ctx := NewLoadContext() + return LoadRunTurnResult(data, ctx) +} + +// FromYAML creates RunTurnResult from YAML string +func RunTurnResultFromYAML(yamlStr string) (RunTurnResult, error) { + var data map[string]interface{} + if err := yaml.Unmarshal([]byte(yamlStr), &data); err != nil { + return RunTurnResult{}, err + } + ctx := NewLoadContext() + return LoadRunTurnResult(data, ctx) +} diff --git a/runtime/go/prompty/model/run_turn_result_test.go b/runtime/go/prompty/model/run_turn_result_test.go new file mode 100644 index 00000000..06b1fd88 --- /dev/null +++ b/runtime/go/prompty/model/run_turn_result_test.go @@ -0,0 +1,253 @@ +// +// Code generated by Typra emitter; DO NOT EDIT. + +package prompty_test + +import ( + "encoding/json" + "testing" + + "gopkg.in/yaml.v3" + + "prompty/model" +) + +// TestRunTurnResultLoadJSON tests loading RunTurnResult from JSON +func TestRunTurnResultLoadJSON(t *testing.T) { + jsonData := ` +{ + "sessionId": "sess_abc123", + "turnId": "turn_abc123", + "iterations": 1 +} +` + var data map[string]interface{} + if err := json.Unmarshal([]byte(jsonData), &data); err != nil { + t.Fatalf("Failed to parse JSON: %v", err) + } + + ctx := prompty.NewLoadContext() + instance, err := prompty.LoadRunTurnResult(data, ctx) + if err != nil { + t.Fatalf("Failed to load RunTurnResult: %v", err) + } + if instance.SessionId != "sess_abc123" { + t.Errorf(`Expected SessionId to be "sess_abc123", got %v`, instance.SessionId) + } + if instance.TurnId != "turn_abc123" { + t.Errorf(`Expected TurnId to be "turn_abc123", got %v`, instance.TurnId) + } + if instance.Iterations != 1 { + t.Errorf(`Expected Iterations to be 1, got %v`, instance.Iterations) + } +} + +// TestRunTurnResultLoadYAML tests loading RunTurnResult from YAML +func TestRunTurnResultLoadYAML(t *testing.T) { + yamlData := ` +sessionId: sess_abc123 +turnId: turn_abc123 +iterations: 1 + +` + var data map[string]interface{} + if err := yaml.Unmarshal([]byte(yamlData), &data); err != nil { + t.Fatalf("Failed to parse YAML: %v", err) + } + + ctx := prompty.NewLoadContext() + instance, err := prompty.LoadRunTurnResult(data, ctx) + if err != nil { + t.Fatalf("Failed to load RunTurnResult: %v", err) + } + if instance.SessionId != "sess_abc123" { + t.Errorf(`Expected SessionId to be "sess_abc123", got %v`, instance.SessionId) + } + if instance.TurnId != "turn_abc123" { + t.Errorf(`Expected TurnId to be "turn_abc123", got %v`, instance.TurnId) + } + if instance.Iterations != 1 { + t.Errorf(`Expected Iterations to be 1, got %v`, instance.Iterations) + } +} + +// TestRunTurnResultFromJSON tests loading RunTurnResult through the generated JSON helper +func TestRunTurnResultFromJSON(t *testing.T) { + jsonData := ` +{ + "sessionId": "sess_abc123", + "turnId": "turn_abc123", + "iterations": 1 +} +` + + instance, err := prompty.RunTurnResultFromJSON(jsonData) + if err != nil { + t.Fatalf("Failed to load RunTurnResult from JSON helper: %v", err) + } + if instance.SessionId != "sess_abc123" { + t.Errorf(`Expected SessionId to be "sess_abc123", got %v`, instance.SessionId) + } + if instance.TurnId != "turn_abc123" { + t.Errorf(`Expected TurnId to be "turn_abc123", got %v`, instance.TurnId) + } + if instance.Iterations != 1 { + t.Errorf(`Expected Iterations to be 1, got %v`, instance.Iterations) + } +} + +// TestRunTurnResultFromYAML tests loading RunTurnResult through the generated YAML helper +func TestRunTurnResultFromYAML(t *testing.T) { + yamlData := ` +sessionId: sess_abc123 +turnId: turn_abc123 +iterations: 1 + +` + + instance, err := prompty.RunTurnResultFromYAML(yamlData) + if err != nil { + t.Fatalf("Failed to load RunTurnResult from YAML helper: %v", err) + } + if instance.SessionId != "sess_abc123" { + t.Errorf(`Expected SessionId to be "sess_abc123", got %v`, instance.SessionId) + } + if instance.TurnId != "turn_abc123" { + t.Errorf(`Expected TurnId to be "turn_abc123", got %v`, instance.TurnId) + } + if instance.Iterations != 1 { + t.Errorf(`Expected Iterations to be 1, got %v`, instance.Iterations) + } +} + +// TestRunTurnResultRoundtrip tests load -> save -> load produces equivalent data +func TestRunTurnResultRoundtrip(t *testing.T) { + jsonData := ` +{ + "sessionId": "sess_abc123", + "turnId": "turn_abc123", + "iterations": 1 +} +` + var data map[string]interface{} + if err := json.Unmarshal([]byte(jsonData), &data); err != nil { + t.Fatalf("Failed to parse JSON: %v", err) + } + + loadCtx := prompty.NewLoadContext() + instance, err := prompty.LoadRunTurnResult(data, loadCtx) + if err != nil { + t.Fatalf("Failed to load RunTurnResult: %v", err) + } + saveCtx := prompty.NewSaveContext() + savedData := instance.Save(saveCtx) + + reloaded, err := prompty.LoadRunTurnResult(savedData, loadCtx) + if err != nil { + t.Fatalf("Failed to reload RunTurnResult: %v", err) + } + if reloaded.SessionId != "sess_abc123" { + t.Errorf(`Expected SessionId to be "sess_abc123", got %v`, reloaded.SessionId) + } + if reloaded.TurnId != "turn_abc123" { + t.Errorf(`Expected TurnId to be "turn_abc123", got %v`, reloaded.TurnId) + } + if reloaded.Iterations != 1 { + t.Errorf(`Expected Iterations to be 1, got %v`, reloaded.Iterations) + } +} + +// TestRunTurnResultToJSON tests that ToJSON produces valid JSON +func TestRunTurnResultToJSON(t *testing.T) { + jsonData := ` +{ + "sessionId": "sess_abc123", + "turnId": "turn_abc123", + "iterations": 1 +} +` + var data map[string]interface{} + if err := json.Unmarshal([]byte(jsonData), &data); err != nil { + t.Fatalf("Failed to parse JSON: %v", err) + } + + ctx := prompty.NewLoadContext() + instance, err := prompty.LoadRunTurnResult(data, ctx) + if err != nil { + t.Fatalf("Failed to load RunTurnResult: %v", err) + } + jsonOutput, err := instance.ToJSON() + if err != nil { + t.Fatalf("Failed to convert to JSON: %v", err) + } + + var parsed map[string]interface{} + if err := json.Unmarshal([]byte(jsonOutput), &parsed); err != nil { + t.Fatalf("Failed to parse generated JSON: %v", err) + } + + reloaded, err := prompty.LoadRunTurnResult(parsed, ctx) + if err != nil { + t.Fatalf("Failed to reload generated JSON: %v", err) + } + if reloaded.SessionId != "sess_abc123" { + t.Errorf(`Expected SessionId to be "sess_abc123", got %v`, reloaded.SessionId) + } + if reloaded.TurnId != "turn_abc123" { + t.Errorf(`Expected TurnId to be "turn_abc123", got %v`, reloaded.TurnId) + } + if reloaded.Iterations != 1 { + t.Errorf(`Expected Iterations to be 1, got %v`, reloaded.Iterations) + } +} + +// TestRunTurnResultToYAML tests that ToYAML produces valid YAML +func TestRunTurnResultToYAML(t *testing.T) { + jsonData := ` +{ + "sessionId": "sess_abc123", + "turnId": "turn_abc123", + "iterations": 1 +} +` + var data map[string]interface{} + if err := json.Unmarshal([]byte(jsonData), &data); err != nil { + t.Fatalf("Failed to parse JSON: %v", err) + } + + ctx := prompty.NewLoadContext() + instance, err := prompty.LoadRunTurnResult(data, ctx) + if err != nil { + t.Fatalf("Failed to load RunTurnResult: %v", err) + } + yamlOutput, err := instance.ToYAML() + if err != nil { + t.Fatalf("Failed to convert to YAML: %v", err) + } + + var parsed map[string]interface{} + if err := yaml.Unmarshal([]byte(yamlOutput), &parsed); err != nil { + t.Fatalf("Failed to parse generated YAML: %v", err) + } + + reloaded, err := prompty.LoadRunTurnResult(parsed, ctx) + if err != nil { + t.Fatalf("Failed to reload generated YAML: %v", err) + } + if reloaded.SessionId != "sess_abc123" { + t.Errorf(`Expected SessionId to be "sess_abc123", got %v`, reloaded.SessionId) + } + if reloaded.TurnId != "turn_abc123" { + t.Errorf(`Expected TurnId to be "turn_abc123", got %v`, reloaded.TurnId) + } + if reloaded.Iterations != 1 { + t.Errorf(`Expected Iterations to be 1, got %v`, reloaded.Iterations) + } +} + +// TestRunTurnResultFromJSONInvalid rejects malformed JSON instead of silently defaulting +func TestRunTurnResultFromJSONInvalid(t *testing.T) { + if _, err := prompty.RunTurnResultFromJSON("{"); err == nil { + t.Fatalf("Expected malformed JSON to fail") + } +} diff --git a/runtime/go/prompty/model/session_end_payload.go b/runtime/go/prompty/model/session_end_payload.go new file mode 100644 index 00000000..76599b37 --- /dev/null +++ b/runtime/go/prompty/model/session_end_payload.go @@ -0,0 +1,126 @@ +// +// Code generated by Typra emitter; DO NOT EDIT. +// Group: events + +package prompty + +import ( + "encoding/json" + + "gopkg.in/yaml.v3" +) + +// SessionEndStatus represents the allowed values for SessionEndStatus. +type SessionEndStatus string + +const ( + SessionEndStatusSuccess SessionEndStatus = "success" + SessionEndStatusError SessionEndStatus = "error" + SessionEndStatusCancelled SessionEndStatus = "cancelled" + SessionEndStatusInterrupted SessionEndStatus = "interrupted" +) + +// SessionEndPayload represents Payload for "session_end" events. + +type SessionEndPayload struct { + SessionId *string `json:"sessionId,omitempty" yaml:"sessionId,omitempty"` + Status *SessionEndStatus `json:"status,omitempty" yaml:"status,omitempty"` + Reason *string `json:"reason,omitempty" yaml:"reason,omitempty"` + DurationMs *float64 `json:"durationMs,omitempty" yaml:"durationMs,omitempty"` +} + +// LoadSessionEndPayload creates a SessionEndPayload from a map[string]interface{} +func LoadSessionEndPayload(data interface{}, ctx *LoadContext) (SessionEndPayload, error) { + result := SessionEndPayload{} + + // Load from map + if m, ok := data.(map[string]interface{}); ok { + if val, ok := m["sessionId"]; ok && val != nil { + v := string(val.(string)) + result.SessionId = &v + } + if val, ok := m["status"]; ok && val != nil { + v := SessionEndStatus(val.(string)) + result.Status = &v + } + if val, ok := m["reason"]; ok && val != nil { + v := string(val.(string)) + result.Reason = &v + } + if val, ok := m["durationMs"]; ok && val != nil { // Handle various numeric types from JSON/YAML/roundtrip + var v float64 + switch n := val.(type) { + case int: + v = float64(n) + case int32: + v = float64(n) + case int64: + v = float64(n) + case float32: + v = float64(n) + case float64: + v = n + } + result.DurationMs = &v + } + } + + return result, nil +} + +// Save serializes SessionEndPayload to map[string]interface{} +func (obj SessionEndPayload) Save(ctx *SaveContext) map[string]interface{} { + result := make(map[string]interface{}) + if obj.SessionId != nil { + result["sessionId"] = *obj.SessionId + } + if obj.Status != nil { + result["status"] = string(*obj.Status) + } + if obj.Reason != nil { + result["reason"] = *obj.Reason + } + if obj.DurationMs != nil { + result["durationMs"] = *obj.DurationMs + } + + return result +} + +// ToJSON serializes SessionEndPayload to JSON string +func (obj *SessionEndPayload) ToJSON() (string, error) { + ctx := NewSaveContext() + data := obj.Save(ctx) + bytes, err := json.Marshal(data) + if err != nil { + return "", err + } + return string(bytes), nil +} + +// ToYAML serializes SessionEndPayload to YAML string +func (obj *SessionEndPayload) ToYAML() (string, error) { + ctx := NewSaveContext() + data := obj.Save(ctx) + return marshalYAMLDocument(data) +} + +// FromJSON creates SessionEndPayload from JSON string +func SessionEndPayloadFromJSON(jsonStr string) (SessionEndPayload, error) { + var data map[string]interface{} + if err := json.Unmarshal([]byte(jsonStr), &data); err != nil { + return SessionEndPayload{}, err + } + ctx := NewLoadContext() + return LoadSessionEndPayload(data, ctx) +} + +// FromYAML creates SessionEndPayload from YAML string +func SessionEndPayloadFromYAML(yamlStr string) (SessionEndPayload, error) { + var data map[string]interface{} + if err := yaml.Unmarshal([]byte(yamlStr), &data); err != nil { + return SessionEndPayload{}, err + } + ctx := NewLoadContext() + return LoadSessionEndPayload(data, ctx) +} diff --git a/runtime/go/prompty/model/session_end_payload_test.go b/runtime/go/prompty/model/session_end_payload_test.go new file mode 100644 index 00000000..076e371c --- /dev/null +++ b/runtime/go/prompty/model/session_end_payload_test.go @@ -0,0 +1,281 @@ +// +// Code generated by Typra emitter; DO NOT EDIT. + +package prompty_test + +import ( + "encoding/json" + "testing" + + "gopkg.in/yaml.v3" + + "prompty/model" +) + +// TestSessionEndPayloadLoadJSON tests loading SessionEndPayload from JSON +func TestSessionEndPayloadLoadJSON(t *testing.T) { + jsonData := ` +{ + "sessionId": "sess_abc123", + "status": "success", + "reason": "complete", + "durationMs": 12500 +} +` + var data map[string]interface{} + if err := json.Unmarshal([]byte(jsonData), &data); err != nil { + t.Fatalf("Failed to parse JSON: %v", err) + } + + ctx := prompty.NewLoadContext() + instance, err := prompty.LoadSessionEndPayload(data, ctx) + if err != nil { + t.Fatalf("Failed to load SessionEndPayload: %v", err) + } + if instance.SessionId == nil || *instance.SessionId != "sess_abc123" { + t.Errorf(`Expected SessionId to be "sess_abc123", got %v`, instance.SessionId) + } + if instance.Status == nil || *instance.Status != "success" { + t.Errorf(`Expected Status to be "success", got %v`, instance.Status) + } + if instance.Reason == nil || *instance.Reason != "complete" { + t.Errorf(`Expected Reason to be "complete", got %v`, instance.Reason) + } + if instance.DurationMs == nil || *instance.DurationMs != 12500 { + t.Errorf(`Expected DurationMs to be 12500, got %v`, instance.DurationMs) + } +} + +// TestSessionEndPayloadLoadYAML tests loading SessionEndPayload from YAML +func TestSessionEndPayloadLoadYAML(t *testing.T) { + yamlData := ` +sessionId: sess_abc123 +status: success +reason: complete +durationMs: 12500 + +` + var data map[string]interface{} + if err := yaml.Unmarshal([]byte(yamlData), &data); err != nil { + t.Fatalf("Failed to parse YAML: %v", err) + } + + ctx := prompty.NewLoadContext() + instance, err := prompty.LoadSessionEndPayload(data, ctx) + if err != nil { + t.Fatalf("Failed to load SessionEndPayload: %v", err) + } + if instance.SessionId == nil || *instance.SessionId != "sess_abc123" { + t.Errorf(`Expected SessionId to be "sess_abc123", got %v`, instance.SessionId) + } + if instance.Status == nil || *instance.Status != "success" { + t.Errorf(`Expected Status to be "success", got %v`, instance.Status) + } + if instance.Reason == nil || *instance.Reason != "complete" { + t.Errorf(`Expected Reason to be "complete", got %v`, instance.Reason) + } + if instance.DurationMs == nil || *instance.DurationMs != 12500 { + t.Errorf(`Expected DurationMs to be 12500, got %v`, instance.DurationMs) + } +} + +// TestSessionEndPayloadFromJSON tests loading SessionEndPayload through the generated JSON helper +func TestSessionEndPayloadFromJSON(t *testing.T) { + jsonData := ` +{ + "sessionId": "sess_abc123", + "status": "success", + "reason": "complete", + "durationMs": 12500 +} +` + + instance, err := prompty.SessionEndPayloadFromJSON(jsonData) + if err != nil { + t.Fatalf("Failed to load SessionEndPayload from JSON helper: %v", err) + } + if instance.SessionId == nil || *instance.SessionId != "sess_abc123" { + t.Errorf(`Expected SessionId to be "sess_abc123", got %v`, instance.SessionId) + } + if instance.Status == nil || *instance.Status != "success" { + t.Errorf(`Expected Status to be "success", got %v`, instance.Status) + } + if instance.Reason == nil || *instance.Reason != "complete" { + t.Errorf(`Expected Reason to be "complete", got %v`, instance.Reason) + } + if instance.DurationMs == nil || *instance.DurationMs != 12500 { + t.Errorf(`Expected DurationMs to be 12500, got %v`, instance.DurationMs) + } +} + +// TestSessionEndPayloadFromYAML tests loading SessionEndPayload through the generated YAML helper +func TestSessionEndPayloadFromYAML(t *testing.T) { + yamlData := ` +sessionId: sess_abc123 +status: success +reason: complete +durationMs: 12500 + +` + + instance, err := prompty.SessionEndPayloadFromYAML(yamlData) + if err != nil { + t.Fatalf("Failed to load SessionEndPayload from YAML helper: %v", err) + } + if instance.SessionId == nil || *instance.SessionId != "sess_abc123" { + t.Errorf(`Expected SessionId to be "sess_abc123", got %v`, instance.SessionId) + } + if instance.Status == nil || *instance.Status != "success" { + t.Errorf(`Expected Status to be "success", got %v`, instance.Status) + } + if instance.Reason == nil || *instance.Reason != "complete" { + t.Errorf(`Expected Reason to be "complete", got %v`, instance.Reason) + } + if instance.DurationMs == nil || *instance.DurationMs != 12500 { + t.Errorf(`Expected DurationMs to be 12500, got %v`, instance.DurationMs) + } +} + +// TestSessionEndPayloadRoundtrip tests load -> save -> load produces equivalent data +func TestSessionEndPayloadRoundtrip(t *testing.T) { + jsonData := ` +{ + "sessionId": "sess_abc123", + "status": "success", + "reason": "complete", + "durationMs": 12500 +} +` + var data map[string]interface{} + if err := json.Unmarshal([]byte(jsonData), &data); err != nil { + t.Fatalf("Failed to parse JSON: %v", err) + } + + loadCtx := prompty.NewLoadContext() + instance, err := prompty.LoadSessionEndPayload(data, loadCtx) + if err != nil { + t.Fatalf("Failed to load SessionEndPayload: %v", err) + } + saveCtx := prompty.NewSaveContext() + savedData := instance.Save(saveCtx) + + reloaded, err := prompty.LoadSessionEndPayload(savedData, loadCtx) + if err != nil { + t.Fatalf("Failed to reload SessionEndPayload: %v", err) + } + if reloaded.SessionId == nil || *reloaded.SessionId != "sess_abc123" { + t.Errorf(`Expected SessionId to be "sess_abc123", got %v`, reloaded.SessionId) + } + if reloaded.Status == nil || *reloaded.Status != "success" { + t.Errorf(`Expected Status to be "success", got %v`, reloaded.Status) + } + if reloaded.Reason == nil || *reloaded.Reason != "complete" { + t.Errorf(`Expected Reason to be "complete", got %v`, reloaded.Reason) + } + if reloaded.DurationMs == nil || *reloaded.DurationMs != 12500 { + t.Errorf(`Expected DurationMs to be 12500, got %v`, reloaded.DurationMs) + } +} + +// TestSessionEndPayloadToJSON tests that ToJSON produces valid JSON +func TestSessionEndPayloadToJSON(t *testing.T) { + jsonData := ` +{ + "sessionId": "sess_abc123", + "status": "success", + "reason": "complete", + "durationMs": 12500 +} +` + var data map[string]interface{} + if err := json.Unmarshal([]byte(jsonData), &data); err != nil { + t.Fatalf("Failed to parse JSON: %v", err) + } + + ctx := prompty.NewLoadContext() + instance, err := prompty.LoadSessionEndPayload(data, ctx) + if err != nil { + t.Fatalf("Failed to load SessionEndPayload: %v", err) + } + jsonOutput, err := instance.ToJSON() + if err != nil { + t.Fatalf("Failed to convert to JSON: %v", err) + } + + var parsed map[string]interface{} + if err := json.Unmarshal([]byte(jsonOutput), &parsed); err != nil { + t.Fatalf("Failed to parse generated JSON: %v", err) + } + + reloaded, err := prompty.LoadSessionEndPayload(parsed, ctx) + if err != nil { + t.Fatalf("Failed to reload generated JSON: %v", err) + } + if reloaded.SessionId == nil || *reloaded.SessionId != "sess_abc123" { + t.Errorf(`Expected SessionId to be "sess_abc123", got %v`, reloaded.SessionId) + } + if reloaded.Status == nil || *reloaded.Status != "success" { + t.Errorf(`Expected Status to be "success", got %v`, reloaded.Status) + } + if reloaded.Reason == nil || *reloaded.Reason != "complete" { + t.Errorf(`Expected Reason to be "complete", got %v`, reloaded.Reason) + } + if reloaded.DurationMs == nil || *reloaded.DurationMs != 12500 { + t.Errorf(`Expected DurationMs to be 12500, got %v`, reloaded.DurationMs) + } +} + +// TestSessionEndPayloadToYAML tests that ToYAML produces valid YAML +func TestSessionEndPayloadToYAML(t *testing.T) { + jsonData := ` +{ + "sessionId": "sess_abc123", + "status": "success", + "reason": "complete", + "durationMs": 12500 +} +` + var data map[string]interface{} + if err := json.Unmarshal([]byte(jsonData), &data); err != nil { + t.Fatalf("Failed to parse JSON: %v", err) + } + + ctx := prompty.NewLoadContext() + instance, err := prompty.LoadSessionEndPayload(data, ctx) + if err != nil { + t.Fatalf("Failed to load SessionEndPayload: %v", err) + } + yamlOutput, err := instance.ToYAML() + if err != nil { + t.Fatalf("Failed to convert to YAML: %v", err) + } + + var parsed map[string]interface{} + if err := yaml.Unmarshal([]byte(yamlOutput), &parsed); err != nil { + t.Fatalf("Failed to parse generated YAML: %v", err) + } + + reloaded, err := prompty.LoadSessionEndPayload(parsed, ctx) + if err != nil { + t.Fatalf("Failed to reload generated YAML: %v", err) + } + if reloaded.SessionId == nil || *reloaded.SessionId != "sess_abc123" { + t.Errorf(`Expected SessionId to be "sess_abc123", got %v`, reloaded.SessionId) + } + if reloaded.Status == nil || *reloaded.Status != "success" { + t.Errorf(`Expected Status to be "success", got %v`, reloaded.Status) + } + if reloaded.Reason == nil || *reloaded.Reason != "complete" { + t.Errorf(`Expected Reason to be "complete", got %v`, reloaded.Reason) + } + if reloaded.DurationMs == nil || *reloaded.DurationMs != 12500 { + t.Errorf(`Expected DurationMs to be 12500, got %v`, reloaded.DurationMs) + } +} + +// TestSessionEndPayloadFromJSONInvalid rejects malformed JSON instead of silently defaulting +func TestSessionEndPayloadFromJSONInvalid(t *testing.T) { + if _, err := prompty.SessionEndPayloadFromJSON("{"); err == nil { + t.Fatalf("Expected malformed JSON to fail") + } +} diff --git a/runtime/go/prompty/model/session_event.go b/runtime/go/prompty/model/session_event.go new file mode 100644 index 00000000..adb21111 --- /dev/null +++ b/runtime/go/prompty/model/session_event.go @@ -0,0 +1,149 @@ +// +// Code generated by Typra emitter; DO NOT EDIT. +// Group: events + +package prompty + +import ( + "encoding/json" + + "gopkg.in/yaml.v3" +) + +// SessionEventType represents the allowed values for SessionEventType. +type SessionEventType string + +const ( + SessionEventTypeSessionStart SessionEventType = "session_start" + SessionEventTypeSessionEnd SessionEventType = "session_end" + SessionEventTypeSessionWarning SessionEventType = "session_warning" + SessionEventTypeSessionHookStart SessionEventType = "session_hook_start" + SessionEventTypeSessionHookEnd SessionEventType = "session_hook_end" + SessionEventTypeCheckpointCreated SessionEventType = "checkpoint_created" + SessionEventTypeTrajectoryEvent SessionEventType = "trajectory_event" +) + +// SessionEvent represents A canonical event envelope emitted by an outer harness session. + +type SessionEvent struct { + Id string `json:"id" yaml:"id"` + Type SessionEventType `json:"type" yaml:"type"` + Timestamp string `json:"timestamp" yaml:"timestamp"` + SessionId *string `json:"sessionId,omitempty" yaml:"sessionId,omitempty"` + TurnId *string `json:"turnId,omitempty" yaml:"turnId,omitempty"` + ParentId *string `json:"parentId,omitempty" yaml:"parentId,omitempty"` + SpanId *string `json:"spanId,omitempty" yaml:"spanId,omitempty"` + Payload map[string]interface{} `json:"payload" yaml:"payload"` + Redaction *RedactionMetadata `json:"redaction,omitempty" yaml:"redaction,omitempty"` +} + +// LoadSessionEvent creates a SessionEvent from a map[string]interface{} +func LoadSessionEvent(data interface{}, ctx *LoadContext) (SessionEvent, error) { + result := SessionEvent{} + + // Load from map + if m, ok := data.(map[string]interface{}); ok { + if val, ok := m["id"]; ok && val != nil { + result.Id = string(val.(string)) + } + if val, ok := m["type"]; ok && val != nil { + result.Type = SessionEventType(val.(string)) + } + if val, ok := m["timestamp"]; ok && val != nil { + result.Timestamp = string(val.(string)) + } + if val, ok := m["sessionId"]; ok && val != nil { + v := string(val.(string)) + result.SessionId = &v + } + if val, ok := m["turnId"]; ok && val != nil { + v := string(val.(string)) + result.TurnId = &v + } + if val, ok := m["parentId"]; ok && val != nil { + v := string(val.(string)) + result.ParentId = &v + } + if val, ok := m["spanId"]; ok && val != nil { + v := string(val.(string)) + result.SpanId = &v + } + if val, ok := m["payload"]; ok && val != nil { + if m, ok := val.(map[string]interface{}); ok { + result.Payload = m + } + } + if val, ok := m["redaction"]; ok && val != nil { + if m, ok := val.(map[string]interface{}); ok { + loaded, _ := LoadRedactionMetadata(m, ctx) + result.Redaction = &loaded + } + } + } + + return result, nil +} + +// Save serializes SessionEvent to map[string]interface{} +func (obj SessionEvent) Save(ctx *SaveContext) map[string]interface{} { + result := make(map[string]interface{}) + result["id"] = obj.Id + result["type"] = string(obj.Type) + result["timestamp"] = obj.Timestamp + if obj.SessionId != nil { + result["sessionId"] = *obj.SessionId + } + if obj.TurnId != nil { + result["turnId"] = *obj.TurnId + } + if obj.ParentId != nil { + result["parentId"] = *obj.ParentId + } + if obj.SpanId != nil { + result["spanId"] = *obj.SpanId + } + result["payload"] = obj.Payload + if obj.Redaction != nil { + result["redaction"] = obj.Redaction.Save(ctx) + } + + return result +} + +// ToJSON serializes SessionEvent to JSON string +func (obj *SessionEvent) ToJSON() (string, error) { + ctx := NewSaveContext() + data := obj.Save(ctx) + bytes, err := json.Marshal(data) + if err != nil { + return "", err + } + return string(bytes), nil +} + +// ToYAML serializes SessionEvent to YAML string +func (obj *SessionEvent) ToYAML() (string, error) { + ctx := NewSaveContext() + data := obj.Save(ctx) + return marshalYAMLDocument(data) +} + +// FromJSON creates SessionEvent from JSON string +func SessionEventFromJSON(jsonStr string) (SessionEvent, error) { + var data map[string]interface{} + if err := json.Unmarshal([]byte(jsonStr), &data); err != nil { + return SessionEvent{}, err + } + ctx := NewLoadContext() + return LoadSessionEvent(data, ctx) +} + +// FromYAML creates SessionEvent from YAML string +func SessionEventFromYAML(yamlStr string) (SessionEvent, error) { + var data map[string]interface{} + if err := yaml.Unmarshal([]byte(yamlStr), &data); err != nil { + return SessionEvent{}, err + } + ctx := NewLoadContext() + return LoadSessionEvent(data, ctx) +} diff --git a/runtime/go/prompty/model/session_event_test.go b/runtime/go/prompty/model/session_event_test.go new file mode 100644 index 00000000..c253e9d4 --- /dev/null +++ b/runtime/go/prompty/model/session_event_test.go @@ -0,0 +1,337 @@ +// +// Code generated by Typra emitter; DO NOT EDIT. + +package prompty_test + +import ( + "encoding/json" + "testing" + + "gopkg.in/yaml.v3" + + "prompty/model" +) + +// TestSessionEventLoadJSON tests loading SessionEvent from JSON +func TestSessionEventLoadJSON(t *testing.T) { + jsonData := ` +{ + "id": "evt_abc123", + "timestamp": "2026-06-09T20:00:00Z", + "sessionId": "sess_abc123", + "turnId": "turn_001", + "parentId": "evt_parent", + "spanId": "span_hook_001" +} +` + var data map[string]interface{} + if err := json.Unmarshal([]byte(jsonData), &data); err != nil { + t.Fatalf("Failed to parse JSON: %v", err) + } + + ctx := prompty.NewLoadContext() + instance, err := prompty.LoadSessionEvent(data, ctx) + if err != nil { + t.Fatalf("Failed to load SessionEvent: %v", err) + } + if instance.Id != "evt_abc123" { + t.Errorf(`Expected Id to be "evt_abc123", got %v`, instance.Id) + } + if instance.Timestamp != "2026-06-09T20:00:00Z" { + t.Errorf(`Expected Timestamp to be "2026-06-09T20:00:00Z", got %v`, instance.Timestamp) + } + if instance.SessionId == nil || *instance.SessionId != "sess_abc123" { + t.Errorf(`Expected SessionId to be "sess_abc123", got %v`, instance.SessionId) + } + if instance.TurnId == nil || *instance.TurnId != "turn_001" { + t.Errorf(`Expected TurnId to be "turn_001", got %v`, instance.TurnId) + } + if instance.ParentId == nil || *instance.ParentId != "evt_parent" { + t.Errorf(`Expected ParentId to be "evt_parent", got %v`, instance.ParentId) + } + if instance.SpanId == nil || *instance.SpanId != "span_hook_001" { + t.Errorf(`Expected SpanId to be "span_hook_001", got %v`, instance.SpanId) + } +} + +// TestSessionEventLoadYAML tests loading SessionEvent from YAML +func TestSessionEventLoadYAML(t *testing.T) { + yamlData := ` +id: evt_abc123 +timestamp: "2026-06-09T20:00:00Z" +sessionId: sess_abc123 +turnId: turn_001 +parentId: evt_parent +spanId: span_hook_001 + +` + var data map[string]interface{} + if err := yaml.Unmarshal([]byte(yamlData), &data); err != nil { + t.Fatalf("Failed to parse YAML: %v", err) + } + + ctx := prompty.NewLoadContext() + instance, err := prompty.LoadSessionEvent(data, ctx) + if err != nil { + t.Fatalf("Failed to load SessionEvent: %v", err) + } + if instance.Id != "evt_abc123" { + t.Errorf(`Expected Id to be "evt_abc123", got %v`, instance.Id) + } + if instance.Timestamp != "2026-06-09T20:00:00Z" { + t.Errorf(`Expected Timestamp to be "2026-06-09T20:00:00Z", got %v`, instance.Timestamp) + } + if instance.SessionId == nil || *instance.SessionId != "sess_abc123" { + t.Errorf(`Expected SessionId to be "sess_abc123", got %v`, instance.SessionId) + } + if instance.TurnId == nil || *instance.TurnId != "turn_001" { + t.Errorf(`Expected TurnId to be "turn_001", got %v`, instance.TurnId) + } + if instance.ParentId == nil || *instance.ParentId != "evt_parent" { + t.Errorf(`Expected ParentId to be "evt_parent", got %v`, instance.ParentId) + } + if instance.SpanId == nil || *instance.SpanId != "span_hook_001" { + t.Errorf(`Expected SpanId to be "span_hook_001", got %v`, instance.SpanId) + } +} + +// TestSessionEventFromJSON tests loading SessionEvent through the generated JSON helper +func TestSessionEventFromJSON(t *testing.T) { + jsonData := ` +{ + "id": "evt_abc123", + "timestamp": "2026-06-09T20:00:00Z", + "sessionId": "sess_abc123", + "turnId": "turn_001", + "parentId": "evt_parent", + "spanId": "span_hook_001" +} +` + + instance, err := prompty.SessionEventFromJSON(jsonData) + if err != nil { + t.Fatalf("Failed to load SessionEvent from JSON helper: %v", err) + } + if instance.Id != "evt_abc123" { + t.Errorf(`Expected Id to be "evt_abc123", got %v`, instance.Id) + } + if instance.Timestamp != "2026-06-09T20:00:00Z" { + t.Errorf(`Expected Timestamp to be "2026-06-09T20:00:00Z", got %v`, instance.Timestamp) + } + if instance.SessionId == nil || *instance.SessionId != "sess_abc123" { + t.Errorf(`Expected SessionId to be "sess_abc123", got %v`, instance.SessionId) + } + if instance.TurnId == nil || *instance.TurnId != "turn_001" { + t.Errorf(`Expected TurnId to be "turn_001", got %v`, instance.TurnId) + } + if instance.ParentId == nil || *instance.ParentId != "evt_parent" { + t.Errorf(`Expected ParentId to be "evt_parent", got %v`, instance.ParentId) + } + if instance.SpanId == nil || *instance.SpanId != "span_hook_001" { + t.Errorf(`Expected SpanId to be "span_hook_001", got %v`, instance.SpanId) + } +} + +// TestSessionEventFromYAML tests loading SessionEvent through the generated YAML helper +func TestSessionEventFromYAML(t *testing.T) { + yamlData := ` +id: evt_abc123 +timestamp: "2026-06-09T20:00:00Z" +sessionId: sess_abc123 +turnId: turn_001 +parentId: evt_parent +spanId: span_hook_001 + +` + + instance, err := prompty.SessionEventFromYAML(yamlData) + if err != nil { + t.Fatalf("Failed to load SessionEvent from YAML helper: %v", err) + } + if instance.Id != "evt_abc123" { + t.Errorf(`Expected Id to be "evt_abc123", got %v`, instance.Id) + } + if instance.Timestamp != "2026-06-09T20:00:00Z" { + t.Errorf(`Expected Timestamp to be "2026-06-09T20:00:00Z", got %v`, instance.Timestamp) + } + if instance.SessionId == nil || *instance.SessionId != "sess_abc123" { + t.Errorf(`Expected SessionId to be "sess_abc123", got %v`, instance.SessionId) + } + if instance.TurnId == nil || *instance.TurnId != "turn_001" { + t.Errorf(`Expected TurnId to be "turn_001", got %v`, instance.TurnId) + } + if instance.ParentId == nil || *instance.ParentId != "evt_parent" { + t.Errorf(`Expected ParentId to be "evt_parent", got %v`, instance.ParentId) + } + if instance.SpanId == nil || *instance.SpanId != "span_hook_001" { + t.Errorf(`Expected SpanId to be "span_hook_001", got %v`, instance.SpanId) + } +} + +// TestSessionEventRoundtrip tests load -> save -> load produces equivalent data +func TestSessionEventRoundtrip(t *testing.T) { + jsonData := ` +{ + "id": "evt_abc123", + "timestamp": "2026-06-09T20:00:00Z", + "sessionId": "sess_abc123", + "turnId": "turn_001", + "parentId": "evt_parent", + "spanId": "span_hook_001" +} +` + var data map[string]interface{} + if err := json.Unmarshal([]byte(jsonData), &data); err != nil { + t.Fatalf("Failed to parse JSON: %v", err) + } + + loadCtx := prompty.NewLoadContext() + instance, err := prompty.LoadSessionEvent(data, loadCtx) + if err != nil { + t.Fatalf("Failed to load SessionEvent: %v", err) + } + saveCtx := prompty.NewSaveContext() + savedData := instance.Save(saveCtx) + + reloaded, err := prompty.LoadSessionEvent(savedData, loadCtx) + if err != nil { + t.Fatalf("Failed to reload SessionEvent: %v", err) + } + if reloaded.Id != "evt_abc123" { + t.Errorf(`Expected Id to be "evt_abc123", got %v`, reloaded.Id) + } + if reloaded.Timestamp != "2026-06-09T20:00:00Z" { + t.Errorf(`Expected Timestamp to be "2026-06-09T20:00:00Z", got %v`, reloaded.Timestamp) + } + if reloaded.SessionId == nil || *reloaded.SessionId != "sess_abc123" { + t.Errorf(`Expected SessionId to be "sess_abc123", got %v`, reloaded.SessionId) + } + if reloaded.TurnId == nil || *reloaded.TurnId != "turn_001" { + t.Errorf(`Expected TurnId to be "turn_001", got %v`, reloaded.TurnId) + } + if reloaded.ParentId == nil || *reloaded.ParentId != "evt_parent" { + t.Errorf(`Expected ParentId to be "evt_parent", got %v`, reloaded.ParentId) + } + if reloaded.SpanId == nil || *reloaded.SpanId != "span_hook_001" { + t.Errorf(`Expected SpanId to be "span_hook_001", got %v`, reloaded.SpanId) + } +} + +// TestSessionEventToJSON tests that ToJSON produces valid JSON +func TestSessionEventToJSON(t *testing.T) { + jsonData := ` +{ + "id": "evt_abc123", + "timestamp": "2026-06-09T20:00:00Z", + "sessionId": "sess_abc123", + "turnId": "turn_001", + "parentId": "evt_parent", + "spanId": "span_hook_001" +} +` + var data map[string]interface{} + if err := json.Unmarshal([]byte(jsonData), &data); err != nil { + t.Fatalf("Failed to parse JSON: %v", err) + } + + ctx := prompty.NewLoadContext() + instance, err := prompty.LoadSessionEvent(data, ctx) + if err != nil { + t.Fatalf("Failed to load SessionEvent: %v", err) + } + jsonOutput, err := instance.ToJSON() + if err != nil { + t.Fatalf("Failed to convert to JSON: %v", err) + } + + var parsed map[string]interface{} + if err := json.Unmarshal([]byte(jsonOutput), &parsed); err != nil { + t.Fatalf("Failed to parse generated JSON: %v", err) + } + + reloaded, err := prompty.LoadSessionEvent(parsed, ctx) + if err != nil { + t.Fatalf("Failed to reload generated JSON: %v", err) + } + if reloaded.Id != "evt_abc123" { + t.Errorf(`Expected Id to be "evt_abc123", got %v`, reloaded.Id) + } + if reloaded.Timestamp != "2026-06-09T20:00:00Z" { + t.Errorf(`Expected Timestamp to be "2026-06-09T20:00:00Z", got %v`, reloaded.Timestamp) + } + if reloaded.SessionId == nil || *reloaded.SessionId != "sess_abc123" { + t.Errorf(`Expected SessionId to be "sess_abc123", got %v`, reloaded.SessionId) + } + if reloaded.TurnId == nil || *reloaded.TurnId != "turn_001" { + t.Errorf(`Expected TurnId to be "turn_001", got %v`, reloaded.TurnId) + } + if reloaded.ParentId == nil || *reloaded.ParentId != "evt_parent" { + t.Errorf(`Expected ParentId to be "evt_parent", got %v`, reloaded.ParentId) + } + if reloaded.SpanId == nil || *reloaded.SpanId != "span_hook_001" { + t.Errorf(`Expected SpanId to be "span_hook_001", got %v`, reloaded.SpanId) + } +} + +// TestSessionEventToYAML tests that ToYAML produces valid YAML +func TestSessionEventToYAML(t *testing.T) { + jsonData := ` +{ + "id": "evt_abc123", + "timestamp": "2026-06-09T20:00:00Z", + "sessionId": "sess_abc123", + "turnId": "turn_001", + "parentId": "evt_parent", + "spanId": "span_hook_001" +} +` + var data map[string]interface{} + if err := json.Unmarshal([]byte(jsonData), &data); err != nil { + t.Fatalf("Failed to parse JSON: %v", err) + } + + ctx := prompty.NewLoadContext() + instance, err := prompty.LoadSessionEvent(data, ctx) + if err != nil { + t.Fatalf("Failed to load SessionEvent: %v", err) + } + yamlOutput, err := instance.ToYAML() + if err != nil { + t.Fatalf("Failed to convert to YAML: %v", err) + } + + var parsed map[string]interface{} + if err := yaml.Unmarshal([]byte(yamlOutput), &parsed); err != nil { + t.Fatalf("Failed to parse generated YAML: %v", err) + } + + reloaded, err := prompty.LoadSessionEvent(parsed, ctx) + if err != nil { + t.Fatalf("Failed to reload generated YAML: %v", err) + } + if reloaded.Id != "evt_abc123" { + t.Errorf(`Expected Id to be "evt_abc123", got %v`, reloaded.Id) + } + if reloaded.Timestamp != "2026-06-09T20:00:00Z" { + t.Errorf(`Expected Timestamp to be "2026-06-09T20:00:00Z", got %v`, reloaded.Timestamp) + } + if reloaded.SessionId == nil || *reloaded.SessionId != "sess_abc123" { + t.Errorf(`Expected SessionId to be "sess_abc123", got %v`, reloaded.SessionId) + } + if reloaded.TurnId == nil || *reloaded.TurnId != "turn_001" { + t.Errorf(`Expected TurnId to be "turn_001", got %v`, reloaded.TurnId) + } + if reloaded.ParentId == nil || *reloaded.ParentId != "evt_parent" { + t.Errorf(`Expected ParentId to be "evt_parent", got %v`, reloaded.ParentId) + } + if reloaded.SpanId == nil || *reloaded.SpanId != "span_hook_001" { + t.Errorf(`Expected SpanId to be "span_hook_001", got %v`, reloaded.SpanId) + } +} + +// TestSessionEventFromJSONInvalid rejects malformed JSON instead of silently defaulting +func TestSessionEventFromJSONInvalid(t *testing.T) { + if _, err := prompty.SessionEventFromJSON("{"); err == nil { + t.Fatalf("Expected malformed JSON to fail") + } +} diff --git a/runtime/go/prompty/model/session_file_ref.go b/runtime/go/prompty/model/session_file_ref.go new file mode 100644 index 00000000..d120057c --- /dev/null +++ b/runtime/go/prompty/model/session_file_ref.go @@ -0,0 +1,119 @@ +// +// Code generated by Typra emitter; DO NOT EDIT. +// Group: events + +package prompty + +import ( + "encoding/json" + + "gopkg.in/yaml.v3" +) + +// SessionFileRef represents A file observed or touched by a harness session. + +type SessionFileRef struct { + SessionId *string `json:"sessionId,omitempty" yaml:"sessionId,omitempty"` + Path string `json:"path" yaml:"path"` + ToolName *string `json:"toolName,omitempty" yaml:"toolName,omitempty"` + TurnIndex *int32 `json:"turnIndex,omitempty" yaml:"turnIndex,omitempty"` + FirstSeenAt *string `json:"firstSeenAt,omitempty" yaml:"firstSeenAt,omitempty"` +} + +// LoadSessionFileRef creates a SessionFileRef from a map[string]interface{} +func LoadSessionFileRef(data interface{}, ctx *LoadContext) (SessionFileRef, error) { + result := SessionFileRef{} + + // Load from map + if m, ok := data.(map[string]interface{}); ok { + if val, ok := m["sessionId"]; ok && val != nil { + v := string(val.(string)) + result.SessionId = &v + } + if val, ok := m["path"]; ok && val != nil { + result.Path = string(val.(string)) + } + if val, ok := m["toolName"]; ok && val != nil { + v := string(val.(string)) + result.ToolName = &v + } + if val, ok := m["turnIndex"]; ok && val != nil { // Handle various numeric types from JSON/YAML/roundtrip + var v int32 + switch n := val.(type) { + case int: + v = int32(n) + case int32: + v = int32(n) + case int64: + v = int32(n) + case float64: + v = int32(n) + } + result.TurnIndex = &v + } + if val, ok := m["firstSeenAt"]; ok && val != nil { + v := string(val.(string)) + result.FirstSeenAt = &v + } + } + + return result, nil +} + +// Save serializes SessionFileRef to map[string]interface{} +func (obj SessionFileRef) Save(ctx *SaveContext) map[string]interface{} { + result := make(map[string]interface{}) + if obj.SessionId != nil { + result["sessionId"] = *obj.SessionId + } + result["path"] = obj.Path + if obj.ToolName != nil { + result["toolName"] = *obj.ToolName + } + if obj.TurnIndex != nil { + result["turnIndex"] = *obj.TurnIndex + } + if obj.FirstSeenAt != nil { + result["firstSeenAt"] = *obj.FirstSeenAt + } + + return result +} + +// ToJSON serializes SessionFileRef to JSON string +func (obj *SessionFileRef) ToJSON() (string, error) { + ctx := NewSaveContext() + data := obj.Save(ctx) + bytes, err := json.Marshal(data) + if err != nil { + return "", err + } + return string(bytes), nil +} + +// ToYAML serializes SessionFileRef to YAML string +func (obj *SessionFileRef) ToYAML() (string, error) { + ctx := NewSaveContext() + data := obj.Save(ctx) + return marshalYAMLDocument(data) +} + +// FromJSON creates SessionFileRef from JSON string +func SessionFileRefFromJSON(jsonStr string) (SessionFileRef, error) { + var data map[string]interface{} + if err := json.Unmarshal([]byte(jsonStr), &data); err != nil { + return SessionFileRef{}, err + } + ctx := NewLoadContext() + return LoadSessionFileRef(data, ctx) +} + +// FromYAML creates SessionFileRef from YAML string +func SessionFileRefFromYAML(yamlStr string) (SessionFileRef, error) { + var data map[string]interface{} + if err := yaml.Unmarshal([]byte(yamlStr), &data); err != nil { + return SessionFileRef{}, err + } + ctx := NewLoadContext() + return LoadSessionFileRef(data, ctx) +} diff --git a/runtime/go/prompty/model/session_file_ref_test.go b/runtime/go/prompty/model/session_file_ref_test.go new file mode 100644 index 00000000..3a1d0c51 --- /dev/null +++ b/runtime/go/prompty/model/session_file_ref_test.go @@ -0,0 +1,309 @@ +// +// Code generated by Typra emitter; DO NOT EDIT. + +package prompty_test + +import ( + "encoding/json" + "testing" + + "gopkg.in/yaml.v3" + + "prompty/model" +) + +// TestSessionFileRefLoadJSON tests loading SessionFileRef from JSON +func TestSessionFileRefLoadJSON(t *testing.T) { + jsonData := ` +{ + "sessionId": "sess_abc123", + "path": "src/index.ts", + "toolName": "view", + "turnIndex": 2, + "firstSeenAt": "2026-06-09T20:00:00Z" +} +` + var data map[string]interface{} + if err := json.Unmarshal([]byte(jsonData), &data); err != nil { + t.Fatalf("Failed to parse JSON: %v", err) + } + + ctx := prompty.NewLoadContext() + instance, err := prompty.LoadSessionFileRef(data, ctx) + if err != nil { + t.Fatalf("Failed to load SessionFileRef: %v", err) + } + if instance.SessionId == nil || *instance.SessionId != "sess_abc123" { + t.Errorf(`Expected SessionId to be "sess_abc123", got %v`, instance.SessionId) + } + if instance.Path != "src/index.ts" { + t.Errorf(`Expected Path to be "src/index.ts", got %v`, instance.Path) + } + if instance.ToolName == nil || *instance.ToolName != "view" { + t.Errorf(`Expected ToolName to be "view", got %v`, instance.ToolName) + } + if instance.TurnIndex == nil || *instance.TurnIndex != 2 { + t.Errorf(`Expected TurnIndex to be 2, got %v`, instance.TurnIndex) + } + if instance.FirstSeenAt == nil || *instance.FirstSeenAt != "2026-06-09T20:00:00Z" { + t.Errorf(`Expected FirstSeenAt to be "2026-06-09T20:00:00Z", got %v`, instance.FirstSeenAt) + } +} + +// TestSessionFileRefLoadYAML tests loading SessionFileRef from YAML +func TestSessionFileRefLoadYAML(t *testing.T) { + yamlData := ` +sessionId: sess_abc123 +path: src/index.ts +toolName: view +turnIndex: 2 +firstSeenAt: "2026-06-09T20:00:00Z" + +` + var data map[string]interface{} + if err := yaml.Unmarshal([]byte(yamlData), &data); err != nil { + t.Fatalf("Failed to parse YAML: %v", err) + } + + ctx := prompty.NewLoadContext() + instance, err := prompty.LoadSessionFileRef(data, ctx) + if err != nil { + t.Fatalf("Failed to load SessionFileRef: %v", err) + } + if instance.SessionId == nil || *instance.SessionId != "sess_abc123" { + t.Errorf(`Expected SessionId to be "sess_abc123", got %v`, instance.SessionId) + } + if instance.Path != "src/index.ts" { + t.Errorf(`Expected Path to be "src/index.ts", got %v`, instance.Path) + } + if instance.ToolName == nil || *instance.ToolName != "view" { + t.Errorf(`Expected ToolName to be "view", got %v`, instance.ToolName) + } + if instance.TurnIndex == nil || *instance.TurnIndex != 2 { + t.Errorf(`Expected TurnIndex to be 2, got %v`, instance.TurnIndex) + } + if instance.FirstSeenAt == nil || *instance.FirstSeenAt != "2026-06-09T20:00:00Z" { + t.Errorf(`Expected FirstSeenAt to be "2026-06-09T20:00:00Z", got %v`, instance.FirstSeenAt) + } +} + +// TestSessionFileRefFromJSON tests loading SessionFileRef through the generated JSON helper +func TestSessionFileRefFromJSON(t *testing.T) { + jsonData := ` +{ + "sessionId": "sess_abc123", + "path": "src/index.ts", + "toolName": "view", + "turnIndex": 2, + "firstSeenAt": "2026-06-09T20:00:00Z" +} +` + + instance, err := prompty.SessionFileRefFromJSON(jsonData) + if err != nil { + t.Fatalf("Failed to load SessionFileRef from JSON helper: %v", err) + } + if instance.SessionId == nil || *instance.SessionId != "sess_abc123" { + t.Errorf(`Expected SessionId to be "sess_abc123", got %v`, instance.SessionId) + } + if instance.Path != "src/index.ts" { + t.Errorf(`Expected Path to be "src/index.ts", got %v`, instance.Path) + } + if instance.ToolName == nil || *instance.ToolName != "view" { + t.Errorf(`Expected ToolName to be "view", got %v`, instance.ToolName) + } + if instance.TurnIndex == nil || *instance.TurnIndex != 2 { + t.Errorf(`Expected TurnIndex to be 2, got %v`, instance.TurnIndex) + } + if instance.FirstSeenAt == nil || *instance.FirstSeenAt != "2026-06-09T20:00:00Z" { + t.Errorf(`Expected FirstSeenAt to be "2026-06-09T20:00:00Z", got %v`, instance.FirstSeenAt) + } +} + +// TestSessionFileRefFromYAML tests loading SessionFileRef through the generated YAML helper +func TestSessionFileRefFromYAML(t *testing.T) { + yamlData := ` +sessionId: sess_abc123 +path: src/index.ts +toolName: view +turnIndex: 2 +firstSeenAt: "2026-06-09T20:00:00Z" + +` + + instance, err := prompty.SessionFileRefFromYAML(yamlData) + if err != nil { + t.Fatalf("Failed to load SessionFileRef from YAML helper: %v", err) + } + if instance.SessionId == nil || *instance.SessionId != "sess_abc123" { + t.Errorf(`Expected SessionId to be "sess_abc123", got %v`, instance.SessionId) + } + if instance.Path != "src/index.ts" { + t.Errorf(`Expected Path to be "src/index.ts", got %v`, instance.Path) + } + if instance.ToolName == nil || *instance.ToolName != "view" { + t.Errorf(`Expected ToolName to be "view", got %v`, instance.ToolName) + } + if instance.TurnIndex == nil || *instance.TurnIndex != 2 { + t.Errorf(`Expected TurnIndex to be 2, got %v`, instance.TurnIndex) + } + if instance.FirstSeenAt == nil || *instance.FirstSeenAt != "2026-06-09T20:00:00Z" { + t.Errorf(`Expected FirstSeenAt to be "2026-06-09T20:00:00Z", got %v`, instance.FirstSeenAt) + } +} + +// TestSessionFileRefRoundtrip tests load -> save -> load produces equivalent data +func TestSessionFileRefRoundtrip(t *testing.T) { + jsonData := ` +{ + "sessionId": "sess_abc123", + "path": "src/index.ts", + "toolName": "view", + "turnIndex": 2, + "firstSeenAt": "2026-06-09T20:00:00Z" +} +` + var data map[string]interface{} + if err := json.Unmarshal([]byte(jsonData), &data); err != nil { + t.Fatalf("Failed to parse JSON: %v", err) + } + + loadCtx := prompty.NewLoadContext() + instance, err := prompty.LoadSessionFileRef(data, loadCtx) + if err != nil { + t.Fatalf("Failed to load SessionFileRef: %v", err) + } + saveCtx := prompty.NewSaveContext() + savedData := instance.Save(saveCtx) + + reloaded, err := prompty.LoadSessionFileRef(savedData, loadCtx) + if err != nil { + t.Fatalf("Failed to reload SessionFileRef: %v", err) + } + if reloaded.SessionId == nil || *reloaded.SessionId != "sess_abc123" { + t.Errorf(`Expected SessionId to be "sess_abc123", got %v`, reloaded.SessionId) + } + if reloaded.Path != "src/index.ts" { + t.Errorf(`Expected Path to be "src/index.ts", got %v`, reloaded.Path) + } + if reloaded.ToolName == nil || *reloaded.ToolName != "view" { + t.Errorf(`Expected ToolName to be "view", got %v`, reloaded.ToolName) + } + if reloaded.TurnIndex == nil || *reloaded.TurnIndex != 2 { + t.Errorf(`Expected TurnIndex to be 2, got %v`, reloaded.TurnIndex) + } + if reloaded.FirstSeenAt == nil || *reloaded.FirstSeenAt != "2026-06-09T20:00:00Z" { + t.Errorf(`Expected FirstSeenAt to be "2026-06-09T20:00:00Z", got %v`, reloaded.FirstSeenAt) + } +} + +// TestSessionFileRefToJSON tests that ToJSON produces valid JSON +func TestSessionFileRefToJSON(t *testing.T) { + jsonData := ` +{ + "sessionId": "sess_abc123", + "path": "src/index.ts", + "toolName": "view", + "turnIndex": 2, + "firstSeenAt": "2026-06-09T20:00:00Z" +} +` + var data map[string]interface{} + if err := json.Unmarshal([]byte(jsonData), &data); err != nil { + t.Fatalf("Failed to parse JSON: %v", err) + } + + ctx := prompty.NewLoadContext() + instance, err := prompty.LoadSessionFileRef(data, ctx) + if err != nil { + t.Fatalf("Failed to load SessionFileRef: %v", err) + } + jsonOutput, err := instance.ToJSON() + if err != nil { + t.Fatalf("Failed to convert to JSON: %v", err) + } + + var parsed map[string]interface{} + if err := json.Unmarshal([]byte(jsonOutput), &parsed); err != nil { + t.Fatalf("Failed to parse generated JSON: %v", err) + } + + reloaded, err := prompty.LoadSessionFileRef(parsed, ctx) + if err != nil { + t.Fatalf("Failed to reload generated JSON: %v", err) + } + if reloaded.SessionId == nil || *reloaded.SessionId != "sess_abc123" { + t.Errorf(`Expected SessionId to be "sess_abc123", got %v`, reloaded.SessionId) + } + if reloaded.Path != "src/index.ts" { + t.Errorf(`Expected Path to be "src/index.ts", got %v`, reloaded.Path) + } + if reloaded.ToolName == nil || *reloaded.ToolName != "view" { + t.Errorf(`Expected ToolName to be "view", got %v`, reloaded.ToolName) + } + if reloaded.TurnIndex == nil || *reloaded.TurnIndex != 2 { + t.Errorf(`Expected TurnIndex to be 2, got %v`, reloaded.TurnIndex) + } + if reloaded.FirstSeenAt == nil || *reloaded.FirstSeenAt != "2026-06-09T20:00:00Z" { + t.Errorf(`Expected FirstSeenAt to be "2026-06-09T20:00:00Z", got %v`, reloaded.FirstSeenAt) + } +} + +// TestSessionFileRefToYAML tests that ToYAML produces valid YAML +func TestSessionFileRefToYAML(t *testing.T) { + jsonData := ` +{ + "sessionId": "sess_abc123", + "path": "src/index.ts", + "toolName": "view", + "turnIndex": 2, + "firstSeenAt": "2026-06-09T20:00:00Z" +} +` + var data map[string]interface{} + if err := json.Unmarshal([]byte(jsonData), &data); err != nil { + t.Fatalf("Failed to parse JSON: %v", err) + } + + ctx := prompty.NewLoadContext() + instance, err := prompty.LoadSessionFileRef(data, ctx) + if err != nil { + t.Fatalf("Failed to load SessionFileRef: %v", err) + } + yamlOutput, err := instance.ToYAML() + if err != nil { + t.Fatalf("Failed to convert to YAML: %v", err) + } + + var parsed map[string]interface{} + if err := yaml.Unmarshal([]byte(yamlOutput), &parsed); err != nil { + t.Fatalf("Failed to parse generated YAML: %v", err) + } + + reloaded, err := prompty.LoadSessionFileRef(parsed, ctx) + if err != nil { + t.Fatalf("Failed to reload generated YAML: %v", err) + } + if reloaded.SessionId == nil || *reloaded.SessionId != "sess_abc123" { + t.Errorf(`Expected SessionId to be "sess_abc123", got %v`, reloaded.SessionId) + } + if reloaded.Path != "src/index.ts" { + t.Errorf(`Expected Path to be "src/index.ts", got %v`, reloaded.Path) + } + if reloaded.ToolName == nil || *reloaded.ToolName != "view" { + t.Errorf(`Expected ToolName to be "view", got %v`, reloaded.ToolName) + } + if reloaded.TurnIndex == nil || *reloaded.TurnIndex != 2 { + t.Errorf(`Expected TurnIndex to be 2, got %v`, reloaded.TurnIndex) + } + if reloaded.FirstSeenAt == nil || *reloaded.FirstSeenAt != "2026-06-09T20:00:00Z" { + t.Errorf(`Expected FirstSeenAt to be "2026-06-09T20:00:00Z", got %v`, reloaded.FirstSeenAt) + } +} + +// TestSessionFileRefFromJSONInvalid rejects malformed JSON instead of silently defaulting +func TestSessionFileRefFromJSONInvalid(t *testing.T) { + if _, err := prompty.SessionFileRefFromJSON("{"); err == nil { + t.Fatalf("Expected malformed JSON to fail") + } +} diff --git a/runtime/go/prompty/model/session_ref.go b/runtime/go/prompty/model/session_ref.go new file mode 100644 index 00000000..f262df1c --- /dev/null +++ b/runtime/go/prompty/model/session_ref.go @@ -0,0 +1,116 @@ +// +// Code generated by Typra emitter; DO NOT EDIT. +// Group: events + +package prompty + +import ( + "encoding/json" + + "gopkg.in/yaml.v3" +) + +// SessionRef represents A non-file reference observed by a harness session. + +type SessionRef struct { + SessionId *string `json:"sessionId,omitempty" yaml:"sessionId,omitempty"` + RefType string `json:"refType" yaml:"refType"` + RefValue string `json:"refValue" yaml:"refValue"` + TurnIndex *int32 `json:"turnIndex,omitempty" yaml:"turnIndex,omitempty"` + CreatedAt *string `json:"createdAt,omitempty" yaml:"createdAt,omitempty"` +} + +// LoadSessionRef creates a SessionRef from a map[string]interface{} +func LoadSessionRef(data interface{}, ctx *LoadContext) (SessionRef, error) { + result := SessionRef{} + + // Load from map + if m, ok := data.(map[string]interface{}); ok { + if val, ok := m["sessionId"]; ok && val != nil { + v := string(val.(string)) + result.SessionId = &v + } + if val, ok := m["refType"]; ok && val != nil { + result.RefType = string(val.(string)) + } + if val, ok := m["refValue"]; ok && val != nil { + result.RefValue = string(val.(string)) + } + if val, ok := m["turnIndex"]; ok && val != nil { // Handle various numeric types from JSON/YAML/roundtrip + var v int32 + switch n := val.(type) { + case int: + v = int32(n) + case int32: + v = int32(n) + case int64: + v = int32(n) + case float64: + v = int32(n) + } + result.TurnIndex = &v + } + if val, ok := m["createdAt"]; ok && val != nil { + v := string(val.(string)) + result.CreatedAt = &v + } + } + + return result, nil +} + +// Save serializes SessionRef to map[string]interface{} +func (obj SessionRef) Save(ctx *SaveContext) map[string]interface{} { + result := make(map[string]interface{}) + if obj.SessionId != nil { + result["sessionId"] = *obj.SessionId + } + result["refType"] = obj.RefType + result["refValue"] = obj.RefValue + if obj.TurnIndex != nil { + result["turnIndex"] = *obj.TurnIndex + } + if obj.CreatedAt != nil { + result["createdAt"] = *obj.CreatedAt + } + + return result +} + +// ToJSON serializes SessionRef to JSON string +func (obj *SessionRef) ToJSON() (string, error) { + ctx := NewSaveContext() + data := obj.Save(ctx) + bytes, err := json.Marshal(data) + if err != nil { + return "", err + } + return string(bytes), nil +} + +// ToYAML serializes SessionRef to YAML string +func (obj *SessionRef) ToYAML() (string, error) { + ctx := NewSaveContext() + data := obj.Save(ctx) + return marshalYAMLDocument(data) +} + +// FromJSON creates SessionRef from JSON string +func SessionRefFromJSON(jsonStr string) (SessionRef, error) { + var data map[string]interface{} + if err := json.Unmarshal([]byte(jsonStr), &data); err != nil { + return SessionRef{}, err + } + ctx := NewLoadContext() + return LoadSessionRef(data, ctx) +} + +// FromYAML creates SessionRef from YAML string +func SessionRefFromYAML(yamlStr string) (SessionRef, error) { + var data map[string]interface{} + if err := yaml.Unmarshal([]byte(yamlStr), &data); err != nil { + return SessionRef{}, err + } + ctx := NewLoadContext() + return LoadSessionRef(data, ctx) +} diff --git a/runtime/go/prompty/model/session_ref_test.go b/runtime/go/prompty/model/session_ref_test.go new file mode 100644 index 00000000..6014effd --- /dev/null +++ b/runtime/go/prompty/model/session_ref_test.go @@ -0,0 +1,309 @@ +// +// Code generated by Typra emitter; DO NOT EDIT. + +package prompty_test + +import ( + "encoding/json" + "testing" + + "gopkg.in/yaml.v3" + + "prompty/model" +) + +// TestSessionRefLoadJSON tests loading SessionRef from JSON +func TestSessionRefLoadJSON(t *testing.T) { + jsonData := ` +{ + "sessionId": "sess_abc123", + "refType": "issue", + "refValue": "owner/repo#123", + "turnIndex": 2, + "createdAt": "2026-06-09T20:00:00Z" +} +` + var data map[string]interface{} + if err := json.Unmarshal([]byte(jsonData), &data); err != nil { + t.Fatalf("Failed to parse JSON: %v", err) + } + + ctx := prompty.NewLoadContext() + instance, err := prompty.LoadSessionRef(data, ctx) + if err != nil { + t.Fatalf("Failed to load SessionRef: %v", err) + } + if instance.SessionId == nil || *instance.SessionId != "sess_abc123" { + t.Errorf(`Expected SessionId to be "sess_abc123", got %v`, instance.SessionId) + } + if instance.RefType != "issue" { + t.Errorf(`Expected RefType to be "issue", got %v`, instance.RefType) + } + if instance.RefValue != "owner/repo#123" { + t.Errorf(`Expected RefValue to be "owner/repo#123", got %v`, instance.RefValue) + } + if instance.TurnIndex == nil || *instance.TurnIndex != 2 { + t.Errorf(`Expected TurnIndex to be 2, got %v`, instance.TurnIndex) + } + if instance.CreatedAt == nil || *instance.CreatedAt != "2026-06-09T20:00:00Z" { + t.Errorf(`Expected CreatedAt to be "2026-06-09T20:00:00Z", got %v`, instance.CreatedAt) + } +} + +// TestSessionRefLoadYAML tests loading SessionRef from YAML +func TestSessionRefLoadYAML(t *testing.T) { + yamlData := ` +sessionId: sess_abc123 +refType: issue +refValue: "owner/repo#123" +turnIndex: 2 +createdAt: "2026-06-09T20:00:00Z" + +` + var data map[string]interface{} + if err := yaml.Unmarshal([]byte(yamlData), &data); err != nil { + t.Fatalf("Failed to parse YAML: %v", err) + } + + ctx := prompty.NewLoadContext() + instance, err := prompty.LoadSessionRef(data, ctx) + if err != nil { + t.Fatalf("Failed to load SessionRef: %v", err) + } + if instance.SessionId == nil || *instance.SessionId != "sess_abc123" { + t.Errorf(`Expected SessionId to be "sess_abc123", got %v`, instance.SessionId) + } + if instance.RefType != "issue" { + t.Errorf(`Expected RefType to be "issue", got %v`, instance.RefType) + } + if instance.RefValue != "owner/repo#123" { + t.Errorf(`Expected RefValue to be "owner/repo#123", got %v`, instance.RefValue) + } + if instance.TurnIndex == nil || *instance.TurnIndex != 2 { + t.Errorf(`Expected TurnIndex to be 2, got %v`, instance.TurnIndex) + } + if instance.CreatedAt == nil || *instance.CreatedAt != "2026-06-09T20:00:00Z" { + t.Errorf(`Expected CreatedAt to be "2026-06-09T20:00:00Z", got %v`, instance.CreatedAt) + } +} + +// TestSessionRefFromJSON tests loading SessionRef through the generated JSON helper +func TestSessionRefFromJSON(t *testing.T) { + jsonData := ` +{ + "sessionId": "sess_abc123", + "refType": "issue", + "refValue": "owner/repo#123", + "turnIndex": 2, + "createdAt": "2026-06-09T20:00:00Z" +} +` + + instance, err := prompty.SessionRefFromJSON(jsonData) + if err != nil { + t.Fatalf("Failed to load SessionRef from JSON helper: %v", err) + } + if instance.SessionId == nil || *instance.SessionId != "sess_abc123" { + t.Errorf(`Expected SessionId to be "sess_abc123", got %v`, instance.SessionId) + } + if instance.RefType != "issue" { + t.Errorf(`Expected RefType to be "issue", got %v`, instance.RefType) + } + if instance.RefValue != "owner/repo#123" { + t.Errorf(`Expected RefValue to be "owner/repo#123", got %v`, instance.RefValue) + } + if instance.TurnIndex == nil || *instance.TurnIndex != 2 { + t.Errorf(`Expected TurnIndex to be 2, got %v`, instance.TurnIndex) + } + if instance.CreatedAt == nil || *instance.CreatedAt != "2026-06-09T20:00:00Z" { + t.Errorf(`Expected CreatedAt to be "2026-06-09T20:00:00Z", got %v`, instance.CreatedAt) + } +} + +// TestSessionRefFromYAML tests loading SessionRef through the generated YAML helper +func TestSessionRefFromYAML(t *testing.T) { + yamlData := ` +sessionId: sess_abc123 +refType: issue +refValue: "owner/repo#123" +turnIndex: 2 +createdAt: "2026-06-09T20:00:00Z" + +` + + instance, err := prompty.SessionRefFromYAML(yamlData) + if err != nil { + t.Fatalf("Failed to load SessionRef from YAML helper: %v", err) + } + if instance.SessionId == nil || *instance.SessionId != "sess_abc123" { + t.Errorf(`Expected SessionId to be "sess_abc123", got %v`, instance.SessionId) + } + if instance.RefType != "issue" { + t.Errorf(`Expected RefType to be "issue", got %v`, instance.RefType) + } + if instance.RefValue != "owner/repo#123" { + t.Errorf(`Expected RefValue to be "owner/repo#123", got %v`, instance.RefValue) + } + if instance.TurnIndex == nil || *instance.TurnIndex != 2 { + t.Errorf(`Expected TurnIndex to be 2, got %v`, instance.TurnIndex) + } + if instance.CreatedAt == nil || *instance.CreatedAt != "2026-06-09T20:00:00Z" { + t.Errorf(`Expected CreatedAt to be "2026-06-09T20:00:00Z", got %v`, instance.CreatedAt) + } +} + +// TestSessionRefRoundtrip tests load -> save -> load produces equivalent data +func TestSessionRefRoundtrip(t *testing.T) { + jsonData := ` +{ + "sessionId": "sess_abc123", + "refType": "issue", + "refValue": "owner/repo#123", + "turnIndex": 2, + "createdAt": "2026-06-09T20:00:00Z" +} +` + var data map[string]interface{} + if err := json.Unmarshal([]byte(jsonData), &data); err != nil { + t.Fatalf("Failed to parse JSON: %v", err) + } + + loadCtx := prompty.NewLoadContext() + instance, err := prompty.LoadSessionRef(data, loadCtx) + if err != nil { + t.Fatalf("Failed to load SessionRef: %v", err) + } + saveCtx := prompty.NewSaveContext() + savedData := instance.Save(saveCtx) + + reloaded, err := prompty.LoadSessionRef(savedData, loadCtx) + if err != nil { + t.Fatalf("Failed to reload SessionRef: %v", err) + } + if reloaded.SessionId == nil || *reloaded.SessionId != "sess_abc123" { + t.Errorf(`Expected SessionId to be "sess_abc123", got %v`, reloaded.SessionId) + } + if reloaded.RefType != "issue" { + t.Errorf(`Expected RefType to be "issue", got %v`, reloaded.RefType) + } + if reloaded.RefValue != "owner/repo#123" { + t.Errorf(`Expected RefValue to be "owner/repo#123", got %v`, reloaded.RefValue) + } + if reloaded.TurnIndex == nil || *reloaded.TurnIndex != 2 { + t.Errorf(`Expected TurnIndex to be 2, got %v`, reloaded.TurnIndex) + } + if reloaded.CreatedAt == nil || *reloaded.CreatedAt != "2026-06-09T20:00:00Z" { + t.Errorf(`Expected CreatedAt to be "2026-06-09T20:00:00Z", got %v`, reloaded.CreatedAt) + } +} + +// TestSessionRefToJSON tests that ToJSON produces valid JSON +func TestSessionRefToJSON(t *testing.T) { + jsonData := ` +{ + "sessionId": "sess_abc123", + "refType": "issue", + "refValue": "owner/repo#123", + "turnIndex": 2, + "createdAt": "2026-06-09T20:00:00Z" +} +` + var data map[string]interface{} + if err := json.Unmarshal([]byte(jsonData), &data); err != nil { + t.Fatalf("Failed to parse JSON: %v", err) + } + + ctx := prompty.NewLoadContext() + instance, err := prompty.LoadSessionRef(data, ctx) + if err != nil { + t.Fatalf("Failed to load SessionRef: %v", err) + } + jsonOutput, err := instance.ToJSON() + if err != nil { + t.Fatalf("Failed to convert to JSON: %v", err) + } + + var parsed map[string]interface{} + if err := json.Unmarshal([]byte(jsonOutput), &parsed); err != nil { + t.Fatalf("Failed to parse generated JSON: %v", err) + } + + reloaded, err := prompty.LoadSessionRef(parsed, ctx) + if err != nil { + t.Fatalf("Failed to reload generated JSON: %v", err) + } + if reloaded.SessionId == nil || *reloaded.SessionId != "sess_abc123" { + t.Errorf(`Expected SessionId to be "sess_abc123", got %v`, reloaded.SessionId) + } + if reloaded.RefType != "issue" { + t.Errorf(`Expected RefType to be "issue", got %v`, reloaded.RefType) + } + if reloaded.RefValue != "owner/repo#123" { + t.Errorf(`Expected RefValue to be "owner/repo#123", got %v`, reloaded.RefValue) + } + if reloaded.TurnIndex == nil || *reloaded.TurnIndex != 2 { + t.Errorf(`Expected TurnIndex to be 2, got %v`, reloaded.TurnIndex) + } + if reloaded.CreatedAt == nil || *reloaded.CreatedAt != "2026-06-09T20:00:00Z" { + t.Errorf(`Expected CreatedAt to be "2026-06-09T20:00:00Z", got %v`, reloaded.CreatedAt) + } +} + +// TestSessionRefToYAML tests that ToYAML produces valid YAML +func TestSessionRefToYAML(t *testing.T) { + jsonData := ` +{ + "sessionId": "sess_abc123", + "refType": "issue", + "refValue": "owner/repo#123", + "turnIndex": 2, + "createdAt": "2026-06-09T20:00:00Z" +} +` + var data map[string]interface{} + if err := json.Unmarshal([]byte(jsonData), &data); err != nil { + t.Fatalf("Failed to parse JSON: %v", err) + } + + ctx := prompty.NewLoadContext() + instance, err := prompty.LoadSessionRef(data, ctx) + if err != nil { + t.Fatalf("Failed to load SessionRef: %v", err) + } + yamlOutput, err := instance.ToYAML() + if err != nil { + t.Fatalf("Failed to convert to YAML: %v", err) + } + + var parsed map[string]interface{} + if err := yaml.Unmarshal([]byte(yamlOutput), &parsed); err != nil { + t.Fatalf("Failed to parse generated YAML: %v", err) + } + + reloaded, err := prompty.LoadSessionRef(parsed, ctx) + if err != nil { + t.Fatalf("Failed to reload generated YAML: %v", err) + } + if reloaded.SessionId == nil || *reloaded.SessionId != "sess_abc123" { + t.Errorf(`Expected SessionId to be "sess_abc123", got %v`, reloaded.SessionId) + } + if reloaded.RefType != "issue" { + t.Errorf(`Expected RefType to be "issue", got %v`, reloaded.RefType) + } + if reloaded.RefValue != "owner/repo#123" { + t.Errorf(`Expected RefValue to be "owner/repo#123", got %v`, reloaded.RefValue) + } + if reloaded.TurnIndex == nil || *reloaded.TurnIndex != 2 { + t.Errorf(`Expected TurnIndex to be 2, got %v`, reloaded.TurnIndex) + } + if reloaded.CreatedAt == nil || *reloaded.CreatedAt != "2026-06-09T20:00:00Z" { + t.Errorf(`Expected CreatedAt to be "2026-06-09T20:00:00Z", got %v`, reloaded.CreatedAt) + } +} + +// TestSessionRefFromJSONInvalid rejects malformed JSON instead of silently defaulting +func TestSessionRefFromJSONInvalid(t *testing.T) { + if _, err := prompty.SessionRefFromJSON("{"); err == nil { + t.Fatalf("Expected malformed JSON to fail") + } +} diff --git a/runtime/go/prompty/model/session_start_payload.go b/runtime/go/prompty/model/session_start_payload.go new file mode 100644 index 00000000..cd483fc9 --- /dev/null +++ b/runtime/go/prompty/model/session_start_payload.go @@ -0,0 +1,143 @@ +// +// Code generated by Typra emitter; DO NOT EDIT. +// Group: events + +package prompty + +import ( + "encoding/json" + + "gopkg.in/yaml.v3" +) + +// SessionStartPayload represents Payload for "session_start" events. + +type SessionStartPayload struct { + SessionId string `json:"sessionId" yaml:"sessionId"` + SchemaVersion *string `json:"schemaVersion,omitempty" yaml:"schemaVersion,omitempty"` + Producer *string `json:"producer,omitempty" yaml:"producer,omitempty"` + Runtime *string `json:"runtime,omitempty" yaml:"runtime,omitempty"` + PromptyVersion *string `json:"promptyVersion,omitempty" yaml:"promptyVersion,omitempty"` + StartTime *string `json:"startTime,omitempty" yaml:"startTime,omitempty"` + SelectedModel *string `json:"selectedModel,omitempty" yaml:"selectedModel,omitempty"` + ReasoningEffort *string `json:"reasoningEffort,omitempty" yaml:"reasoningEffort,omitempty"` + Context *HarnessContext `json:"context,omitempty" yaml:"context,omitempty"` +} + +// LoadSessionStartPayload creates a SessionStartPayload from a map[string]interface{} +func LoadSessionStartPayload(data interface{}, ctx *LoadContext) (SessionStartPayload, error) { + result := SessionStartPayload{} + + // Load from map + if m, ok := data.(map[string]interface{}); ok { + if val, ok := m["sessionId"]; ok && val != nil { + result.SessionId = string(val.(string)) + } + if val, ok := m["schemaVersion"]; ok && val != nil { + v := string(val.(string)) + result.SchemaVersion = &v + } + if val, ok := m["producer"]; ok && val != nil { + v := string(val.(string)) + result.Producer = &v + } + if val, ok := m["runtime"]; ok && val != nil { + v := string(val.(string)) + result.Runtime = &v + } + if val, ok := m["promptyVersion"]; ok && val != nil { + v := string(val.(string)) + result.PromptyVersion = &v + } + if val, ok := m["startTime"]; ok && val != nil { + v := string(val.(string)) + result.StartTime = &v + } + if val, ok := m["selectedModel"]; ok && val != nil { + v := string(val.(string)) + result.SelectedModel = &v + } + if val, ok := m["reasoningEffort"]; ok && val != nil { + v := string(val.(string)) + result.ReasoningEffort = &v + } + if val, ok := m["context"]; ok && val != nil { + if m, ok := val.(map[string]interface{}); ok { + loaded, _ := LoadHarnessContext(m, ctx) + result.Context = &loaded + } + } + } + + return result, nil +} + +// Save serializes SessionStartPayload to map[string]interface{} +func (obj SessionStartPayload) Save(ctx *SaveContext) map[string]interface{} { + result := make(map[string]interface{}) + result["sessionId"] = obj.SessionId + if obj.SchemaVersion != nil { + result["schemaVersion"] = *obj.SchemaVersion + } + if obj.Producer != nil { + result["producer"] = *obj.Producer + } + if obj.Runtime != nil { + result["runtime"] = *obj.Runtime + } + if obj.PromptyVersion != nil { + result["promptyVersion"] = *obj.PromptyVersion + } + if obj.StartTime != nil { + result["startTime"] = *obj.StartTime + } + if obj.SelectedModel != nil { + result["selectedModel"] = *obj.SelectedModel + } + if obj.ReasoningEffort != nil { + result["reasoningEffort"] = *obj.ReasoningEffort + } + if obj.Context != nil { + result["context"] = obj.Context.Save(ctx) + } + + return result +} + +// ToJSON serializes SessionStartPayload to JSON string +func (obj *SessionStartPayload) ToJSON() (string, error) { + ctx := NewSaveContext() + data := obj.Save(ctx) + bytes, err := json.Marshal(data) + if err != nil { + return "", err + } + return string(bytes), nil +} + +// ToYAML serializes SessionStartPayload to YAML string +func (obj *SessionStartPayload) ToYAML() (string, error) { + ctx := NewSaveContext() + data := obj.Save(ctx) + return marshalYAMLDocument(data) +} + +// FromJSON creates SessionStartPayload from JSON string +func SessionStartPayloadFromJSON(jsonStr string) (SessionStartPayload, error) { + var data map[string]interface{} + if err := json.Unmarshal([]byte(jsonStr), &data); err != nil { + return SessionStartPayload{}, err + } + ctx := NewLoadContext() + return LoadSessionStartPayload(data, ctx) +} + +// FromYAML creates SessionStartPayload from YAML string +func SessionStartPayloadFromYAML(yamlStr string) (SessionStartPayload, error) { + var data map[string]interface{} + if err := yaml.Unmarshal([]byte(yamlStr), &data); err != nil { + return SessionStartPayload{}, err + } + ctx := NewLoadContext() + return LoadSessionStartPayload(data, ctx) +} diff --git a/runtime/go/prompty/model/session_start_payload_test.go b/runtime/go/prompty/model/session_start_payload_test.go new file mode 100644 index 00000000..0a4c1ec5 --- /dev/null +++ b/runtime/go/prompty/model/session_start_payload_test.go @@ -0,0 +1,393 @@ +// +// Code generated by Typra emitter; DO NOT EDIT. + +package prompty_test + +import ( + "encoding/json" + "testing" + + "gopkg.in/yaml.v3" + + "prompty/model" +) + +// TestSessionStartPayloadLoadJSON tests loading SessionStartPayload from JSON +func TestSessionStartPayloadLoadJSON(t *testing.T) { + jsonData := ` +{ + "sessionId": "sess_abc123", + "schemaVersion": "1", + "producer": "prompty-agent", + "runtime": "typescript", + "promptyVersion": "2.0.0", + "startTime": "2026-06-09T20:00:00Z", + "selectedModel": "gpt-4o-mini", + "reasoningEffort": "medium" +} +` + var data map[string]interface{} + if err := json.Unmarshal([]byte(jsonData), &data); err != nil { + t.Fatalf("Failed to parse JSON: %v", err) + } + + ctx := prompty.NewLoadContext() + instance, err := prompty.LoadSessionStartPayload(data, ctx) + if err != nil { + t.Fatalf("Failed to load SessionStartPayload: %v", err) + } + if instance.SessionId != "sess_abc123" { + t.Errorf(`Expected SessionId to be "sess_abc123", got %v`, instance.SessionId) + } + if instance.SchemaVersion == nil || *instance.SchemaVersion != "1" { + t.Errorf(`Expected SchemaVersion to be "1", got %v`, instance.SchemaVersion) + } + if instance.Producer == nil || *instance.Producer != "prompty-agent" { + t.Errorf(`Expected Producer to be "prompty-agent", got %v`, instance.Producer) + } + if instance.Runtime == nil || *instance.Runtime != "typescript" { + t.Errorf(`Expected Runtime to be "typescript", got %v`, instance.Runtime) + } + if instance.PromptyVersion == nil || *instance.PromptyVersion != "2.0.0" { + t.Errorf(`Expected PromptyVersion to be "2.0.0", got %v`, instance.PromptyVersion) + } + if instance.StartTime == nil || *instance.StartTime != "2026-06-09T20:00:00Z" { + t.Errorf(`Expected StartTime to be "2026-06-09T20:00:00Z", got %v`, instance.StartTime) + } + if instance.SelectedModel == nil || *instance.SelectedModel != "gpt-4o-mini" { + t.Errorf(`Expected SelectedModel to be "gpt-4o-mini", got %v`, instance.SelectedModel) + } + if instance.ReasoningEffort == nil || *instance.ReasoningEffort != "medium" { + t.Errorf(`Expected ReasoningEffort to be "medium", got %v`, instance.ReasoningEffort) + } +} + +// TestSessionStartPayloadLoadYAML tests loading SessionStartPayload from YAML +func TestSessionStartPayloadLoadYAML(t *testing.T) { + yamlData := ` +sessionId: sess_abc123 +schemaVersion: "1" +producer: prompty-agent +runtime: typescript +promptyVersion: 2.0.0 +startTime: "2026-06-09T20:00:00Z" +selectedModel: gpt-4o-mini +reasoningEffort: medium + +` + var data map[string]interface{} + if err := yaml.Unmarshal([]byte(yamlData), &data); err != nil { + t.Fatalf("Failed to parse YAML: %v", err) + } + + ctx := prompty.NewLoadContext() + instance, err := prompty.LoadSessionStartPayload(data, ctx) + if err != nil { + t.Fatalf("Failed to load SessionStartPayload: %v", err) + } + if instance.SessionId != "sess_abc123" { + t.Errorf(`Expected SessionId to be "sess_abc123", got %v`, instance.SessionId) + } + if instance.SchemaVersion == nil || *instance.SchemaVersion != "1" { + t.Errorf(`Expected SchemaVersion to be "1", got %v`, instance.SchemaVersion) + } + if instance.Producer == nil || *instance.Producer != "prompty-agent" { + t.Errorf(`Expected Producer to be "prompty-agent", got %v`, instance.Producer) + } + if instance.Runtime == nil || *instance.Runtime != "typescript" { + t.Errorf(`Expected Runtime to be "typescript", got %v`, instance.Runtime) + } + if instance.PromptyVersion == nil || *instance.PromptyVersion != "2.0.0" { + t.Errorf(`Expected PromptyVersion to be "2.0.0", got %v`, instance.PromptyVersion) + } + if instance.StartTime == nil || *instance.StartTime != "2026-06-09T20:00:00Z" { + t.Errorf(`Expected StartTime to be "2026-06-09T20:00:00Z", got %v`, instance.StartTime) + } + if instance.SelectedModel == nil || *instance.SelectedModel != "gpt-4o-mini" { + t.Errorf(`Expected SelectedModel to be "gpt-4o-mini", got %v`, instance.SelectedModel) + } + if instance.ReasoningEffort == nil || *instance.ReasoningEffort != "medium" { + t.Errorf(`Expected ReasoningEffort to be "medium", got %v`, instance.ReasoningEffort) + } +} + +// TestSessionStartPayloadFromJSON tests loading SessionStartPayload through the generated JSON helper +func TestSessionStartPayloadFromJSON(t *testing.T) { + jsonData := ` +{ + "sessionId": "sess_abc123", + "schemaVersion": "1", + "producer": "prompty-agent", + "runtime": "typescript", + "promptyVersion": "2.0.0", + "startTime": "2026-06-09T20:00:00Z", + "selectedModel": "gpt-4o-mini", + "reasoningEffort": "medium" +} +` + + instance, err := prompty.SessionStartPayloadFromJSON(jsonData) + if err != nil { + t.Fatalf("Failed to load SessionStartPayload from JSON helper: %v", err) + } + if instance.SessionId != "sess_abc123" { + t.Errorf(`Expected SessionId to be "sess_abc123", got %v`, instance.SessionId) + } + if instance.SchemaVersion == nil || *instance.SchemaVersion != "1" { + t.Errorf(`Expected SchemaVersion to be "1", got %v`, instance.SchemaVersion) + } + if instance.Producer == nil || *instance.Producer != "prompty-agent" { + t.Errorf(`Expected Producer to be "prompty-agent", got %v`, instance.Producer) + } + if instance.Runtime == nil || *instance.Runtime != "typescript" { + t.Errorf(`Expected Runtime to be "typescript", got %v`, instance.Runtime) + } + if instance.PromptyVersion == nil || *instance.PromptyVersion != "2.0.0" { + t.Errorf(`Expected PromptyVersion to be "2.0.0", got %v`, instance.PromptyVersion) + } + if instance.StartTime == nil || *instance.StartTime != "2026-06-09T20:00:00Z" { + t.Errorf(`Expected StartTime to be "2026-06-09T20:00:00Z", got %v`, instance.StartTime) + } + if instance.SelectedModel == nil || *instance.SelectedModel != "gpt-4o-mini" { + t.Errorf(`Expected SelectedModel to be "gpt-4o-mini", got %v`, instance.SelectedModel) + } + if instance.ReasoningEffort == nil || *instance.ReasoningEffort != "medium" { + t.Errorf(`Expected ReasoningEffort to be "medium", got %v`, instance.ReasoningEffort) + } +} + +// TestSessionStartPayloadFromYAML tests loading SessionStartPayload through the generated YAML helper +func TestSessionStartPayloadFromYAML(t *testing.T) { + yamlData := ` +sessionId: sess_abc123 +schemaVersion: "1" +producer: prompty-agent +runtime: typescript +promptyVersion: 2.0.0 +startTime: "2026-06-09T20:00:00Z" +selectedModel: gpt-4o-mini +reasoningEffort: medium + +` + + instance, err := prompty.SessionStartPayloadFromYAML(yamlData) + if err != nil { + t.Fatalf("Failed to load SessionStartPayload from YAML helper: %v", err) + } + if instance.SessionId != "sess_abc123" { + t.Errorf(`Expected SessionId to be "sess_abc123", got %v`, instance.SessionId) + } + if instance.SchemaVersion == nil || *instance.SchemaVersion != "1" { + t.Errorf(`Expected SchemaVersion to be "1", got %v`, instance.SchemaVersion) + } + if instance.Producer == nil || *instance.Producer != "prompty-agent" { + t.Errorf(`Expected Producer to be "prompty-agent", got %v`, instance.Producer) + } + if instance.Runtime == nil || *instance.Runtime != "typescript" { + t.Errorf(`Expected Runtime to be "typescript", got %v`, instance.Runtime) + } + if instance.PromptyVersion == nil || *instance.PromptyVersion != "2.0.0" { + t.Errorf(`Expected PromptyVersion to be "2.0.0", got %v`, instance.PromptyVersion) + } + if instance.StartTime == nil || *instance.StartTime != "2026-06-09T20:00:00Z" { + t.Errorf(`Expected StartTime to be "2026-06-09T20:00:00Z", got %v`, instance.StartTime) + } + if instance.SelectedModel == nil || *instance.SelectedModel != "gpt-4o-mini" { + t.Errorf(`Expected SelectedModel to be "gpt-4o-mini", got %v`, instance.SelectedModel) + } + if instance.ReasoningEffort == nil || *instance.ReasoningEffort != "medium" { + t.Errorf(`Expected ReasoningEffort to be "medium", got %v`, instance.ReasoningEffort) + } +} + +// TestSessionStartPayloadRoundtrip tests load -> save -> load produces equivalent data +func TestSessionStartPayloadRoundtrip(t *testing.T) { + jsonData := ` +{ + "sessionId": "sess_abc123", + "schemaVersion": "1", + "producer": "prompty-agent", + "runtime": "typescript", + "promptyVersion": "2.0.0", + "startTime": "2026-06-09T20:00:00Z", + "selectedModel": "gpt-4o-mini", + "reasoningEffort": "medium" +} +` + var data map[string]interface{} + if err := json.Unmarshal([]byte(jsonData), &data); err != nil { + t.Fatalf("Failed to parse JSON: %v", err) + } + + loadCtx := prompty.NewLoadContext() + instance, err := prompty.LoadSessionStartPayload(data, loadCtx) + if err != nil { + t.Fatalf("Failed to load SessionStartPayload: %v", err) + } + saveCtx := prompty.NewSaveContext() + savedData := instance.Save(saveCtx) + + reloaded, err := prompty.LoadSessionStartPayload(savedData, loadCtx) + if err != nil { + t.Fatalf("Failed to reload SessionStartPayload: %v", err) + } + if reloaded.SessionId != "sess_abc123" { + t.Errorf(`Expected SessionId to be "sess_abc123", got %v`, reloaded.SessionId) + } + if reloaded.SchemaVersion == nil || *reloaded.SchemaVersion != "1" { + t.Errorf(`Expected SchemaVersion to be "1", got %v`, reloaded.SchemaVersion) + } + if reloaded.Producer == nil || *reloaded.Producer != "prompty-agent" { + t.Errorf(`Expected Producer to be "prompty-agent", got %v`, reloaded.Producer) + } + if reloaded.Runtime == nil || *reloaded.Runtime != "typescript" { + t.Errorf(`Expected Runtime to be "typescript", got %v`, reloaded.Runtime) + } + if reloaded.PromptyVersion == nil || *reloaded.PromptyVersion != "2.0.0" { + t.Errorf(`Expected PromptyVersion to be "2.0.0", got %v`, reloaded.PromptyVersion) + } + if reloaded.StartTime == nil || *reloaded.StartTime != "2026-06-09T20:00:00Z" { + t.Errorf(`Expected StartTime to be "2026-06-09T20:00:00Z", got %v`, reloaded.StartTime) + } + if reloaded.SelectedModel == nil || *reloaded.SelectedModel != "gpt-4o-mini" { + t.Errorf(`Expected SelectedModel to be "gpt-4o-mini", got %v`, reloaded.SelectedModel) + } + if reloaded.ReasoningEffort == nil || *reloaded.ReasoningEffort != "medium" { + t.Errorf(`Expected ReasoningEffort to be "medium", got %v`, reloaded.ReasoningEffort) + } +} + +// TestSessionStartPayloadToJSON tests that ToJSON produces valid JSON +func TestSessionStartPayloadToJSON(t *testing.T) { + jsonData := ` +{ + "sessionId": "sess_abc123", + "schemaVersion": "1", + "producer": "prompty-agent", + "runtime": "typescript", + "promptyVersion": "2.0.0", + "startTime": "2026-06-09T20:00:00Z", + "selectedModel": "gpt-4o-mini", + "reasoningEffort": "medium" +} +` + var data map[string]interface{} + if err := json.Unmarshal([]byte(jsonData), &data); err != nil { + t.Fatalf("Failed to parse JSON: %v", err) + } + + ctx := prompty.NewLoadContext() + instance, err := prompty.LoadSessionStartPayload(data, ctx) + if err != nil { + t.Fatalf("Failed to load SessionStartPayload: %v", err) + } + jsonOutput, err := instance.ToJSON() + if err != nil { + t.Fatalf("Failed to convert to JSON: %v", err) + } + + var parsed map[string]interface{} + if err := json.Unmarshal([]byte(jsonOutput), &parsed); err != nil { + t.Fatalf("Failed to parse generated JSON: %v", err) + } + + reloaded, err := prompty.LoadSessionStartPayload(parsed, ctx) + if err != nil { + t.Fatalf("Failed to reload generated JSON: %v", err) + } + if reloaded.SessionId != "sess_abc123" { + t.Errorf(`Expected SessionId to be "sess_abc123", got %v`, reloaded.SessionId) + } + if reloaded.SchemaVersion == nil || *reloaded.SchemaVersion != "1" { + t.Errorf(`Expected SchemaVersion to be "1", got %v`, reloaded.SchemaVersion) + } + if reloaded.Producer == nil || *reloaded.Producer != "prompty-agent" { + t.Errorf(`Expected Producer to be "prompty-agent", got %v`, reloaded.Producer) + } + if reloaded.Runtime == nil || *reloaded.Runtime != "typescript" { + t.Errorf(`Expected Runtime to be "typescript", got %v`, reloaded.Runtime) + } + if reloaded.PromptyVersion == nil || *reloaded.PromptyVersion != "2.0.0" { + t.Errorf(`Expected PromptyVersion to be "2.0.0", got %v`, reloaded.PromptyVersion) + } + if reloaded.StartTime == nil || *reloaded.StartTime != "2026-06-09T20:00:00Z" { + t.Errorf(`Expected StartTime to be "2026-06-09T20:00:00Z", got %v`, reloaded.StartTime) + } + if reloaded.SelectedModel == nil || *reloaded.SelectedModel != "gpt-4o-mini" { + t.Errorf(`Expected SelectedModel to be "gpt-4o-mini", got %v`, reloaded.SelectedModel) + } + if reloaded.ReasoningEffort == nil || *reloaded.ReasoningEffort != "medium" { + t.Errorf(`Expected ReasoningEffort to be "medium", got %v`, reloaded.ReasoningEffort) + } +} + +// TestSessionStartPayloadToYAML tests that ToYAML produces valid YAML +func TestSessionStartPayloadToYAML(t *testing.T) { + jsonData := ` +{ + "sessionId": "sess_abc123", + "schemaVersion": "1", + "producer": "prompty-agent", + "runtime": "typescript", + "promptyVersion": "2.0.0", + "startTime": "2026-06-09T20:00:00Z", + "selectedModel": "gpt-4o-mini", + "reasoningEffort": "medium" +} +` + var data map[string]interface{} + if err := json.Unmarshal([]byte(jsonData), &data); err != nil { + t.Fatalf("Failed to parse JSON: %v", err) + } + + ctx := prompty.NewLoadContext() + instance, err := prompty.LoadSessionStartPayload(data, ctx) + if err != nil { + t.Fatalf("Failed to load SessionStartPayload: %v", err) + } + yamlOutput, err := instance.ToYAML() + if err != nil { + t.Fatalf("Failed to convert to YAML: %v", err) + } + + var parsed map[string]interface{} + if err := yaml.Unmarshal([]byte(yamlOutput), &parsed); err != nil { + t.Fatalf("Failed to parse generated YAML: %v", err) + } + + reloaded, err := prompty.LoadSessionStartPayload(parsed, ctx) + if err != nil { + t.Fatalf("Failed to reload generated YAML: %v", err) + } + if reloaded.SessionId != "sess_abc123" { + t.Errorf(`Expected SessionId to be "sess_abc123", got %v`, reloaded.SessionId) + } + if reloaded.SchemaVersion == nil || *reloaded.SchemaVersion != "1" { + t.Errorf(`Expected SchemaVersion to be "1", got %v`, reloaded.SchemaVersion) + } + if reloaded.Producer == nil || *reloaded.Producer != "prompty-agent" { + t.Errorf(`Expected Producer to be "prompty-agent", got %v`, reloaded.Producer) + } + if reloaded.Runtime == nil || *reloaded.Runtime != "typescript" { + t.Errorf(`Expected Runtime to be "typescript", got %v`, reloaded.Runtime) + } + if reloaded.PromptyVersion == nil || *reloaded.PromptyVersion != "2.0.0" { + t.Errorf(`Expected PromptyVersion to be "2.0.0", got %v`, reloaded.PromptyVersion) + } + if reloaded.StartTime == nil || *reloaded.StartTime != "2026-06-09T20:00:00Z" { + t.Errorf(`Expected StartTime to be "2026-06-09T20:00:00Z", got %v`, reloaded.StartTime) + } + if reloaded.SelectedModel == nil || *reloaded.SelectedModel != "gpt-4o-mini" { + t.Errorf(`Expected SelectedModel to be "gpt-4o-mini", got %v`, reloaded.SelectedModel) + } + if reloaded.ReasoningEffort == nil || *reloaded.ReasoningEffort != "medium" { + t.Errorf(`Expected ReasoningEffort to be "medium", got %v`, reloaded.ReasoningEffort) + } +} + +// TestSessionStartPayloadFromJSONInvalid rejects malformed JSON instead of silently defaulting +func TestSessionStartPayloadFromJSONInvalid(t *testing.T) { + if _, err := prompty.SessionStartPayloadFromJSON("{"); err == nil { + t.Fatalf("Expected malformed JSON to fail") + } +} diff --git a/runtime/go/prompty/model/session_summary.go b/runtime/go/prompty/model/session_summary.go new file mode 100644 index 00000000..255110db --- /dev/null +++ b/runtime/go/prompty/model/session_summary.go @@ -0,0 +1,161 @@ +// +// Code generated by Typra emitter; DO NOT EDIT. +// Group: events + +package prompty + +import ( + "encoding/json" + + "gopkg.in/yaml.v3" +) + +// SessionSummaryStatus represents the allowed values for SessionSummaryStatus. +type SessionSummaryStatus string + +const ( + SessionSummaryStatusSuccess SessionSummaryStatus = "success" + SessionSummaryStatusError SessionSummaryStatus = "error" + SessionSummaryStatusCancelled SessionSummaryStatus = "cancelled" + SessionSummaryStatusInterrupted SessionSummaryStatus = "interrupted" +) + +// SessionSummary represents Summary statistics for a completed session trace. + +type SessionSummary struct { + SessionId string `json:"sessionId" yaml:"sessionId"` + Status *SessionSummaryStatus `json:"status,omitempty" yaml:"status,omitempty"` + Turns *int32 `json:"turns,omitempty" yaml:"turns,omitempty"` + Checkpoints *int32 `json:"checkpoints,omitempty" yaml:"checkpoints,omitempty"` + Usage *TokenUsage `json:"usage,omitempty" yaml:"usage,omitempty"` + DurationMs *float64 `json:"durationMs,omitempty" yaml:"durationMs,omitempty"` +} + +// LoadSessionSummary creates a SessionSummary from a map[string]interface{} +func LoadSessionSummary(data interface{}, ctx *LoadContext) (SessionSummary, error) { + result := SessionSummary{} + + // Load from map + if m, ok := data.(map[string]interface{}); ok { + if val, ok := m["sessionId"]; ok && val != nil { + result.SessionId = string(val.(string)) + } + if val, ok := m["status"]; ok && val != nil { + v := SessionSummaryStatus(val.(string)) + result.Status = &v + } + if val, ok := m["turns"]; ok && val != nil { // Handle various numeric types from JSON/YAML/roundtrip + var v int32 + switch n := val.(type) { + case int: + v = int32(n) + case int32: + v = int32(n) + case int64: + v = int32(n) + case float64: + v = int32(n) + } + result.Turns = &v + } + if val, ok := m["checkpoints"]; ok && val != nil { // Handle various numeric types from JSON/YAML/roundtrip + var v int32 + switch n := val.(type) { + case int: + v = int32(n) + case int32: + v = int32(n) + case int64: + v = int32(n) + case float64: + v = int32(n) + } + result.Checkpoints = &v + } + if val, ok := m["usage"]; ok && val != nil { + if m, ok := val.(map[string]interface{}); ok { + loaded, _ := LoadTokenUsage(m, ctx) + result.Usage = &loaded + } + } + if val, ok := m["durationMs"]; ok && val != nil { // Handle various numeric types from JSON/YAML/roundtrip + var v float64 + switch n := val.(type) { + case int: + v = float64(n) + case int32: + v = float64(n) + case int64: + v = float64(n) + case float32: + v = float64(n) + case float64: + v = n + } + result.DurationMs = &v + } + } + + return result, nil +} + +// Save serializes SessionSummary to map[string]interface{} +func (obj SessionSummary) Save(ctx *SaveContext) map[string]interface{} { + result := make(map[string]interface{}) + result["sessionId"] = obj.SessionId + if obj.Status != nil { + result["status"] = string(*obj.Status) + } + if obj.Turns != nil { + result["turns"] = *obj.Turns + } + if obj.Checkpoints != nil { + result["checkpoints"] = *obj.Checkpoints + } + if obj.Usage != nil { + result["usage"] = obj.Usage.Save(ctx) + } + if obj.DurationMs != nil { + result["durationMs"] = *obj.DurationMs + } + + return result +} + +// ToJSON serializes SessionSummary to JSON string +func (obj *SessionSummary) ToJSON() (string, error) { + ctx := NewSaveContext() + data := obj.Save(ctx) + bytes, err := json.Marshal(data) + if err != nil { + return "", err + } + return string(bytes), nil +} + +// ToYAML serializes SessionSummary to YAML string +func (obj *SessionSummary) ToYAML() (string, error) { + ctx := NewSaveContext() + data := obj.Save(ctx) + return marshalYAMLDocument(data) +} + +// FromJSON creates SessionSummary from JSON string +func SessionSummaryFromJSON(jsonStr string) (SessionSummary, error) { + var data map[string]interface{} + if err := json.Unmarshal([]byte(jsonStr), &data); err != nil { + return SessionSummary{}, err + } + ctx := NewLoadContext() + return LoadSessionSummary(data, ctx) +} + +// FromYAML creates SessionSummary from YAML string +func SessionSummaryFromYAML(yamlStr string) (SessionSummary, error) { + var data map[string]interface{} + if err := yaml.Unmarshal([]byte(yamlStr), &data); err != nil { + return SessionSummary{}, err + } + ctx := NewLoadContext() + return LoadSessionSummary(data, ctx) +} diff --git a/runtime/go/prompty/model/session_summary_test.go b/runtime/go/prompty/model/session_summary_test.go new file mode 100644 index 00000000..ecbb0b8f --- /dev/null +++ b/runtime/go/prompty/model/session_summary_test.go @@ -0,0 +1,309 @@ +// +// Code generated by Typra emitter; DO NOT EDIT. + +package prompty_test + +import ( + "encoding/json" + "testing" + + "gopkg.in/yaml.v3" + + "prompty/model" +) + +// TestSessionSummaryLoadJSON tests loading SessionSummary from JSON +func TestSessionSummaryLoadJSON(t *testing.T) { + jsonData := ` +{ + "sessionId": "sess_abc123", + "status": "success", + "turns": 5, + "checkpoints": 2, + "durationMs": 12500 +} +` + var data map[string]interface{} + if err := json.Unmarshal([]byte(jsonData), &data); err != nil { + t.Fatalf("Failed to parse JSON: %v", err) + } + + ctx := prompty.NewLoadContext() + instance, err := prompty.LoadSessionSummary(data, ctx) + if err != nil { + t.Fatalf("Failed to load SessionSummary: %v", err) + } + if instance.SessionId != "sess_abc123" { + t.Errorf(`Expected SessionId to be "sess_abc123", got %v`, instance.SessionId) + } + if instance.Status == nil || *instance.Status != "success" { + t.Errorf(`Expected Status to be "success", got %v`, instance.Status) + } + if instance.Turns == nil || *instance.Turns != 5 { + t.Errorf(`Expected Turns to be 5, got %v`, instance.Turns) + } + if instance.Checkpoints == nil || *instance.Checkpoints != 2 { + t.Errorf(`Expected Checkpoints to be 2, got %v`, instance.Checkpoints) + } + if instance.DurationMs == nil || *instance.DurationMs != 12500 { + t.Errorf(`Expected DurationMs to be 12500, got %v`, instance.DurationMs) + } +} + +// TestSessionSummaryLoadYAML tests loading SessionSummary from YAML +func TestSessionSummaryLoadYAML(t *testing.T) { + yamlData := ` +sessionId: sess_abc123 +status: success +turns: 5 +checkpoints: 2 +durationMs: 12500 + +` + var data map[string]interface{} + if err := yaml.Unmarshal([]byte(yamlData), &data); err != nil { + t.Fatalf("Failed to parse YAML: %v", err) + } + + ctx := prompty.NewLoadContext() + instance, err := prompty.LoadSessionSummary(data, ctx) + if err != nil { + t.Fatalf("Failed to load SessionSummary: %v", err) + } + if instance.SessionId != "sess_abc123" { + t.Errorf(`Expected SessionId to be "sess_abc123", got %v`, instance.SessionId) + } + if instance.Status == nil || *instance.Status != "success" { + t.Errorf(`Expected Status to be "success", got %v`, instance.Status) + } + if instance.Turns == nil || *instance.Turns != 5 { + t.Errorf(`Expected Turns to be 5, got %v`, instance.Turns) + } + if instance.Checkpoints == nil || *instance.Checkpoints != 2 { + t.Errorf(`Expected Checkpoints to be 2, got %v`, instance.Checkpoints) + } + if instance.DurationMs == nil || *instance.DurationMs != 12500 { + t.Errorf(`Expected DurationMs to be 12500, got %v`, instance.DurationMs) + } +} + +// TestSessionSummaryFromJSON tests loading SessionSummary through the generated JSON helper +func TestSessionSummaryFromJSON(t *testing.T) { + jsonData := ` +{ + "sessionId": "sess_abc123", + "status": "success", + "turns": 5, + "checkpoints": 2, + "durationMs": 12500 +} +` + + instance, err := prompty.SessionSummaryFromJSON(jsonData) + if err != nil { + t.Fatalf("Failed to load SessionSummary from JSON helper: %v", err) + } + if instance.SessionId != "sess_abc123" { + t.Errorf(`Expected SessionId to be "sess_abc123", got %v`, instance.SessionId) + } + if instance.Status == nil || *instance.Status != "success" { + t.Errorf(`Expected Status to be "success", got %v`, instance.Status) + } + if instance.Turns == nil || *instance.Turns != 5 { + t.Errorf(`Expected Turns to be 5, got %v`, instance.Turns) + } + if instance.Checkpoints == nil || *instance.Checkpoints != 2 { + t.Errorf(`Expected Checkpoints to be 2, got %v`, instance.Checkpoints) + } + if instance.DurationMs == nil || *instance.DurationMs != 12500 { + t.Errorf(`Expected DurationMs to be 12500, got %v`, instance.DurationMs) + } +} + +// TestSessionSummaryFromYAML tests loading SessionSummary through the generated YAML helper +func TestSessionSummaryFromYAML(t *testing.T) { + yamlData := ` +sessionId: sess_abc123 +status: success +turns: 5 +checkpoints: 2 +durationMs: 12500 + +` + + instance, err := prompty.SessionSummaryFromYAML(yamlData) + if err != nil { + t.Fatalf("Failed to load SessionSummary from YAML helper: %v", err) + } + if instance.SessionId != "sess_abc123" { + t.Errorf(`Expected SessionId to be "sess_abc123", got %v`, instance.SessionId) + } + if instance.Status == nil || *instance.Status != "success" { + t.Errorf(`Expected Status to be "success", got %v`, instance.Status) + } + if instance.Turns == nil || *instance.Turns != 5 { + t.Errorf(`Expected Turns to be 5, got %v`, instance.Turns) + } + if instance.Checkpoints == nil || *instance.Checkpoints != 2 { + t.Errorf(`Expected Checkpoints to be 2, got %v`, instance.Checkpoints) + } + if instance.DurationMs == nil || *instance.DurationMs != 12500 { + t.Errorf(`Expected DurationMs to be 12500, got %v`, instance.DurationMs) + } +} + +// TestSessionSummaryRoundtrip tests load -> save -> load produces equivalent data +func TestSessionSummaryRoundtrip(t *testing.T) { + jsonData := ` +{ + "sessionId": "sess_abc123", + "status": "success", + "turns": 5, + "checkpoints": 2, + "durationMs": 12500 +} +` + var data map[string]interface{} + if err := json.Unmarshal([]byte(jsonData), &data); err != nil { + t.Fatalf("Failed to parse JSON: %v", err) + } + + loadCtx := prompty.NewLoadContext() + instance, err := prompty.LoadSessionSummary(data, loadCtx) + if err != nil { + t.Fatalf("Failed to load SessionSummary: %v", err) + } + saveCtx := prompty.NewSaveContext() + savedData := instance.Save(saveCtx) + + reloaded, err := prompty.LoadSessionSummary(savedData, loadCtx) + if err != nil { + t.Fatalf("Failed to reload SessionSummary: %v", err) + } + if reloaded.SessionId != "sess_abc123" { + t.Errorf(`Expected SessionId to be "sess_abc123", got %v`, reloaded.SessionId) + } + if reloaded.Status == nil || *reloaded.Status != "success" { + t.Errorf(`Expected Status to be "success", got %v`, reloaded.Status) + } + if reloaded.Turns == nil || *reloaded.Turns != 5 { + t.Errorf(`Expected Turns to be 5, got %v`, reloaded.Turns) + } + if reloaded.Checkpoints == nil || *reloaded.Checkpoints != 2 { + t.Errorf(`Expected Checkpoints to be 2, got %v`, reloaded.Checkpoints) + } + if reloaded.DurationMs == nil || *reloaded.DurationMs != 12500 { + t.Errorf(`Expected DurationMs to be 12500, got %v`, reloaded.DurationMs) + } +} + +// TestSessionSummaryToJSON tests that ToJSON produces valid JSON +func TestSessionSummaryToJSON(t *testing.T) { + jsonData := ` +{ + "sessionId": "sess_abc123", + "status": "success", + "turns": 5, + "checkpoints": 2, + "durationMs": 12500 +} +` + var data map[string]interface{} + if err := json.Unmarshal([]byte(jsonData), &data); err != nil { + t.Fatalf("Failed to parse JSON: %v", err) + } + + ctx := prompty.NewLoadContext() + instance, err := prompty.LoadSessionSummary(data, ctx) + if err != nil { + t.Fatalf("Failed to load SessionSummary: %v", err) + } + jsonOutput, err := instance.ToJSON() + if err != nil { + t.Fatalf("Failed to convert to JSON: %v", err) + } + + var parsed map[string]interface{} + if err := json.Unmarshal([]byte(jsonOutput), &parsed); err != nil { + t.Fatalf("Failed to parse generated JSON: %v", err) + } + + reloaded, err := prompty.LoadSessionSummary(parsed, ctx) + if err != nil { + t.Fatalf("Failed to reload generated JSON: %v", err) + } + if reloaded.SessionId != "sess_abc123" { + t.Errorf(`Expected SessionId to be "sess_abc123", got %v`, reloaded.SessionId) + } + if reloaded.Status == nil || *reloaded.Status != "success" { + t.Errorf(`Expected Status to be "success", got %v`, reloaded.Status) + } + if reloaded.Turns == nil || *reloaded.Turns != 5 { + t.Errorf(`Expected Turns to be 5, got %v`, reloaded.Turns) + } + if reloaded.Checkpoints == nil || *reloaded.Checkpoints != 2 { + t.Errorf(`Expected Checkpoints to be 2, got %v`, reloaded.Checkpoints) + } + if reloaded.DurationMs == nil || *reloaded.DurationMs != 12500 { + t.Errorf(`Expected DurationMs to be 12500, got %v`, reloaded.DurationMs) + } +} + +// TestSessionSummaryToYAML tests that ToYAML produces valid YAML +func TestSessionSummaryToYAML(t *testing.T) { + jsonData := ` +{ + "sessionId": "sess_abc123", + "status": "success", + "turns": 5, + "checkpoints": 2, + "durationMs": 12500 +} +` + var data map[string]interface{} + if err := json.Unmarshal([]byte(jsonData), &data); err != nil { + t.Fatalf("Failed to parse JSON: %v", err) + } + + ctx := prompty.NewLoadContext() + instance, err := prompty.LoadSessionSummary(data, ctx) + if err != nil { + t.Fatalf("Failed to load SessionSummary: %v", err) + } + yamlOutput, err := instance.ToYAML() + if err != nil { + t.Fatalf("Failed to convert to YAML: %v", err) + } + + var parsed map[string]interface{} + if err := yaml.Unmarshal([]byte(yamlOutput), &parsed); err != nil { + t.Fatalf("Failed to parse generated YAML: %v", err) + } + + reloaded, err := prompty.LoadSessionSummary(parsed, ctx) + if err != nil { + t.Fatalf("Failed to reload generated YAML: %v", err) + } + if reloaded.SessionId != "sess_abc123" { + t.Errorf(`Expected SessionId to be "sess_abc123", got %v`, reloaded.SessionId) + } + if reloaded.Status == nil || *reloaded.Status != "success" { + t.Errorf(`Expected Status to be "success", got %v`, reloaded.Status) + } + if reloaded.Turns == nil || *reloaded.Turns != 5 { + t.Errorf(`Expected Turns to be 5, got %v`, reloaded.Turns) + } + if reloaded.Checkpoints == nil || *reloaded.Checkpoints != 2 { + t.Errorf(`Expected Checkpoints to be 2, got %v`, reloaded.Checkpoints) + } + if reloaded.DurationMs == nil || *reloaded.DurationMs != 12500 { + t.Errorf(`Expected DurationMs to be 12500, got %v`, reloaded.DurationMs) + } +} + +// TestSessionSummaryFromJSONInvalid rejects malformed JSON instead of silently defaulting +func TestSessionSummaryFromJSONInvalid(t *testing.T) { + if _, err := prompty.SessionSummaryFromJSON("{"); err == nil { + t.Fatalf("Expected malformed JSON to fail") + } +} diff --git a/runtime/go/prompty/model/session_trace.go b/runtime/go/prompty/model/session_trace.go new file mode 100644 index 00000000..4e8aca84 --- /dev/null +++ b/runtime/go/prompty/model/session_trace.go @@ -0,0 +1,225 @@ +// +// Code generated by Typra emitter; DO NOT EDIT. +// Group: events + +package prompty + +import ( + "encoding/json" + + "gopkg.in/yaml.v3" +) + +// SessionTrace represents Portable replay container for an outer harness session. + +type SessionTrace struct { + Version string `json:"version" yaml:"version"` + Runtime *string `json:"runtime,omitempty" yaml:"runtime,omitempty"` + PromptyVersion *string `json:"promptyVersion,omitempty" yaml:"promptyVersion,omitempty"` + SessionId *string `json:"sessionId,omitempty" yaml:"sessionId,omitempty"` + Events []SessionEvent `json:"events" yaml:"events"` + Turns []TurnTrace `json:"turns,omitempty" yaml:"turns,omitempty"` + Checkpoints []Checkpoint `json:"checkpoints,omitempty" yaml:"checkpoints,omitempty"` + Trajectory []TrajectoryEvent `json:"trajectory,omitempty" yaml:"trajectory,omitempty"` + Files []SessionFileRef `json:"files,omitempty" yaml:"files,omitempty"` + Refs []SessionRef `json:"refs,omitempty" yaml:"refs,omitempty"` + Summary *SessionSummary `json:"summary,omitempty" yaml:"summary,omitempty"` +} + +// LoadSessionTrace creates a SessionTrace from a map[string]interface{} +func LoadSessionTrace(data interface{}, ctx *LoadContext) (SessionTrace, error) { + result := SessionTrace{} + + // Load from map + if m, ok := data.(map[string]interface{}); ok { + if val, ok := m["version"]; ok && val != nil { + result.Version = string(val.(string)) + } + if val, ok := m["runtime"]; ok && val != nil { + v := string(val.(string)) + result.Runtime = &v + } + if val, ok := m["promptyVersion"]; ok && val != nil { + v := string(val.(string)) + result.PromptyVersion = &v + } + if val, ok := m["sessionId"]; ok && val != nil { + v := string(val.(string)) + result.SessionId = &v + } + if val, ok := m["events"]; ok && val != nil { + if arr, ok := val.([]interface{}); ok { + result.Events = make([]SessionEvent, len(arr)) + for i, v := range arr { + if item, ok := v.(map[string]interface{}); ok { + loaded, _ := LoadSessionEvent(item, ctx) + result.Events[i] = loaded + } + } + } + } + if val, ok := m["turns"]; ok && val != nil { + if arr, ok := val.([]interface{}); ok { + result.Turns = make([]TurnTrace, len(arr)) + for i, v := range arr { + if item, ok := v.(map[string]interface{}); ok { + loaded, _ := LoadTurnTrace(item, ctx) + result.Turns[i] = loaded + } + } + } + } + if val, ok := m["checkpoints"]; ok && val != nil { + if arr, ok := val.([]interface{}); ok { + result.Checkpoints = make([]Checkpoint, len(arr)) + for i, v := range arr { + if item, ok := v.(map[string]interface{}); ok { + loaded, _ := LoadCheckpoint(item, ctx) + result.Checkpoints[i] = loaded + } + } + } + } + if val, ok := m["trajectory"]; ok && val != nil { + if arr, ok := val.([]interface{}); ok { + result.Trajectory = make([]TrajectoryEvent, len(arr)) + for i, v := range arr { + if item, ok := v.(map[string]interface{}); ok { + loaded, _ := LoadTrajectoryEvent(item, ctx) + result.Trajectory[i] = loaded + } + } + } + } + if val, ok := m["files"]; ok && val != nil { + if arr, ok := val.([]interface{}); ok { + result.Files = make([]SessionFileRef, len(arr)) + for i, v := range arr { + if item, ok := v.(map[string]interface{}); ok { + loaded, _ := LoadSessionFileRef(item, ctx) + result.Files[i] = loaded + } + } + } + } + if val, ok := m["refs"]; ok && val != nil { + if arr, ok := val.([]interface{}); ok { + result.Refs = make([]SessionRef, len(arr)) + for i, v := range arr { + if item, ok := v.(map[string]interface{}); ok { + loaded, _ := LoadSessionRef(item, ctx) + result.Refs[i] = loaded + } + } + } + } + if val, ok := m["summary"]; ok && val != nil { + if m, ok := val.(map[string]interface{}); ok { + loaded, _ := LoadSessionSummary(m, ctx) + result.Summary = &loaded + } + } + } + + return result, nil +} + +// Save serializes SessionTrace to map[string]interface{} +func (obj SessionTrace) Save(ctx *SaveContext) map[string]interface{} { + result := make(map[string]interface{}) + result["version"] = obj.Version + if obj.Runtime != nil { + result["runtime"] = *obj.Runtime + } + if obj.PromptyVersion != nil { + result["promptyVersion"] = *obj.PromptyVersion + } + if obj.SessionId != nil { + result["sessionId"] = *obj.SessionId + } + if obj.Events != nil { + arr := make([]interface{}, len(obj.Events)) + for i, item := range obj.Events { + arr[i] = item.Save(ctx) + } + result["events"] = arr + } + if obj.Turns != nil { + arr := make([]interface{}, len(obj.Turns)) + for i, item := range obj.Turns { + arr[i] = item.Save(ctx) + } + result["turns"] = arr + } + if obj.Checkpoints != nil { + arr := make([]interface{}, len(obj.Checkpoints)) + for i, item := range obj.Checkpoints { + arr[i] = item.Save(ctx) + } + result["checkpoints"] = arr + } + if obj.Trajectory != nil { + arr := make([]interface{}, len(obj.Trajectory)) + for i, item := range obj.Trajectory { + arr[i] = item.Save(ctx) + } + result["trajectory"] = arr + } + if obj.Files != nil { + arr := make([]interface{}, len(obj.Files)) + for i, item := range obj.Files { + arr[i] = item.Save(ctx) + } + result["files"] = arr + } + if obj.Refs != nil { + arr := make([]interface{}, len(obj.Refs)) + for i, item := range obj.Refs { + arr[i] = item.Save(ctx) + } + result["refs"] = arr + } + if obj.Summary != nil { + result["summary"] = obj.Summary.Save(ctx) + } + + return result +} + +// ToJSON serializes SessionTrace to JSON string +func (obj *SessionTrace) ToJSON() (string, error) { + ctx := NewSaveContext() + data := obj.Save(ctx) + bytes, err := json.Marshal(data) + if err != nil { + return "", err + } + return string(bytes), nil +} + +// ToYAML serializes SessionTrace to YAML string +func (obj *SessionTrace) ToYAML() (string, error) { + ctx := NewSaveContext() + data := obj.Save(ctx) + return marshalYAMLDocument(data) +} + +// FromJSON creates SessionTrace from JSON string +func SessionTraceFromJSON(jsonStr string) (SessionTrace, error) { + var data map[string]interface{} + if err := json.Unmarshal([]byte(jsonStr), &data); err != nil { + return SessionTrace{}, err + } + ctx := NewLoadContext() + return LoadSessionTrace(data, ctx) +} + +// FromYAML creates SessionTrace from YAML string +func SessionTraceFromYAML(yamlStr string) (SessionTrace, error) { + var data map[string]interface{} + if err := yaml.Unmarshal([]byte(yamlStr), &data); err != nil { + return SessionTrace{}, err + } + ctx := NewLoadContext() + return LoadSessionTrace(data, ctx) +} diff --git a/runtime/go/prompty/model/session_trace_test.go b/runtime/go/prompty/model/session_trace_test.go new file mode 100644 index 00000000..0c6403ee --- /dev/null +++ b/runtime/go/prompty/model/session_trace_test.go @@ -0,0 +1,281 @@ +// +// Code generated by Typra emitter; DO NOT EDIT. + +package prompty_test + +import ( + "encoding/json" + "testing" + + "gopkg.in/yaml.v3" + + "prompty/model" +) + +// TestSessionTraceLoadJSON tests loading SessionTrace from JSON +func TestSessionTraceLoadJSON(t *testing.T) { + jsonData := ` +{ + "version": "1", + "runtime": "typescript", + "promptyVersion": "2.0.0", + "sessionId": "sess_abc123" +} +` + var data map[string]interface{} + if err := json.Unmarshal([]byte(jsonData), &data); err != nil { + t.Fatalf("Failed to parse JSON: %v", err) + } + + ctx := prompty.NewLoadContext() + instance, err := prompty.LoadSessionTrace(data, ctx) + if err != nil { + t.Fatalf("Failed to load SessionTrace: %v", err) + } + if instance.Version != "1" { + t.Errorf(`Expected Version to be "1", got %v`, instance.Version) + } + if instance.Runtime == nil || *instance.Runtime != "typescript" { + t.Errorf(`Expected Runtime to be "typescript", got %v`, instance.Runtime) + } + if instance.PromptyVersion == nil || *instance.PromptyVersion != "2.0.0" { + t.Errorf(`Expected PromptyVersion to be "2.0.0", got %v`, instance.PromptyVersion) + } + if instance.SessionId == nil || *instance.SessionId != "sess_abc123" { + t.Errorf(`Expected SessionId to be "sess_abc123", got %v`, instance.SessionId) + } +} + +// TestSessionTraceLoadYAML tests loading SessionTrace from YAML +func TestSessionTraceLoadYAML(t *testing.T) { + yamlData := ` +version: "1" +runtime: typescript +promptyVersion: 2.0.0 +sessionId: sess_abc123 + +` + var data map[string]interface{} + if err := yaml.Unmarshal([]byte(yamlData), &data); err != nil { + t.Fatalf("Failed to parse YAML: %v", err) + } + + ctx := prompty.NewLoadContext() + instance, err := prompty.LoadSessionTrace(data, ctx) + if err != nil { + t.Fatalf("Failed to load SessionTrace: %v", err) + } + if instance.Version != "1" { + t.Errorf(`Expected Version to be "1", got %v`, instance.Version) + } + if instance.Runtime == nil || *instance.Runtime != "typescript" { + t.Errorf(`Expected Runtime to be "typescript", got %v`, instance.Runtime) + } + if instance.PromptyVersion == nil || *instance.PromptyVersion != "2.0.0" { + t.Errorf(`Expected PromptyVersion to be "2.0.0", got %v`, instance.PromptyVersion) + } + if instance.SessionId == nil || *instance.SessionId != "sess_abc123" { + t.Errorf(`Expected SessionId to be "sess_abc123", got %v`, instance.SessionId) + } +} + +// TestSessionTraceFromJSON tests loading SessionTrace through the generated JSON helper +func TestSessionTraceFromJSON(t *testing.T) { + jsonData := ` +{ + "version": "1", + "runtime": "typescript", + "promptyVersion": "2.0.0", + "sessionId": "sess_abc123" +} +` + + instance, err := prompty.SessionTraceFromJSON(jsonData) + if err != nil { + t.Fatalf("Failed to load SessionTrace from JSON helper: %v", err) + } + if instance.Version != "1" { + t.Errorf(`Expected Version to be "1", got %v`, instance.Version) + } + if instance.Runtime == nil || *instance.Runtime != "typescript" { + t.Errorf(`Expected Runtime to be "typescript", got %v`, instance.Runtime) + } + if instance.PromptyVersion == nil || *instance.PromptyVersion != "2.0.0" { + t.Errorf(`Expected PromptyVersion to be "2.0.0", got %v`, instance.PromptyVersion) + } + if instance.SessionId == nil || *instance.SessionId != "sess_abc123" { + t.Errorf(`Expected SessionId to be "sess_abc123", got %v`, instance.SessionId) + } +} + +// TestSessionTraceFromYAML tests loading SessionTrace through the generated YAML helper +func TestSessionTraceFromYAML(t *testing.T) { + yamlData := ` +version: "1" +runtime: typescript +promptyVersion: 2.0.0 +sessionId: sess_abc123 + +` + + instance, err := prompty.SessionTraceFromYAML(yamlData) + if err != nil { + t.Fatalf("Failed to load SessionTrace from YAML helper: %v", err) + } + if instance.Version != "1" { + t.Errorf(`Expected Version to be "1", got %v`, instance.Version) + } + if instance.Runtime == nil || *instance.Runtime != "typescript" { + t.Errorf(`Expected Runtime to be "typescript", got %v`, instance.Runtime) + } + if instance.PromptyVersion == nil || *instance.PromptyVersion != "2.0.0" { + t.Errorf(`Expected PromptyVersion to be "2.0.0", got %v`, instance.PromptyVersion) + } + if instance.SessionId == nil || *instance.SessionId != "sess_abc123" { + t.Errorf(`Expected SessionId to be "sess_abc123", got %v`, instance.SessionId) + } +} + +// TestSessionTraceRoundtrip tests load -> save -> load produces equivalent data +func TestSessionTraceRoundtrip(t *testing.T) { + jsonData := ` +{ + "version": "1", + "runtime": "typescript", + "promptyVersion": "2.0.0", + "sessionId": "sess_abc123" +} +` + var data map[string]interface{} + if err := json.Unmarshal([]byte(jsonData), &data); err != nil { + t.Fatalf("Failed to parse JSON: %v", err) + } + + loadCtx := prompty.NewLoadContext() + instance, err := prompty.LoadSessionTrace(data, loadCtx) + if err != nil { + t.Fatalf("Failed to load SessionTrace: %v", err) + } + saveCtx := prompty.NewSaveContext() + savedData := instance.Save(saveCtx) + + reloaded, err := prompty.LoadSessionTrace(savedData, loadCtx) + if err != nil { + t.Fatalf("Failed to reload SessionTrace: %v", err) + } + if reloaded.Version != "1" { + t.Errorf(`Expected Version to be "1", got %v`, reloaded.Version) + } + if reloaded.Runtime == nil || *reloaded.Runtime != "typescript" { + t.Errorf(`Expected Runtime to be "typescript", got %v`, reloaded.Runtime) + } + if reloaded.PromptyVersion == nil || *reloaded.PromptyVersion != "2.0.0" { + t.Errorf(`Expected PromptyVersion to be "2.0.0", got %v`, reloaded.PromptyVersion) + } + if reloaded.SessionId == nil || *reloaded.SessionId != "sess_abc123" { + t.Errorf(`Expected SessionId to be "sess_abc123", got %v`, reloaded.SessionId) + } +} + +// TestSessionTraceToJSON tests that ToJSON produces valid JSON +func TestSessionTraceToJSON(t *testing.T) { + jsonData := ` +{ + "version": "1", + "runtime": "typescript", + "promptyVersion": "2.0.0", + "sessionId": "sess_abc123" +} +` + var data map[string]interface{} + if err := json.Unmarshal([]byte(jsonData), &data); err != nil { + t.Fatalf("Failed to parse JSON: %v", err) + } + + ctx := prompty.NewLoadContext() + instance, err := prompty.LoadSessionTrace(data, ctx) + if err != nil { + t.Fatalf("Failed to load SessionTrace: %v", err) + } + jsonOutput, err := instance.ToJSON() + if err != nil { + t.Fatalf("Failed to convert to JSON: %v", err) + } + + var parsed map[string]interface{} + if err := json.Unmarshal([]byte(jsonOutput), &parsed); err != nil { + t.Fatalf("Failed to parse generated JSON: %v", err) + } + + reloaded, err := prompty.LoadSessionTrace(parsed, ctx) + if err != nil { + t.Fatalf("Failed to reload generated JSON: %v", err) + } + if reloaded.Version != "1" { + t.Errorf(`Expected Version to be "1", got %v`, reloaded.Version) + } + if reloaded.Runtime == nil || *reloaded.Runtime != "typescript" { + t.Errorf(`Expected Runtime to be "typescript", got %v`, reloaded.Runtime) + } + if reloaded.PromptyVersion == nil || *reloaded.PromptyVersion != "2.0.0" { + t.Errorf(`Expected PromptyVersion to be "2.0.0", got %v`, reloaded.PromptyVersion) + } + if reloaded.SessionId == nil || *reloaded.SessionId != "sess_abc123" { + t.Errorf(`Expected SessionId to be "sess_abc123", got %v`, reloaded.SessionId) + } +} + +// TestSessionTraceToYAML tests that ToYAML produces valid YAML +func TestSessionTraceToYAML(t *testing.T) { + jsonData := ` +{ + "version": "1", + "runtime": "typescript", + "promptyVersion": "2.0.0", + "sessionId": "sess_abc123" +} +` + var data map[string]interface{} + if err := json.Unmarshal([]byte(jsonData), &data); err != nil { + t.Fatalf("Failed to parse JSON: %v", err) + } + + ctx := prompty.NewLoadContext() + instance, err := prompty.LoadSessionTrace(data, ctx) + if err != nil { + t.Fatalf("Failed to load SessionTrace: %v", err) + } + yamlOutput, err := instance.ToYAML() + if err != nil { + t.Fatalf("Failed to convert to YAML: %v", err) + } + + var parsed map[string]interface{} + if err := yaml.Unmarshal([]byte(yamlOutput), &parsed); err != nil { + t.Fatalf("Failed to parse generated YAML: %v", err) + } + + reloaded, err := prompty.LoadSessionTrace(parsed, ctx) + if err != nil { + t.Fatalf("Failed to reload generated YAML: %v", err) + } + if reloaded.Version != "1" { + t.Errorf(`Expected Version to be "1", got %v`, reloaded.Version) + } + if reloaded.Runtime == nil || *reloaded.Runtime != "typescript" { + t.Errorf(`Expected Runtime to be "typescript", got %v`, reloaded.Runtime) + } + if reloaded.PromptyVersion == nil || *reloaded.PromptyVersion != "2.0.0" { + t.Errorf(`Expected PromptyVersion to be "2.0.0", got %v`, reloaded.PromptyVersion) + } + if reloaded.SessionId == nil || *reloaded.SessionId != "sess_abc123" { + t.Errorf(`Expected SessionId to be "sess_abc123", got %v`, reloaded.SessionId) + } +} + +// TestSessionTraceFromJSONInvalid rejects malformed JSON instead of silently defaulting +func TestSessionTraceFromJSONInvalid(t *testing.T) { + if _, err := prompty.SessionTraceFromJSON("{"); err == nil { + t.Fatalf("Expected malformed JSON to fail") + } +} diff --git a/runtime/go/prompty/model/session_warning_payload.go b/runtime/go/prompty/model/session_warning_payload.go new file mode 100644 index 00000000..fd368017 --- /dev/null +++ b/runtime/go/prompty/model/session_warning_payload.go @@ -0,0 +1,91 @@ +// +// Code generated by Typra emitter; DO NOT EDIT. +// Group: events + +package prompty + +import ( + "encoding/json" + + "gopkg.in/yaml.v3" +) + +// SessionWarningPayload represents Payload for "session_warning" events. + +type SessionWarningPayload struct { + WarningType string `json:"warningType" yaml:"warningType"` + Message string `json:"message" yaml:"message"` + Details map[string]interface{} `json:"details,omitempty" yaml:"details,omitempty"` +} + +// LoadSessionWarningPayload creates a SessionWarningPayload from a map[string]interface{} +func LoadSessionWarningPayload(data interface{}, ctx *LoadContext) (SessionWarningPayload, error) { + result := SessionWarningPayload{} + + // Load from map + if m, ok := data.(map[string]interface{}); ok { + if val, ok := m["warningType"]; ok && val != nil { + result.WarningType = string(val.(string)) + } + if val, ok := m["message"]; ok && val != nil { + result.Message = string(val.(string)) + } + if val, ok := m["details"]; ok && val != nil { + if m, ok := val.(map[string]interface{}); ok { + result.Details = m + } + } + } + + return result, nil +} + +// Save serializes SessionWarningPayload to map[string]interface{} +func (obj SessionWarningPayload) Save(ctx *SaveContext) map[string]interface{} { + result := make(map[string]interface{}) + result["warningType"] = obj.WarningType + result["message"] = obj.Message + if obj.Details != nil { + result["details"] = obj.Details + } + + return result +} + +// ToJSON serializes SessionWarningPayload to JSON string +func (obj *SessionWarningPayload) ToJSON() (string, error) { + ctx := NewSaveContext() + data := obj.Save(ctx) + bytes, err := json.Marshal(data) + if err != nil { + return "", err + } + return string(bytes), nil +} + +// ToYAML serializes SessionWarningPayload to YAML string +func (obj *SessionWarningPayload) ToYAML() (string, error) { + ctx := NewSaveContext() + data := obj.Save(ctx) + return marshalYAMLDocument(data) +} + +// FromJSON creates SessionWarningPayload from JSON string +func SessionWarningPayloadFromJSON(jsonStr string) (SessionWarningPayload, error) { + var data map[string]interface{} + if err := json.Unmarshal([]byte(jsonStr), &data); err != nil { + return SessionWarningPayload{}, err + } + ctx := NewLoadContext() + return LoadSessionWarningPayload(data, ctx) +} + +// FromYAML creates SessionWarningPayload from YAML string +func SessionWarningPayloadFromYAML(yamlStr string) (SessionWarningPayload, error) { + var data map[string]interface{} + if err := yaml.Unmarshal([]byte(yamlStr), &data); err != nil { + return SessionWarningPayload{}, err + } + ctx := NewLoadContext() + return LoadSessionWarningPayload(data, ctx) +} diff --git a/runtime/go/prompty/model/session_warning_payload_test.go b/runtime/go/prompty/model/session_warning_payload_test.go new file mode 100644 index 00000000..e8356bb7 --- /dev/null +++ b/runtime/go/prompty/model/session_warning_payload_test.go @@ -0,0 +1,225 @@ +// +// Code generated by Typra emitter; DO NOT EDIT. + +package prompty_test + +import ( + "encoding/json" + "testing" + + "gopkg.in/yaml.v3" + + "prompty/model" +) + +// TestSessionWarningPayloadLoadJSON tests loading SessionWarningPayload from JSON +func TestSessionWarningPayloadLoadJSON(t *testing.T) { + jsonData := ` +{ + "warningType": "remote", + "message": "Remote session disabled" +} +` + var data map[string]interface{} + if err := json.Unmarshal([]byte(jsonData), &data); err != nil { + t.Fatalf("Failed to parse JSON: %v", err) + } + + ctx := prompty.NewLoadContext() + instance, err := prompty.LoadSessionWarningPayload(data, ctx) + if err != nil { + t.Fatalf("Failed to load SessionWarningPayload: %v", err) + } + if instance.WarningType != "remote" { + t.Errorf(`Expected WarningType to be "remote", got %v`, instance.WarningType) + } + if instance.Message != "Remote session disabled" { + t.Errorf(`Expected Message to be "Remote session disabled", got %v`, instance.Message) + } +} + +// TestSessionWarningPayloadLoadYAML tests loading SessionWarningPayload from YAML +func TestSessionWarningPayloadLoadYAML(t *testing.T) { + yamlData := ` +warningType: remote +message: Remote session disabled + +` + var data map[string]interface{} + if err := yaml.Unmarshal([]byte(yamlData), &data); err != nil { + t.Fatalf("Failed to parse YAML: %v", err) + } + + ctx := prompty.NewLoadContext() + instance, err := prompty.LoadSessionWarningPayload(data, ctx) + if err != nil { + t.Fatalf("Failed to load SessionWarningPayload: %v", err) + } + if instance.WarningType != "remote" { + t.Errorf(`Expected WarningType to be "remote", got %v`, instance.WarningType) + } + if instance.Message != "Remote session disabled" { + t.Errorf(`Expected Message to be "Remote session disabled", got %v`, instance.Message) + } +} + +// TestSessionWarningPayloadFromJSON tests loading SessionWarningPayload through the generated JSON helper +func TestSessionWarningPayloadFromJSON(t *testing.T) { + jsonData := ` +{ + "warningType": "remote", + "message": "Remote session disabled" +} +` + + instance, err := prompty.SessionWarningPayloadFromJSON(jsonData) + if err != nil { + t.Fatalf("Failed to load SessionWarningPayload from JSON helper: %v", err) + } + if instance.WarningType != "remote" { + t.Errorf(`Expected WarningType to be "remote", got %v`, instance.WarningType) + } + if instance.Message != "Remote session disabled" { + t.Errorf(`Expected Message to be "Remote session disabled", got %v`, instance.Message) + } +} + +// TestSessionWarningPayloadFromYAML tests loading SessionWarningPayload through the generated YAML helper +func TestSessionWarningPayloadFromYAML(t *testing.T) { + yamlData := ` +warningType: remote +message: Remote session disabled + +` + + instance, err := prompty.SessionWarningPayloadFromYAML(yamlData) + if err != nil { + t.Fatalf("Failed to load SessionWarningPayload from YAML helper: %v", err) + } + if instance.WarningType != "remote" { + t.Errorf(`Expected WarningType to be "remote", got %v`, instance.WarningType) + } + if instance.Message != "Remote session disabled" { + t.Errorf(`Expected Message to be "Remote session disabled", got %v`, instance.Message) + } +} + +// TestSessionWarningPayloadRoundtrip tests load -> save -> load produces equivalent data +func TestSessionWarningPayloadRoundtrip(t *testing.T) { + jsonData := ` +{ + "warningType": "remote", + "message": "Remote session disabled" +} +` + var data map[string]interface{} + if err := json.Unmarshal([]byte(jsonData), &data); err != nil { + t.Fatalf("Failed to parse JSON: %v", err) + } + + loadCtx := prompty.NewLoadContext() + instance, err := prompty.LoadSessionWarningPayload(data, loadCtx) + if err != nil { + t.Fatalf("Failed to load SessionWarningPayload: %v", err) + } + saveCtx := prompty.NewSaveContext() + savedData := instance.Save(saveCtx) + + reloaded, err := prompty.LoadSessionWarningPayload(savedData, loadCtx) + if err != nil { + t.Fatalf("Failed to reload SessionWarningPayload: %v", err) + } + if reloaded.WarningType != "remote" { + t.Errorf(`Expected WarningType to be "remote", got %v`, reloaded.WarningType) + } + if reloaded.Message != "Remote session disabled" { + t.Errorf(`Expected Message to be "Remote session disabled", got %v`, reloaded.Message) + } +} + +// TestSessionWarningPayloadToJSON tests that ToJSON produces valid JSON +func TestSessionWarningPayloadToJSON(t *testing.T) { + jsonData := ` +{ + "warningType": "remote", + "message": "Remote session disabled" +} +` + var data map[string]interface{} + if err := json.Unmarshal([]byte(jsonData), &data); err != nil { + t.Fatalf("Failed to parse JSON: %v", err) + } + + ctx := prompty.NewLoadContext() + instance, err := prompty.LoadSessionWarningPayload(data, ctx) + if err != nil { + t.Fatalf("Failed to load SessionWarningPayload: %v", err) + } + jsonOutput, err := instance.ToJSON() + if err != nil { + t.Fatalf("Failed to convert to JSON: %v", err) + } + + var parsed map[string]interface{} + if err := json.Unmarshal([]byte(jsonOutput), &parsed); err != nil { + t.Fatalf("Failed to parse generated JSON: %v", err) + } + + reloaded, err := prompty.LoadSessionWarningPayload(parsed, ctx) + if err != nil { + t.Fatalf("Failed to reload generated JSON: %v", err) + } + if reloaded.WarningType != "remote" { + t.Errorf(`Expected WarningType to be "remote", got %v`, reloaded.WarningType) + } + if reloaded.Message != "Remote session disabled" { + t.Errorf(`Expected Message to be "Remote session disabled", got %v`, reloaded.Message) + } +} + +// TestSessionWarningPayloadToYAML tests that ToYAML produces valid YAML +func TestSessionWarningPayloadToYAML(t *testing.T) { + jsonData := ` +{ + "warningType": "remote", + "message": "Remote session disabled" +} +` + var data map[string]interface{} + if err := json.Unmarshal([]byte(jsonData), &data); err != nil { + t.Fatalf("Failed to parse JSON: %v", err) + } + + ctx := prompty.NewLoadContext() + instance, err := prompty.LoadSessionWarningPayload(data, ctx) + if err != nil { + t.Fatalf("Failed to load SessionWarningPayload: %v", err) + } + yamlOutput, err := instance.ToYAML() + if err != nil { + t.Fatalf("Failed to convert to YAML: %v", err) + } + + var parsed map[string]interface{} + if err := yaml.Unmarshal([]byte(yamlOutput), &parsed); err != nil { + t.Fatalf("Failed to parse generated YAML: %v", err) + } + + reloaded, err := prompty.LoadSessionWarningPayload(parsed, ctx) + if err != nil { + t.Fatalf("Failed to reload generated YAML: %v", err) + } + if reloaded.WarningType != "remote" { + t.Errorf(`Expected WarningType to be "remote", got %v`, reloaded.WarningType) + } + if reloaded.Message != "Remote session disabled" { + t.Errorf(`Expected Message to be "Remote session disabled", got %v`, reloaded.Message) + } +} + +// TestSessionWarningPayloadFromJSONInvalid rejects malformed JSON instead of silently defaulting +func TestSessionWarningPayloadFromJSONInvalid(t *testing.T) { + if _, err := prompty.SessionWarningPayloadFromJSON("{"); err == nil { + t.Fatalf("Expected malformed JSON to fail") + } +} diff --git a/runtime/go/prompty/model/status_event_payload.go b/runtime/go/prompty/model/status_event_payload.go index 6b0d6e20..51b68a98 100644 --- a/runtime/go/prompty/model/status_event_payload.go +++ b/runtime/go/prompty/model/status_event_payload.go @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. // Group: events package prompty @@ -30,7 +31,7 @@ func LoadStatusEventPayload(data interface{}, ctx *LoadContext) (StatusEventPayl } // Save serializes StatusEventPayload to map[string]interface{} -func (obj *StatusEventPayload) Save(ctx *SaveContext) map[string]interface{} { +func (obj StatusEventPayload) Save(ctx *SaveContext) map[string]interface{} { result := make(map[string]interface{}) result["message"] = obj.Message @@ -52,11 +53,7 @@ func (obj *StatusEventPayload) ToJSON() (string, error) { func (obj *StatusEventPayload) ToYAML() (string, error) { ctx := NewSaveContext() data := obj.Save(ctx) - bytes, err := yaml.Marshal(data) - if err != nil { - return "", err - } - return string(bytes), nil + return marshalYAMLDocument(data) } // FromJSON creates StatusEventPayload from JSON string diff --git a/runtime/go/prompty/model/status_event_payload_test.go b/runtime/go/prompty/model/status_event_payload_test.go index f5a6cdd0..202a4114 100644 --- a/runtime/go/prompty/model/status_event_payload_test.go +++ b/runtime/go/prompty/model/status_event_payload_test.go @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. package prompty_test @@ -54,6 +55,39 @@ message: Starting iteration 3 } } +// TestStatusEventPayloadFromJSON tests loading StatusEventPayload through the generated JSON helper +func TestStatusEventPayloadFromJSON(t *testing.T) { + jsonData := ` +{ + "message": "Starting iteration 3" +} +` + + instance, err := prompty.StatusEventPayloadFromJSON(jsonData) + if err != nil { + t.Fatalf("Failed to load StatusEventPayload from JSON helper: %v", err) + } + if instance.Message != "Starting iteration 3" { + t.Errorf(`Expected Message to be "Starting iteration 3", got %v`, instance.Message) + } +} + +// TestStatusEventPayloadFromYAML tests loading StatusEventPayload through the generated YAML helper +func TestStatusEventPayloadFromYAML(t *testing.T) { + yamlData := ` +message: Starting iteration 3 + +` + + instance, err := prompty.StatusEventPayloadFromYAML(yamlData) + if err != nil { + t.Fatalf("Failed to load StatusEventPayload from YAML helper: %v", err) + } + if instance.Message != "Starting iteration 3" { + t.Errorf(`Expected Message to be "Starting iteration 3", got %v`, instance.Message) + } +} + // TestStatusEventPayloadRoundtrip tests load -> save -> load produces equivalent data func TestStatusEventPayloadRoundtrip(t *testing.T) { jsonData := ` @@ -109,6 +143,14 @@ func TestStatusEventPayloadToJSON(t *testing.T) { if err := json.Unmarshal([]byte(jsonOutput), &parsed); err != nil { t.Fatalf("Failed to parse generated JSON: %v", err) } + + reloaded, err := prompty.LoadStatusEventPayload(parsed, ctx) + if err != nil { + t.Fatalf("Failed to reload generated JSON: %v", err) + } + if reloaded.Message != "Starting iteration 3" { + t.Errorf(`Expected Message to be "Starting iteration 3", got %v`, reloaded.Message) + } } // TestStatusEventPayloadToYAML tests that ToYAML produces valid YAML @@ -137,4 +179,19 @@ func TestStatusEventPayloadToYAML(t *testing.T) { if err := yaml.Unmarshal([]byte(yamlOutput), &parsed); err != nil { t.Fatalf("Failed to parse generated YAML: %v", err) } + + reloaded, err := prompty.LoadStatusEventPayload(parsed, ctx) + if err != nil { + t.Fatalf("Failed to reload generated YAML: %v", err) + } + if reloaded.Message != "Starting iteration 3" { + t.Errorf(`Expected Message to be "Starting iteration 3", got %v`, reloaded.Message) + } +} + +// TestStatusEventPayloadFromJSONInvalid rejects malformed JSON instead of silently defaulting +func TestStatusEventPayloadFromJSONInvalid(t *testing.T) { + if _, err := prompty.StatusEventPayloadFromJSON("{"); err == nil { + t.Fatalf("Expected malformed JSON to fail") + } } diff --git a/runtime/go/prompty/model/stream_chunk.go b/runtime/go/prompty/model/stream_chunk.go index c0a17e33..ab03e351 100644 --- a/runtime/go/prompty/model/stream_chunk.go +++ b/runtime/go/prompty/model/stream_chunk.go @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. // Group: events package prompty @@ -47,7 +48,7 @@ func LoadStreamChunk(data interface{}, ctx *LoadContext) (interface{}, error) { } // Save serializes StreamChunk to map[string]interface{} -func (obj *StreamChunk) Save(ctx *SaveContext) map[string]interface{} { +func (obj StreamChunk) Save(ctx *SaveContext) map[string]interface{} { result := make(map[string]interface{}) result["kind"] = obj.Kind @@ -69,11 +70,7 @@ func (obj *StreamChunk) ToJSON() (string, error) { func (obj *StreamChunk) ToYAML() (string, error) { ctx := NewSaveContext() data := obj.Save(ctx) - bytes, err := yaml.Marshal(data) - if err != nil { - return "", err - } - return string(bytes), nil + return marshalYAMLDocument(data) } // FromJSON creates StreamChunk from JSON string @@ -123,7 +120,7 @@ func LoadTextChunk(data interface{}, ctx *LoadContext) (TextChunk, error) { } // Save serializes TextChunk to map[string]interface{} -func (obj *TextChunk) Save(ctx *SaveContext) map[string]interface{} { +func (obj TextChunk) Save(ctx *SaveContext) map[string]interface{} { result := make(map[string]interface{}) result["kind"] = obj.Kind result["value"] = obj.Value @@ -146,11 +143,7 @@ func (obj *TextChunk) ToJSON() (string, error) { func (obj *TextChunk) ToYAML() (string, error) { ctx := NewSaveContext() data := obj.Save(ctx) - bytes, err := yaml.Marshal(data) - if err != nil { - return "", err - } - return string(bytes), nil + return marshalYAMLDocument(data) } // FromJSON creates TextChunk from JSON string @@ -198,7 +191,7 @@ func LoadThinkingChunk(data interface{}, ctx *LoadContext) (ThinkingChunk, error } // Save serializes ThinkingChunk to map[string]interface{} -func (obj *ThinkingChunk) Save(ctx *SaveContext) map[string]interface{} { +func (obj ThinkingChunk) Save(ctx *SaveContext) map[string]interface{} { result := make(map[string]interface{}) result["kind"] = obj.Kind result["value"] = obj.Value @@ -221,11 +214,7 @@ func (obj *ThinkingChunk) ToJSON() (string, error) { func (obj *ThinkingChunk) ToYAML() (string, error) { ctx := NewSaveContext() data := obj.Save(ctx) - bytes, err := yaml.Marshal(data) - if err != nil { - return "", err - } - return string(bytes), nil + return marshalYAMLDocument(data) } // FromJSON creates ThinkingChunk from JSON string @@ -276,7 +265,7 @@ func LoadToolChunk(data interface{}, ctx *LoadContext) (ToolChunk, error) { } // Save serializes ToolChunk to map[string]interface{} -func (obj *ToolChunk) Save(ctx *SaveContext) map[string]interface{} { +func (obj ToolChunk) Save(ctx *SaveContext) map[string]interface{} { result := make(map[string]interface{}) result["kind"] = obj.Kind @@ -300,11 +289,7 @@ func (obj *ToolChunk) ToJSON() (string, error) { func (obj *ToolChunk) ToYAML() (string, error) { ctx := NewSaveContext() data := obj.Save(ctx) - bytes, err := yaml.Marshal(data) - if err != nil { - return "", err - } - return string(bytes), nil + return marshalYAMLDocument(data) } // FromJSON creates ToolChunk from JSON string @@ -352,7 +337,7 @@ func LoadErrorChunk(data interface{}, ctx *LoadContext) (ErrorChunk, error) { } // Save serializes ErrorChunk to map[string]interface{} -func (obj *ErrorChunk) Save(ctx *SaveContext) map[string]interface{} { +func (obj ErrorChunk) Save(ctx *SaveContext) map[string]interface{} { result := make(map[string]interface{}) result["kind"] = obj.Kind result["message"] = obj.Message @@ -375,11 +360,7 @@ func (obj *ErrorChunk) ToJSON() (string, error) { func (obj *ErrorChunk) ToYAML() (string, error) { ctx := NewSaveContext() data := obj.Save(ctx) - bytes, err := yaml.Marshal(data) - if err != nil { - return "", err - } - return string(bytes), nil + return marshalYAMLDocument(data) } // FromJSON creates ErrorChunk from JSON string diff --git a/runtime/go/prompty/model/stream_chunk_test.go b/runtime/go/prompty/model/stream_chunk_test.go index 3df1459b..9cc5440c 100644 --- a/runtime/go/prompty/model/stream_chunk_test.go +++ b/runtime/go/prompty/model/stream_chunk_test.go @@ -1,3 +1,4 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. package prompty_test diff --git a/runtime/go/prompty/model/stream_options.go b/runtime/go/prompty/model/stream_options.go index 951ad9f8..a874b014 100644 --- a/runtime/go/prompty/model/stream_options.go +++ b/runtime/go/prompty/model/stream_options.go @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. // Group: streaming package prompty @@ -32,7 +33,7 @@ func LoadStreamOptions(data interface{}, ctx *LoadContext) (StreamOptions, error } // Save serializes StreamOptions to map[string]interface{} -func (obj *StreamOptions) Save(ctx *SaveContext) map[string]interface{} { +func (obj StreamOptions) Save(ctx *SaveContext) map[string]interface{} { result := make(map[string]interface{}) if obj.IncludeUsage != nil { result["includeUsage"] = *obj.IncludeUsage @@ -56,11 +57,7 @@ func (obj *StreamOptions) ToJSON() (string, error) { func (obj *StreamOptions) ToYAML() (string, error) { ctx := NewSaveContext() data := obj.Save(ctx) - bytes, err := yaml.Marshal(data) - if err != nil { - return "", err - } - return string(bytes), nil + return marshalYAMLDocument(data) } // FromJSON creates StreamOptions from JSON string diff --git a/runtime/go/prompty/model/stream_options_test.go b/runtime/go/prompty/model/stream_options_test.go index ac3fd0ab..a2b95ff9 100644 --- a/runtime/go/prompty/model/stream_options_test.go +++ b/runtime/go/prompty/model/stream_options_test.go @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. package prompty_test @@ -54,6 +55,39 @@ includeUsage: true } } +// TestStreamOptionsFromJSON tests loading StreamOptions through the generated JSON helper +func TestStreamOptionsFromJSON(t *testing.T) { + jsonData := ` +{ + "includeUsage": true +} +` + + instance, err := prompty.StreamOptionsFromJSON(jsonData) + if err != nil { + t.Fatalf("Failed to load StreamOptions from JSON helper: %v", err) + } + if instance.IncludeUsage == nil || *instance.IncludeUsage != true { + t.Errorf(`Expected IncludeUsage to be true, got %v`, instance.IncludeUsage) + } +} + +// TestStreamOptionsFromYAML tests loading StreamOptions through the generated YAML helper +func TestStreamOptionsFromYAML(t *testing.T) { + yamlData := ` +includeUsage: true + +` + + instance, err := prompty.StreamOptionsFromYAML(yamlData) + if err != nil { + t.Fatalf("Failed to load StreamOptions from YAML helper: %v", err) + } + if instance.IncludeUsage == nil || *instance.IncludeUsage != true { + t.Errorf(`Expected IncludeUsage to be true, got %v`, instance.IncludeUsage) + } +} + // TestStreamOptionsRoundtrip tests load -> save -> load produces equivalent data func TestStreamOptionsRoundtrip(t *testing.T) { jsonData := ` @@ -109,6 +143,14 @@ func TestStreamOptionsToJSON(t *testing.T) { if err := json.Unmarshal([]byte(jsonOutput), &parsed); err != nil { t.Fatalf("Failed to parse generated JSON: %v", err) } + + reloaded, err := prompty.LoadStreamOptions(parsed, ctx) + if err != nil { + t.Fatalf("Failed to reload generated JSON: %v", err) + } + if reloaded.IncludeUsage == nil || *reloaded.IncludeUsage != true { + t.Errorf(`Expected IncludeUsage to be true, got %v`, reloaded.IncludeUsage) + } } // TestStreamOptionsToYAML tests that ToYAML produces valid YAML @@ -137,4 +179,19 @@ func TestStreamOptionsToYAML(t *testing.T) { if err := yaml.Unmarshal([]byte(yamlOutput), &parsed); err != nil { t.Fatalf("Failed to parse generated YAML: %v", err) } + + reloaded, err := prompty.LoadStreamOptions(parsed, ctx) + if err != nil { + t.Fatalf("Failed to reload generated YAML: %v", err) + } + if reloaded.IncludeUsage == nil || *reloaded.IncludeUsage != true { + t.Errorf(`Expected IncludeUsage to be true, got %v`, reloaded.IncludeUsage) + } +} + +// TestStreamOptionsFromJSONInvalid rejects malformed JSON instead of silently defaulting +func TestStreamOptionsFromJSONInvalid(t *testing.T) { + if _, err := prompty.StreamOptionsFromJSON("{"); err == nil { + t.Fatalf("Expected malformed JSON to fail") + } } diff --git a/runtime/go/prompty/model/template.go b/runtime/go/prompty/model/template.go index 00de91da..c4c84054 100644 --- a/runtime/go/prompty/model/template.go +++ b/runtime/go/prompty/model/template.go @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. // Group: template package prompty @@ -33,12 +34,18 @@ func LoadTemplate(data interface{}, ctx *LoadContext) (Template, error) { if m, ok := val.(map[string]interface{}); ok { loaded, _ := LoadFormatConfig(m, ctx) result.Format = loaded + } else { + loaded, _ := LoadFormatConfig(val, ctx) + result.Format = loaded } } if val, ok := m["parser"]; ok && val != nil { if m, ok := val.(map[string]interface{}); ok { loaded, _ := LoadParserConfig(m, ctx) result.Parser = loaded + } else { + loaded, _ := LoadParserConfig(val, ctx) + result.Parser = loaded } } } @@ -47,7 +54,7 @@ func LoadTemplate(data interface{}, ctx *LoadContext) (Template, error) { } // Save serializes Template to map[string]interface{} -func (obj *Template) Save(ctx *SaveContext) map[string]interface{} { +func (obj Template) Save(ctx *SaveContext) map[string]interface{} { result := make(map[string]interface{}) result["format"] = obj.Format.Save(ctx) @@ -72,11 +79,7 @@ func (obj *Template) ToJSON() (string, error) { func (obj *Template) ToYAML() (string, error) { ctx := NewSaveContext() data := obj.Save(ctx) - bytes, err := yaml.Marshal(data) - if err != nil { - return "", err - } - return string(bytes), nil + return marshalYAMLDocument(data) } // FromJSON creates Template from JSON string diff --git a/runtime/go/prompty/model/template_test.go b/runtime/go/prompty/model/template_test.go index 2e38d149..06d277b8 100644 --- a/runtime/go/prompty/model/template_test.go +++ b/runtime/go/prompty/model/template_test.go @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. package prompty_test @@ -34,6 +35,12 @@ func TestTemplateLoadJSON(t *testing.T) { t.Fatalf("Failed to load Template: %v", err) } _ = instance // No scalar properties to validate + if instance.Format.Kind != "mustache" { + t.Errorf(`Expected Format.Kind to be "mustache", got %v`, instance.Format.Kind) + } + if instance.Parser.Kind != "mustache" { + t.Errorf(`Expected Parser.Kind to be "mustache", got %v`, instance.Parser.Kind) + } } // TestTemplateLoadYAML tests loading Template from YAML @@ -56,6 +63,61 @@ parser: t.Fatalf("Failed to load Template: %v", err) } _ = instance // No scalar properties to validate + if instance.Format.Kind != "mustache" { + t.Errorf(`Expected Format.Kind to be "mustache", got %v`, instance.Format.Kind) + } + if instance.Parser.Kind != "mustache" { + t.Errorf(`Expected Parser.Kind to be "mustache", got %v`, instance.Parser.Kind) + } +} + +// TestTemplateFromJSON tests loading Template through the generated JSON helper +func TestTemplateFromJSON(t *testing.T) { + jsonData := ` +{ + "format": { + "kind": "mustache" + }, + "parser": { + "kind": "mustache" + } +} +` + + instance, err := prompty.TemplateFromJSON(jsonData) + if err != nil { + t.Fatalf("Failed to load Template from JSON helper: %v", err) + } + _ = instance // No scalar properties to validate + if instance.Format.Kind != "mustache" { + t.Errorf(`Expected Format.Kind to be "mustache", got %v`, instance.Format.Kind) + } + if instance.Parser.Kind != "mustache" { + t.Errorf(`Expected Parser.Kind to be "mustache", got %v`, instance.Parser.Kind) + } +} + +// TestTemplateFromYAML tests loading Template through the generated YAML helper +func TestTemplateFromYAML(t *testing.T) { + yamlData := ` +format: + kind: mustache +parser: + kind: mustache + +` + + instance, err := prompty.TemplateFromYAML(yamlData) + if err != nil { + t.Fatalf("Failed to load Template from YAML helper: %v", err) + } + _ = instance // No scalar properties to validate + if instance.Format.Kind != "mustache" { + t.Errorf(`Expected Format.Kind to be "mustache", got %v`, instance.Format.Kind) + } + if instance.Parser.Kind != "mustache" { + t.Errorf(`Expected Parser.Kind to be "mustache", got %v`, instance.Parser.Kind) + } } // TestTemplateRoundtrip tests load -> save -> load produces equivalent data @@ -88,6 +150,12 @@ func TestTemplateRoundtrip(t *testing.T) { t.Fatalf("Failed to reload Template: %v", err) } _ = reloaded // No scalar properties to validate + if reloaded.Format.Kind != "mustache" { + t.Errorf(`Expected Format.Kind to be "mustache", got %v`, reloaded.Format.Kind) + } + if reloaded.Parser.Kind != "mustache" { + t.Errorf(`Expected Parser.Kind to be "mustache", got %v`, reloaded.Parser.Kind) + } } // TestTemplateToJSON tests that ToJSON produces valid JSON @@ -121,6 +189,18 @@ func TestTemplateToJSON(t *testing.T) { if err := json.Unmarshal([]byte(jsonOutput), &parsed); err != nil { t.Fatalf("Failed to parse generated JSON: %v", err) } + + reloaded, err := prompty.LoadTemplate(parsed, ctx) + if err != nil { + t.Fatalf("Failed to reload generated JSON: %v", err) + } + _ = reloaded // No scalar properties to validate + if reloaded.Format.Kind != "mustache" { + t.Errorf(`Expected Format.Kind to be "mustache", got %v`, reloaded.Format.Kind) + } + if reloaded.Parser.Kind != "mustache" { + t.Errorf(`Expected Parser.Kind to be "mustache", got %v`, reloaded.Parser.Kind) + } } // TestTemplateToYAML tests that ToYAML produces valid YAML @@ -154,4 +234,23 @@ func TestTemplateToYAML(t *testing.T) { if err := yaml.Unmarshal([]byte(yamlOutput), &parsed); err != nil { t.Fatalf("Failed to parse generated YAML: %v", err) } + + reloaded, err := prompty.LoadTemplate(parsed, ctx) + if err != nil { + t.Fatalf("Failed to reload generated YAML: %v", err) + } + _ = reloaded // No scalar properties to validate + if reloaded.Format.Kind != "mustache" { + t.Errorf(`Expected Format.Kind to be "mustache", got %v`, reloaded.Format.Kind) + } + if reloaded.Parser.Kind != "mustache" { + t.Errorf(`Expected Parser.Kind to be "mustache", got %v`, reloaded.Parser.Kind) + } +} + +// TestTemplateFromJSONInvalid rejects malformed JSON instead of silently defaulting +func TestTemplateFromJSONInvalid(t *testing.T) { + if _, err := prompty.TemplateFromJSON("{"); err == nil { + t.Fatalf("Expected malformed JSON to fail") + } } diff --git a/runtime/go/prompty/model/text_chunk_test.go b/runtime/go/prompty/model/text_chunk_test.go index a02caf8a..282c0e30 100644 --- a/runtime/go/prompty/model/text_chunk_test.go +++ b/runtime/go/prompty/model/text_chunk_test.go @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. package prompty_test @@ -54,6 +55,39 @@ value: Hello } } +// TestTextChunkFromJSON tests loading TextChunk through the generated JSON helper +func TestTextChunkFromJSON(t *testing.T) { + jsonData := ` +{ + "value": "Hello" +} +` + + instance, err := prompty.TextChunkFromJSON(jsonData) + if err != nil { + t.Fatalf("Failed to load TextChunk from JSON helper: %v", err) + } + if instance.Value != "Hello" { + t.Errorf(`Expected Value to be "Hello", got %v`, instance.Value) + } +} + +// TestTextChunkFromYAML tests loading TextChunk through the generated YAML helper +func TestTextChunkFromYAML(t *testing.T) { + yamlData := ` +value: Hello + +` + + instance, err := prompty.TextChunkFromYAML(yamlData) + if err != nil { + t.Fatalf("Failed to load TextChunk from YAML helper: %v", err) + } + if instance.Value != "Hello" { + t.Errorf(`Expected Value to be "Hello", got %v`, instance.Value) + } +} + // TestTextChunkRoundtrip tests load -> save -> load produces equivalent data func TestTextChunkRoundtrip(t *testing.T) { jsonData := ` @@ -109,6 +143,14 @@ func TestTextChunkToJSON(t *testing.T) { if err := json.Unmarshal([]byte(jsonOutput), &parsed); err != nil { t.Fatalf("Failed to parse generated JSON: %v", err) } + + reloaded, err := prompty.LoadTextChunk(parsed, ctx) + if err != nil { + t.Fatalf("Failed to reload generated JSON: %v", err) + } + if reloaded.Value != "Hello" { + t.Errorf(`Expected Value to be "Hello", got %v`, reloaded.Value) + } } // TestTextChunkToYAML tests that ToYAML produces valid YAML @@ -137,4 +179,19 @@ func TestTextChunkToYAML(t *testing.T) { if err := yaml.Unmarshal([]byte(yamlOutput), &parsed); err != nil { t.Fatalf("Failed to parse generated YAML: %v", err) } + + reloaded, err := prompty.LoadTextChunk(parsed, ctx) + if err != nil { + t.Fatalf("Failed to reload generated YAML: %v", err) + } + if reloaded.Value != "Hello" { + t.Errorf(`Expected Value to be "Hello", got %v`, reloaded.Value) + } +} + +// TestTextChunkFromJSONInvalid rejects malformed JSON instead of silently defaulting +func TestTextChunkFromJSONInvalid(t *testing.T) { + if _, err := prompty.TextChunkFromJSON("{"); err == nil { + t.Fatalf("Expected malformed JSON to fail") + } } diff --git a/runtime/go/prompty/model/text_part_test.go b/runtime/go/prompty/model/text_part_test.go index f1eb5b6f..6822a429 100644 --- a/runtime/go/prompty/model/text_part_test.go +++ b/runtime/go/prompty/model/text_part_test.go @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. package prompty_test @@ -54,6 +55,39 @@ value: Hello, world! } } +// TestTextPartFromJSON tests loading TextPart through the generated JSON helper +func TestTextPartFromJSON(t *testing.T) { + jsonData := ` +{ + "value": "Hello, world!" +} +` + + instance, err := prompty.TextPartFromJSON(jsonData) + if err != nil { + t.Fatalf("Failed to load TextPart from JSON helper: %v", err) + } + if instance.Value != "Hello, world!" { + t.Errorf(`Expected Value to be "Hello, world!", got %v`, instance.Value) + } +} + +// TestTextPartFromYAML tests loading TextPart through the generated YAML helper +func TestTextPartFromYAML(t *testing.T) { + yamlData := ` +value: Hello, world! + +` + + instance, err := prompty.TextPartFromYAML(yamlData) + if err != nil { + t.Fatalf("Failed to load TextPart from YAML helper: %v", err) + } + if instance.Value != "Hello, world!" { + t.Errorf(`Expected Value to be "Hello, world!", got %v`, instance.Value) + } +} + // TestTextPartRoundtrip tests load -> save -> load produces equivalent data func TestTextPartRoundtrip(t *testing.T) { jsonData := ` @@ -109,6 +143,14 @@ func TestTextPartToJSON(t *testing.T) { if err := json.Unmarshal([]byte(jsonOutput), &parsed); err != nil { t.Fatalf("Failed to parse generated JSON: %v", err) } + + reloaded, err := prompty.LoadTextPart(parsed, ctx) + if err != nil { + t.Fatalf("Failed to reload generated JSON: %v", err) + } + if reloaded.Value != "Hello, world!" { + t.Errorf(`Expected Value to be "Hello, world!", got %v`, reloaded.Value) + } } // TestTextPartToYAML tests that ToYAML produces valid YAML @@ -137,4 +179,19 @@ func TestTextPartToYAML(t *testing.T) { if err := yaml.Unmarshal([]byte(yamlOutput), &parsed); err != nil { t.Fatalf("Failed to parse generated YAML: %v", err) } + + reloaded, err := prompty.LoadTextPart(parsed, ctx) + if err != nil { + t.Fatalf("Failed to reload generated YAML: %v", err) + } + if reloaded.Value != "Hello, world!" { + t.Errorf(`Expected Value to be "Hello, world!", got %v`, reloaded.Value) + } +} + +// TestTextPartFromJSONInvalid rejects malformed JSON instead of silently defaulting +func TestTextPartFromJSONInvalid(t *testing.T) { + if _, err := prompty.TextPartFromJSON("{"); err == nil { + t.Fatalf("Expected malformed JSON to fail") + } } diff --git a/runtime/go/prompty/model/thinking_chunk_test.go b/runtime/go/prompty/model/thinking_chunk_test.go index 3c2ea35d..480833f1 100644 --- a/runtime/go/prompty/model/thinking_chunk_test.go +++ b/runtime/go/prompty/model/thinking_chunk_test.go @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. package prompty_test @@ -54,6 +55,39 @@ value: Let me consider... } } +// TestThinkingChunkFromJSON tests loading ThinkingChunk through the generated JSON helper +func TestThinkingChunkFromJSON(t *testing.T) { + jsonData := ` +{ + "value": "Let me consider..." +} +` + + instance, err := prompty.ThinkingChunkFromJSON(jsonData) + if err != nil { + t.Fatalf("Failed to load ThinkingChunk from JSON helper: %v", err) + } + if instance.Value != "Let me consider..." { + t.Errorf(`Expected Value to be "Let me consider...", got %v`, instance.Value) + } +} + +// TestThinkingChunkFromYAML tests loading ThinkingChunk through the generated YAML helper +func TestThinkingChunkFromYAML(t *testing.T) { + yamlData := ` +value: Let me consider... + +` + + instance, err := prompty.ThinkingChunkFromYAML(yamlData) + if err != nil { + t.Fatalf("Failed to load ThinkingChunk from YAML helper: %v", err) + } + if instance.Value != "Let me consider..." { + t.Errorf(`Expected Value to be "Let me consider...", got %v`, instance.Value) + } +} + // TestThinkingChunkRoundtrip tests load -> save -> load produces equivalent data func TestThinkingChunkRoundtrip(t *testing.T) { jsonData := ` @@ -109,6 +143,14 @@ func TestThinkingChunkToJSON(t *testing.T) { if err := json.Unmarshal([]byte(jsonOutput), &parsed); err != nil { t.Fatalf("Failed to parse generated JSON: %v", err) } + + reloaded, err := prompty.LoadThinkingChunk(parsed, ctx) + if err != nil { + t.Fatalf("Failed to reload generated JSON: %v", err) + } + if reloaded.Value != "Let me consider..." { + t.Errorf(`Expected Value to be "Let me consider...", got %v`, reloaded.Value) + } } // TestThinkingChunkToYAML tests that ToYAML produces valid YAML @@ -137,4 +179,19 @@ func TestThinkingChunkToYAML(t *testing.T) { if err := yaml.Unmarshal([]byte(yamlOutput), &parsed); err != nil { t.Fatalf("Failed to parse generated YAML: %v", err) } + + reloaded, err := prompty.LoadThinkingChunk(parsed, ctx) + if err != nil { + t.Fatalf("Failed to reload generated YAML: %v", err) + } + if reloaded.Value != "Let me consider..." { + t.Errorf(`Expected Value to be "Let me consider...", got %v`, reloaded.Value) + } +} + +// TestThinkingChunkFromJSONInvalid rejects malformed JSON instead of silently defaulting +func TestThinkingChunkFromJSONInvalid(t *testing.T) { + if _, err := prompty.ThinkingChunkFromJSON("{"); err == nil { + t.Fatalf("Expected malformed JSON to fail") + } } diff --git a/runtime/go/prompty/model/thinking_event_payload.go b/runtime/go/prompty/model/thinking_event_payload.go index 123e7306..901854f5 100644 --- a/runtime/go/prompty/model/thinking_event_payload.go +++ b/runtime/go/prompty/model/thinking_event_payload.go @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. // Group: events package prompty @@ -30,7 +31,7 @@ func LoadThinkingEventPayload(data interface{}, ctx *LoadContext) (ThinkingEvent } // Save serializes ThinkingEventPayload to map[string]interface{} -func (obj *ThinkingEventPayload) Save(ctx *SaveContext) map[string]interface{} { +func (obj ThinkingEventPayload) Save(ctx *SaveContext) map[string]interface{} { result := make(map[string]interface{}) result["token"] = obj.Token @@ -52,11 +53,7 @@ func (obj *ThinkingEventPayload) ToJSON() (string, error) { func (obj *ThinkingEventPayload) ToYAML() (string, error) { ctx := NewSaveContext() data := obj.Save(ctx) - bytes, err := yaml.Marshal(data) - if err != nil { - return "", err - } - return string(bytes), nil + return marshalYAMLDocument(data) } // FromJSON creates ThinkingEventPayload from JSON string diff --git a/runtime/go/prompty/model/thinking_event_payload_test.go b/runtime/go/prompty/model/thinking_event_payload_test.go index 9902cea5..12bd2da2 100644 --- a/runtime/go/prompty/model/thinking_event_payload_test.go +++ b/runtime/go/prompty/model/thinking_event_payload_test.go @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. package prompty_test @@ -54,6 +55,39 @@ token: Let me consider... } } +// TestThinkingEventPayloadFromJSON tests loading ThinkingEventPayload through the generated JSON helper +func TestThinkingEventPayloadFromJSON(t *testing.T) { + jsonData := ` +{ + "token": "Let me consider..." +} +` + + instance, err := prompty.ThinkingEventPayloadFromJSON(jsonData) + if err != nil { + t.Fatalf("Failed to load ThinkingEventPayload from JSON helper: %v", err) + } + if instance.Token != "Let me consider..." { + t.Errorf(`Expected Token to be "Let me consider...", got %v`, instance.Token) + } +} + +// TestThinkingEventPayloadFromYAML tests loading ThinkingEventPayload through the generated YAML helper +func TestThinkingEventPayloadFromYAML(t *testing.T) { + yamlData := ` +token: Let me consider... + +` + + instance, err := prompty.ThinkingEventPayloadFromYAML(yamlData) + if err != nil { + t.Fatalf("Failed to load ThinkingEventPayload from YAML helper: %v", err) + } + if instance.Token != "Let me consider..." { + t.Errorf(`Expected Token to be "Let me consider...", got %v`, instance.Token) + } +} + // TestThinkingEventPayloadRoundtrip tests load -> save -> load produces equivalent data func TestThinkingEventPayloadRoundtrip(t *testing.T) { jsonData := ` @@ -109,6 +143,14 @@ func TestThinkingEventPayloadToJSON(t *testing.T) { if err := json.Unmarshal([]byte(jsonOutput), &parsed); err != nil { t.Fatalf("Failed to parse generated JSON: %v", err) } + + reloaded, err := prompty.LoadThinkingEventPayload(parsed, ctx) + if err != nil { + t.Fatalf("Failed to reload generated JSON: %v", err) + } + if reloaded.Token != "Let me consider..." { + t.Errorf(`Expected Token to be "Let me consider...", got %v`, reloaded.Token) + } } // TestThinkingEventPayloadToYAML tests that ToYAML produces valid YAML @@ -137,4 +179,19 @@ func TestThinkingEventPayloadToYAML(t *testing.T) { if err := yaml.Unmarshal([]byte(yamlOutput), &parsed); err != nil { t.Fatalf("Failed to parse generated YAML: %v", err) } + + reloaded, err := prompty.LoadThinkingEventPayload(parsed, ctx) + if err != nil { + t.Fatalf("Failed to reload generated YAML: %v", err) + } + if reloaded.Token != "Let me consider..." { + t.Errorf(`Expected Token to be "Let me consider...", got %v`, reloaded.Token) + } +} + +// TestThinkingEventPayloadFromJSONInvalid rejects malformed JSON instead of silently defaulting +func TestThinkingEventPayloadFromJSONInvalid(t *testing.T) { + if _, err := prompty.ThinkingEventPayloadFromJSON("{"); err == nil { + t.Fatalf("Expected malformed JSON to fail") + } } diff --git a/runtime/go/prompty/model/thread_marker.go b/runtime/go/prompty/model/thread_marker.go index 6c0cc46c..6d9b85da 100644 --- a/runtime/go/prompty/model/thread_marker.go +++ b/runtime/go/prompty/model/thread_marker.go @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. // Group: conversation package prompty @@ -38,7 +39,7 @@ func LoadThreadMarker(data interface{}, ctx *LoadContext) (ThreadMarker, error) } // Save serializes ThreadMarker to map[string]interface{} -func (obj *ThreadMarker) Save(ctx *SaveContext) map[string]interface{} { +func (obj ThreadMarker) Save(ctx *SaveContext) map[string]interface{} { result := make(map[string]interface{}) result["name"] = obj.Name result["kind"] = obj.Kind @@ -61,11 +62,7 @@ func (obj *ThreadMarker) ToJSON() (string, error) { func (obj *ThreadMarker) ToYAML() (string, error) { ctx := NewSaveContext() data := obj.Save(ctx) - bytes, err := yaml.Marshal(data) - if err != nil { - return "", err - } - return string(bytes), nil + return marshalYAMLDocument(data) } // FromJSON creates ThreadMarker from JSON string diff --git a/runtime/go/prompty/model/thread_marker_test.go b/runtime/go/prompty/model/thread_marker_test.go index cfa2c6ed..ae2792f2 100644 --- a/runtime/go/prompty/model/thread_marker_test.go +++ b/runtime/go/prompty/model/thread_marker_test.go @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. package prompty_test @@ -62,6 +63,47 @@ kind: thread } } +// TestThreadMarkerFromJSON tests loading ThreadMarker through the generated JSON helper +func TestThreadMarkerFromJSON(t *testing.T) { + jsonData := ` +{ + "name": "thread", + "kind": "thread" +} +` + + instance, err := prompty.ThreadMarkerFromJSON(jsonData) + if err != nil { + t.Fatalf("Failed to load ThreadMarker from JSON helper: %v", err) + } + if instance.Name != "thread" { + t.Errorf(`Expected Name to be "thread", got %v`, instance.Name) + } + if instance.Kind != "thread" { + t.Errorf(`Expected Kind to be "thread", got %v`, instance.Kind) + } +} + +// TestThreadMarkerFromYAML tests loading ThreadMarker through the generated YAML helper +func TestThreadMarkerFromYAML(t *testing.T) { + yamlData := ` +name: thread +kind: thread + +` + + instance, err := prompty.ThreadMarkerFromYAML(yamlData) + if err != nil { + t.Fatalf("Failed to load ThreadMarker from YAML helper: %v", err) + } + if instance.Name != "thread" { + t.Errorf(`Expected Name to be "thread", got %v`, instance.Name) + } + if instance.Kind != "thread" { + t.Errorf(`Expected Kind to be "thread", got %v`, instance.Kind) + } +} + // TestThreadMarkerRoundtrip tests load -> save -> load produces equivalent data func TestThreadMarkerRoundtrip(t *testing.T) { jsonData := ` @@ -122,6 +164,17 @@ func TestThreadMarkerToJSON(t *testing.T) { if err := json.Unmarshal([]byte(jsonOutput), &parsed); err != nil { t.Fatalf("Failed to parse generated JSON: %v", err) } + + reloaded, err := prompty.LoadThreadMarker(parsed, ctx) + if err != nil { + t.Fatalf("Failed to reload generated JSON: %v", err) + } + if reloaded.Name != "thread" { + t.Errorf(`Expected Name to be "thread", got %v`, reloaded.Name) + } + if reloaded.Kind != "thread" { + t.Errorf(`Expected Kind to be "thread", got %v`, reloaded.Kind) + } } // TestThreadMarkerToYAML tests that ToYAML produces valid YAML @@ -151,4 +204,22 @@ func TestThreadMarkerToYAML(t *testing.T) { if err := yaml.Unmarshal([]byte(yamlOutput), &parsed); err != nil { t.Fatalf("Failed to parse generated YAML: %v", err) } + + reloaded, err := prompty.LoadThreadMarker(parsed, ctx) + if err != nil { + t.Fatalf("Failed to reload generated YAML: %v", err) + } + if reloaded.Name != "thread" { + t.Errorf(`Expected Name to be "thread", got %v`, reloaded.Name) + } + if reloaded.Kind != "thread" { + t.Errorf(`Expected Kind to be "thread", got %v`, reloaded.Kind) + } +} + +// TestThreadMarkerFromJSONInvalid rejects malformed JSON instead of silently defaulting +func TestThreadMarkerFromJSONInvalid(t *testing.T) { + if _, err := prompty.ThreadMarkerFromJSON("{"); err == nil { + t.Fatalf("Expected malformed JSON to fail") + } } diff --git a/runtime/go/prompty/model/token_event_payload.go b/runtime/go/prompty/model/token_event_payload.go index c7fa00f0..172f300b 100644 --- a/runtime/go/prompty/model/token_event_payload.go +++ b/runtime/go/prompty/model/token_event_payload.go @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. // Group: events package prompty @@ -30,7 +31,7 @@ func LoadTokenEventPayload(data interface{}, ctx *LoadContext) (TokenEventPayloa } // Save serializes TokenEventPayload to map[string]interface{} -func (obj *TokenEventPayload) Save(ctx *SaveContext) map[string]interface{} { +func (obj TokenEventPayload) Save(ctx *SaveContext) map[string]interface{} { result := make(map[string]interface{}) result["token"] = obj.Token @@ -52,11 +53,7 @@ func (obj *TokenEventPayload) ToJSON() (string, error) { func (obj *TokenEventPayload) ToYAML() (string, error) { ctx := NewSaveContext() data := obj.Save(ctx) - bytes, err := yaml.Marshal(data) - if err != nil { - return "", err - } - return string(bytes), nil + return marshalYAMLDocument(data) } // FromJSON creates TokenEventPayload from JSON string diff --git a/runtime/go/prompty/model/token_event_payload_test.go b/runtime/go/prompty/model/token_event_payload_test.go index c6221d92..7408d58a 100644 --- a/runtime/go/prompty/model/token_event_payload_test.go +++ b/runtime/go/prompty/model/token_event_payload_test.go @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. package prompty_test @@ -54,6 +55,39 @@ token: Hello } } +// TestTokenEventPayloadFromJSON tests loading TokenEventPayload through the generated JSON helper +func TestTokenEventPayloadFromJSON(t *testing.T) { + jsonData := ` +{ + "token": "Hello" +} +` + + instance, err := prompty.TokenEventPayloadFromJSON(jsonData) + if err != nil { + t.Fatalf("Failed to load TokenEventPayload from JSON helper: %v", err) + } + if instance.Token != "Hello" { + t.Errorf(`Expected Token to be "Hello", got %v`, instance.Token) + } +} + +// TestTokenEventPayloadFromYAML tests loading TokenEventPayload through the generated YAML helper +func TestTokenEventPayloadFromYAML(t *testing.T) { + yamlData := ` +token: Hello + +` + + instance, err := prompty.TokenEventPayloadFromYAML(yamlData) + if err != nil { + t.Fatalf("Failed to load TokenEventPayload from YAML helper: %v", err) + } + if instance.Token != "Hello" { + t.Errorf(`Expected Token to be "Hello", got %v`, instance.Token) + } +} + // TestTokenEventPayloadRoundtrip tests load -> save -> load produces equivalent data func TestTokenEventPayloadRoundtrip(t *testing.T) { jsonData := ` @@ -109,6 +143,14 @@ func TestTokenEventPayloadToJSON(t *testing.T) { if err := json.Unmarshal([]byte(jsonOutput), &parsed); err != nil { t.Fatalf("Failed to parse generated JSON: %v", err) } + + reloaded, err := prompty.LoadTokenEventPayload(parsed, ctx) + if err != nil { + t.Fatalf("Failed to reload generated JSON: %v", err) + } + if reloaded.Token != "Hello" { + t.Errorf(`Expected Token to be "Hello", got %v`, reloaded.Token) + } } // TestTokenEventPayloadToYAML tests that ToYAML produces valid YAML @@ -137,4 +179,19 @@ func TestTokenEventPayloadToYAML(t *testing.T) { if err := yaml.Unmarshal([]byte(yamlOutput), &parsed); err != nil { t.Fatalf("Failed to parse generated YAML: %v", err) } + + reloaded, err := prompty.LoadTokenEventPayload(parsed, ctx) + if err != nil { + t.Fatalf("Failed to reload generated YAML: %v", err) + } + if reloaded.Token != "Hello" { + t.Errorf(`Expected Token to be "Hello", got %v`, reloaded.Token) + } +} + +// TestTokenEventPayloadFromJSONInvalid rejects malformed JSON instead of silently defaulting +func TestTokenEventPayloadFromJSONInvalid(t *testing.T) { + if _, err := prompty.TokenEventPayloadFromJSON("{"); err == nil { + t.Fatalf("Expected malformed JSON to fail") + } } diff --git a/runtime/go/prompty/model/token_usage.go b/runtime/go/prompty/model/token_usage.go index e19bee07..195468ce 100644 --- a/runtime/go/prompty/model/token_usage.go +++ b/runtime/go/prompty/model/token_usage.go @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. // Group: model package prompty @@ -73,7 +74,7 @@ func LoadTokenUsage(data interface{}, ctx *LoadContext) (TokenUsage, error) { } // Save serializes TokenUsage to map[string]interface{} -func (obj *TokenUsage) Save(ctx *SaveContext) map[string]interface{} { +func (obj TokenUsage) Save(ctx *SaveContext) map[string]interface{} { result := make(map[string]interface{}) if obj.PromptTokens != nil { result["promptTokens"] = *obj.PromptTokens @@ -122,11 +123,7 @@ func (obj *TokenUsage) ToJSON() (string, error) { func (obj *TokenUsage) ToYAML() (string, error) { ctx := NewSaveContext() data := obj.Save(ctx) - bytes, err := yaml.Marshal(data) - if err != nil { - return "", err - } - return string(bytes), nil + return marshalYAMLDocument(data) } // FromJSON creates TokenUsage from JSON string diff --git a/runtime/go/prompty/model/token_usage_test.go b/runtime/go/prompty/model/token_usage_test.go index 1e662647..c90347f6 100644 --- a/runtime/go/prompty/model/token_usage_test.go +++ b/runtime/go/prompty/model/token_usage_test.go @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. package prompty_test @@ -70,6 +71,55 @@ totalTokens: 192 } } +// TestTokenUsageFromJSON tests loading TokenUsage through the generated JSON helper +func TestTokenUsageFromJSON(t *testing.T) { + jsonData := ` +{ + "promptTokens": 150, + "completionTokens": 42, + "totalTokens": 192 +} +` + + instance, err := prompty.TokenUsageFromJSON(jsonData) + if err != nil { + t.Fatalf("Failed to load TokenUsage from JSON helper: %v", err) + } + if instance.PromptTokens == nil || *instance.PromptTokens != 150 { + t.Errorf(`Expected PromptTokens to be 150, got %v`, instance.PromptTokens) + } + if instance.CompletionTokens == nil || *instance.CompletionTokens != 42 { + t.Errorf(`Expected CompletionTokens to be 42, got %v`, instance.CompletionTokens) + } + if instance.TotalTokens == nil || *instance.TotalTokens != 192 { + t.Errorf(`Expected TotalTokens to be 192, got %v`, instance.TotalTokens) + } +} + +// TestTokenUsageFromYAML tests loading TokenUsage through the generated YAML helper +func TestTokenUsageFromYAML(t *testing.T) { + yamlData := ` +promptTokens: 150 +completionTokens: 42 +totalTokens: 192 + +` + + instance, err := prompty.TokenUsageFromYAML(yamlData) + if err != nil { + t.Fatalf("Failed to load TokenUsage from YAML helper: %v", err) + } + if instance.PromptTokens == nil || *instance.PromptTokens != 150 { + t.Errorf(`Expected PromptTokens to be 150, got %v`, instance.PromptTokens) + } + if instance.CompletionTokens == nil || *instance.CompletionTokens != 42 { + t.Errorf(`Expected CompletionTokens to be 42, got %v`, instance.CompletionTokens) + } + if instance.TotalTokens == nil || *instance.TotalTokens != 192 { + t.Errorf(`Expected TotalTokens to be 192, got %v`, instance.TotalTokens) + } +} + // TestTokenUsageRoundtrip tests load -> save -> load produces equivalent data func TestTokenUsageRoundtrip(t *testing.T) { jsonData := ` @@ -135,6 +185,20 @@ func TestTokenUsageToJSON(t *testing.T) { if err := json.Unmarshal([]byte(jsonOutput), &parsed); err != nil { t.Fatalf("Failed to parse generated JSON: %v", err) } + + reloaded, err := prompty.LoadTokenUsage(parsed, ctx) + if err != nil { + t.Fatalf("Failed to reload generated JSON: %v", err) + } + if reloaded.PromptTokens == nil || *reloaded.PromptTokens != 150 { + t.Errorf(`Expected PromptTokens to be 150, got %v`, reloaded.PromptTokens) + } + if reloaded.CompletionTokens == nil || *reloaded.CompletionTokens != 42 { + t.Errorf(`Expected CompletionTokens to be 42, got %v`, reloaded.CompletionTokens) + } + if reloaded.TotalTokens == nil || *reloaded.TotalTokens != 192 { + t.Errorf(`Expected TotalTokens to be 192, got %v`, reloaded.TotalTokens) + } } // TestTokenUsageToYAML tests that ToYAML produces valid YAML @@ -165,4 +229,80 @@ func TestTokenUsageToYAML(t *testing.T) { if err := yaml.Unmarshal([]byte(yamlOutput), &parsed); err != nil { t.Fatalf("Failed to parse generated YAML: %v", err) } + + reloaded, err := prompty.LoadTokenUsage(parsed, ctx) + if err != nil { + t.Fatalf("Failed to reload generated YAML: %v", err) + } + if reloaded.PromptTokens == nil || *reloaded.PromptTokens != 150 { + t.Errorf(`Expected PromptTokens to be 150, got %v`, reloaded.PromptTokens) + } + if reloaded.CompletionTokens == nil || *reloaded.CompletionTokens != 42 { + t.Errorf(`Expected CompletionTokens to be 42, got %v`, reloaded.CompletionTokens) + } + if reloaded.TotalTokens == nil || *reloaded.TotalTokens != 192 { + t.Errorf(`Expected TotalTokens to be 192, got %v`, reloaded.TotalTokens) + } +} + +// TestTokenUsageFromJSONInvalid rejects malformed JSON instead of silently defaulting +func TestTokenUsageFromJSONInvalid(t *testing.T) { + if _, err := prompty.TokenUsageFromJSON("{"); err == nil { + t.Fatalf("Expected malformed JSON to fail") + } +} + +// TestTokenUsageToWire tests provider-specific wire field names +func TestTokenUsageToWire(t *testing.T) { + jsonData := ` +{ + "promptTokens": 150, + "completionTokens": 42, + "totalTokens": 192 +} +` + var data map[string]interface{} + if err := json.Unmarshal([]byte(jsonData), &data); err != nil { + t.Fatalf("Failed to parse JSON: %v", err) + } + + ctx := prompty.NewLoadContext() + instance, err := prompty.LoadTokenUsage(data, ctx) + if err != nil { + t.Fatalf("Failed to load TokenUsage: %v", err) + } + + openaiWire := instance.ToWire("openai") + if _, ok := openaiWire["prompt_tokens"]; !ok { + t.Errorf("Expected openai wire output to include prompt_tokens") + } + if _, ok := openaiWire["promptTokens"]; ok { + t.Errorf("Expected openai wire output to omit source field promptTokens") + } + if _, ok := openaiWire["completion_tokens"]; !ok { + t.Errorf("Expected openai wire output to include completion_tokens") + } + if _, ok := openaiWire["completionTokens"]; ok { + t.Errorf("Expected openai wire output to omit source field completionTokens") + } + if _, ok := openaiWire["total_tokens"]; !ok { + t.Errorf("Expected openai wire output to include total_tokens") + } + if _, ok := openaiWire["totalTokens"]; ok { + t.Errorf("Expected openai wire output to omit source field totalTokens") + } + + anthropicWire := instance.ToWire("anthropic") + if _, ok := anthropicWire["input_tokens"]; !ok { + t.Errorf("Expected anthropic wire output to include input_tokens") + } + if _, ok := anthropicWire["promptTokens"]; ok { + t.Errorf("Expected anthropic wire output to omit source field promptTokens") + } + if _, ok := anthropicWire["output_tokens"]; !ok { + t.Errorf("Expected anthropic wire output to include output_tokens") + } + if _, ok := anthropicWire["completionTokens"]; ok { + t.Errorf("Expected anthropic wire output to omit source field completionTokens") + } } diff --git a/runtime/go/prompty/model/tool.go b/runtime/go/prompty/model/tool.go index 9124b57c..af834d26 100644 --- a/runtime/go/prompty/model/tool.go +++ b/runtime/go/prompty/model/tool.go @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. // Group: tools package prompty @@ -69,7 +70,7 @@ func LoadTool(data interface{}, ctx *LoadContext) (interface{}, error) { } // Save serializes Tool to map[string]interface{} -func (obj *Tool) Save(ctx *SaveContext) map[string]interface{} { +func (obj Tool) Save(ctx *SaveContext) map[string]interface{} { result := make(map[string]interface{}) result["name"] = obj.Name result["kind"] = obj.Kind @@ -102,11 +103,7 @@ func (obj *Tool) ToJSON() (string, error) { func (obj *Tool) ToYAML() (string, error) { ctx := NewSaveContext() data := obj.Save(ctx) - bytes, err := yaml.Marshal(data) - if err != nil { - return "", err - } - return string(bytes), nil + return marshalYAMLDocument(data) } // FromJSON creates Tool from JSON string @@ -170,7 +167,7 @@ func LoadFunctionTool(data interface{}, ctx *LoadContext) (FunctionTool, error) } // Save serializes FunctionTool to map[string]interface{} -func (obj *FunctionTool) Save(ctx *SaveContext) map[string]interface{} { +func (obj FunctionTool) Save(ctx *SaveContext) map[string]interface{} { result := make(map[string]interface{}) result["kind"] = obj.Kind if obj.Parameters != nil { @@ -210,11 +207,7 @@ func (obj *FunctionTool) ToJSON() (string, error) { func (obj *FunctionTool) ToYAML() (string, error) { ctx := NewSaveContext() data := obj.Save(ctx) - bytes, err := yaml.Marshal(data) - if err != nil { - return "", err - } - return string(bytes), nil + return marshalYAMLDocument(data) } // FromJSON creates FunctionTool from JSON string @@ -276,7 +269,7 @@ func LoadCustomTool(data interface{}, ctx *LoadContext) (CustomTool, error) { } // Save serializes CustomTool to map[string]interface{} -func (obj *CustomTool) Save(ctx *SaveContext) map[string]interface{} { +func (obj CustomTool) Save(ctx *SaveContext) map[string]interface{} { result := make(map[string]interface{}) result["kind"] = obj.Kind @@ -309,11 +302,7 @@ func (obj *CustomTool) ToJSON() (string, error) { func (obj *CustomTool) ToYAML() (string, error) { ctx := NewSaveContext() data := obj.Save(ctx) - bytes, err := yaml.Marshal(data) - if err != nil { - return "", err - } - return string(bytes), nil + return marshalYAMLDocument(data) } // FromJSON creates CustomTool from JSON string @@ -374,14 +363,20 @@ func LoadMcpTool(data interface{}, ctx *LoadContext) (McpTool, error) { if m, ok := val.(map[string]interface{}); ok { loaded, _ := LoadMcpApprovalMode(m, ctx) result.ApprovalMode = loaded + } else { + loaded, _ := LoadMcpApprovalMode(val, ctx) + result.ApprovalMode = loaded } } if val, ok := m["allowedTools"]; ok && val != nil { - if arr, ok := val.([]interface{}); ok { + switch arr := val.(type) { + case []interface{}: result.AllowedTools = make([]string, len(arr)) for i, v := range arr { result.AllowedTools[i] = v.(string) } + case []string: + result.AllowedTools = arr } } } @@ -390,7 +385,7 @@ func LoadMcpTool(data interface{}, ctx *LoadContext) (McpTool, error) { } // Save serializes McpTool to map[string]interface{} -func (obj *McpTool) Save(ctx *SaveContext) map[string]interface{} { +func (obj McpTool) Save(ctx *SaveContext) map[string]interface{} { result := make(map[string]interface{}) result["kind"] = obj.Kind @@ -429,11 +424,7 @@ func (obj *McpTool) ToJSON() (string, error) { func (obj *McpTool) ToYAML() (string, error) { ctx := NewSaveContext() data := obj.Save(ctx) - bytes, err := yaml.Marshal(data) - if err != nil { - return "", err - } - return string(bytes), nil + return marshalYAMLDocument(data) } // FromJSON creates McpTool from JSON string @@ -488,7 +479,7 @@ func LoadOpenApiTool(data interface{}, ctx *LoadContext) (OpenApiTool, error) { } // Save serializes OpenApiTool to map[string]interface{} -func (obj *OpenApiTool) Save(ctx *SaveContext) map[string]interface{} { +func (obj OpenApiTool) Save(ctx *SaveContext) map[string]interface{} { result := make(map[string]interface{}) result["kind"] = obj.Kind @@ -521,11 +512,7 @@ func (obj *OpenApiTool) ToJSON() (string, error) { func (obj *OpenApiTool) ToYAML() (string, error) { ctx := NewSaveContext() data := obj.Save(ctx) - bytes, err := yaml.Marshal(data) - if err != nil { - return "", err - } - return string(bytes), nil + return marshalYAMLDocument(data) } // FromJSON creates OpenApiTool from JSON string @@ -580,7 +567,7 @@ func LoadPromptyTool(data interface{}, ctx *LoadContext) (PromptyTool, error) { } // Save serializes PromptyTool to map[string]interface{} -func (obj *PromptyTool) Save(ctx *SaveContext) map[string]interface{} { +func (obj PromptyTool) Save(ctx *SaveContext) map[string]interface{} { result := make(map[string]interface{}) result["kind"] = obj.Kind result["path"] = obj.Path @@ -604,11 +591,7 @@ func (obj *PromptyTool) ToJSON() (string, error) { func (obj *PromptyTool) ToYAML() (string, error) { ctx := NewSaveContext() data := obj.Save(ctx) - bytes, err := yaml.Marshal(data) - if err != nil { - return "", err - } - return string(bytes), nil + return marshalYAMLDocument(data) } // FromJSON creates PromptyTool from JSON string diff --git a/runtime/go/prompty/model/tool_call.go b/runtime/go/prompty/model/tool_call.go index 7584ed99..e9100ae1 100644 --- a/runtime/go/prompty/model/tool_call.go +++ b/runtime/go/prompty/model/tool_call.go @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. // Group: conversation package prompty @@ -39,7 +40,7 @@ func LoadToolCall(data interface{}, ctx *LoadContext) (ToolCall, error) { } // Save serializes ToolCall to map[string]interface{} -func (obj *ToolCall) Save(ctx *SaveContext) map[string]interface{} { +func (obj ToolCall) Save(ctx *SaveContext) map[string]interface{} { result := make(map[string]interface{}) result["id"] = obj.Id result["name"] = obj.Name @@ -63,11 +64,7 @@ func (obj *ToolCall) ToJSON() (string, error) { func (obj *ToolCall) ToYAML() (string, error) { ctx := NewSaveContext() data := obj.Save(ctx) - bytes, err := yaml.Marshal(data) - if err != nil { - return "", err - } - return string(bytes), nil + return marshalYAMLDocument(data) } // FromJSON creates ToolCall from JSON string diff --git a/runtime/go/prompty/model/tool_call_complete_payload.go b/runtime/go/prompty/model/tool_call_complete_payload.go new file mode 100644 index 00000000..1669573e --- /dev/null +++ b/runtime/go/prompty/model/tool_call_complete_payload.go @@ -0,0 +1,128 @@ +// +// Code generated by Typra emitter; DO NOT EDIT. +// Group: events + +package prompty + +import ( + "encoding/json" + + "gopkg.in/yaml.v3" +) + +// ToolCallCompletePayload represents Payload for "tool_call_complete" events — a tool dispatch finished. + +type ToolCallCompletePayload struct { + Id *string `json:"id,omitempty" yaml:"id,omitempty"` + Name string `json:"name" yaml:"name"` + Success bool `json:"success" yaml:"success"` + Result *ToolResult `json:"result,omitempty" yaml:"result,omitempty"` + DurationMs *float64 `json:"durationMs,omitempty" yaml:"durationMs,omitempty"` + ErrorKind *string `json:"errorKind,omitempty" yaml:"errorKind,omitempty"` +} + +// LoadToolCallCompletePayload creates a ToolCallCompletePayload from a map[string]interface{} +func LoadToolCallCompletePayload(data interface{}, ctx *LoadContext) (ToolCallCompletePayload, error) { + result := ToolCallCompletePayload{} + + // Load from map + if m, ok := data.(map[string]interface{}); ok { + if val, ok := m["id"]; ok && val != nil { + v := string(val.(string)) + result.Id = &v + } + if val, ok := m["name"]; ok && val != nil { + result.Name = string(val.(string)) + } + if val, ok := m["success"]; ok && val != nil { + result.Success = val.(bool) + } + if val, ok := m["result"]; ok && val != nil { + if m, ok := val.(map[string]interface{}); ok { + loaded, _ := LoadToolResult(m, ctx) + result.Result = &loaded + } + } + if val, ok := m["durationMs"]; ok && val != nil { // Handle various numeric types from JSON/YAML/roundtrip + var v float64 + switch n := val.(type) { + case int: + v = float64(n) + case int32: + v = float64(n) + case int64: + v = float64(n) + case float32: + v = float64(n) + case float64: + v = n + } + result.DurationMs = &v + } + if val, ok := m["errorKind"]; ok && val != nil { + v := string(val.(string)) + result.ErrorKind = &v + } + } + + return result, nil +} + +// Save serializes ToolCallCompletePayload to map[string]interface{} +func (obj ToolCallCompletePayload) Save(ctx *SaveContext) map[string]interface{} { + result := make(map[string]interface{}) + if obj.Id != nil { + result["id"] = *obj.Id + } + result["name"] = obj.Name + result["success"] = obj.Success + if obj.Result != nil { + result["result"] = obj.Result.Save(ctx) + } + if obj.DurationMs != nil { + result["durationMs"] = *obj.DurationMs + } + if obj.ErrorKind != nil { + result["errorKind"] = *obj.ErrorKind + } + + return result +} + +// ToJSON serializes ToolCallCompletePayload to JSON string +func (obj *ToolCallCompletePayload) ToJSON() (string, error) { + ctx := NewSaveContext() + data := obj.Save(ctx) + bytes, err := json.Marshal(data) + if err != nil { + return "", err + } + return string(bytes), nil +} + +// ToYAML serializes ToolCallCompletePayload to YAML string +func (obj *ToolCallCompletePayload) ToYAML() (string, error) { + ctx := NewSaveContext() + data := obj.Save(ctx) + return marshalYAMLDocument(data) +} + +// FromJSON creates ToolCallCompletePayload from JSON string +func ToolCallCompletePayloadFromJSON(jsonStr string) (ToolCallCompletePayload, error) { + var data map[string]interface{} + if err := json.Unmarshal([]byte(jsonStr), &data); err != nil { + return ToolCallCompletePayload{}, err + } + ctx := NewLoadContext() + return LoadToolCallCompletePayload(data, ctx) +} + +// FromYAML creates ToolCallCompletePayload from YAML string +func ToolCallCompletePayloadFromYAML(yamlStr string) (ToolCallCompletePayload, error) { + var data map[string]interface{} + if err := yaml.Unmarshal([]byte(yamlStr), &data); err != nil { + return ToolCallCompletePayload{}, err + } + ctx := NewLoadContext() + return LoadToolCallCompletePayload(data, ctx) +} diff --git a/runtime/go/prompty/model/tool_call_complete_payload_test.go b/runtime/go/prompty/model/tool_call_complete_payload_test.go new file mode 100644 index 00000000..9c481a2e --- /dev/null +++ b/runtime/go/prompty/model/tool_call_complete_payload_test.go @@ -0,0 +1,309 @@ +// +// Code generated by Typra emitter; DO NOT EDIT. + +package prompty_test + +import ( + "encoding/json" + "testing" + + "gopkg.in/yaml.v3" + + "prompty/model" +) + +// TestToolCallCompletePayloadLoadJSON tests loading ToolCallCompletePayload from JSON +func TestToolCallCompletePayloadLoadJSON(t *testing.T) { + jsonData := ` +{ + "id": "call_abc123", + "name": "get_weather", + "success": true, + "durationMs": 42, + "errorKind": "timeout" +} +` + var data map[string]interface{} + if err := json.Unmarshal([]byte(jsonData), &data); err != nil { + t.Fatalf("Failed to parse JSON: %v", err) + } + + ctx := prompty.NewLoadContext() + instance, err := prompty.LoadToolCallCompletePayload(data, ctx) + if err != nil { + t.Fatalf("Failed to load ToolCallCompletePayload: %v", err) + } + if instance.Id == nil || *instance.Id != "call_abc123" { + t.Errorf(`Expected Id to be "call_abc123", got %v`, instance.Id) + } + if instance.Name != "get_weather" { + t.Errorf(`Expected Name to be "get_weather", got %v`, instance.Name) + } + if instance.Success != true { + t.Errorf(`Expected Success to be true, got %v`, instance.Success) + } + if instance.DurationMs == nil || *instance.DurationMs != 42 { + t.Errorf(`Expected DurationMs to be 42, got %v`, instance.DurationMs) + } + if instance.ErrorKind == nil || *instance.ErrorKind != "timeout" { + t.Errorf(`Expected ErrorKind to be "timeout", got %v`, instance.ErrorKind) + } +} + +// TestToolCallCompletePayloadLoadYAML tests loading ToolCallCompletePayload from YAML +func TestToolCallCompletePayloadLoadYAML(t *testing.T) { + yamlData := ` +id: call_abc123 +name: get_weather +success: true +durationMs: 42 +errorKind: timeout + +` + var data map[string]interface{} + if err := yaml.Unmarshal([]byte(yamlData), &data); err != nil { + t.Fatalf("Failed to parse YAML: %v", err) + } + + ctx := prompty.NewLoadContext() + instance, err := prompty.LoadToolCallCompletePayload(data, ctx) + if err != nil { + t.Fatalf("Failed to load ToolCallCompletePayload: %v", err) + } + if instance.Id == nil || *instance.Id != "call_abc123" { + t.Errorf(`Expected Id to be "call_abc123", got %v`, instance.Id) + } + if instance.Name != "get_weather" { + t.Errorf(`Expected Name to be "get_weather", got %v`, instance.Name) + } + if instance.Success != true { + t.Errorf(`Expected Success to be true, got %v`, instance.Success) + } + if instance.DurationMs == nil || *instance.DurationMs != 42 { + t.Errorf(`Expected DurationMs to be 42, got %v`, instance.DurationMs) + } + if instance.ErrorKind == nil || *instance.ErrorKind != "timeout" { + t.Errorf(`Expected ErrorKind to be "timeout", got %v`, instance.ErrorKind) + } +} + +// TestToolCallCompletePayloadFromJSON tests loading ToolCallCompletePayload through the generated JSON helper +func TestToolCallCompletePayloadFromJSON(t *testing.T) { + jsonData := ` +{ + "id": "call_abc123", + "name": "get_weather", + "success": true, + "durationMs": 42, + "errorKind": "timeout" +} +` + + instance, err := prompty.ToolCallCompletePayloadFromJSON(jsonData) + if err != nil { + t.Fatalf("Failed to load ToolCallCompletePayload from JSON helper: %v", err) + } + if instance.Id == nil || *instance.Id != "call_abc123" { + t.Errorf(`Expected Id to be "call_abc123", got %v`, instance.Id) + } + if instance.Name != "get_weather" { + t.Errorf(`Expected Name to be "get_weather", got %v`, instance.Name) + } + if instance.Success != true { + t.Errorf(`Expected Success to be true, got %v`, instance.Success) + } + if instance.DurationMs == nil || *instance.DurationMs != 42 { + t.Errorf(`Expected DurationMs to be 42, got %v`, instance.DurationMs) + } + if instance.ErrorKind == nil || *instance.ErrorKind != "timeout" { + t.Errorf(`Expected ErrorKind to be "timeout", got %v`, instance.ErrorKind) + } +} + +// TestToolCallCompletePayloadFromYAML tests loading ToolCallCompletePayload through the generated YAML helper +func TestToolCallCompletePayloadFromYAML(t *testing.T) { + yamlData := ` +id: call_abc123 +name: get_weather +success: true +durationMs: 42 +errorKind: timeout + +` + + instance, err := prompty.ToolCallCompletePayloadFromYAML(yamlData) + if err != nil { + t.Fatalf("Failed to load ToolCallCompletePayload from YAML helper: %v", err) + } + if instance.Id == nil || *instance.Id != "call_abc123" { + t.Errorf(`Expected Id to be "call_abc123", got %v`, instance.Id) + } + if instance.Name != "get_weather" { + t.Errorf(`Expected Name to be "get_weather", got %v`, instance.Name) + } + if instance.Success != true { + t.Errorf(`Expected Success to be true, got %v`, instance.Success) + } + if instance.DurationMs == nil || *instance.DurationMs != 42 { + t.Errorf(`Expected DurationMs to be 42, got %v`, instance.DurationMs) + } + if instance.ErrorKind == nil || *instance.ErrorKind != "timeout" { + t.Errorf(`Expected ErrorKind to be "timeout", got %v`, instance.ErrorKind) + } +} + +// TestToolCallCompletePayloadRoundtrip tests load -> save -> load produces equivalent data +func TestToolCallCompletePayloadRoundtrip(t *testing.T) { + jsonData := ` +{ + "id": "call_abc123", + "name": "get_weather", + "success": true, + "durationMs": 42, + "errorKind": "timeout" +} +` + var data map[string]interface{} + if err := json.Unmarshal([]byte(jsonData), &data); err != nil { + t.Fatalf("Failed to parse JSON: %v", err) + } + + loadCtx := prompty.NewLoadContext() + instance, err := prompty.LoadToolCallCompletePayload(data, loadCtx) + if err != nil { + t.Fatalf("Failed to load ToolCallCompletePayload: %v", err) + } + saveCtx := prompty.NewSaveContext() + savedData := instance.Save(saveCtx) + + reloaded, err := prompty.LoadToolCallCompletePayload(savedData, loadCtx) + if err != nil { + t.Fatalf("Failed to reload ToolCallCompletePayload: %v", err) + } + if reloaded.Id == nil || *reloaded.Id != "call_abc123" { + t.Errorf(`Expected Id to be "call_abc123", got %v`, reloaded.Id) + } + if reloaded.Name != "get_weather" { + t.Errorf(`Expected Name to be "get_weather", got %v`, reloaded.Name) + } + if reloaded.Success != true { + t.Errorf(`Expected Success to be true, got %v`, reloaded.Success) + } + if reloaded.DurationMs == nil || *reloaded.DurationMs != 42 { + t.Errorf(`Expected DurationMs to be 42, got %v`, reloaded.DurationMs) + } + if reloaded.ErrorKind == nil || *reloaded.ErrorKind != "timeout" { + t.Errorf(`Expected ErrorKind to be "timeout", got %v`, reloaded.ErrorKind) + } +} + +// TestToolCallCompletePayloadToJSON tests that ToJSON produces valid JSON +func TestToolCallCompletePayloadToJSON(t *testing.T) { + jsonData := ` +{ + "id": "call_abc123", + "name": "get_weather", + "success": true, + "durationMs": 42, + "errorKind": "timeout" +} +` + var data map[string]interface{} + if err := json.Unmarshal([]byte(jsonData), &data); err != nil { + t.Fatalf("Failed to parse JSON: %v", err) + } + + ctx := prompty.NewLoadContext() + instance, err := prompty.LoadToolCallCompletePayload(data, ctx) + if err != nil { + t.Fatalf("Failed to load ToolCallCompletePayload: %v", err) + } + jsonOutput, err := instance.ToJSON() + if err != nil { + t.Fatalf("Failed to convert to JSON: %v", err) + } + + var parsed map[string]interface{} + if err := json.Unmarshal([]byte(jsonOutput), &parsed); err != nil { + t.Fatalf("Failed to parse generated JSON: %v", err) + } + + reloaded, err := prompty.LoadToolCallCompletePayload(parsed, ctx) + if err != nil { + t.Fatalf("Failed to reload generated JSON: %v", err) + } + if reloaded.Id == nil || *reloaded.Id != "call_abc123" { + t.Errorf(`Expected Id to be "call_abc123", got %v`, reloaded.Id) + } + if reloaded.Name != "get_weather" { + t.Errorf(`Expected Name to be "get_weather", got %v`, reloaded.Name) + } + if reloaded.Success != true { + t.Errorf(`Expected Success to be true, got %v`, reloaded.Success) + } + if reloaded.DurationMs == nil || *reloaded.DurationMs != 42 { + t.Errorf(`Expected DurationMs to be 42, got %v`, reloaded.DurationMs) + } + if reloaded.ErrorKind == nil || *reloaded.ErrorKind != "timeout" { + t.Errorf(`Expected ErrorKind to be "timeout", got %v`, reloaded.ErrorKind) + } +} + +// TestToolCallCompletePayloadToYAML tests that ToYAML produces valid YAML +func TestToolCallCompletePayloadToYAML(t *testing.T) { + jsonData := ` +{ + "id": "call_abc123", + "name": "get_weather", + "success": true, + "durationMs": 42, + "errorKind": "timeout" +} +` + var data map[string]interface{} + if err := json.Unmarshal([]byte(jsonData), &data); err != nil { + t.Fatalf("Failed to parse JSON: %v", err) + } + + ctx := prompty.NewLoadContext() + instance, err := prompty.LoadToolCallCompletePayload(data, ctx) + if err != nil { + t.Fatalf("Failed to load ToolCallCompletePayload: %v", err) + } + yamlOutput, err := instance.ToYAML() + if err != nil { + t.Fatalf("Failed to convert to YAML: %v", err) + } + + var parsed map[string]interface{} + if err := yaml.Unmarshal([]byte(yamlOutput), &parsed); err != nil { + t.Fatalf("Failed to parse generated YAML: %v", err) + } + + reloaded, err := prompty.LoadToolCallCompletePayload(parsed, ctx) + if err != nil { + t.Fatalf("Failed to reload generated YAML: %v", err) + } + if reloaded.Id == nil || *reloaded.Id != "call_abc123" { + t.Errorf(`Expected Id to be "call_abc123", got %v`, reloaded.Id) + } + if reloaded.Name != "get_weather" { + t.Errorf(`Expected Name to be "get_weather", got %v`, reloaded.Name) + } + if reloaded.Success != true { + t.Errorf(`Expected Success to be true, got %v`, reloaded.Success) + } + if reloaded.DurationMs == nil || *reloaded.DurationMs != 42 { + t.Errorf(`Expected DurationMs to be 42, got %v`, reloaded.DurationMs) + } + if reloaded.ErrorKind == nil || *reloaded.ErrorKind != "timeout" { + t.Errorf(`Expected ErrorKind to be "timeout", got %v`, reloaded.ErrorKind) + } +} + +// TestToolCallCompletePayloadFromJSONInvalid rejects malformed JSON instead of silently defaulting +func TestToolCallCompletePayloadFromJSONInvalid(t *testing.T) { + if _, err := prompty.ToolCallCompletePayloadFromJSON("{"); err == nil { + t.Fatalf("Expected malformed JSON to fail") + } +} diff --git a/runtime/go/prompty/model/tool_call_start_payload.go b/runtime/go/prompty/model/tool_call_start_payload.go index 378d9516..87e23f1f 100644 --- a/runtime/go/prompty/model/tool_call_start_payload.go +++ b/runtime/go/prompty/model/tool_call_start_payload.go @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. // Group: events package prompty @@ -12,8 +13,9 @@ import ( // ToolCallStartPayload represents Payload for "tool_call_start" events — the LLM has requested a tool call. type ToolCallStartPayload struct { - Name string `json:"name" yaml:"name"` - Arguments string `json:"arguments" yaml:"arguments"` + Id *string `json:"id,omitempty" yaml:"id,omitempty"` + Name string `json:"name" yaml:"name"` + Arguments string `json:"arguments" yaml:"arguments"` } // LoadToolCallStartPayload creates a ToolCallStartPayload from a map[string]interface{} @@ -22,6 +24,10 @@ func LoadToolCallStartPayload(data interface{}, ctx *LoadContext) (ToolCallStart // Load from map if m, ok := data.(map[string]interface{}); ok { + if val, ok := m["id"]; ok && val != nil { + v := string(val.(string)) + result.Id = &v + } if val, ok := m["name"]; ok && val != nil { result.Name = string(val.(string)) } @@ -34,8 +40,11 @@ func LoadToolCallStartPayload(data interface{}, ctx *LoadContext) (ToolCallStart } // Save serializes ToolCallStartPayload to map[string]interface{} -func (obj *ToolCallStartPayload) Save(ctx *SaveContext) map[string]interface{} { +func (obj ToolCallStartPayload) Save(ctx *SaveContext) map[string]interface{} { result := make(map[string]interface{}) + if obj.Id != nil { + result["id"] = *obj.Id + } result["name"] = obj.Name result["arguments"] = obj.Arguments @@ -57,11 +66,7 @@ func (obj *ToolCallStartPayload) ToJSON() (string, error) { func (obj *ToolCallStartPayload) ToYAML() (string, error) { ctx := NewSaveContext() data := obj.Save(ctx) - bytes, err := yaml.Marshal(data) - if err != nil { - return "", err - } - return string(bytes), nil + return marshalYAMLDocument(data) } // FromJSON creates ToolCallStartPayload from JSON string diff --git a/runtime/go/prompty/model/tool_call_start_payload_test.go b/runtime/go/prompty/model/tool_call_start_payload_test.go index 3e70a811..70ae6cef 100644 --- a/runtime/go/prompty/model/tool_call_start_payload_test.go +++ b/runtime/go/prompty/model/tool_call_start_payload_test.go @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. package prompty_test @@ -15,6 +16,7 @@ import ( func TestToolCallStartPayloadLoadJSON(t *testing.T) { jsonData := ` { + "id": "call_abc123", "name": "get_weather", "arguments": "{\"city\": \"Paris\"}" } @@ -29,6 +31,9 @@ func TestToolCallStartPayloadLoadJSON(t *testing.T) { if err != nil { t.Fatalf("Failed to load ToolCallStartPayload: %v", err) } + if instance.Id == nil || *instance.Id != "call_abc123" { + t.Errorf(`Expected Id to be "call_abc123", got %v`, instance.Id) + } if instance.Name != "get_weather" { t.Errorf(`Expected Name to be "get_weather", got %v`, instance.Name) } @@ -40,6 +45,7 @@ func TestToolCallStartPayloadLoadJSON(t *testing.T) { // TestToolCallStartPayloadLoadYAML tests loading ToolCallStartPayload from YAML func TestToolCallStartPayloadLoadYAML(t *testing.T) { yamlData := ` +id: call_abc123 name: get_weather arguments: "{\"city\": \"Paris\"}" @@ -54,6 +60,58 @@ arguments: "{\"city\": \"Paris\"}" if err != nil { t.Fatalf("Failed to load ToolCallStartPayload: %v", err) } + if instance.Id == nil || *instance.Id != "call_abc123" { + t.Errorf(`Expected Id to be "call_abc123", got %v`, instance.Id) + } + if instance.Name != "get_weather" { + t.Errorf(`Expected Name to be "get_weather", got %v`, instance.Name) + } + if instance.Arguments != "{\"city\": \"Paris\"}" { + t.Errorf(`Expected Arguments to be "{\"city\": \"Paris\"}", got %v`, instance.Arguments) + } +} + +// TestToolCallStartPayloadFromJSON tests loading ToolCallStartPayload through the generated JSON helper +func TestToolCallStartPayloadFromJSON(t *testing.T) { + jsonData := ` +{ + "id": "call_abc123", + "name": "get_weather", + "arguments": "{\"city\": \"Paris\"}" +} +` + + instance, err := prompty.ToolCallStartPayloadFromJSON(jsonData) + if err != nil { + t.Fatalf("Failed to load ToolCallStartPayload from JSON helper: %v", err) + } + if instance.Id == nil || *instance.Id != "call_abc123" { + t.Errorf(`Expected Id to be "call_abc123", got %v`, instance.Id) + } + if instance.Name != "get_weather" { + t.Errorf(`Expected Name to be "get_weather", got %v`, instance.Name) + } + if instance.Arguments != "{\"city\": \"Paris\"}" { + t.Errorf(`Expected Arguments to be "{\"city\": \"Paris\"}", got %v`, instance.Arguments) + } +} + +// TestToolCallStartPayloadFromYAML tests loading ToolCallStartPayload through the generated YAML helper +func TestToolCallStartPayloadFromYAML(t *testing.T) { + yamlData := ` +id: call_abc123 +name: get_weather +arguments: "{\"city\": \"Paris\"}" + +` + + instance, err := prompty.ToolCallStartPayloadFromYAML(yamlData) + if err != nil { + t.Fatalf("Failed to load ToolCallStartPayload from YAML helper: %v", err) + } + if instance.Id == nil || *instance.Id != "call_abc123" { + t.Errorf(`Expected Id to be "call_abc123", got %v`, instance.Id) + } if instance.Name != "get_weather" { t.Errorf(`Expected Name to be "get_weather", got %v`, instance.Name) } @@ -66,6 +124,7 @@ arguments: "{\"city\": \"Paris\"}" func TestToolCallStartPayloadRoundtrip(t *testing.T) { jsonData := ` { + "id": "call_abc123", "name": "get_weather", "arguments": "{\"city\": \"Paris\"}" } @@ -87,6 +146,9 @@ func TestToolCallStartPayloadRoundtrip(t *testing.T) { if err != nil { t.Fatalf("Failed to reload ToolCallStartPayload: %v", err) } + if reloaded.Id == nil || *reloaded.Id != "call_abc123" { + t.Errorf(`Expected Id to be "call_abc123", got %v`, reloaded.Id) + } if reloaded.Name != "get_weather" { t.Errorf(`Expected Name to be "get_weather", got %v`, reloaded.Name) } @@ -99,6 +161,7 @@ func TestToolCallStartPayloadRoundtrip(t *testing.T) { func TestToolCallStartPayloadToJSON(t *testing.T) { jsonData := ` { + "id": "call_abc123", "name": "get_weather", "arguments": "{\"city\": \"Paris\"}" } @@ -122,12 +185,27 @@ func TestToolCallStartPayloadToJSON(t *testing.T) { if err := json.Unmarshal([]byte(jsonOutput), &parsed); err != nil { t.Fatalf("Failed to parse generated JSON: %v", err) } + + reloaded, err := prompty.LoadToolCallStartPayload(parsed, ctx) + if err != nil { + t.Fatalf("Failed to reload generated JSON: %v", err) + } + if reloaded.Id == nil || *reloaded.Id != "call_abc123" { + t.Errorf(`Expected Id to be "call_abc123", got %v`, reloaded.Id) + } + if reloaded.Name != "get_weather" { + t.Errorf(`Expected Name to be "get_weather", got %v`, reloaded.Name) + } + if reloaded.Arguments != "{\"city\": \"Paris\"}" { + t.Errorf(`Expected Arguments to be "{\"city\": \"Paris\"}", got %v`, reloaded.Arguments) + } } // TestToolCallStartPayloadToYAML tests that ToYAML produces valid YAML func TestToolCallStartPayloadToYAML(t *testing.T) { jsonData := ` { + "id": "call_abc123", "name": "get_weather", "arguments": "{\"city\": \"Paris\"}" } @@ -151,4 +229,25 @@ func TestToolCallStartPayloadToYAML(t *testing.T) { if err := yaml.Unmarshal([]byte(yamlOutput), &parsed); err != nil { t.Fatalf("Failed to parse generated YAML: %v", err) } + + reloaded, err := prompty.LoadToolCallStartPayload(parsed, ctx) + if err != nil { + t.Fatalf("Failed to reload generated YAML: %v", err) + } + if reloaded.Id == nil || *reloaded.Id != "call_abc123" { + t.Errorf(`Expected Id to be "call_abc123", got %v`, reloaded.Id) + } + if reloaded.Name != "get_weather" { + t.Errorf(`Expected Name to be "get_weather", got %v`, reloaded.Name) + } + if reloaded.Arguments != "{\"city\": \"Paris\"}" { + t.Errorf(`Expected Arguments to be "{\"city\": \"Paris\"}", got %v`, reloaded.Arguments) + } +} + +// TestToolCallStartPayloadFromJSONInvalid rejects malformed JSON instead of silently defaulting +func TestToolCallStartPayloadFromJSONInvalid(t *testing.T) { + if _, err := prompty.ToolCallStartPayloadFromJSON("{"); err == nil { + t.Fatalf("Expected malformed JSON to fail") + } } diff --git a/runtime/go/prompty/model/tool_call_test.go b/runtime/go/prompty/model/tool_call_test.go index d52a1740..691a2a89 100644 --- a/runtime/go/prompty/model/tool_call_test.go +++ b/runtime/go/prompty/model/tool_call_test.go @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. package prompty_test @@ -70,6 +71,55 @@ arguments: "{\"city\": \"Paris\"}" } } +// TestToolCallFromJSON tests loading ToolCall through the generated JSON helper +func TestToolCallFromJSON(t *testing.T) { + jsonData := ` +{ + "id": "call_abc123", + "name": "get_weather", + "arguments": "{\"city\": \"Paris\"}" +} +` + + instance, err := prompty.ToolCallFromJSON(jsonData) + if err != nil { + t.Fatalf("Failed to load ToolCall from JSON helper: %v", err) + } + if instance.Id != "call_abc123" { + t.Errorf(`Expected Id to be "call_abc123", got %v`, instance.Id) + } + if instance.Name != "get_weather" { + t.Errorf(`Expected Name to be "get_weather", got %v`, instance.Name) + } + if instance.Arguments != "{\"city\": \"Paris\"}" { + t.Errorf(`Expected Arguments to be "{\"city\": \"Paris\"}", got %v`, instance.Arguments) + } +} + +// TestToolCallFromYAML tests loading ToolCall through the generated YAML helper +func TestToolCallFromYAML(t *testing.T) { + yamlData := ` +id: call_abc123 +name: get_weather +arguments: "{\"city\": \"Paris\"}" + +` + + instance, err := prompty.ToolCallFromYAML(yamlData) + if err != nil { + t.Fatalf("Failed to load ToolCall from YAML helper: %v", err) + } + if instance.Id != "call_abc123" { + t.Errorf(`Expected Id to be "call_abc123", got %v`, instance.Id) + } + if instance.Name != "get_weather" { + t.Errorf(`Expected Name to be "get_weather", got %v`, instance.Name) + } + if instance.Arguments != "{\"city\": \"Paris\"}" { + t.Errorf(`Expected Arguments to be "{\"city\": \"Paris\"}", got %v`, instance.Arguments) + } +} + // TestToolCallRoundtrip tests load -> save -> load produces equivalent data func TestToolCallRoundtrip(t *testing.T) { jsonData := ` @@ -135,6 +185,20 @@ func TestToolCallToJSON(t *testing.T) { if err := json.Unmarshal([]byte(jsonOutput), &parsed); err != nil { t.Fatalf("Failed to parse generated JSON: %v", err) } + + reloaded, err := prompty.LoadToolCall(parsed, ctx) + if err != nil { + t.Fatalf("Failed to reload generated JSON: %v", err) + } + if reloaded.Id != "call_abc123" { + t.Errorf(`Expected Id to be "call_abc123", got %v`, reloaded.Id) + } + if reloaded.Name != "get_weather" { + t.Errorf(`Expected Name to be "get_weather", got %v`, reloaded.Name) + } + if reloaded.Arguments != "{\"city\": \"Paris\"}" { + t.Errorf(`Expected Arguments to be "{\"city\": \"Paris\"}", got %v`, reloaded.Arguments) + } } // TestToolCallToYAML tests that ToYAML produces valid YAML @@ -165,4 +229,25 @@ func TestToolCallToYAML(t *testing.T) { if err := yaml.Unmarshal([]byte(yamlOutput), &parsed); err != nil { t.Fatalf("Failed to parse generated YAML: %v", err) } + + reloaded, err := prompty.LoadToolCall(parsed, ctx) + if err != nil { + t.Fatalf("Failed to reload generated YAML: %v", err) + } + if reloaded.Id != "call_abc123" { + t.Errorf(`Expected Id to be "call_abc123", got %v`, reloaded.Id) + } + if reloaded.Name != "get_weather" { + t.Errorf(`Expected Name to be "get_weather", got %v`, reloaded.Name) + } + if reloaded.Arguments != "{\"city\": \"Paris\"}" { + t.Errorf(`Expected Arguments to be "{\"city\": \"Paris\"}", got %v`, reloaded.Arguments) + } +} + +// TestToolCallFromJSONInvalid rejects malformed JSON instead of silently defaulting +func TestToolCallFromJSONInvalid(t *testing.T) { + if _, err := prompty.ToolCallFromJSON("{"); err == nil { + t.Fatalf("Expected malformed JSON to fail") + } } diff --git a/runtime/go/prompty/model/tool_chunk_test.go b/runtime/go/prompty/model/tool_chunk_test.go index b8a70749..3ce05079 100644 --- a/runtime/go/prompty/model/tool_chunk_test.go +++ b/runtime/go/prompty/model/tool_chunk_test.go @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. package prompty_test @@ -33,6 +34,15 @@ func TestToolChunkLoadJSON(t *testing.T) { t.Fatalf("Failed to load ToolChunk: %v", err) } _ = instance // No scalar properties to validate + if instance.ToolCall.Id != "call_abc123" { + t.Errorf(`Expected ToolCall.Id to be "call_abc123", got %v`, instance.ToolCall.Id) + } + if instance.ToolCall.Name != "get_weather" { + t.Errorf(`Expected ToolCall.Name to be "get_weather", got %v`, instance.ToolCall.Name) + } + if instance.ToolCall.Arguments != "{\"city\": \"Paris\"}" { + t.Errorf(`Expected ToolCall.Arguments to be "{\"city\": \"Paris\"}", got %v`, instance.ToolCall.Arguments) + } } // TestToolChunkLoadYAML tests loading ToolChunk from YAML @@ -55,6 +65,69 @@ toolCall: t.Fatalf("Failed to load ToolChunk: %v", err) } _ = instance // No scalar properties to validate + if instance.ToolCall.Id != "call_abc123" { + t.Errorf(`Expected ToolCall.Id to be "call_abc123", got %v`, instance.ToolCall.Id) + } + if instance.ToolCall.Name != "get_weather" { + t.Errorf(`Expected ToolCall.Name to be "get_weather", got %v`, instance.ToolCall.Name) + } + if instance.ToolCall.Arguments != "{\"city\": \"Paris\"}" { + t.Errorf(`Expected ToolCall.Arguments to be "{\"city\": \"Paris\"}", got %v`, instance.ToolCall.Arguments) + } +} + +// TestToolChunkFromJSON tests loading ToolChunk through the generated JSON helper +func TestToolChunkFromJSON(t *testing.T) { + jsonData := ` +{ + "toolCall": { + "id": "call_abc123", + "name": "get_weather", + "arguments": "{\"city\": \"Paris\"}" + } +} +` + + instance, err := prompty.ToolChunkFromJSON(jsonData) + if err != nil { + t.Fatalf("Failed to load ToolChunk from JSON helper: %v", err) + } + _ = instance // No scalar properties to validate + if instance.ToolCall.Id != "call_abc123" { + t.Errorf(`Expected ToolCall.Id to be "call_abc123", got %v`, instance.ToolCall.Id) + } + if instance.ToolCall.Name != "get_weather" { + t.Errorf(`Expected ToolCall.Name to be "get_weather", got %v`, instance.ToolCall.Name) + } + if instance.ToolCall.Arguments != "{\"city\": \"Paris\"}" { + t.Errorf(`Expected ToolCall.Arguments to be "{\"city\": \"Paris\"}", got %v`, instance.ToolCall.Arguments) + } +} + +// TestToolChunkFromYAML tests loading ToolChunk through the generated YAML helper +func TestToolChunkFromYAML(t *testing.T) { + yamlData := ` +toolCall: + id: call_abc123 + name: get_weather + arguments: "{\"city\": \"Paris\"}" + +` + + instance, err := prompty.ToolChunkFromYAML(yamlData) + if err != nil { + t.Fatalf("Failed to load ToolChunk from YAML helper: %v", err) + } + _ = instance // No scalar properties to validate + if instance.ToolCall.Id != "call_abc123" { + t.Errorf(`Expected ToolCall.Id to be "call_abc123", got %v`, instance.ToolCall.Id) + } + if instance.ToolCall.Name != "get_weather" { + t.Errorf(`Expected ToolCall.Name to be "get_weather", got %v`, instance.ToolCall.Name) + } + if instance.ToolCall.Arguments != "{\"city\": \"Paris\"}" { + t.Errorf(`Expected ToolCall.Arguments to be "{\"city\": \"Paris\"}", got %v`, instance.ToolCall.Arguments) + } } // TestToolChunkRoundtrip tests load -> save -> load produces equivalent data @@ -86,6 +159,15 @@ func TestToolChunkRoundtrip(t *testing.T) { t.Fatalf("Failed to reload ToolChunk: %v", err) } _ = reloaded // No scalar properties to validate + if reloaded.ToolCall.Id != "call_abc123" { + t.Errorf(`Expected ToolCall.Id to be "call_abc123", got %v`, reloaded.ToolCall.Id) + } + if reloaded.ToolCall.Name != "get_weather" { + t.Errorf(`Expected ToolCall.Name to be "get_weather", got %v`, reloaded.ToolCall.Name) + } + if reloaded.ToolCall.Arguments != "{\"city\": \"Paris\"}" { + t.Errorf(`Expected ToolCall.Arguments to be "{\"city\": \"Paris\"}", got %v`, reloaded.ToolCall.Arguments) + } } // TestToolChunkToJSON tests that ToJSON produces valid JSON @@ -118,6 +200,21 @@ func TestToolChunkToJSON(t *testing.T) { if err := json.Unmarshal([]byte(jsonOutput), &parsed); err != nil { t.Fatalf("Failed to parse generated JSON: %v", err) } + + reloaded, err := prompty.LoadToolChunk(parsed, ctx) + if err != nil { + t.Fatalf("Failed to reload generated JSON: %v", err) + } + _ = reloaded // No scalar properties to validate + if reloaded.ToolCall.Id != "call_abc123" { + t.Errorf(`Expected ToolCall.Id to be "call_abc123", got %v`, reloaded.ToolCall.Id) + } + if reloaded.ToolCall.Name != "get_weather" { + t.Errorf(`Expected ToolCall.Name to be "get_weather", got %v`, reloaded.ToolCall.Name) + } + if reloaded.ToolCall.Arguments != "{\"city\": \"Paris\"}" { + t.Errorf(`Expected ToolCall.Arguments to be "{\"city\": \"Paris\"}", got %v`, reloaded.ToolCall.Arguments) + } } // TestToolChunkToYAML tests that ToYAML produces valid YAML @@ -150,4 +247,26 @@ func TestToolChunkToYAML(t *testing.T) { if err := yaml.Unmarshal([]byte(yamlOutput), &parsed); err != nil { t.Fatalf("Failed to parse generated YAML: %v", err) } + + reloaded, err := prompty.LoadToolChunk(parsed, ctx) + if err != nil { + t.Fatalf("Failed to reload generated YAML: %v", err) + } + _ = reloaded // No scalar properties to validate + if reloaded.ToolCall.Id != "call_abc123" { + t.Errorf(`Expected ToolCall.Id to be "call_abc123", got %v`, reloaded.ToolCall.Id) + } + if reloaded.ToolCall.Name != "get_weather" { + t.Errorf(`Expected ToolCall.Name to be "get_weather", got %v`, reloaded.ToolCall.Name) + } + if reloaded.ToolCall.Arguments != "{\"city\": \"Paris\"}" { + t.Errorf(`Expected ToolCall.Arguments to be "{\"city\": \"Paris\"}", got %v`, reloaded.ToolCall.Arguments) + } +} + +// TestToolChunkFromJSONInvalid rejects malformed JSON instead of silently defaulting +func TestToolChunkFromJSONInvalid(t *testing.T) { + if _, err := prompty.ToolChunkFromJSON("{"); err == nil { + t.Fatalf("Expected malformed JSON to fail") + } } diff --git a/runtime/go/prompty/model/tool_context.go b/runtime/go/prompty/model/tool_context.go index 5c15a4ea..cde2c7b6 100644 --- a/runtime/go/prompty/model/tool_context.go +++ b/runtime/go/prompty/model/tool_context.go @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. // Group: tools package prompty @@ -46,7 +47,7 @@ func LoadToolContext(data interface{}, ctx *LoadContext) (ToolContext, error) { } // Save serializes ToolContext to map[string]interface{} -func (obj *ToolContext) Save(ctx *SaveContext) map[string]interface{} { +func (obj ToolContext) Save(ctx *SaveContext) map[string]interface{} { result := make(map[string]interface{}) if obj.Messages != nil { arr := make([]interface{}, len(obj.Messages)) @@ -77,11 +78,7 @@ func (obj *ToolContext) ToJSON() (string, error) { func (obj *ToolContext) ToYAML() (string, error) { ctx := NewSaveContext() data := obj.Save(ctx) - bytes, err := yaml.Marshal(data) - if err != nil { - return "", err - } - return string(bytes), nil + return marshalYAMLDocument(data) } // FromJSON creates ToolContext from JSON string diff --git a/runtime/go/prompty/model/tool_context_test.go b/runtime/go/prompty/model/tool_context_test.go index 4dddc6bf..b106068a 100644 --- a/runtime/go/prompty/model/tool_context_test.go +++ b/runtime/go/prompty/model/tool_context_test.go @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. package prompty_test @@ -31,6 +32,12 @@ func TestToolContextLoadJSON(t *testing.T) { t.Fatalf("Failed to load ToolContext: %v", err) } _ = instance // No scalar properties to validate + if instance.Metadata == nil { + t.Fatalf("Expected Metadata to be populated") + } + if got := instance.Metadata["userId"]; got != "user-123" { + t.Errorf(`Expected Metadata["userId"] to be "user-123", got %v`, got) + } } // TestToolContextLoadYAML tests loading ToolContext from YAML @@ -51,6 +58,56 @@ metadata: t.Fatalf("Failed to load ToolContext: %v", err) } _ = instance // No scalar properties to validate + if instance.Metadata == nil { + t.Fatalf("Expected Metadata to be populated") + } + if got := instance.Metadata["userId"]; got != "user-123" { + t.Errorf(`Expected Metadata["userId"] to be "user-123", got %v`, got) + } +} + +// TestToolContextFromJSON tests loading ToolContext through the generated JSON helper +func TestToolContextFromJSON(t *testing.T) { + jsonData := ` +{ + "metadata": { + "userId": "user-123" + } +} +` + + instance, err := prompty.ToolContextFromJSON(jsonData) + if err != nil { + t.Fatalf("Failed to load ToolContext from JSON helper: %v", err) + } + _ = instance // No scalar properties to validate + if instance.Metadata == nil { + t.Fatalf("Expected Metadata to be populated") + } + if got := instance.Metadata["userId"]; got != "user-123" { + t.Errorf(`Expected Metadata["userId"] to be "user-123", got %v`, got) + } +} + +// TestToolContextFromYAML tests loading ToolContext through the generated YAML helper +func TestToolContextFromYAML(t *testing.T) { + yamlData := ` +metadata: + userId: user-123 + +` + + instance, err := prompty.ToolContextFromYAML(yamlData) + if err != nil { + t.Fatalf("Failed to load ToolContext from YAML helper: %v", err) + } + _ = instance // No scalar properties to validate + if instance.Metadata == nil { + t.Fatalf("Expected Metadata to be populated") + } + if got := instance.Metadata["userId"]; got != "user-123" { + t.Errorf(`Expected Metadata["userId"] to be "user-123", got %v`, got) + } } // TestToolContextRoundtrip tests load -> save -> load produces equivalent data @@ -80,6 +137,12 @@ func TestToolContextRoundtrip(t *testing.T) { t.Fatalf("Failed to reload ToolContext: %v", err) } _ = reloaded // No scalar properties to validate + if reloaded.Metadata == nil { + t.Fatalf("Expected Metadata to be populated") + } + if got := reloaded.Metadata["userId"]; got != "user-123" { + t.Errorf(`Expected Metadata["userId"] to be "user-123", got %v`, got) + } } // TestToolContextToJSON tests that ToJSON produces valid JSON @@ -110,6 +173,18 @@ func TestToolContextToJSON(t *testing.T) { if err := json.Unmarshal([]byte(jsonOutput), &parsed); err != nil { t.Fatalf("Failed to parse generated JSON: %v", err) } + + reloaded, err := prompty.LoadToolContext(parsed, ctx) + if err != nil { + t.Fatalf("Failed to reload generated JSON: %v", err) + } + _ = reloaded // No scalar properties to validate + if reloaded.Metadata == nil { + t.Fatalf("Expected Metadata to be populated") + } + if got := reloaded.Metadata["userId"]; got != "user-123" { + t.Errorf(`Expected Metadata["userId"] to be "user-123", got %v`, got) + } } // TestToolContextToYAML tests that ToYAML produces valid YAML @@ -140,4 +215,23 @@ func TestToolContextToYAML(t *testing.T) { if err := yaml.Unmarshal([]byte(yamlOutput), &parsed); err != nil { t.Fatalf("Failed to parse generated YAML: %v", err) } + + reloaded, err := prompty.LoadToolContext(parsed, ctx) + if err != nil { + t.Fatalf("Failed to reload generated YAML: %v", err) + } + _ = reloaded // No scalar properties to validate + if reloaded.Metadata == nil { + t.Fatalf("Expected Metadata to be populated") + } + if got := reloaded.Metadata["userId"]; got != "user-123" { + t.Errorf(`Expected Metadata["userId"] to be "user-123", got %v`, got) + } +} + +// TestToolContextFromJSONInvalid rejects malformed JSON instead of silently defaulting +func TestToolContextFromJSONInvalid(t *testing.T) { + if _, err := prompty.ToolContextFromJSON("{"); err == nil { + t.Fatalf("Expected malformed JSON to fail") + } } diff --git a/runtime/go/prompty/model/tool_dispatch_result.go b/runtime/go/prompty/model/tool_dispatch_result.go index 6492d5c5..4a52b4a0 100644 --- a/runtime/go/prompty/model/tool_dispatch_result.go +++ b/runtime/go/prompty/model/tool_dispatch_result.go @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. // Group: tools package prompty @@ -43,7 +44,7 @@ func LoadToolDispatchResult(data interface{}, ctx *LoadContext) (ToolDispatchRes } // Save serializes ToolDispatchResult to map[string]interface{} -func (obj *ToolDispatchResult) Save(ctx *SaveContext) map[string]interface{} { +func (obj ToolDispatchResult) Save(ctx *SaveContext) map[string]interface{} { result := make(map[string]interface{}) result["toolCallId"] = obj.ToolCallId result["name"] = obj.Name @@ -68,11 +69,7 @@ func (obj *ToolDispatchResult) ToJSON() (string, error) { func (obj *ToolDispatchResult) ToYAML() (string, error) { ctx := NewSaveContext() data := obj.Save(ctx) - bytes, err := yaml.Marshal(data) - if err != nil { - return "", err - } - return string(bytes), nil + return marshalYAMLDocument(data) } // FromJSON creates ToolDispatchResult from JSON string diff --git a/runtime/go/prompty/model/tool_dispatch_result_test.go b/runtime/go/prompty/model/tool_dispatch_result_test.go index 354ecf68..0d74571e 100644 --- a/runtime/go/prompty/model/tool_dispatch_result_test.go +++ b/runtime/go/prompty/model/tool_dispatch_result_test.go @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. package prompty_test @@ -74,6 +75,59 @@ result: } } +// TestToolDispatchResultFromJSON tests loading ToolDispatchResult through the generated JSON helper +func TestToolDispatchResultFromJSON(t *testing.T) { + jsonData := ` +{ + "toolCallId": "call_abc123", + "name": "get_weather", + "result": { + "parts": [ + { + "kind": "text", + "value": "72°F and sunny" + } + ] + } +} +` + + instance, err := prompty.ToolDispatchResultFromJSON(jsonData) + if err != nil { + t.Fatalf("Failed to load ToolDispatchResult from JSON helper: %v", err) + } + if instance.ToolCallId != "call_abc123" { + t.Errorf(`Expected ToolCallId to be "call_abc123", got %v`, instance.ToolCallId) + } + if instance.Name != "get_weather" { + t.Errorf(`Expected Name to be "get_weather", got %v`, instance.Name) + } +} + +// TestToolDispatchResultFromYAML tests loading ToolDispatchResult through the generated YAML helper +func TestToolDispatchResultFromYAML(t *testing.T) { + yamlData := ` +toolCallId: call_abc123 +name: get_weather +result: + parts: + - kind: text + value: 72°F and sunny + +` + + instance, err := prompty.ToolDispatchResultFromYAML(yamlData) + if err != nil { + t.Fatalf("Failed to load ToolDispatchResult from YAML helper: %v", err) + } + if instance.ToolCallId != "call_abc123" { + t.Errorf(`Expected ToolCallId to be "call_abc123", got %v`, instance.ToolCallId) + } + if instance.Name != "get_weather" { + t.Errorf(`Expected Name to be "get_weather", got %v`, instance.Name) + } +} + // TestToolDispatchResultRoundtrip tests load -> save -> load produces equivalent data func TestToolDispatchResultRoundtrip(t *testing.T) { jsonData := ` @@ -150,6 +204,17 @@ func TestToolDispatchResultToJSON(t *testing.T) { if err := json.Unmarshal([]byte(jsonOutput), &parsed); err != nil { t.Fatalf("Failed to parse generated JSON: %v", err) } + + reloaded, err := prompty.LoadToolDispatchResult(parsed, ctx) + if err != nil { + t.Fatalf("Failed to reload generated JSON: %v", err) + } + if reloaded.ToolCallId != "call_abc123" { + t.Errorf(`Expected ToolCallId to be "call_abc123", got %v`, reloaded.ToolCallId) + } + if reloaded.Name != "get_weather" { + t.Errorf(`Expected Name to be "get_weather", got %v`, reloaded.Name) + } } // TestToolDispatchResultToYAML tests that ToYAML produces valid YAML @@ -187,4 +252,22 @@ func TestToolDispatchResultToYAML(t *testing.T) { if err := yaml.Unmarshal([]byte(yamlOutput), &parsed); err != nil { t.Fatalf("Failed to parse generated YAML: %v", err) } + + reloaded, err := prompty.LoadToolDispatchResult(parsed, ctx) + if err != nil { + t.Fatalf("Failed to reload generated YAML: %v", err) + } + if reloaded.ToolCallId != "call_abc123" { + t.Errorf(`Expected ToolCallId to be "call_abc123", got %v`, reloaded.ToolCallId) + } + if reloaded.Name != "get_weather" { + t.Errorf(`Expected Name to be "get_weather", got %v`, reloaded.Name) + } +} + +// TestToolDispatchResultFromJSONInvalid rejects malformed JSON instead of silently defaulting +func TestToolDispatchResultFromJSONInvalid(t *testing.T) { + if _, err := prompty.ToolDispatchResultFromJSON("{"); err == nil { + t.Fatalf("Expected malformed JSON to fail") + } } diff --git a/runtime/go/prompty/model/tool_execution_complete_payload.go b/runtime/go/prompty/model/tool_execution_complete_payload.go new file mode 100644 index 00000000..bee0b54b --- /dev/null +++ b/runtime/go/prompty/model/tool_execution_complete_payload.go @@ -0,0 +1,170 @@ +// +// Code generated by Typra emitter; DO NOT EDIT. +// Group: events + +package prompty + +import ( + "encoding/json" + + "gopkg.in/yaml.v3" +) + +// ToolExecutionCompletePayload represents Payload for "tool_execution_complete" events — a concrete host tool execution finished. + +type ToolExecutionCompletePayload struct { + RequestId *string `json:"requestId,omitempty" yaml:"requestId,omitempty"` + ToolCallId *string `json:"toolCallId,omitempty" yaml:"toolCallId,omitempty"` + ToolName string `json:"toolName" yaml:"toolName"` + Success bool `json:"success" yaml:"success"` + Result *interface{} `json:"result,omitempty" yaml:"result,omitempty"` + ExitCode *int32 `json:"exitCode,omitempty" yaml:"exitCode,omitempty"` + DurationMs *float64 `json:"durationMs,omitempty" yaml:"durationMs,omitempty"` + ErrorKind *string `json:"errorKind,omitempty" yaml:"errorKind,omitempty"` + Telemetry map[string]interface{} `json:"telemetry,omitempty" yaml:"telemetry,omitempty"` + Redaction *RedactionMetadata `json:"redaction,omitempty" yaml:"redaction,omitempty"` +} + +// LoadToolExecutionCompletePayload creates a ToolExecutionCompletePayload from a map[string]interface{} +func LoadToolExecutionCompletePayload(data interface{}, ctx *LoadContext) (ToolExecutionCompletePayload, error) { + result := ToolExecutionCompletePayload{} + + // Load from map + if m, ok := data.(map[string]interface{}); ok { + if val, ok := m["requestId"]; ok && val != nil { + v := string(val.(string)) + result.RequestId = &v + } + if val, ok := m["toolCallId"]; ok && val != nil { + v := string(val.(string)) + result.ToolCallId = &v + } + if val, ok := m["toolName"]; ok && val != nil { + result.ToolName = string(val.(string)) + } + if val, ok := m["success"]; ok && val != nil { + result.Success = val.(bool) + } + if val, ok := m["result"]; ok && val != nil { + result.Result = &val + } + if val, ok := m["exitCode"]; ok && val != nil { // Handle various numeric types from JSON/YAML/roundtrip + var v int32 + switch n := val.(type) { + case int: + v = int32(n) + case int32: + v = int32(n) + case int64: + v = int32(n) + case float64: + v = int32(n) + } + result.ExitCode = &v + } + if val, ok := m["durationMs"]; ok && val != nil { // Handle various numeric types from JSON/YAML/roundtrip + var v float64 + switch n := val.(type) { + case int: + v = float64(n) + case int32: + v = float64(n) + case int64: + v = float64(n) + case float32: + v = float64(n) + case float64: + v = n + } + result.DurationMs = &v + } + if val, ok := m["errorKind"]; ok && val != nil { + v := string(val.(string)) + result.ErrorKind = &v + } + if val, ok := m["telemetry"]; ok && val != nil { + if m, ok := val.(map[string]interface{}); ok { + result.Telemetry = m + } + } + if val, ok := m["redaction"]; ok && val != nil { + if m, ok := val.(map[string]interface{}); ok { + loaded, _ := LoadRedactionMetadata(m, ctx) + result.Redaction = &loaded + } + } + } + + return result, nil +} + +// Save serializes ToolExecutionCompletePayload to map[string]interface{} +func (obj ToolExecutionCompletePayload) Save(ctx *SaveContext) map[string]interface{} { + result := make(map[string]interface{}) + if obj.RequestId != nil { + result["requestId"] = *obj.RequestId + } + if obj.ToolCallId != nil { + result["toolCallId"] = *obj.ToolCallId + } + result["toolName"] = obj.ToolName + result["success"] = obj.Success + if obj.Result != nil { + result["result"] = *obj.Result + } + if obj.ExitCode != nil { + result["exitCode"] = *obj.ExitCode + } + if obj.DurationMs != nil { + result["durationMs"] = *obj.DurationMs + } + if obj.ErrorKind != nil { + result["errorKind"] = *obj.ErrorKind + } + if obj.Telemetry != nil { + result["telemetry"] = obj.Telemetry + } + if obj.Redaction != nil { + result["redaction"] = obj.Redaction.Save(ctx) + } + + return result +} + +// ToJSON serializes ToolExecutionCompletePayload to JSON string +func (obj *ToolExecutionCompletePayload) ToJSON() (string, error) { + ctx := NewSaveContext() + data := obj.Save(ctx) + bytes, err := json.Marshal(data) + if err != nil { + return "", err + } + return string(bytes), nil +} + +// ToYAML serializes ToolExecutionCompletePayload to YAML string +func (obj *ToolExecutionCompletePayload) ToYAML() (string, error) { + ctx := NewSaveContext() + data := obj.Save(ctx) + return marshalYAMLDocument(data) +} + +// FromJSON creates ToolExecutionCompletePayload from JSON string +func ToolExecutionCompletePayloadFromJSON(jsonStr string) (ToolExecutionCompletePayload, error) { + var data map[string]interface{} + if err := json.Unmarshal([]byte(jsonStr), &data); err != nil { + return ToolExecutionCompletePayload{}, err + } + ctx := NewLoadContext() + return LoadToolExecutionCompletePayload(data, ctx) +} + +// FromYAML creates ToolExecutionCompletePayload from YAML string +func ToolExecutionCompletePayloadFromYAML(yamlStr string) (ToolExecutionCompletePayload, error) { + var data map[string]interface{} + if err := yaml.Unmarshal([]byte(yamlStr), &data); err != nil { + return ToolExecutionCompletePayload{}, err + } + ctx := NewLoadContext() + return LoadToolExecutionCompletePayload(data, ctx) +} diff --git a/runtime/go/prompty/model/tool_execution_complete_payload_test.go b/runtime/go/prompty/model/tool_execution_complete_payload_test.go new file mode 100644 index 00000000..6f481ee3 --- /dev/null +++ b/runtime/go/prompty/model/tool_execution_complete_payload_test.go @@ -0,0 +1,365 @@ +// +// Code generated by Typra emitter; DO NOT EDIT. + +package prompty_test + +import ( + "encoding/json" + "testing" + + "gopkg.in/yaml.v3" + + "prompty/model" +) + +// TestToolExecutionCompletePayloadLoadJSON tests loading ToolExecutionCompletePayload from JSON +func TestToolExecutionCompletePayloadLoadJSON(t *testing.T) { + jsonData := ` +{ + "requestId": "exec_abc123", + "toolCallId": "call_abc123", + "toolName": "powershell", + "success": true, + "exitCode": 0, + "durationMs": 250, + "errorKind": "timeout" +} +` + var data map[string]interface{} + if err := json.Unmarshal([]byte(jsonData), &data); err != nil { + t.Fatalf("Failed to parse JSON: %v", err) + } + + ctx := prompty.NewLoadContext() + instance, err := prompty.LoadToolExecutionCompletePayload(data, ctx) + if err != nil { + t.Fatalf("Failed to load ToolExecutionCompletePayload: %v", err) + } + if instance.RequestId == nil || *instance.RequestId != "exec_abc123" { + t.Errorf(`Expected RequestId to be "exec_abc123", got %v`, instance.RequestId) + } + if instance.ToolCallId == nil || *instance.ToolCallId != "call_abc123" { + t.Errorf(`Expected ToolCallId to be "call_abc123", got %v`, instance.ToolCallId) + } + if instance.ToolName != "powershell" { + t.Errorf(`Expected ToolName to be "powershell", got %v`, instance.ToolName) + } + if instance.Success != true { + t.Errorf(`Expected Success to be true, got %v`, instance.Success) + } + if instance.ExitCode == nil || *instance.ExitCode != 0 { + t.Errorf(`Expected ExitCode to be 0, got %v`, instance.ExitCode) + } + if instance.DurationMs == nil || *instance.DurationMs != 250 { + t.Errorf(`Expected DurationMs to be 250, got %v`, instance.DurationMs) + } + if instance.ErrorKind == nil || *instance.ErrorKind != "timeout" { + t.Errorf(`Expected ErrorKind to be "timeout", got %v`, instance.ErrorKind) + } +} + +// TestToolExecutionCompletePayloadLoadYAML tests loading ToolExecutionCompletePayload from YAML +func TestToolExecutionCompletePayloadLoadYAML(t *testing.T) { + yamlData := ` +requestId: exec_abc123 +toolCallId: call_abc123 +toolName: powershell +success: true +exitCode: 0 +durationMs: 250 +errorKind: timeout + +` + var data map[string]interface{} + if err := yaml.Unmarshal([]byte(yamlData), &data); err != nil { + t.Fatalf("Failed to parse YAML: %v", err) + } + + ctx := prompty.NewLoadContext() + instance, err := prompty.LoadToolExecutionCompletePayload(data, ctx) + if err != nil { + t.Fatalf("Failed to load ToolExecutionCompletePayload: %v", err) + } + if instance.RequestId == nil || *instance.RequestId != "exec_abc123" { + t.Errorf(`Expected RequestId to be "exec_abc123", got %v`, instance.RequestId) + } + if instance.ToolCallId == nil || *instance.ToolCallId != "call_abc123" { + t.Errorf(`Expected ToolCallId to be "call_abc123", got %v`, instance.ToolCallId) + } + if instance.ToolName != "powershell" { + t.Errorf(`Expected ToolName to be "powershell", got %v`, instance.ToolName) + } + if instance.Success != true { + t.Errorf(`Expected Success to be true, got %v`, instance.Success) + } + if instance.ExitCode == nil || *instance.ExitCode != 0 { + t.Errorf(`Expected ExitCode to be 0, got %v`, instance.ExitCode) + } + if instance.DurationMs == nil || *instance.DurationMs != 250 { + t.Errorf(`Expected DurationMs to be 250, got %v`, instance.DurationMs) + } + if instance.ErrorKind == nil || *instance.ErrorKind != "timeout" { + t.Errorf(`Expected ErrorKind to be "timeout", got %v`, instance.ErrorKind) + } +} + +// TestToolExecutionCompletePayloadFromJSON tests loading ToolExecutionCompletePayload through the generated JSON helper +func TestToolExecutionCompletePayloadFromJSON(t *testing.T) { + jsonData := ` +{ + "requestId": "exec_abc123", + "toolCallId": "call_abc123", + "toolName": "powershell", + "success": true, + "exitCode": 0, + "durationMs": 250, + "errorKind": "timeout" +} +` + + instance, err := prompty.ToolExecutionCompletePayloadFromJSON(jsonData) + if err != nil { + t.Fatalf("Failed to load ToolExecutionCompletePayload from JSON helper: %v", err) + } + if instance.RequestId == nil || *instance.RequestId != "exec_abc123" { + t.Errorf(`Expected RequestId to be "exec_abc123", got %v`, instance.RequestId) + } + if instance.ToolCallId == nil || *instance.ToolCallId != "call_abc123" { + t.Errorf(`Expected ToolCallId to be "call_abc123", got %v`, instance.ToolCallId) + } + if instance.ToolName != "powershell" { + t.Errorf(`Expected ToolName to be "powershell", got %v`, instance.ToolName) + } + if instance.Success != true { + t.Errorf(`Expected Success to be true, got %v`, instance.Success) + } + if instance.ExitCode == nil || *instance.ExitCode != 0 { + t.Errorf(`Expected ExitCode to be 0, got %v`, instance.ExitCode) + } + if instance.DurationMs == nil || *instance.DurationMs != 250 { + t.Errorf(`Expected DurationMs to be 250, got %v`, instance.DurationMs) + } + if instance.ErrorKind == nil || *instance.ErrorKind != "timeout" { + t.Errorf(`Expected ErrorKind to be "timeout", got %v`, instance.ErrorKind) + } +} + +// TestToolExecutionCompletePayloadFromYAML tests loading ToolExecutionCompletePayload through the generated YAML helper +func TestToolExecutionCompletePayloadFromYAML(t *testing.T) { + yamlData := ` +requestId: exec_abc123 +toolCallId: call_abc123 +toolName: powershell +success: true +exitCode: 0 +durationMs: 250 +errorKind: timeout + +` + + instance, err := prompty.ToolExecutionCompletePayloadFromYAML(yamlData) + if err != nil { + t.Fatalf("Failed to load ToolExecutionCompletePayload from YAML helper: %v", err) + } + if instance.RequestId == nil || *instance.RequestId != "exec_abc123" { + t.Errorf(`Expected RequestId to be "exec_abc123", got %v`, instance.RequestId) + } + if instance.ToolCallId == nil || *instance.ToolCallId != "call_abc123" { + t.Errorf(`Expected ToolCallId to be "call_abc123", got %v`, instance.ToolCallId) + } + if instance.ToolName != "powershell" { + t.Errorf(`Expected ToolName to be "powershell", got %v`, instance.ToolName) + } + if instance.Success != true { + t.Errorf(`Expected Success to be true, got %v`, instance.Success) + } + if instance.ExitCode == nil || *instance.ExitCode != 0 { + t.Errorf(`Expected ExitCode to be 0, got %v`, instance.ExitCode) + } + if instance.DurationMs == nil || *instance.DurationMs != 250 { + t.Errorf(`Expected DurationMs to be 250, got %v`, instance.DurationMs) + } + if instance.ErrorKind == nil || *instance.ErrorKind != "timeout" { + t.Errorf(`Expected ErrorKind to be "timeout", got %v`, instance.ErrorKind) + } +} + +// TestToolExecutionCompletePayloadRoundtrip tests load -> save -> load produces equivalent data +func TestToolExecutionCompletePayloadRoundtrip(t *testing.T) { + jsonData := ` +{ + "requestId": "exec_abc123", + "toolCallId": "call_abc123", + "toolName": "powershell", + "success": true, + "exitCode": 0, + "durationMs": 250, + "errorKind": "timeout" +} +` + var data map[string]interface{} + if err := json.Unmarshal([]byte(jsonData), &data); err != nil { + t.Fatalf("Failed to parse JSON: %v", err) + } + + loadCtx := prompty.NewLoadContext() + instance, err := prompty.LoadToolExecutionCompletePayload(data, loadCtx) + if err != nil { + t.Fatalf("Failed to load ToolExecutionCompletePayload: %v", err) + } + saveCtx := prompty.NewSaveContext() + savedData := instance.Save(saveCtx) + + reloaded, err := prompty.LoadToolExecutionCompletePayload(savedData, loadCtx) + if err != nil { + t.Fatalf("Failed to reload ToolExecutionCompletePayload: %v", err) + } + if reloaded.RequestId == nil || *reloaded.RequestId != "exec_abc123" { + t.Errorf(`Expected RequestId to be "exec_abc123", got %v`, reloaded.RequestId) + } + if reloaded.ToolCallId == nil || *reloaded.ToolCallId != "call_abc123" { + t.Errorf(`Expected ToolCallId to be "call_abc123", got %v`, reloaded.ToolCallId) + } + if reloaded.ToolName != "powershell" { + t.Errorf(`Expected ToolName to be "powershell", got %v`, reloaded.ToolName) + } + if reloaded.Success != true { + t.Errorf(`Expected Success to be true, got %v`, reloaded.Success) + } + if reloaded.ExitCode == nil || *reloaded.ExitCode != 0 { + t.Errorf(`Expected ExitCode to be 0, got %v`, reloaded.ExitCode) + } + if reloaded.DurationMs == nil || *reloaded.DurationMs != 250 { + t.Errorf(`Expected DurationMs to be 250, got %v`, reloaded.DurationMs) + } + if reloaded.ErrorKind == nil || *reloaded.ErrorKind != "timeout" { + t.Errorf(`Expected ErrorKind to be "timeout", got %v`, reloaded.ErrorKind) + } +} + +// TestToolExecutionCompletePayloadToJSON tests that ToJSON produces valid JSON +func TestToolExecutionCompletePayloadToJSON(t *testing.T) { + jsonData := ` +{ + "requestId": "exec_abc123", + "toolCallId": "call_abc123", + "toolName": "powershell", + "success": true, + "exitCode": 0, + "durationMs": 250, + "errorKind": "timeout" +} +` + var data map[string]interface{} + if err := json.Unmarshal([]byte(jsonData), &data); err != nil { + t.Fatalf("Failed to parse JSON: %v", err) + } + + ctx := prompty.NewLoadContext() + instance, err := prompty.LoadToolExecutionCompletePayload(data, ctx) + if err != nil { + t.Fatalf("Failed to load ToolExecutionCompletePayload: %v", err) + } + jsonOutput, err := instance.ToJSON() + if err != nil { + t.Fatalf("Failed to convert to JSON: %v", err) + } + + var parsed map[string]interface{} + if err := json.Unmarshal([]byte(jsonOutput), &parsed); err != nil { + t.Fatalf("Failed to parse generated JSON: %v", err) + } + + reloaded, err := prompty.LoadToolExecutionCompletePayload(parsed, ctx) + if err != nil { + t.Fatalf("Failed to reload generated JSON: %v", err) + } + if reloaded.RequestId == nil || *reloaded.RequestId != "exec_abc123" { + t.Errorf(`Expected RequestId to be "exec_abc123", got %v`, reloaded.RequestId) + } + if reloaded.ToolCallId == nil || *reloaded.ToolCallId != "call_abc123" { + t.Errorf(`Expected ToolCallId to be "call_abc123", got %v`, reloaded.ToolCallId) + } + if reloaded.ToolName != "powershell" { + t.Errorf(`Expected ToolName to be "powershell", got %v`, reloaded.ToolName) + } + if reloaded.Success != true { + t.Errorf(`Expected Success to be true, got %v`, reloaded.Success) + } + if reloaded.ExitCode == nil || *reloaded.ExitCode != 0 { + t.Errorf(`Expected ExitCode to be 0, got %v`, reloaded.ExitCode) + } + if reloaded.DurationMs == nil || *reloaded.DurationMs != 250 { + t.Errorf(`Expected DurationMs to be 250, got %v`, reloaded.DurationMs) + } + if reloaded.ErrorKind == nil || *reloaded.ErrorKind != "timeout" { + t.Errorf(`Expected ErrorKind to be "timeout", got %v`, reloaded.ErrorKind) + } +} + +// TestToolExecutionCompletePayloadToYAML tests that ToYAML produces valid YAML +func TestToolExecutionCompletePayloadToYAML(t *testing.T) { + jsonData := ` +{ + "requestId": "exec_abc123", + "toolCallId": "call_abc123", + "toolName": "powershell", + "success": true, + "exitCode": 0, + "durationMs": 250, + "errorKind": "timeout" +} +` + var data map[string]interface{} + if err := json.Unmarshal([]byte(jsonData), &data); err != nil { + t.Fatalf("Failed to parse JSON: %v", err) + } + + ctx := prompty.NewLoadContext() + instance, err := prompty.LoadToolExecutionCompletePayload(data, ctx) + if err != nil { + t.Fatalf("Failed to load ToolExecutionCompletePayload: %v", err) + } + yamlOutput, err := instance.ToYAML() + if err != nil { + t.Fatalf("Failed to convert to YAML: %v", err) + } + + var parsed map[string]interface{} + if err := yaml.Unmarshal([]byte(yamlOutput), &parsed); err != nil { + t.Fatalf("Failed to parse generated YAML: %v", err) + } + + reloaded, err := prompty.LoadToolExecutionCompletePayload(parsed, ctx) + if err != nil { + t.Fatalf("Failed to reload generated YAML: %v", err) + } + if reloaded.RequestId == nil || *reloaded.RequestId != "exec_abc123" { + t.Errorf(`Expected RequestId to be "exec_abc123", got %v`, reloaded.RequestId) + } + if reloaded.ToolCallId == nil || *reloaded.ToolCallId != "call_abc123" { + t.Errorf(`Expected ToolCallId to be "call_abc123", got %v`, reloaded.ToolCallId) + } + if reloaded.ToolName != "powershell" { + t.Errorf(`Expected ToolName to be "powershell", got %v`, reloaded.ToolName) + } + if reloaded.Success != true { + t.Errorf(`Expected Success to be true, got %v`, reloaded.Success) + } + if reloaded.ExitCode == nil || *reloaded.ExitCode != 0 { + t.Errorf(`Expected ExitCode to be 0, got %v`, reloaded.ExitCode) + } + if reloaded.DurationMs == nil || *reloaded.DurationMs != 250 { + t.Errorf(`Expected DurationMs to be 250, got %v`, reloaded.DurationMs) + } + if reloaded.ErrorKind == nil || *reloaded.ErrorKind != "timeout" { + t.Errorf(`Expected ErrorKind to be "timeout", got %v`, reloaded.ErrorKind) + } +} + +// TestToolExecutionCompletePayloadFromJSONInvalid rejects malformed JSON instead of silently defaulting +func TestToolExecutionCompletePayloadFromJSONInvalid(t *testing.T) { + if _, err := prompty.ToolExecutionCompletePayloadFromJSON("{"); err == nil { + t.Fatalf("Expected malformed JSON to fail") + } +} diff --git a/runtime/go/prompty/model/tool_execution_start_payload.go b/runtime/go/prompty/model/tool_execution_start_payload.go new file mode 100644 index 00000000..a1e9c88a --- /dev/null +++ b/runtime/go/prompty/model/tool_execution_start_payload.go @@ -0,0 +1,123 @@ +// +// Code generated by Typra emitter; DO NOT EDIT. +// Group: events + +package prompty + +import ( + "encoding/json" + + "gopkg.in/yaml.v3" +) + +// ToolExecutionStartPayload represents Payload for "tool_execution_start" events — the host is about to execute a concrete tool request. +// +// This is distinct from "tool_call_start", which records the model requesting a tool. +// Tool execution events capture the harness-side action after policy and permission checks. + +type ToolExecutionStartPayload struct { + RequestId *string `json:"requestId,omitempty" yaml:"requestId,omitempty"` + ToolCallId *string `json:"toolCallId,omitempty" yaml:"toolCallId,omitempty"` + ToolName string `json:"toolName" yaml:"toolName"` + Arguments map[string]interface{} `json:"arguments,omitempty" yaml:"arguments,omitempty"` + WorkingDirectory *string `json:"workingDirectory,omitempty" yaml:"workingDirectory,omitempty"` + Redaction *RedactionMetadata `json:"redaction,omitempty" yaml:"redaction,omitempty"` +} + +// LoadToolExecutionStartPayload creates a ToolExecutionStartPayload from a map[string]interface{} +func LoadToolExecutionStartPayload(data interface{}, ctx *LoadContext) (ToolExecutionStartPayload, error) { + result := ToolExecutionStartPayload{} + + // Load from map + if m, ok := data.(map[string]interface{}); ok { + if val, ok := m["requestId"]; ok && val != nil { + v := string(val.(string)) + result.RequestId = &v + } + if val, ok := m["toolCallId"]; ok && val != nil { + v := string(val.(string)) + result.ToolCallId = &v + } + if val, ok := m["toolName"]; ok && val != nil { + result.ToolName = string(val.(string)) + } + if val, ok := m["arguments"]; ok && val != nil { + if m, ok := val.(map[string]interface{}); ok { + result.Arguments = m + } + } + if val, ok := m["workingDirectory"]; ok && val != nil { + v := string(val.(string)) + result.WorkingDirectory = &v + } + if val, ok := m["redaction"]; ok && val != nil { + if m, ok := val.(map[string]interface{}); ok { + loaded, _ := LoadRedactionMetadata(m, ctx) + result.Redaction = &loaded + } + } + } + + return result, nil +} + +// Save serializes ToolExecutionStartPayload to map[string]interface{} +func (obj ToolExecutionStartPayload) Save(ctx *SaveContext) map[string]interface{} { + result := make(map[string]interface{}) + if obj.RequestId != nil { + result["requestId"] = *obj.RequestId + } + if obj.ToolCallId != nil { + result["toolCallId"] = *obj.ToolCallId + } + result["toolName"] = obj.ToolName + if obj.Arguments != nil { + result["arguments"] = obj.Arguments + } + if obj.WorkingDirectory != nil { + result["workingDirectory"] = *obj.WorkingDirectory + } + if obj.Redaction != nil { + result["redaction"] = obj.Redaction.Save(ctx) + } + + return result +} + +// ToJSON serializes ToolExecutionStartPayload to JSON string +func (obj *ToolExecutionStartPayload) ToJSON() (string, error) { + ctx := NewSaveContext() + data := obj.Save(ctx) + bytes, err := json.Marshal(data) + if err != nil { + return "", err + } + return string(bytes), nil +} + +// ToYAML serializes ToolExecutionStartPayload to YAML string +func (obj *ToolExecutionStartPayload) ToYAML() (string, error) { + ctx := NewSaveContext() + data := obj.Save(ctx) + return marshalYAMLDocument(data) +} + +// FromJSON creates ToolExecutionStartPayload from JSON string +func ToolExecutionStartPayloadFromJSON(jsonStr string) (ToolExecutionStartPayload, error) { + var data map[string]interface{} + if err := json.Unmarshal([]byte(jsonStr), &data); err != nil { + return ToolExecutionStartPayload{}, err + } + ctx := NewLoadContext() + return LoadToolExecutionStartPayload(data, ctx) +} + +// FromYAML creates ToolExecutionStartPayload from YAML string +func ToolExecutionStartPayloadFromYAML(yamlStr string) (ToolExecutionStartPayload, error) { + var data map[string]interface{} + if err := yaml.Unmarshal([]byte(yamlStr), &data); err != nil { + return ToolExecutionStartPayload{}, err + } + ctx := NewLoadContext() + return LoadToolExecutionStartPayload(data, ctx) +} diff --git a/runtime/go/prompty/model/tool_execution_start_payload_test.go b/runtime/go/prompty/model/tool_execution_start_payload_test.go new file mode 100644 index 00000000..48da0c52 --- /dev/null +++ b/runtime/go/prompty/model/tool_execution_start_payload_test.go @@ -0,0 +1,281 @@ +// +// Code generated by Typra emitter; DO NOT EDIT. + +package prompty_test + +import ( + "encoding/json" + "testing" + + "gopkg.in/yaml.v3" + + "prompty/model" +) + +// TestToolExecutionStartPayloadLoadJSON tests loading ToolExecutionStartPayload from JSON +func TestToolExecutionStartPayloadLoadJSON(t *testing.T) { + jsonData := ` +{ + "requestId": "exec_abc123", + "toolCallId": "call_abc123", + "toolName": "powershell", + "workingDirectory": "/workspace/project" +} +` + var data map[string]interface{} + if err := json.Unmarshal([]byte(jsonData), &data); err != nil { + t.Fatalf("Failed to parse JSON: %v", err) + } + + ctx := prompty.NewLoadContext() + instance, err := prompty.LoadToolExecutionStartPayload(data, ctx) + if err != nil { + t.Fatalf("Failed to load ToolExecutionStartPayload: %v", err) + } + if instance.RequestId == nil || *instance.RequestId != "exec_abc123" { + t.Errorf(`Expected RequestId to be "exec_abc123", got %v`, instance.RequestId) + } + if instance.ToolCallId == nil || *instance.ToolCallId != "call_abc123" { + t.Errorf(`Expected ToolCallId to be "call_abc123", got %v`, instance.ToolCallId) + } + if instance.ToolName != "powershell" { + t.Errorf(`Expected ToolName to be "powershell", got %v`, instance.ToolName) + } + if instance.WorkingDirectory == nil || *instance.WorkingDirectory != "/workspace/project" { + t.Errorf(`Expected WorkingDirectory to be "/workspace/project", got %v`, instance.WorkingDirectory) + } +} + +// TestToolExecutionStartPayloadLoadYAML tests loading ToolExecutionStartPayload from YAML +func TestToolExecutionStartPayloadLoadYAML(t *testing.T) { + yamlData := ` +requestId: exec_abc123 +toolCallId: call_abc123 +toolName: powershell +workingDirectory: /workspace/project + +` + var data map[string]interface{} + if err := yaml.Unmarshal([]byte(yamlData), &data); err != nil { + t.Fatalf("Failed to parse YAML: %v", err) + } + + ctx := prompty.NewLoadContext() + instance, err := prompty.LoadToolExecutionStartPayload(data, ctx) + if err != nil { + t.Fatalf("Failed to load ToolExecutionStartPayload: %v", err) + } + if instance.RequestId == nil || *instance.RequestId != "exec_abc123" { + t.Errorf(`Expected RequestId to be "exec_abc123", got %v`, instance.RequestId) + } + if instance.ToolCallId == nil || *instance.ToolCallId != "call_abc123" { + t.Errorf(`Expected ToolCallId to be "call_abc123", got %v`, instance.ToolCallId) + } + if instance.ToolName != "powershell" { + t.Errorf(`Expected ToolName to be "powershell", got %v`, instance.ToolName) + } + if instance.WorkingDirectory == nil || *instance.WorkingDirectory != "/workspace/project" { + t.Errorf(`Expected WorkingDirectory to be "/workspace/project", got %v`, instance.WorkingDirectory) + } +} + +// TestToolExecutionStartPayloadFromJSON tests loading ToolExecutionStartPayload through the generated JSON helper +func TestToolExecutionStartPayloadFromJSON(t *testing.T) { + jsonData := ` +{ + "requestId": "exec_abc123", + "toolCallId": "call_abc123", + "toolName": "powershell", + "workingDirectory": "/workspace/project" +} +` + + instance, err := prompty.ToolExecutionStartPayloadFromJSON(jsonData) + if err != nil { + t.Fatalf("Failed to load ToolExecutionStartPayload from JSON helper: %v", err) + } + if instance.RequestId == nil || *instance.RequestId != "exec_abc123" { + t.Errorf(`Expected RequestId to be "exec_abc123", got %v`, instance.RequestId) + } + if instance.ToolCallId == nil || *instance.ToolCallId != "call_abc123" { + t.Errorf(`Expected ToolCallId to be "call_abc123", got %v`, instance.ToolCallId) + } + if instance.ToolName != "powershell" { + t.Errorf(`Expected ToolName to be "powershell", got %v`, instance.ToolName) + } + if instance.WorkingDirectory == nil || *instance.WorkingDirectory != "/workspace/project" { + t.Errorf(`Expected WorkingDirectory to be "/workspace/project", got %v`, instance.WorkingDirectory) + } +} + +// TestToolExecutionStartPayloadFromYAML tests loading ToolExecutionStartPayload through the generated YAML helper +func TestToolExecutionStartPayloadFromYAML(t *testing.T) { + yamlData := ` +requestId: exec_abc123 +toolCallId: call_abc123 +toolName: powershell +workingDirectory: /workspace/project + +` + + instance, err := prompty.ToolExecutionStartPayloadFromYAML(yamlData) + if err != nil { + t.Fatalf("Failed to load ToolExecutionStartPayload from YAML helper: %v", err) + } + if instance.RequestId == nil || *instance.RequestId != "exec_abc123" { + t.Errorf(`Expected RequestId to be "exec_abc123", got %v`, instance.RequestId) + } + if instance.ToolCallId == nil || *instance.ToolCallId != "call_abc123" { + t.Errorf(`Expected ToolCallId to be "call_abc123", got %v`, instance.ToolCallId) + } + if instance.ToolName != "powershell" { + t.Errorf(`Expected ToolName to be "powershell", got %v`, instance.ToolName) + } + if instance.WorkingDirectory == nil || *instance.WorkingDirectory != "/workspace/project" { + t.Errorf(`Expected WorkingDirectory to be "/workspace/project", got %v`, instance.WorkingDirectory) + } +} + +// TestToolExecutionStartPayloadRoundtrip tests load -> save -> load produces equivalent data +func TestToolExecutionStartPayloadRoundtrip(t *testing.T) { + jsonData := ` +{ + "requestId": "exec_abc123", + "toolCallId": "call_abc123", + "toolName": "powershell", + "workingDirectory": "/workspace/project" +} +` + var data map[string]interface{} + if err := json.Unmarshal([]byte(jsonData), &data); err != nil { + t.Fatalf("Failed to parse JSON: %v", err) + } + + loadCtx := prompty.NewLoadContext() + instance, err := prompty.LoadToolExecutionStartPayload(data, loadCtx) + if err != nil { + t.Fatalf("Failed to load ToolExecutionStartPayload: %v", err) + } + saveCtx := prompty.NewSaveContext() + savedData := instance.Save(saveCtx) + + reloaded, err := prompty.LoadToolExecutionStartPayload(savedData, loadCtx) + if err != nil { + t.Fatalf("Failed to reload ToolExecutionStartPayload: %v", err) + } + if reloaded.RequestId == nil || *reloaded.RequestId != "exec_abc123" { + t.Errorf(`Expected RequestId to be "exec_abc123", got %v`, reloaded.RequestId) + } + if reloaded.ToolCallId == nil || *reloaded.ToolCallId != "call_abc123" { + t.Errorf(`Expected ToolCallId to be "call_abc123", got %v`, reloaded.ToolCallId) + } + if reloaded.ToolName != "powershell" { + t.Errorf(`Expected ToolName to be "powershell", got %v`, reloaded.ToolName) + } + if reloaded.WorkingDirectory == nil || *reloaded.WorkingDirectory != "/workspace/project" { + t.Errorf(`Expected WorkingDirectory to be "/workspace/project", got %v`, reloaded.WorkingDirectory) + } +} + +// TestToolExecutionStartPayloadToJSON tests that ToJSON produces valid JSON +func TestToolExecutionStartPayloadToJSON(t *testing.T) { + jsonData := ` +{ + "requestId": "exec_abc123", + "toolCallId": "call_abc123", + "toolName": "powershell", + "workingDirectory": "/workspace/project" +} +` + var data map[string]interface{} + if err := json.Unmarshal([]byte(jsonData), &data); err != nil { + t.Fatalf("Failed to parse JSON: %v", err) + } + + ctx := prompty.NewLoadContext() + instance, err := prompty.LoadToolExecutionStartPayload(data, ctx) + if err != nil { + t.Fatalf("Failed to load ToolExecutionStartPayload: %v", err) + } + jsonOutput, err := instance.ToJSON() + if err != nil { + t.Fatalf("Failed to convert to JSON: %v", err) + } + + var parsed map[string]interface{} + if err := json.Unmarshal([]byte(jsonOutput), &parsed); err != nil { + t.Fatalf("Failed to parse generated JSON: %v", err) + } + + reloaded, err := prompty.LoadToolExecutionStartPayload(parsed, ctx) + if err != nil { + t.Fatalf("Failed to reload generated JSON: %v", err) + } + if reloaded.RequestId == nil || *reloaded.RequestId != "exec_abc123" { + t.Errorf(`Expected RequestId to be "exec_abc123", got %v`, reloaded.RequestId) + } + if reloaded.ToolCallId == nil || *reloaded.ToolCallId != "call_abc123" { + t.Errorf(`Expected ToolCallId to be "call_abc123", got %v`, reloaded.ToolCallId) + } + if reloaded.ToolName != "powershell" { + t.Errorf(`Expected ToolName to be "powershell", got %v`, reloaded.ToolName) + } + if reloaded.WorkingDirectory == nil || *reloaded.WorkingDirectory != "/workspace/project" { + t.Errorf(`Expected WorkingDirectory to be "/workspace/project", got %v`, reloaded.WorkingDirectory) + } +} + +// TestToolExecutionStartPayloadToYAML tests that ToYAML produces valid YAML +func TestToolExecutionStartPayloadToYAML(t *testing.T) { + jsonData := ` +{ + "requestId": "exec_abc123", + "toolCallId": "call_abc123", + "toolName": "powershell", + "workingDirectory": "/workspace/project" +} +` + var data map[string]interface{} + if err := json.Unmarshal([]byte(jsonData), &data); err != nil { + t.Fatalf("Failed to parse JSON: %v", err) + } + + ctx := prompty.NewLoadContext() + instance, err := prompty.LoadToolExecutionStartPayload(data, ctx) + if err != nil { + t.Fatalf("Failed to load ToolExecutionStartPayload: %v", err) + } + yamlOutput, err := instance.ToYAML() + if err != nil { + t.Fatalf("Failed to convert to YAML: %v", err) + } + + var parsed map[string]interface{} + if err := yaml.Unmarshal([]byte(yamlOutput), &parsed); err != nil { + t.Fatalf("Failed to parse generated YAML: %v", err) + } + + reloaded, err := prompty.LoadToolExecutionStartPayload(parsed, ctx) + if err != nil { + t.Fatalf("Failed to reload generated YAML: %v", err) + } + if reloaded.RequestId == nil || *reloaded.RequestId != "exec_abc123" { + t.Errorf(`Expected RequestId to be "exec_abc123", got %v`, reloaded.RequestId) + } + if reloaded.ToolCallId == nil || *reloaded.ToolCallId != "call_abc123" { + t.Errorf(`Expected ToolCallId to be "call_abc123", got %v`, reloaded.ToolCallId) + } + if reloaded.ToolName != "powershell" { + t.Errorf(`Expected ToolName to be "powershell", got %v`, reloaded.ToolName) + } + if reloaded.WorkingDirectory == nil || *reloaded.WorkingDirectory != "/workspace/project" { + t.Errorf(`Expected WorkingDirectory to be "/workspace/project", got %v`, reloaded.WorkingDirectory) + } +} + +// TestToolExecutionStartPayloadFromJSONInvalid rejects malformed JSON instead of silently defaulting +func TestToolExecutionStartPayloadFromJSONInvalid(t *testing.T) { + if _, err := prompty.ToolExecutionStartPayloadFromJSON("{"); err == nil { + t.Fatalf("Expected malformed JSON to fail") + } +} diff --git a/runtime/go/prompty/model/tool_result.go b/runtime/go/prompty/model/tool_result.go index 885c8226..9c91f409 100644 --- a/runtime/go/prompty/model/tool_result.go +++ b/runtime/go/prompty/model/tool_result.go @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. // Group: conversation package prompty @@ -9,6 +10,16 @@ import ( "gopkg.in/yaml.v3" ) +// ToolResultStatus represents the allowed values for ToolResultStatus. +type ToolResultStatus string + +const ( + ToolResultStatusSuccess ToolResultStatus = "success" + ToolResultStatusError ToolResultStatus = "error" + ToolResultStatusCancelled ToolResultStatus = "cancelled" + ToolResultStatusTimeout ToolResultStatus = "timeout" +) + // ToolResult represents The result of a tool execution. Contains a list of content parts, enabling // rich tool results (text, images, files, audio) rather than just strings. // @@ -16,7 +27,11 @@ import ( // containing a single TextPart for backward compatibility. type ToolResult struct { - Parts []interface{} `json:"parts" yaml:"parts"` + Parts []interface{} `json:"parts" yaml:"parts"` + Status *ToolResultStatus `json:"status,omitempty" yaml:"status,omitempty"` + ErrorKind *string `json:"errorKind,omitempty" yaml:"errorKind,omitempty"` + ErrorMessage *string `json:"errorMessage,omitempty" yaml:"errorMessage,omitempty"` + DurationMs *float64 `json:"durationMs,omitempty" yaml:"durationMs,omitempty"` } // LoadToolResult creates a ToolResult from a map[string]interface{} @@ -37,13 +52,41 @@ func LoadToolResult(data interface{}, ctx *LoadContext) (ToolResult, error) { } } } + if val, ok := m["status"]; ok && val != nil { + v := ToolResultStatus(val.(string)) + result.Status = &v + } + if val, ok := m["errorKind"]; ok && val != nil { + v := string(val.(string)) + result.ErrorKind = &v + } + if val, ok := m["errorMessage"]; ok && val != nil { + v := string(val.(string)) + result.ErrorMessage = &v + } + if val, ok := m["durationMs"]; ok && val != nil { // Handle various numeric types from JSON/YAML/roundtrip + var v float64 + switch n := val.(type) { + case int: + v = float64(n) + case int32: + v = float64(n) + case int64: + v = float64(n) + case float32: + v = float64(n) + case float64: + v = n + } + result.DurationMs = &v + } } return result, nil } // Save serializes ToolResult to map[string]interface{} -func (obj *ToolResult) Save(ctx *SaveContext) map[string]interface{} { +func (obj ToolResult) Save(ctx *SaveContext) map[string]interface{} { result := make(map[string]interface{}) if obj.Parts != nil { arr := make([]interface{}, len(obj.Parts)) @@ -60,6 +103,18 @@ func (obj *ToolResult) Save(ctx *SaveContext) map[string]interface{} { } result["parts"] = arr } + if obj.Status != nil { + result["status"] = string(*obj.Status) + } + if obj.ErrorKind != nil { + result["errorKind"] = *obj.ErrorKind + } + if obj.ErrorMessage != nil { + result["errorMessage"] = *obj.ErrorMessage + } + if obj.DurationMs != nil { + result["durationMs"] = *obj.DurationMs + } return result } @@ -79,11 +134,7 @@ func (obj *ToolResult) ToJSON() (string, error) { func (obj *ToolResult) ToYAML() (string, error) { ctx := NewSaveContext() data := obj.Save(ctx) - bytes, err := yaml.Marshal(data) - if err != nil { - return "", err - } - return string(bytes), nil + return marshalYAMLDocument(data) } // FromJSON creates ToolResult from JSON string diff --git a/runtime/go/prompty/model/tool_result_payload.go b/runtime/go/prompty/model/tool_result_payload.go index 514dce79..3e5dfca5 100644 --- a/runtime/go/prompty/model/tool_result_payload.go +++ b/runtime/go/prompty/model/tool_result_payload.go @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. // Group: events package prompty @@ -37,7 +38,7 @@ func LoadToolResultPayload(data interface{}, ctx *LoadContext) (ToolResultPayloa } // Save serializes ToolResultPayload to map[string]interface{} -func (obj *ToolResultPayload) Save(ctx *SaveContext) map[string]interface{} { +func (obj ToolResultPayload) Save(ctx *SaveContext) map[string]interface{} { result := make(map[string]interface{}) result["name"] = obj.Name @@ -61,11 +62,7 @@ func (obj *ToolResultPayload) ToJSON() (string, error) { func (obj *ToolResultPayload) ToYAML() (string, error) { ctx := NewSaveContext() data := obj.Save(ctx) - bytes, err := yaml.Marshal(data) - if err != nil { - return "", err - } - return string(bytes), nil + return marshalYAMLDocument(data) } // FromJSON creates ToolResultPayload from JSON string diff --git a/runtime/go/prompty/model/tool_result_payload_test.go b/runtime/go/prompty/model/tool_result_payload_test.go index 218ad74f..04670733 100644 --- a/runtime/go/prompty/model/tool_result_payload_test.go +++ b/runtime/go/prompty/model/tool_result_payload_test.go @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. package prompty_test @@ -66,6 +67,51 @@ result: } } +// TestToolResultPayloadFromJSON tests loading ToolResultPayload through the generated JSON helper +func TestToolResultPayloadFromJSON(t *testing.T) { + jsonData := ` +{ + "name": "get_weather", + "result": { + "parts": [ + { + "kind": "text", + "value": "72°F and sunny" + } + ] + } +} +` + + instance, err := prompty.ToolResultPayloadFromJSON(jsonData) + if err != nil { + t.Fatalf("Failed to load ToolResultPayload from JSON helper: %v", err) + } + if instance.Name != "get_weather" { + t.Errorf(`Expected Name to be "get_weather", got %v`, instance.Name) + } +} + +// TestToolResultPayloadFromYAML tests loading ToolResultPayload through the generated YAML helper +func TestToolResultPayloadFromYAML(t *testing.T) { + yamlData := ` +name: get_weather +result: + parts: + - kind: text + value: 72°F and sunny + +` + + instance, err := prompty.ToolResultPayloadFromYAML(yamlData) + if err != nil { + t.Fatalf("Failed to load ToolResultPayload from YAML helper: %v", err) + } + if instance.Name != "get_weather" { + t.Errorf(`Expected Name to be "get_weather", got %v`, instance.Name) + } +} + // TestToolResultPayloadRoundtrip tests load -> save -> load produces equivalent data func TestToolResultPayloadRoundtrip(t *testing.T) { jsonData := ` @@ -137,6 +183,14 @@ func TestToolResultPayloadToJSON(t *testing.T) { if err := json.Unmarshal([]byte(jsonOutput), &parsed); err != nil { t.Fatalf("Failed to parse generated JSON: %v", err) } + + reloaded, err := prompty.LoadToolResultPayload(parsed, ctx) + if err != nil { + t.Fatalf("Failed to reload generated JSON: %v", err) + } + if reloaded.Name != "get_weather" { + t.Errorf(`Expected Name to be "get_weather", got %v`, reloaded.Name) + } } // TestToolResultPayloadToYAML tests that ToYAML produces valid YAML @@ -173,4 +227,19 @@ func TestToolResultPayloadToYAML(t *testing.T) { if err := yaml.Unmarshal([]byte(yamlOutput), &parsed); err != nil { t.Fatalf("Failed to parse generated YAML: %v", err) } + + reloaded, err := prompty.LoadToolResultPayload(parsed, ctx) + if err != nil { + t.Fatalf("Failed to reload generated YAML: %v", err) + } + if reloaded.Name != "get_weather" { + t.Errorf(`Expected Name to be "get_weather", got %v`, reloaded.Name) + } +} + +// TestToolResultPayloadFromJSONInvalid rejects malformed JSON instead of silently defaulting +func TestToolResultPayloadFromJSONInvalid(t *testing.T) { + if _, err := prompty.ToolResultPayloadFromJSON("{"); err == nil { + t.Fatalf("Expected malformed JSON to fail") + } } diff --git a/runtime/go/prompty/model/tool_result_test.go b/runtime/go/prompty/model/tool_result_test.go index 4a623f37..5cf823fb 100644 --- a/runtime/go/prompty/model/tool_result_test.go +++ b/runtime/go/prompty/model/tool_result_test.go @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. package prompty_test @@ -20,7 +21,10 @@ func TestToolResultLoadJSON(t *testing.T) { "kind": "text", "value": "72°F and sunny" } - ] + ], + "errorKind": "missing_tool", + "errorMessage": "Tool 'get_weather' is not registered", + "durationMs": 42 } ` var data map[string]interface{} @@ -33,7 +37,28 @@ func TestToolResultLoadJSON(t *testing.T) { if err != nil { t.Fatalf("Failed to load ToolResult: %v", err) } - _ = instance // No scalar properties to validate + if instance.ErrorKind == nil || *instance.ErrorKind != "missing_tool" { + t.Errorf(`Expected ErrorKind to be "missing_tool", got %v`, instance.ErrorKind) + } + if instance.ErrorMessage == nil || *instance.ErrorMessage != "Tool 'get_weather' is not registered" { + t.Errorf(`Expected ErrorMessage to be "Tool 'get_weather' is not registered", got %v`, instance.ErrorMessage) + } + if instance.DurationMs == nil || *instance.DurationMs != 42 { + t.Errorf(`Expected DurationMs to be 42, got %v`, instance.DurationMs) + } + if len(instance.Parts) != 1 { + t.Fatalf("Expected Parts length to be 1, got %d", len(instance.Parts)) + } + parts0Value, ok := instance.Parts[0].(prompty.TextPart) + if !ok { + t.Fatalf("Expected Parts[0] to be prompty.TextPart, got %T", instance.Parts[0]) + } + if parts0Value.Kind != "text" { + t.Errorf(`Expected Kind to be "text", got %v`, parts0Value.Kind) + } + if parts0Value.Value != "72°F and sunny" { + t.Errorf(`Expected Value to be "72°F and sunny", got %v`, parts0Value.Value) + } } // TestToolResultLoadYAML tests loading ToolResult from YAML @@ -42,6 +67,9 @@ func TestToolResultLoadYAML(t *testing.T) { parts: - kind: text value: 72°F and sunny +errorKind: missing_tool +errorMessage: Tool 'get_weather' is not registered +durationMs: 42 ` var data map[string]interface{} @@ -54,7 +82,112 @@ parts: if err != nil { t.Fatalf("Failed to load ToolResult: %v", err) } - _ = instance // No scalar properties to validate + if instance.ErrorKind == nil || *instance.ErrorKind != "missing_tool" { + t.Errorf(`Expected ErrorKind to be "missing_tool", got %v`, instance.ErrorKind) + } + if instance.ErrorMessage == nil || *instance.ErrorMessage != "Tool 'get_weather' is not registered" { + t.Errorf(`Expected ErrorMessage to be "Tool 'get_weather' is not registered", got %v`, instance.ErrorMessage) + } + if instance.DurationMs == nil || *instance.DurationMs != 42 { + t.Errorf(`Expected DurationMs to be 42, got %v`, instance.DurationMs) + } + if len(instance.Parts) != 1 { + t.Fatalf("Expected Parts length to be 1, got %d", len(instance.Parts)) + } + parts0Value, ok := instance.Parts[0].(prompty.TextPart) + if !ok { + t.Fatalf("Expected Parts[0] to be prompty.TextPart, got %T", instance.Parts[0]) + } + if parts0Value.Kind != "text" { + t.Errorf(`Expected Kind to be "text", got %v`, parts0Value.Kind) + } + if parts0Value.Value != "72°F and sunny" { + t.Errorf(`Expected Value to be "72°F and sunny", got %v`, parts0Value.Value) + } +} + +// TestToolResultFromJSON tests loading ToolResult through the generated JSON helper +func TestToolResultFromJSON(t *testing.T) { + jsonData := ` +{ + "parts": [ + { + "kind": "text", + "value": "72°F and sunny" + } + ], + "errorKind": "missing_tool", + "errorMessage": "Tool 'get_weather' is not registered", + "durationMs": 42 +} +` + + instance, err := prompty.ToolResultFromJSON(jsonData) + if err != nil { + t.Fatalf("Failed to load ToolResult from JSON helper: %v", err) + } + if instance.ErrorKind == nil || *instance.ErrorKind != "missing_tool" { + t.Errorf(`Expected ErrorKind to be "missing_tool", got %v`, instance.ErrorKind) + } + if instance.ErrorMessage == nil || *instance.ErrorMessage != "Tool 'get_weather' is not registered" { + t.Errorf(`Expected ErrorMessage to be "Tool 'get_weather' is not registered", got %v`, instance.ErrorMessage) + } + if instance.DurationMs == nil || *instance.DurationMs != 42 { + t.Errorf(`Expected DurationMs to be 42, got %v`, instance.DurationMs) + } + if len(instance.Parts) != 1 { + t.Fatalf("Expected Parts length to be 1, got %d", len(instance.Parts)) + } + parts0Value, ok := instance.Parts[0].(prompty.TextPart) + if !ok { + t.Fatalf("Expected Parts[0] to be prompty.TextPart, got %T", instance.Parts[0]) + } + if parts0Value.Kind != "text" { + t.Errorf(`Expected Kind to be "text", got %v`, parts0Value.Kind) + } + if parts0Value.Value != "72°F and sunny" { + t.Errorf(`Expected Value to be "72°F and sunny", got %v`, parts0Value.Value) + } +} + +// TestToolResultFromYAML tests loading ToolResult through the generated YAML helper +func TestToolResultFromYAML(t *testing.T) { + yamlData := ` +parts: + - kind: text + value: 72°F and sunny +errorKind: missing_tool +errorMessage: Tool 'get_weather' is not registered +durationMs: 42 + +` + + instance, err := prompty.ToolResultFromYAML(yamlData) + if err != nil { + t.Fatalf("Failed to load ToolResult from YAML helper: %v", err) + } + if instance.ErrorKind == nil || *instance.ErrorKind != "missing_tool" { + t.Errorf(`Expected ErrorKind to be "missing_tool", got %v`, instance.ErrorKind) + } + if instance.ErrorMessage == nil || *instance.ErrorMessage != "Tool 'get_weather' is not registered" { + t.Errorf(`Expected ErrorMessage to be "Tool 'get_weather' is not registered", got %v`, instance.ErrorMessage) + } + if instance.DurationMs == nil || *instance.DurationMs != 42 { + t.Errorf(`Expected DurationMs to be 42, got %v`, instance.DurationMs) + } + if len(instance.Parts) != 1 { + t.Fatalf("Expected Parts length to be 1, got %d", len(instance.Parts)) + } + parts0Value, ok := instance.Parts[0].(prompty.TextPart) + if !ok { + t.Fatalf("Expected Parts[0] to be prompty.TextPart, got %T", instance.Parts[0]) + } + if parts0Value.Kind != "text" { + t.Errorf(`Expected Kind to be "text", got %v`, parts0Value.Kind) + } + if parts0Value.Value != "72°F and sunny" { + t.Errorf(`Expected Value to be "72°F and sunny", got %v`, parts0Value.Value) + } } // TestToolResultRoundtrip tests load -> save -> load produces equivalent data @@ -66,7 +199,10 @@ func TestToolResultRoundtrip(t *testing.T) { "kind": "text", "value": "72°F and sunny" } - ] + ], + "errorKind": "missing_tool", + "errorMessage": "Tool 'get_weather' is not registered", + "durationMs": 42 } ` var data map[string]interface{} @@ -86,7 +222,28 @@ func TestToolResultRoundtrip(t *testing.T) { if err != nil { t.Fatalf("Failed to reload ToolResult: %v", err) } - _ = reloaded // No scalar properties to validate + if reloaded.ErrorKind == nil || *reloaded.ErrorKind != "missing_tool" { + t.Errorf(`Expected ErrorKind to be "missing_tool", got %v`, reloaded.ErrorKind) + } + if reloaded.ErrorMessage == nil || *reloaded.ErrorMessage != "Tool 'get_weather' is not registered" { + t.Errorf(`Expected ErrorMessage to be "Tool 'get_weather' is not registered", got %v`, reloaded.ErrorMessage) + } + if reloaded.DurationMs == nil || *reloaded.DurationMs != 42 { + t.Errorf(`Expected DurationMs to be 42, got %v`, reloaded.DurationMs) + } + if len(reloaded.Parts) != 1 { + t.Fatalf("Expected Parts length to be 1, got %d", len(reloaded.Parts)) + } + parts0Value, ok := reloaded.Parts[0].(prompty.TextPart) + if !ok { + t.Fatalf("Expected Parts[0] to be prompty.TextPart, got %T", reloaded.Parts[0]) + } + if parts0Value.Kind != "text" { + t.Errorf(`Expected Kind to be "text", got %v`, parts0Value.Kind) + } + if parts0Value.Value != "72°F and sunny" { + t.Errorf(`Expected Value to be "72°F and sunny", got %v`, parts0Value.Value) + } } // TestToolResultToJSON tests that ToJSON produces valid JSON @@ -98,7 +255,10 @@ func TestToolResultToJSON(t *testing.T) { "kind": "text", "value": "72°F and sunny" } - ] + ], + "errorKind": "missing_tool", + "errorMessage": "Tool 'get_weather' is not registered", + "durationMs": 42 } ` var data map[string]interface{} @@ -120,6 +280,33 @@ func TestToolResultToJSON(t *testing.T) { if err := json.Unmarshal([]byte(jsonOutput), &parsed); err != nil { t.Fatalf("Failed to parse generated JSON: %v", err) } + + reloaded, err := prompty.LoadToolResult(parsed, ctx) + if err != nil { + t.Fatalf("Failed to reload generated JSON: %v", err) + } + if reloaded.ErrorKind == nil || *reloaded.ErrorKind != "missing_tool" { + t.Errorf(`Expected ErrorKind to be "missing_tool", got %v`, reloaded.ErrorKind) + } + if reloaded.ErrorMessage == nil || *reloaded.ErrorMessage != "Tool 'get_weather' is not registered" { + t.Errorf(`Expected ErrorMessage to be "Tool 'get_weather' is not registered", got %v`, reloaded.ErrorMessage) + } + if reloaded.DurationMs == nil || *reloaded.DurationMs != 42 { + t.Errorf(`Expected DurationMs to be 42, got %v`, reloaded.DurationMs) + } + if len(reloaded.Parts) != 1 { + t.Fatalf("Expected Parts length to be 1, got %d", len(reloaded.Parts)) + } + parts0Value, ok := reloaded.Parts[0].(prompty.TextPart) + if !ok { + t.Fatalf("Expected Parts[0] to be prompty.TextPart, got %T", reloaded.Parts[0]) + } + if parts0Value.Kind != "text" { + t.Errorf(`Expected Kind to be "text", got %v`, parts0Value.Kind) + } + if parts0Value.Value != "72°F and sunny" { + t.Errorf(`Expected Value to be "72°F and sunny", got %v`, parts0Value.Value) + } } // TestToolResultToYAML tests that ToYAML produces valid YAML @@ -131,7 +318,10 @@ func TestToolResultToYAML(t *testing.T) { "kind": "text", "value": "72°F and sunny" } - ] + ], + "errorKind": "missing_tool", + "errorMessage": "Tool 'get_weather' is not registered", + "durationMs": 42 } ` var data map[string]interface{} @@ -153,4 +343,38 @@ func TestToolResultToYAML(t *testing.T) { if err := yaml.Unmarshal([]byte(yamlOutput), &parsed); err != nil { t.Fatalf("Failed to parse generated YAML: %v", err) } + + reloaded, err := prompty.LoadToolResult(parsed, ctx) + if err != nil { + t.Fatalf("Failed to reload generated YAML: %v", err) + } + if reloaded.ErrorKind == nil || *reloaded.ErrorKind != "missing_tool" { + t.Errorf(`Expected ErrorKind to be "missing_tool", got %v`, reloaded.ErrorKind) + } + if reloaded.ErrorMessage == nil || *reloaded.ErrorMessage != "Tool 'get_weather' is not registered" { + t.Errorf(`Expected ErrorMessage to be "Tool 'get_weather' is not registered", got %v`, reloaded.ErrorMessage) + } + if reloaded.DurationMs == nil || *reloaded.DurationMs != 42 { + t.Errorf(`Expected DurationMs to be 42, got %v`, reloaded.DurationMs) + } + if len(reloaded.Parts) != 1 { + t.Fatalf("Expected Parts length to be 1, got %d", len(reloaded.Parts)) + } + parts0Value, ok := reloaded.Parts[0].(prompty.TextPart) + if !ok { + t.Fatalf("Expected Parts[0] to be prompty.TextPart, got %T", reloaded.Parts[0]) + } + if parts0Value.Kind != "text" { + t.Errorf(`Expected Kind to be "text", got %v`, parts0Value.Kind) + } + if parts0Value.Value != "72°F and sunny" { + t.Errorf(`Expected Value to be "72°F and sunny", got %v`, parts0Value.Value) + } +} + +// TestToolResultFromJSONInvalid rejects malformed JSON instead of silently defaulting +func TestToolResultFromJSONInvalid(t *testing.T) { + if _, err := prompty.ToolResultFromJSON("{"); err == nil { + t.Fatalf("Expected malformed JSON to fail") + } } diff --git a/runtime/go/prompty/model/tool_test.go b/runtime/go/prompty/model/tool_test.go index 8e5519f4..2c8f2653 100644 --- a/runtime/go/prompty/model/tool_test.go +++ b/runtime/go/prompty/model/tool_test.go @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. package prompty_test @@ -63,6 +64,48 @@ bindings: // Note: Validation skipped for polymorphic base types - test child types directly } +// TestToolFromJSON tests loading Tool through the generated JSON helper +func TestToolFromJSON(t *testing.T) { + jsonData := ` +{ + "name": "my-tool", + "kind": "function", + "description": "A description of the tool", + "bindings": { + "input": "value" + } +} +` + + instance, err := prompty.ToolFromJSON(jsonData) + if err != nil { + t.Fatalf("Failed to load Tool from JSON helper: %v", err) + } + // Polymorphic types return interface{}, extract common fields via reflection or type-specific access + _ = instance // Load succeeded, exact type depends on discriminator + // Note: Validation skipped for polymorphic base types - test child types directly +} + +// TestToolFromYAML tests loading Tool through the generated YAML helper +func TestToolFromYAML(t *testing.T) { + yamlData := ` +name: my-tool +kind: function +description: A description of the tool +bindings: + input: value + +` + + instance, err := prompty.ToolFromYAML(yamlData) + if err != nil { + t.Fatalf("Failed to load Tool from YAML helper: %v", err) + } + // Polymorphic types return interface{}, extract common fields via reflection or type-specific access + _ = instance // Load succeeded, exact type depends on discriminator + // Note: Validation skipped for polymorphic base types - test child types directly +} + // TestToolRoundtrip tests load -> save -> load produces equivalent data func TestToolRoundtrip(t *testing.T) { jsonData := ` @@ -143,3 +186,10 @@ func TestToolToYAML(t *testing.T) { _ = instance // Load succeeded, exact type depends on discriminator // Note: ToYAML test skipped for polymorphic base types - test child types directly } + +// TestToolFromJSONInvalid rejects malformed JSON instead of silently defaulting +func TestToolFromJSONInvalid(t *testing.T) { + if _, err := prompty.ToolFromJSON("{"); err == nil { + t.Fatalf("Expected malformed JSON to fail") + } +} diff --git a/runtime/go/prompty/model/trace_file.go b/runtime/go/prompty/model/trace_file.go index bb696675..2458a43c 100644 --- a/runtime/go/prompty/model/trace_file.go +++ b/runtime/go/prompty/model/trace_file.go @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. // Group: tracing package prompty @@ -41,7 +42,7 @@ func LoadTraceFile(data interface{}, ctx *LoadContext) (TraceFile, error) { } // Save serializes TraceFile to map[string]interface{} -func (obj *TraceFile) Save(ctx *SaveContext) map[string]interface{} { +func (obj TraceFile) Save(ctx *SaveContext) map[string]interface{} { result := make(map[string]interface{}) result["runtime"] = obj.Runtime result["version"] = obj.Version @@ -66,11 +67,7 @@ func (obj *TraceFile) ToJSON() (string, error) { func (obj *TraceFile) ToYAML() (string, error) { ctx := NewSaveContext() data := obj.Save(ctx) - bytes, err := yaml.Marshal(data) - if err != nil { - return "", err - } - return string(bytes), nil + return marshalYAMLDocument(data) } // FromJSON creates TraceFile from JSON string diff --git a/runtime/go/prompty/model/trace_file_test.go b/runtime/go/prompty/model/trace_file_test.go index 69658e4a..4c10029e 100644 --- a/runtime/go/prompty/model/trace_file_test.go +++ b/runtime/go/prompty/model/trace_file_test.go @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. package prompty_test @@ -62,6 +63,47 @@ version: 2.0.0 } } +// TestTraceFileFromJSON tests loading TraceFile through the generated JSON helper +func TestTraceFileFromJSON(t *testing.T) { + jsonData := ` +{ + "runtime": "python", + "version": "2.0.0" +} +` + + instance, err := prompty.TraceFileFromJSON(jsonData) + if err != nil { + t.Fatalf("Failed to load TraceFile from JSON helper: %v", err) + } + if instance.Runtime != "python" { + t.Errorf(`Expected Runtime to be "python", got %v`, instance.Runtime) + } + if instance.Version != "2.0.0" { + t.Errorf(`Expected Version to be "2.0.0", got %v`, instance.Version) + } +} + +// TestTraceFileFromYAML tests loading TraceFile through the generated YAML helper +func TestTraceFileFromYAML(t *testing.T) { + yamlData := ` +runtime: python +version: 2.0.0 + +` + + instance, err := prompty.TraceFileFromYAML(yamlData) + if err != nil { + t.Fatalf("Failed to load TraceFile from YAML helper: %v", err) + } + if instance.Runtime != "python" { + t.Errorf(`Expected Runtime to be "python", got %v`, instance.Runtime) + } + if instance.Version != "2.0.0" { + t.Errorf(`Expected Version to be "2.0.0", got %v`, instance.Version) + } +} + // TestTraceFileRoundtrip tests load -> save -> load produces equivalent data func TestTraceFileRoundtrip(t *testing.T) { jsonData := ` @@ -122,6 +164,17 @@ func TestTraceFileToJSON(t *testing.T) { if err := json.Unmarshal([]byte(jsonOutput), &parsed); err != nil { t.Fatalf("Failed to parse generated JSON: %v", err) } + + reloaded, err := prompty.LoadTraceFile(parsed, ctx) + if err != nil { + t.Fatalf("Failed to reload generated JSON: %v", err) + } + if reloaded.Runtime != "python" { + t.Errorf(`Expected Runtime to be "python", got %v`, reloaded.Runtime) + } + if reloaded.Version != "2.0.0" { + t.Errorf(`Expected Version to be "2.0.0", got %v`, reloaded.Version) + } } // TestTraceFileToYAML tests that ToYAML produces valid YAML @@ -151,4 +204,22 @@ func TestTraceFileToYAML(t *testing.T) { if err := yaml.Unmarshal([]byte(yamlOutput), &parsed); err != nil { t.Fatalf("Failed to parse generated YAML: %v", err) } + + reloaded, err := prompty.LoadTraceFile(parsed, ctx) + if err != nil { + t.Fatalf("Failed to reload generated YAML: %v", err) + } + if reloaded.Runtime != "python" { + t.Errorf(`Expected Runtime to be "python", got %v`, reloaded.Runtime) + } + if reloaded.Version != "2.0.0" { + t.Errorf(`Expected Version to be "2.0.0", got %v`, reloaded.Version) + } +} + +// TestTraceFileFromJSONInvalid rejects malformed JSON instead of silently defaulting +func TestTraceFileFromJSONInvalid(t *testing.T) { + if _, err := prompty.TraceFileFromJSON("{"); err == nil { + t.Fatalf("Expected malformed JSON to fail") + } } diff --git a/runtime/go/prompty/model/trace_span.go b/runtime/go/prompty/model/trace_span.go index 1deba3ad..36210bf2 100644 --- a/runtime/go/prompty/model/trace_span.go +++ b/runtime/go/prompty/model/trace_span.go @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. // Group: tracing package prompty @@ -68,7 +69,8 @@ func LoadTraceSpan(data interface{}, ctx *LoadContext) (TraceSpan, error) { } } if val, ok := m["__frames"]; ok && val != nil { - if arr, ok := val.([]interface{}); ok { + switch arr := val.(type) { + case []interface{}: result._Frames = arr } } @@ -78,7 +80,7 @@ func LoadTraceSpan(data interface{}, ctx *LoadContext) (TraceSpan, error) { } // Save serializes TraceSpan to map[string]interface{} -func (obj *TraceSpan) Save(ctx *SaveContext) map[string]interface{} { +func (obj TraceSpan) Save(ctx *SaveContext) map[string]interface{} { result := make(map[string]interface{}) result["name"] = obj.Name @@ -121,11 +123,7 @@ func (obj *TraceSpan) ToJSON() (string, error) { func (obj *TraceSpan) ToYAML() (string, error) { ctx := NewSaveContext() data := obj.Save(ctx) - bytes, err := yaml.Marshal(data) - if err != nil { - return "", err - } - return string(bytes), nil + return marshalYAMLDocument(data) } // FromJSON creates TraceSpan from JSON string diff --git a/runtime/go/prompty/model/trace_span_test.go b/runtime/go/prompty/model/trace_span_test.go index 5e6eae92..530edfba 100644 --- a/runtime/go/prompty/model/trace_span_test.go +++ b/runtime/go/prompty/model/trace_span_test.go @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. package prompty_test @@ -70,6 +71,55 @@ error: Connection refused } } +// TestTraceSpanFromJSON tests loading TraceSpan through the generated JSON helper +func TestTraceSpanFromJSON(t *testing.T) { + jsonData := ` +{ + "name": "prompty.core.pipeline.run", + "signature": "prompty.core.pipeline.run", + "error": "Connection refused" +} +` + + instance, err := prompty.TraceSpanFromJSON(jsonData) + if err != nil { + t.Fatalf("Failed to load TraceSpan from JSON helper: %v", err) + } + if instance.Name != "prompty.core.pipeline.run" { + t.Errorf(`Expected Name to be "prompty.core.pipeline.run", got %v`, instance.Name) + } + if instance.Signature == nil || *instance.Signature != "prompty.core.pipeline.run" { + t.Errorf(`Expected Signature to be "prompty.core.pipeline.run", got %v`, instance.Signature) + } + if instance.Error == nil || *instance.Error != "Connection refused" { + t.Errorf(`Expected Error to be "Connection refused", got %v`, instance.Error) + } +} + +// TestTraceSpanFromYAML tests loading TraceSpan through the generated YAML helper +func TestTraceSpanFromYAML(t *testing.T) { + yamlData := ` +name: prompty.core.pipeline.run +signature: prompty.core.pipeline.run +error: Connection refused + +` + + instance, err := prompty.TraceSpanFromYAML(yamlData) + if err != nil { + t.Fatalf("Failed to load TraceSpan from YAML helper: %v", err) + } + if instance.Name != "prompty.core.pipeline.run" { + t.Errorf(`Expected Name to be "prompty.core.pipeline.run", got %v`, instance.Name) + } + if instance.Signature == nil || *instance.Signature != "prompty.core.pipeline.run" { + t.Errorf(`Expected Signature to be "prompty.core.pipeline.run", got %v`, instance.Signature) + } + if instance.Error == nil || *instance.Error != "Connection refused" { + t.Errorf(`Expected Error to be "Connection refused", got %v`, instance.Error) + } +} + // TestTraceSpanRoundtrip tests load -> save -> load produces equivalent data func TestTraceSpanRoundtrip(t *testing.T) { jsonData := ` @@ -135,6 +185,20 @@ func TestTraceSpanToJSON(t *testing.T) { if err := json.Unmarshal([]byte(jsonOutput), &parsed); err != nil { t.Fatalf("Failed to parse generated JSON: %v", err) } + + reloaded, err := prompty.LoadTraceSpan(parsed, ctx) + if err != nil { + t.Fatalf("Failed to reload generated JSON: %v", err) + } + if reloaded.Name != "prompty.core.pipeline.run" { + t.Errorf(`Expected Name to be "prompty.core.pipeline.run", got %v`, reloaded.Name) + } + if reloaded.Signature == nil || *reloaded.Signature != "prompty.core.pipeline.run" { + t.Errorf(`Expected Signature to be "prompty.core.pipeline.run", got %v`, reloaded.Signature) + } + if reloaded.Error == nil || *reloaded.Error != "Connection refused" { + t.Errorf(`Expected Error to be "Connection refused", got %v`, reloaded.Error) + } } // TestTraceSpanToYAML tests that ToYAML produces valid YAML @@ -165,4 +229,25 @@ func TestTraceSpanToYAML(t *testing.T) { if err := yaml.Unmarshal([]byte(yamlOutput), &parsed); err != nil { t.Fatalf("Failed to parse generated YAML: %v", err) } + + reloaded, err := prompty.LoadTraceSpan(parsed, ctx) + if err != nil { + t.Fatalf("Failed to reload generated YAML: %v", err) + } + if reloaded.Name != "prompty.core.pipeline.run" { + t.Errorf(`Expected Name to be "prompty.core.pipeline.run", got %v`, reloaded.Name) + } + if reloaded.Signature == nil || *reloaded.Signature != "prompty.core.pipeline.run" { + t.Errorf(`Expected Signature to be "prompty.core.pipeline.run", got %v`, reloaded.Signature) + } + if reloaded.Error == nil || *reloaded.Error != "Connection refused" { + t.Errorf(`Expected Error to be "Connection refused", got %v`, reloaded.Error) + } +} + +// TestTraceSpanFromJSONInvalid rejects malformed JSON instead of silently defaulting +func TestTraceSpanFromJSONInvalid(t *testing.T) { + if _, err := prompty.TraceSpanFromJSON("{"); err == nil { + t.Fatalf("Expected malformed JSON to fail") + } } diff --git a/runtime/go/prompty/model/trace_time.go b/runtime/go/prompty/model/trace_time.go index 0fe1db62..ace3171c 100644 --- a/runtime/go/prompty/model/trace_time.go +++ b/runtime/go/prompty/model/trace_time.go @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. // Group: tracing package prompty @@ -51,7 +52,7 @@ func LoadTraceTime(data interface{}, ctx *LoadContext) (TraceTime, error) { } // Save serializes TraceTime to map[string]interface{} -func (obj *TraceTime) Save(ctx *SaveContext) map[string]interface{} { +func (obj TraceTime) Save(ctx *SaveContext) map[string]interface{} { result := make(map[string]interface{}) result["start"] = obj.Start result["end"] = obj.End @@ -75,11 +76,7 @@ func (obj *TraceTime) ToJSON() (string, error) { func (obj *TraceTime) ToYAML() (string, error) { ctx := NewSaveContext() data := obj.Save(ctx) - bytes, err := yaml.Marshal(data) - if err != nil { - return "", err - } - return string(bytes), nil + return marshalYAMLDocument(data) } // FromJSON creates TraceTime from JSON string diff --git a/runtime/go/prompty/model/trace_time_test.go b/runtime/go/prompty/model/trace_time_test.go index 56092ce1..3d303a28 100644 --- a/runtime/go/prompty/model/trace_time_test.go +++ b/runtime/go/prompty/model/trace_time_test.go @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. package prompty_test @@ -70,6 +71,55 @@ duration: 1000 } } +// TestTraceTimeFromJSON tests loading TraceTime through the generated JSON helper +func TestTraceTimeFromJSON(t *testing.T) { + jsonData := ` +{ + "start": "2026-04-04T12:00:00Z", + "end": "2026-04-04T12:00:01Z", + "duration": 1000 +} +` + + instance, err := prompty.TraceTimeFromJSON(jsonData) + if err != nil { + t.Fatalf("Failed to load TraceTime from JSON helper: %v", err) + } + if instance.Start != "2026-04-04T12:00:00Z" { + t.Errorf(`Expected Start to be "2026-04-04T12:00:00Z", got %v`, instance.Start) + } + if instance.End != "2026-04-04T12:00:01Z" { + t.Errorf(`Expected End to be "2026-04-04T12:00:01Z", got %v`, instance.End) + } + if instance.Duration != 1000 { + t.Errorf(`Expected Duration to be 1000, got %v`, instance.Duration) + } +} + +// TestTraceTimeFromYAML tests loading TraceTime through the generated YAML helper +func TestTraceTimeFromYAML(t *testing.T) { + yamlData := ` +start: "2026-04-04T12:00:00Z" +end: "2026-04-04T12:00:01Z" +duration: 1000 + +` + + instance, err := prompty.TraceTimeFromYAML(yamlData) + if err != nil { + t.Fatalf("Failed to load TraceTime from YAML helper: %v", err) + } + if instance.Start != "2026-04-04T12:00:00Z" { + t.Errorf(`Expected Start to be "2026-04-04T12:00:00Z", got %v`, instance.Start) + } + if instance.End != "2026-04-04T12:00:01Z" { + t.Errorf(`Expected End to be "2026-04-04T12:00:01Z", got %v`, instance.End) + } + if instance.Duration != 1000 { + t.Errorf(`Expected Duration to be 1000, got %v`, instance.Duration) + } +} + // TestTraceTimeRoundtrip tests load -> save -> load produces equivalent data func TestTraceTimeRoundtrip(t *testing.T) { jsonData := ` @@ -135,6 +185,20 @@ func TestTraceTimeToJSON(t *testing.T) { if err := json.Unmarshal([]byte(jsonOutput), &parsed); err != nil { t.Fatalf("Failed to parse generated JSON: %v", err) } + + reloaded, err := prompty.LoadTraceTime(parsed, ctx) + if err != nil { + t.Fatalf("Failed to reload generated JSON: %v", err) + } + if reloaded.Start != "2026-04-04T12:00:00Z" { + t.Errorf(`Expected Start to be "2026-04-04T12:00:00Z", got %v`, reloaded.Start) + } + if reloaded.End != "2026-04-04T12:00:01Z" { + t.Errorf(`Expected End to be "2026-04-04T12:00:01Z", got %v`, reloaded.End) + } + if reloaded.Duration != 1000 { + t.Errorf(`Expected Duration to be 1000, got %v`, reloaded.Duration) + } } // TestTraceTimeToYAML tests that ToYAML produces valid YAML @@ -165,4 +229,25 @@ func TestTraceTimeToYAML(t *testing.T) { if err := yaml.Unmarshal([]byte(yamlOutput), &parsed); err != nil { t.Fatalf("Failed to parse generated YAML: %v", err) } + + reloaded, err := prompty.LoadTraceTime(parsed, ctx) + if err != nil { + t.Fatalf("Failed to reload generated YAML: %v", err) + } + if reloaded.Start != "2026-04-04T12:00:00Z" { + t.Errorf(`Expected Start to be "2026-04-04T12:00:00Z", got %v`, reloaded.Start) + } + if reloaded.End != "2026-04-04T12:00:01Z" { + t.Errorf(`Expected End to be "2026-04-04T12:00:01Z", got %v`, reloaded.End) + } + if reloaded.Duration != 1000 { + t.Errorf(`Expected Duration to be 1000, got %v`, reloaded.Duration) + } +} + +// TestTraceTimeFromJSONInvalid rejects malformed JSON instead of silently defaulting +func TestTraceTimeFromJSONInvalid(t *testing.T) { + if _, err := prompty.TraceTimeFromJSON("{"); err == nil { + t.Fatalf("Expected malformed JSON to fail") + } } diff --git a/runtime/go/prompty/model/trajectory_event.go b/runtime/go/prompty/model/trajectory_event.go new file mode 100644 index 00000000..78c416c1 --- /dev/null +++ b/runtime/go/prompty/model/trajectory_event.go @@ -0,0 +1,154 @@ +// +// Code generated by Typra emitter; DO NOT EDIT. +// Group: events + +package prompty + +import ( + "encoding/json" + + "gopkg.in/yaml.v3" +) + +// TrajectoryEvent represents A compact, replay-oriented record of one harness-side action or observation. + +type TrajectoryEvent struct { + Id *string `json:"id,omitempty" yaml:"id,omitempty"` + SessionId *string `json:"sessionId,omitempty" yaml:"sessionId,omitempty"` + TurnId *string `json:"turnId,omitempty" yaml:"turnId,omitempty"` + ToolCallId *string `json:"toolCallId,omitempty" yaml:"toolCallId,omitempty"` + TurnIndex *int32 `json:"turnIndex,omitempty" yaml:"turnIndex,omitempty"` + EventType string `json:"eventType" yaml:"eventType"` + Data map[string]interface{} `json:"data,omitempty" yaml:"data,omitempty"` + CreatedAt *string `json:"createdAt,omitempty" yaml:"createdAt,omitempty"` + Redaction *RedactionMetadata `json:"redaction,omitempty" yaml:"redaction,omitempty"` +} + +// LoadTrajectoryEvent creates a TrajectoryEvent from a map[string]interface{} +func LoadTrajectoryEvent(data interface{}, ctx *LoadContext) (TrajectoryEvent, error) { + result := TrajectoryEvent{} + + // Load from map + if m, ok := data.(map[string]interface{}); ok { + if val, ok := m["id"]; ok && val != nil { + v := string(val.(string)) + result.Id = &v + } + if val, ok := m["sessionId"]; ok && val != nil { + v := string(val.(string)) + result.SessionId = &v + } + if val, ok := m["turnId"]; ok && val != nil { + v := string(val.(string)) + result.TurnId = &v + } + if val, ok := m["toolCallId"]; ok && val != nil { + v := string(val.(string)) + result.ToolCallId = &v + } + if val, ok := m["turnIndex"]; ok && val != nil { // Handle various numeric types from JSON/YAML/roundtrip + var v int32 + switch n := val.(type) { + case int: + v = int32(n) + case int32: + v = int32(n) + case int64: + v = int32(n) + case float64: + v = int32(n) + } + result.TurnIndex = &v + } + if val, ok := m["eventType"]; ok && val != nil { + result.EventType = string(val.(string)) + } + if val, ok := m["data"]; ok && val != nil { + if m, ok := val.(map[string]interface{}); ok { + result.Data = m + } + } + if val, ok := m["createdAt"]; ok && val != nil { + v := string(val.(string)) + result.CreatedAt = &v + } + if val, ok := m["redaction"]; ok && val != nil { + if m, ok := val.(map[string]interface{}); ok { + loaded, _ := LoadRedactionMetadata(m, ctx) + result.Redaction = &loaded + } + } + } + + return result, nil +} + +// Save serializes TrajectoryEvent to map[string]interface{} +func (obj TrajectoryEvent) Save(ctx *SaveContext) map[string]interface{} { + result := make(map[string]interface{}) + if obj.Id != nil { + result["id"] = *obj.Id + } + if obj.SessionId != nil { + result["sessionId"] = *obj.SessionId + } + if obj.TurnId != nil { + result["turnId"] = *obj.TurnId + } + if obj.ToolCallId != nil { + result["toolCallId"] = *obj.ToolCallId + } + if obj.TurnIndex != nil { + result["turnIndex"] = *obj.TurnIndex + } + result["eventType"] = obj.EventType + if obj.Data != nil { + result["data"] = obj.Data + } + if obj.CreatedAt != nil { + result["createdAt"] = *obj.CreatedAt + } + if obj.Redaction != nil { + result["redaction"] = obj.Redaction.Save(ctx) + } + + return result +} + +// ToJSON serializes TrajectoryEvent to JSON string +func (obj *TrajectoryEvent) ToJSON() (string, error) { + ctx := NewSaveContext() + data := obj.Save(ctx) + bytes, err := json.Marshal(data) + if err != nil { + return "", err + } + return string(bytes), nil +} + +// ToYAML serializes TrajectoryEvent to YAML string +func (obj *TrajectoryEvent) ToYAML() (string, error) { + ctx := NewSaveContext() + data := obj.Save(ctx) + return marshalYAMLDocument(data) +} + +// FromJSON creates TrajectoryEvent from JSON string +func TrajectoryEventFromJSON(jsonStr string) (TrajectoryEvent, error) { + var data map[string]interface{} + if err := json.Unmarshal([]byte(jsonStr), &data); err != nil { + return TrajectoryEvent{}, err + } + ctx := NewLoadContext() + return LoadTrajectoryEvent(data, ctx) +} + +// FromYAML creates TrajectoryEvent from YAML string +func TrajectoryEventFromYAML(yamlStr string) (TrajectoryEvent, error) { + var data map[string]interface{} + if err := yaml.Unmarshal([]byte(yamlStr), &data); err != nil { + return TrajectoryEvent{}, err + } + ctx := NewLoadContext() + return LoadTrajectoryEvent(data, ctx) +} diff --git a/runtime/go/prompty/model/trajectory_event_test.go b/runtime/go/prompty/model/trajectory_event_test.go new file mode 100644 index 00000000..5a84002d --- /dev/null +++ b/runtime/go/prompty/model/trajectory_event_test.go @@ -0,0 +1,365 @@ +// +// Code generated by Typra emitter; DO NOT EDIT. + +package prompty_test + +import ( + "encoding/json" + "testing" + + "gopkg.in/yaml.v3" + + "prompty/model" +) + +// TestTrajectoryEventLoadJSON tests loading TrajectoryEvent from JSON +func TestTrajectoryEventLoadJSON(t *testing.T) { + jsonData := ` +{ + "id": "traj_abc123", + "sessionId": "sess_abc123", + "turnId": "turn_001", + "toolCallId": "call_abc123", + "turnIndex": 4, + "eventType": "command", + "createdAt": "2026-06-09T20:00:00Z" +} +` + var data map[string]interface{} + if err := json.Unmarshal([]byte(jsonData), &data); err != nil { + t.Fatalf("Failed to parse JSON: %v", err) + } + + ctx := prompty.NewLoadContext() + instance, err := prompty.LoadTrajectoryEvent(data, ctx) + if err != nil { + t.Fatalf("Failed to load TrajectoryEvent: %v", err) + } + if instance.Id == nil || *instance.Id != "traj_abc123" { + t.Errorf(`Expected Id to be "traj_abc123", got %v`, instance.Id) + } + if instance.SessionId == nil || *instance.SessionId != "sess_abc123" { + t.Errorf(`Expected SessionId to be "sess_abc123", got %v`, instance.SessionId) + } + if instance.TurnId == nil || *instance.TurnId != "turn_001" { + t.Errorf(`Expected TurnId to be "turn_001", got %v`, instance.TurnId) + } + if instance.ToolCallId == nil || *instance.ToolCallId != "call_abc123" { + t.Errorf(`Expected ToolCallId to be "call_abc123", got %v`, instance.ToolCallId) + } + if instance.TurnIndex == nil || *instance.TurnIndex != 4 { + t.Errorf(`Expected TurnIndex to be 4, got %v`, instance.TurnIndex) + } + if instance.EventType != "command" { + t.Errorf(`Expected EventType to be "command", got %v`, instance.EventType) + } + if instance.CreatedAt == nil || *instance.CreatedAt != "2026-06-09T20:00:00Z" { + t.Errorf(`Expected CreatedAt to be "2026-06-09T20:00:00Z", got %v`, instance.CreatedAt) + } +} + +// TestTrajectoryEventLoadYAML tests loading TrajectoryEvent from YAML +func TestTrajectoryEventLoadYAML(t *testing.T) { + yamlData := ` +id: traj_abc123 +sessionId: sess_abc123 +turnId: turn_001 +toolCallId: call_abc123 +turnIndex: 4 +eventType: command +createdAt: "2026-06-09T20:00:00Z" + +` + var data map[string]interface{} + if err := yaml.Unmarshal([]byte(yamlData), &data); err != nil { + t.Fatalf("Failed to parse YAML: %v", err) + } + + ctx := prompty.NewLoadContext() + instance, err := prompty.LoadTrajectoryEvent(data, ctx) + if err != nil { + t.Fatalf("Failed to load TrajectoryEvent: %v", err) + } + if instance.Id == nil || *instance.Id != "traj_abc123" { + t.Errorf(`Expected Id to be "traj_abc123", got %v`, instance.Id) + } + if instance.SessionId == nil || *instance.SessionId != "sess_abc123" { + t.Errorf(`Expected SessionId to be "sess_abc123", got %v`, instance.SessionId) + } + if instance.TurnId == nil || *instance.TurnId != "turn_001" { + t.Errorf(`Expected TurnId to be "turn_001", got %v`, instance.TurnId) + } + if instance.ToolCallId == nil || *instance.ToolCallId != "call_abc123" { + t.Errorf(`Expected ToolCallId to be "call_abc123", got %v`, instance.ToolCallId) + } + if instance.TurnIndex == nil || *instance.TurnIndex != 4 { + t.Errorf(`Expected TurnIndex to be 4, got %v`, instance.TurnIndex) + } + if instance.EventType != "command" { + t.Errorf(`Expected EventType to be "command", got %v`, instance.EventType) + } + if instance.CreatedAt == nil || *instance.CreatedAt != "2026-06-09T20:00:00Z" { + t.Errorf(`Expected CreatedAt to be "2026-06-09T20:00:00Z", got %v`, instance.CreatedAt) + } +} + +// TestTrajectoryEventFromJSON tests loading TrajectoryEvent through the generated JSON helper +func TestTrajectoryEventFromJSON(t *testing.T) { + jsonData := ` +{ + "id": "traj_abc123", + "sessionId": "sess_abc123", + "turnId": "turn_001", + "toolCallId": "call_abc123", + "turnIndex": 4, + "eventType": "command", + "createdAt": "2026-06-09T20:00:00Z" +} +` + + instance, err := prompty.TrajectoryEventFromJSON(jsonData) + if err != nil { + t.Fatalf("Failed to load TrajectoryEvent from JSON helper: %v", err) + } + if instance.Id == nil || *instance.Id != "traj_abc123" { + t.Errorf(`Expected Id to be "traj_abc123", got %v`, instance.Id) + } + if instance.SessionId == nil || *instance.SessionId != "sess_abc123" { + t.Errorf(`Expected SessionId to be "sess_abc123", got %v`, instance.SessionId) + } + if instance.TurnId == nil || *instance.TurnId != "turn_001" { + t.Errorf(`Expected TurnId to be "turn_001", got %v`, instance.TurnId) + } + if instance.ToolCallId == nil || *instance.ToolCallId != "call_abc123" { + t.Errorf(`Expected ToolCallId to be "call_abc123", got %v`, instance.ToolCallId) + } + if instance.TurnIndex == nil || *instance.TurnIndex != 4 { + t.Errorf(`Expected TurnIndex to be 4, got %v`, instance.TurnIndex) + } + if instance.EventType != "command" { + t.Errorf(`Expected EventType to be "command", got %v`, instance.EventType) + } + if instance.CreatedAt == nil || *instance.CreatedAt != "2026-06-09T20:00:00Z" { + t.Errorf(`Expected CreatedAt to be "2026-06-09T20:00:00Z", got %v`, instance.CreatedAt) + } +} + +// TestTrajectoryEventFromYAML tests loading TrajectoryEvent through the generated YAML helper +func TestTrajectoryEventFromYAML(t *testing.T) { + yamlData := ` +id: traj_abc123 +sessionId: sess_abc123 +turnId: turn_001 +toolCallId: call_abc123 +turnIndex: 4 +eventType: command +createdAt: "2026-06-09T20:00:00Z" + +` + + instance, err := prompty.TrajectoryEventFromYAML(yamlData) + if err != nil { + t.Fatalf("Failed to load TrajectoryEvent from YAML helper: %v", err) + } + if instance.Id == nil || *instance.Id != "traj_abc123" { + t.Errorf(`Expected Id to be "traj_abc123", got %v`, instance.Id) + } + if instance.SessionId == nil || *instance.SessionId != "sess_abc123" { + t.Errorf(`Expected SessionId to be "sess_abc123", got %v`, instance.SessionId) + } + if instance.TurnId == nil || *instance.TurnId != "turn_001" { + t.Errorf(`Expected TurnId to be "turn_001", got %v`, instance.TurnId) + } + if instance.ToolCallId == nil || *instance.ToolCallId != "call_abc123" { + t.Errorf(`Expected ToolCallId to be "call_abc123", got %v`, instance.ToolCallId) + } + if instance.TurnIndex == nil || *instance.TurnIndex != 4 { + t.Errorf(`Expected TurnIndex to be 4, got %v`, instance.TurnIndex) + } + if instance.EventType != "command" { + t.Errorf(`Expected EventType to be "command", got %v`, instance.EventType) + } + if instance.CreatedAt == nil || *instance.CreatedAt != "2026-06-09T20:00:00Z" { + t.Errorf(`Expected CreatedAt to be "2026-06-09T20:00:00Z", got %v`, instance.CreatedAt) + } +} + +// TestTrajectoryEventRoundtrip tests load -> save -> load produces equivalent data +func TestTrajectoryEventRoundtrip(t *testing.T) { + jsonData := ` +{ + "id": "traj_abc123", + "sessionId": "sess_abc123", + "turnId": "turn_001", + "toolCallId": "call_abc123", + "turnIndex": 4, + "eventType": "command", + "createdAt": "2026-06-09T20:00:00Z" +} +` + var data map[string]interface{} + if err := json.Unmarshal([]byte(jsonData), &data); err != nil { + t.Fatalf("Failed to parse JSON: %v", err) + } + + loadCtx := prompty.NewLoadContext() + instance, err := prompty.LoadTrajectoryEvent(data, loadCtx) + if err != nil { + t.Fatalf("Failed to load TrajectoryEvent: %v", err) + } + saveCtx := prompty.NewSaveContext() + savedData := instance.Save(saveCtx) + + reloaded, err := prompty.LoadTrajectoryEvent(savedData, loadCtx) + if err != nil { + t.Fatalf("Failed to reload TrajectoryEvent: %v", err) + } + if reloaded.Id == nil || *reloaded.Id != "traj_abc123" { + t.Errorf(`Expected Id to be "traj_abc123", got %v`, reloaded.Id) + } + if reloaded.SessionId == nil || *reloaded.SessionId != "sess_abc123" { + t.Errorf(`Expected SessionId to be "sess_abc123", got %v`, reloaded.SessionId) + } + if reloaded.TurnId == nil || *reloaded.TurnId != "turn_001" { + t.Errorf(`Expected TurnId to be "turn_001", got %v`, reloaded.TurnId) + } + if reloaded.ToolCallId == nil || *reloaded.ToolCallId != "call_abc123" { + t.Errorf(`Expected ToolCallId to be "call_abc123", got %v`, reloaded.ToolCallId) + } + if reloaded.TurnIndex == nil || *reloaded.TurnIndex != 4 { + t.Errorf(`Expected TurnIndex to be 4, got %v`, reloaded.TurnIndex) + } + if reloaded.EventType != "command" { + t.Errorf(`Expected EventType to be "command", got %v`, reloaded.EventType) + } + if reloaded.CreatedAt == nil || *reloaded.CreatedAt != "2026-06-09T20:00:00Z" { + t.Errorf(`Expected CreatedAt to be "2026-06-09T20:00:00Z", got %v`, reloaded.CreatedAt) + } +} + +// TestTrajectoryEventToJSON tests that ToJSON produces valid JSON +func TestTrajectoryEventToJSON(t *testing.T) { + jsonData := ` +{ + "id": "traj_abc123", + "sessionId": "sess_abc123", + "turnId": "turn_001", + "toolCallId": "call_abc123", + "turnIndex": 4, + "eventType": "command", + "createdAt": "2026-06-09T20:00:00Z" +} +` + var data map[string]interface{} + if err := json.Unmarshal([]byte(jsonData), &data); err != nil { + t.Fatalf("Failed to parse JSON: %v", err) + } + + ctx := prompty.NewLoadContext() + instance, err := prompty.LoadTrajectoryEvent(data, ctx) + if err != nil { + t.Fatalf("Failed to load TrajectoryEvent: %v", err) + } + jsonOutput, err := instance.ToJSON() + if err != nil { + t.Fatalf("Failed to convert to JSON: %v", err) + } + + var parsed map[string]interface{} + if err := json.Unmarshal([]byte(jsonOutput), &parsed); err != nil { + t.Fatalf("Failed to parse generated JSON: %v", err) + } + + reloaded, err := prompty.LoadTrajectoryEvent(parsed, ctx) + if err != nil { + t.Fatalf("Failed to reload generated JSON: %v", err) + } + if reloaded.Id == nil || *reloaded.Id != "traj_abc123" { + t.Errorf(`Expected Id to be "traj_abc123", got %v`, reloaded.Id) + } + if reloaded.SessionId == nil || *reloaded.SessionId != "sess_abc123" { + t.Errorf(`Expected SessionId to be "sess_abc123", got %v`, reloaded.SessionId) + } + if reloaded.TurnId == nil || *reloaded.TurnId != "turn_001" { + t.Errorf(`Expected TurnId to be "turn_001", got %v`, reloaded.TurnId) + } + if reloaded.ToolCallId == nil || *reloaded.ToolCallId != "call_abc123" { + t.Errorf(`Expected ToolCallId to be "call_abc123", got %v`, reloaded.ToolCallId) + } + if reloaded.TurnIndex == nil || *reloaded.TurnIndex != 4 { + t.Errorf(`Expected TurnIndex to be 4, got %v`, reloaded.TurnIndex) + } + if reloaded.EventType != "command" { + t.Errorf(`Expected EventType to be "command", got %v`, reloaded.EventType) + } + if reloaded.CreatedAt == nil || *reloaded.CreatedAt != "2026-06-09T20:00:00Z" { + t.Errorf(`Expected CreatedAt to be "2026-06-09T20:00:00Z", got %v`, reloaded.CreatedAt) + } +} + +// TestTrajectoryEventToYAML tests that ToYAML produces valid YAML +func TestTrajectoryEventToYAML(t *testing.T) { + jsonData := ` +{ + "id": "traj_abc123", + "sessionId": "sess_abc123", + "turnId": "turn_001", + "toolCallId": "call_abc123", + "turnIndex": 4, + "eventType": "command", + "createdAt": "2026-06-09T20:00:00Z" +} +` + var data map[string]interface{} + if err := json.Unmarshal([]byte(jsonData), &data); err != nil { + t.Fatalf("Failed to parse JSON: %v", err) + } + + ctx := prompty.NewLoadContext() + instance, err := prompty.LoadTrajectoryEvent(data, ctx) + if err != nil { + t.Fatalf("Failed to load TrajectoryEvent: %v", err) + } + yamlOutput, err := instance.ToYAML() + if err != nil { + t.Fatalf("Failed to convert to YAML: %v", err) + } + + var parsed map[string]interface{} + if err := yaml.Unmarshal([]byte(yamlOutput), &parsed); err != nil { + t.Fatalf("Failed to parse generated YAML: %v", err) + } + + reloaded, err := prompty.LoadTrajectoryEvent(parsed, ctx) + if err != nil { + t.Fatalf("Failed to reload generated YAML: %v", err) + } + if reloaded.Id == nil || *reloaded.Id != "traj_abc123" { + t.Errorf(`Expected Id to be "traj_abc123", got %v`, reloaded.Id) + } + if reloaded.SessionId == nil || *reloaded.SessionId != "sess_abc123" { + t.Errorf(`Expected SessionId to be "sess_abc123", got %v`, reloaded.SessionId) + } + if reloaded.TurnId == nil || *reloaded.TurnId != "turn_001" { + t.Errorf(`Expected TurnId to be "turn_001", got %v`, reloaded.TurnId) + } + if reloaded.ToolCallId == nil || *reloaded.ToolCallId != "call_abc123" { + t.Errorf(`Expected ToolCallId to be "call_abc123", got %v`, reloaded.ToolCallId) + } + if reloaded.TurnIndex == nil || *reloaded.TurnIndex != 4 { + t.Errorf(`Expected TurnIndex to be 4, got %v`, reloaded.TurnIndex) + } + if reloaded.EventType != "command" { + t.Errorf(`Expected EventType to be "command", got %v`, reloaded.EventType) + } + if reloaded.CreatedAt == nil || *reloaded.CreatedAt != "2026-06-09T20:00:00Z" { + t.Errorf(`Expected CreatedAt to be "2026-06-09T20:00:00Z", got %v`, reloaded.CreatedAt) + } +} + +// TestTrajectoryEventFromJSONInvalid rejects malformed JSON instead of silently defaulting +func TestTrajectoryEventFromJSONInvalid(t *testing.T) { + if _, err := prompty.TrajectoryEventFromJSON("{"); err == nil { + t.Fatalf("Expected malformed JSON to fail") + } +} diff --git a/runtime/go/prompty/model/turn_end_payload.go b/runtime/go/prompty/model/turn_end_payload.go new file mode 100644 index 00000000..21ac0027 --- /dev/null +++ b/runtime/go/prompty/model/turn_end_payload.go @@ -0,0 +1,134 @@ +// +// Code generated by Typra emitter; DO NOT EDIT. +// Group: events + +package prompty + +import ( + "encoding/json" + + "gopkg.in/yaml.v3" +) + +// TurnStatus represents the allowed values for TurnStatus. +type TurnStatus string + +const ( + TurnStatusSuccess TurnStatus = "success" + TurnStatusError TurnStatus = "error" + TurnStatusCancelled TurnStatus = "cancelled" +) + +// TurnEndPayload represents Payload for "turn_end" events — a turn has completed. + +type TurnEndPayload struct { + Iterations *int32 `json:"iterations,omitempty" yaml:"iterations,omitempty"` + Status *TurnStatus `json:"status,omitempty" yaml:"status,omitempty"` + Response *interface{} `json:"response,omitempty" yaml:"response,omitempty"` + DurationMs *float64 `json:"durationMs,omitempty" yaml:"durationMs,omitempty"` +} + +// LoadTurnEndPayload creates a TurnEndPayload from a map[string]interface{} +func LoadTurnEndPayload(data interface{}, ctx *LoadContext) (TurnEndPayload, error) { + result := TurnEndPayload{} + + // Load from map + if m, ok := data.(map[string]interface{}); ok { + if val, ok := m["iterations"]; ok && val != nil { // Handle various numeric types from JSON/YAML/roundtrip + var v int32 + switch n := val.(type) { + case int: + v = int32(n) + case int32: + v = int32(n) + case int64: + v = int32(n) + case float64: + v = int32(n) + } + result.Iterations = &v + } + if val, ok := m["status"]; ok && val != nil { + v := TurnStatus(val.(string)) + result.Status = &v + } + if val, ok := m["response"]; ok && val != nil { + result.Response = &val + } + if val, ok := m["durationMs"]; ok && val != nil { // Handle various numeric types from JSON/YAML/roundtrip + var v float64 + switch n := val.(type) { + case int: + v = float64(n) + case int32: + v = float64(n) + case int64: + v = float64(n) + case float32: + v = float64(n) + case float64: + v = n + } + result.DurationMs = &v + } + } + + return result, nil +} + +// Save serializes TurnEndPayload to map[string]interface{} +func (obj TurnEndPayload) Save(ctx *SaveContext) map[string]interface{} { + result := make(map[string]interface{}) + if obj.Iterations != nil { + result["iterations"] = *obj.Iterations + } + if obj.Status != nil { + result["status"] = string(*obj.Status) + } + if obj.Response != nil { + result["response"] = *obj.Response + } + if obj.DurationMs != nil { + result["durationMs"] = *obj.DurationMs + } + + return result +} + +// ToJSON serializes TurnEndPayload to JSON string +func (obj *TurnEndPayload) ToJSON() (string, error) { + ctx := NewSaveContext() + data := obj.Save(ctx) + bytes, err := json.Marshal(data) + if err != nil { + return "", err + } + return string(bytes), nil +} + +// ToYAML serializes TurnEndPayload to YAML string +func (obj *TurnEndPayload) ToYAML() (string, error) { + ctx := NewSaveContext() + data := obj.Save(ctx) + return marshalYAMLDocument(data) +} + +// FromJSON creates TurnEndPayload from JSON string +func TurnEndPayloadFromJSON(jsonStr string) (TurnEndPayload, error) { + var data map[string]interface{} + if err := json.Unmarshal([]byte(jsonStr), &data); err != nil { + return TurnEndPayload{}, err + } + ctx := NewLoadContext() + return LoadTurnEndPayload(data, ctx) +} + +// FromYAML creates TurnEndPayload from YAML string +func TurnEndPayloadFromYAML(yamlStr string) (TurnEndPayload, error) { + var data map[string]interface{} + if err := yaml.Unmarshal([]byte(yamlStr), &data); err != nil { + return TurnEndPayload{}, err + } + ctx := NewLoadContext() + return LoadTurnEndPayload(data, ctx) +} diff --git a/runtime/go/prompty/model/turn_end_payload_test.go b/runtime/go/prompty/model/turn_end_payload_test.go new file mode 100644 index 00000000..30d77b89 --- /dev/null +++ b/runtime/go/prompty/model/turn_end_payload_test.go @@ -0,0 +1,225 @@ +// +// Code generated by Typra emitter; DO NOT EDIT. + +package prompty_test + +import ( + "encoding/json" + "testing" + + "gopkg.in/yaml.v3" + + "prompty/model" +) + +// TestTurnEndPayloadLoadJSON tests loading TurnEndPayload from JSON +func TestTurnEndPayloadLoadJSON(t *testing.T) { + jsonData := ` +{ + "iterations": 2, + "durationMs": 1500 +} +` + var data map[string]interface{} + if err := json.Unmarshal([]byte(jsonData), &data); err != nil { + t.Fatalf("Failed to parse JSON: %v", err) + } + + ctx := prompty.NewLoadContext() + instance, err := prompty.LoadTurnEndPayload(data, ctx) + if err != nil { + t.Fatalf("Failed to load TurnEndPayload: %v", err) + } + if instance.Iterations == nil || *instance.Iterations != 2 { + t.Errorf(`Expected Iterations to be 2, got %v`, instance.Iterations) + } + if instance.DurationMs == nil || *instance.DurationMs != 1500 { + t.Errorf(`Expected DurationMs to be 1500, got %v`, instance.DurationMs) + } +} + +// TestTurnEndPayloadLoadYAML tests loading TurnEndPayload from YAML +func TestTurnEndPayloadLoadYAML(t *testing.T) { + yamlData := ` +iterations: 2 +durationMs: 1500 + +` + var data map[string]interface{} + if err := yaml.Unmarshal([]byte(yamlData), &data); err != nil { + t.Fatalf("Failed to parse YAML: %v", err) + } + + ctx := prompty.NewLoadContext() + instance, err := prompty.LoadTurnEndPayload(data, ctx) + if err != nil { + t.Fatalf("Failed to load TurnEndPayload: %v", err) + } + if instance.Iterations == nil || *instance.Iterations != 2 { + t.Errorf(`Expected Iterations to be 2, got %v`, instance.Iterations) + } + if instance.DurationMs == nil || *instance.DurationMs != 1500 { + t.Errorf(`Expected DurationMs to be 1500, got %v`, instance.DurationMs) + } +} + +// TestTurnEndPayloadFromJSON tests loading TurnEndPayload through the generated JSON helper +func TestTurnEndPayloadFromJSON(t *testing.T) { + jsonData := ` +{ + "iterations": 2, + "durationMs": 1500 +} +` + + instance, err := prompty.TurnEndPayloadFromJSON(jsonData) + if err != nil { + t.Fatalf("Failed to load TurnEndPayload from JSON helper: %v", err) + } + if instance.Iterations == nil || *instance.Iterations != 2 { + t.Errorf(`Expected Iterations to be 2, got %v`, instance.Iterations) + } + if instance.DurationMs == nil || *instance.DurationMs != 1500 { + t.Errorf(`Expected DurationMs to be 1500, got %v`, instance.DurationMs) + } +} + +// TestTurnEndPayloadFromYAML tests loading TurnEndPayload through the generated YAML helper +func TestTurnEndPayloadFromYAML(t *testing.T) { + yamlData := ` +iterations: 2 +durationMs: 1500 + +` + + instance, err := prompty.TurnEndPayloadFromYAML(yamlData) + if err != nil { + t.Fatalf("Failed to load TurnEndPayload from YAML helper: %v", err) + } + if instance.Iterations == nil || *instance.Iterations != 2 { + t.Errorf(`Expected Iterations to be 2, got %v`, instance.Iterations) + } + if instance.DurationMs == nil || *instance.DurationMs != 1500 { + t.Errorf(`Expected DurationMs to be 1500, got %v`, instance.DurationMs) + } +} + +// TestTurnEndPayloadRoundtrip tests load -> save -> load produces equivalent data +func TestTurnEndPayloadRoundtrip(t *testing.T) { + jsonData := ` +{ + "iterations": 2, + "durationMs": 1500 +} +` + var data map[string]interface{} + if err := json.Unmarshal([]byte(jsonData), &data); err != nil { + t.Fatalf("Failed to parse JSON: %v", err) + } + + loadCtx := prompty.NewLoadContext() + instance, err := prompty.LoadTurnEndPayload(data, loadCtx) + if err != nil { + t.Fatalf("Failed to load TurnEndPayload: %v", err) + } + saveCtx := prompty.NewSaveContext() + savedData := instance.Save(saveCtx) + + reloaded, err := prompty.LoadTurnEndPayload(savedData, loadCtx) + if err != nil { + t.Fatalf("Failed to reload TurnEndPayload: %v", err) + } + if reloaded.Iterations == nil || *reloaded.Iterations != 2 { + t.Errorf(`Expected Iterations to be 2, got %v`, reloaded.Iterations) + } + if reloaded.DurationMs == nil || *reloaded.DurationMs != 1500 { + t.Errorf(`Expected DurationMs to be 1500, got %v`, reloaded.DurationMs) + } +} + +// TestTurnEndPayloadToJSON tests that ToJSON produces valid JSON +func TestTurnEndPayloadToJSON(t *testing.T) { + jsonData := ` +{ + "iterations": 2, + "durationMs": 1500 +} +` + var data map[string]interface{} + if err := json.Unmarshal([]byte(jsonData), &data); err != nil { + t.Fatalf("Failed to parse JSON: %v", err) + } + + ctx := prompty.NewLoadContext() + instance, err := prompty.LoadTurnEndPayload(data, ctx) + if err != nil { + t.Fatalf("Failed to load TurnEndPayload: %v", err) + } + jsonOutput, err := instance.ToJSON() + if err != nil { + t.Fatalf("Failed to convert to JSON: %v", err) + } + + var parsed map[string]interface{} + if err := json.Unmarshal([]byte(jsonOutput), &parsed); err != nil { + t.Fatalf("Failed to parse generated JSON: %v", err) + } + + reloaded, err := prompty.LoadTurnEndPayload(parsed, ctx) + if err != nil { + t.Fatalf("Failed to reload generated JSON: %v", err) + } + if reloaded.Iterations == nil || *reloaded.Iterations != 2 { + t.Errorf(`Expected Iterations to be 2, got %v`, reloaded.Iterations) + } + if reloaded.DurationMs == nil || *reloaded.DurationMs != 1500 { + t.Errorf(`Expected DurationMs to be 1500, got %v`, reloaded.DurationMs) + } +} + +// TestTurnEndPayloadToYAML tests that ToYAML produces valid YAML +func TestTurnEndPayloadToYAML(t *testing.T) { + jsonData := ` +{ + "iterations": 2, + "durationMs": 1500 +} +` + var data map[string]interface{} + if err := json.Unmarshal([]byte(jsonData), &data); err != nil { + t.Fatalf("Failed to parse JSON: %v", err) + } + + ctx := prompty.NewLoadContext() + instance, err := prompty.LoadTurnEndPayload(data, ctx) + if err != nil { + t.Fatalf("Failed to load TurnEndPayload: %v", err) + } + yamlOutput, err := instance.ToYAML() + if err != nil { + t.Fatalf("Failed to convert to YAML: %v", err) + } + + var parsed map[string]interface{} + if err := yaml.Unmarshal([]byte(yamlOutput), &parsed); err != nil { + t.Fatalf("Failed to parse generated YAML: %v", err) + } + + reloaded, err := prompty.LoadTurnEndPayload(parsed, ctx) + if err != nil { + t.Fatalf("Failed to reload generated YAML: %v", err) + } + if reloaded.Iterations == nil || *reloaded.Iterations != 2 { + t.Errorf(`Expected Iterations to be 2, got %v`, reloaded.Iterations) + } + if reloaded.DurationMs == nil || *reloaded.DurationMs != 1500 { + t.Errorf(`Expected DurationMs to be 1500, got %v`, reloaded.DurationMs) + } +} + +// TestTurnEndPayloadFromJSONInvalid rejects malformed JSON instead of silently defaulting +func TestTurnEndPayloadFromJSONInvalid(t *testing.T) { + if _, err := prompty.TurnEndPayloadFromJSON("{"); err == nil { + t.Fatalf("Expected malformed JSON to fail") + } +} diff --git a/runtime/go/prompty/model/turn_event.go b/runtime/go/prompty/model/turn_event.go new file mode 100644 index 00000000..e8e7eede --- /dev/null +++ b/runtime/go/prompty/model/turn_event.go @@ -0,0 +1,168 @@ +// +// Code generated by Typra emitter; DO NOT EDIT. +// Group: events + +package prompty + +import ( + "encoding/json" + + "gopkg.in/yaml.v3" +) + +// TurnEventType represents the allowed values for TurnEventType. +type TurnEventType string + +const ( + TurnEventTypeTurnStart TurnEventType = "turn_start" + TurnEventTypeTurnEnd TurnEventType = "turn_end" + TurnEventTypeLlmStart TurnEventType = "llm_start" + TurnEventTypeLlmComplete TurnEventType = "llm_complete" + TurnEventTypeRetry TurnEventType = "retry" + TurnEventTypePermissionRequested TurnEventType = "permission_requested" + TurnEventTypePermissionCompleted TurnEventType = "permission_completed" + TurnEventTypeToken TurnEventType = "token" + TurnEventTypeThinking TurnEventType = "thinking" + TurnEventTypeToolCallStart TurnEventType = "tool_call_start" + TurnEventTypeToolCallComplete TurnEventType = "tool_call_complete" + TurnEventTypeToolExecutionStart TurnEventType = "tool_execution_start" + TurnEventTypeToolExecutionComplete TurnEventType = "tool_execution_complete" + TurnEventTypeToolResult TurnEventType = "tool_result" + TurnEventTypeHookStart TurnEventType = "hook_start" + TurnEventTypeHookEnd TurnEventType = "hook_end" + TurnEventTypeStatus TurnEventType = "status" + TurnEventTypeMessagesUpdated TurnEventType = "messages_updated" + TurnEventTypeDone TurnEventType = "done" + TurnEventTypeError TurnEventType = "error" + TurnEventTypeCancelled TurnEventType = "cancelled" + TurnEventTypeCompactionStart TurnEventType = "compaction_start" + TurnEventTypeCompactionComplete TurnEventType = "compaction_complete" + TurnEventTypeCompactionFailed TurnEventType = "compaction_failed" +) + +// TurnEvent represents A canonical event envelope emitted by the turn harness. The payload is kept +// JSON-shaped so runtimes can load all events even when newer payload types are +// added; event-specific typed payload models below define the canonical shapes. + +type TurnEvent struct { + Id string `json:"id" yaml:"id"` + Type TurnEventType `json:"type" yaml:"type"` + Timestamp string `json:"timestamp" yaml:"timestamp"` + TurnId *string `json:"turnId,omitempty" yaml:"turnId,omitempty"` + Iteration *int32 `json:"iteration,omitempty" yaml:"iteration,omitempty"` + ParentId *string `json:"parentId,omitempty" yaml:"parentId,omitempty"` + SpanId *string `json:"spanId,omitempty" yaml:"spanId,omitempty"` + Payload map[string]interface{} `json:"payload" yaml:"payload"` +} + +// LoadTurnEvent creates a TurnEvent from a map[string]interface{} +func LoadTurnEvent(data interface{}, ctx *LoadContext) (TurnEvent, error) { + result := TurnEvent{} + + // Load from map + if m, ok := data.(map[string]interface{}); ok { + if val, ok := m["id"]; ok && val != nil { + result.Id = string(val.(string)) + } + if val, ok := m["type"]; ok && val != nil { + result.Type = TurnEventType(val.(string)) + } + if val, ok := m["timestamp"]; ok && val != nil { + result.Timestamp = string(val.(string)) + } + if val, ok := m["turnId"]; ok && val != nil { + v := string(val.(string)) + result.TurnId = &v + } + if val, ok := m["iteration"]; ok && val != nil { // Handle various numeric types from JSON/YAML/roundtrip + var v int32 + switch n := val.(type) { + case int: + v = int32(n) + case int32: + v = int32(n) + case int64: + v = int32(n) + case float64: + v = int32(n) + } + result.Iteration = &v + } + if val, ok := m["parentId"]; ok && val != nil { + v := string(val.(string)) + result.ParentId = &v + } + if val, ok := m["spanId"]; ok && val != nil { + v := string(val.(string)) + result.SpanId = &v + } + if val, ok := m["payload"]; ok && val != nil { + if m, ok := val.(map[string]interface{}); ok { + result.Payload = m + } + } + } + + return result, nil +} + +// Save serializes TurnEvent to map[string]interface{} +func (obj TurnEvent) Save(ctx *SaveContext) map[string]interface{} { + result := make(map[string]interface{}) + result["id"] = obj.Id + result["type"] = string(obj.Type) + result["timestamp"] = obj.Timestamp + if obj.TurnId != nil { + result["turnId"] = *obj.TurnId + } + if obj.Iteration != nil { + result["iteration"] = *obj.Iteration + } + if obj.ParentId != nil { + result["parentId"] = *obj.ParentId + } + if obj.SpanId != nil { + result["spanId"] = *obj.SpanId + } + result["payload"] = obj.Payload + + return result +} + +// ToJSON serializes TurnEvent to JSON string +func (obj *TurnEvent) ToJSON() (string, error) { + ctx := NewSaveContext() + data := obj.Save(ctx) + bytes, err := json.Marshal(data) + if err != nil { + return "", err + } + return string(bytes), nil +} + +// ToYAML serializes TurnEvent to YAML string +func (obj *TurnEvent) ToYAML() (string, error) { + ctx := NewSaveContext() + data := obj.Save(ctx) + return marshalYAMLDocument(data) +} + +// FromJSON creates TurnEvent from JSON string +func TurnEventFromJSON(jsonStr string) (TurnEvent, error) { + var data map[string]interface{} + if err := json.Unmarshal([]byte(jsonStr), &data); err != nil { + return TurnEvent{}, err + } + ctx := NewLoadContext() + return LoadTurnEvent(data, ctx) +} + +// FromYAML creates TurnEvent from YAML string +func TurnEventFromYAML(yamlStr string) (TurnEvent, error) { + var data map[string]interface{} + if err := yaml.Unmarshal([]byte(yamlStr), &data); err != nil { + return TurnEvent{}, err + } + ctx := NewLoadContext() + return LoadTurnEvent(data, ctx) +} diff --git a/runtime/go/prompty/model/turn_event_test.go b/runtime/go/prompty/model/turn_event_test.go new file mode 100644 index 00000000..50b96d1a --- /dev/null +++ b/runtime/go/prompty/model/turn_event_test.go @@ -0,0 +1,337 @@ +// +// Code generated by Typra emitter; DO NOT EDIT. + +package prompty_test + +import ( + "encoding/json" + "testing" + + "gopkg.in/yaml.v3" + + "prompty/model" +) + +// TestTurnEventLoadJSON tests loading TurnEvent from JSON +func TestTurnEventLoadJSON(t *testing.T) { + jsonData := ` +{ + "id": "evt_abc123", + "timestamp": "2026-06-09T20:00:00Z", + "turnId": "turn_001", + "iteration": 0, + "parentId": "evt_parent", + "spanId": "span_tool_001" +} +` + var data map[string]interface{} + if err := json.Unmarshal([]byte(jsonData), &data); err != nil { + t.Fatalf("Failed to parse JSON: %v", err) + } + + ctx := prompty.NewLoadContext() + instance, err := prompty.LoadTurnEvent(data, ctx) + if err != nil { + t.Fatalf("Failed to load TurnEvent: %v", err) + } + if instance.Id != "evt_abc123" { + t.Errorf(`Expected Id to be "evt_abc123", got %v`, instance.Id) + } + if instance.Timestamp != "2026-06-09T20:00:00Z" { + t.Errorf(`Expected Timestamp to be "2026-06-09T20:00:00Z", got %v`, instance.Timestamp) + } + if instance.TurnId == nil || *instance.TurnId != "turn_001" { + t.Errorf(`Expected TurnId to be "turn_001", got %v`, instance.TurnId) + } + if instance.Iteration == nil || *instance.Iteration != 0 { + t.Errorf(`Expected Iteration to be 0, got %v`, instance.Iteration) + } + if instance.ParentId == nil || *instance.ParentId != "evt_parent" { + t.Errorf(`Expected ParentId to be "evt_parent", got %v`, instance.ParentId) + } + if instance.SpanId == nil || *instance.SpanId != "span_tool_001" { + t.Errorf(`Expected SpanId to be "span_tool_001", got %v`, instance.SpanId) + } +} + +// TestTurnEventLoadYAML tests loading TurnEvent from YAML +func TestTurnEventLoadYAML(t *testing.T) { + yamlData := ` +id: evt_abc123 +timestamp: "2026-06-09T20:00:00Z" +turnId: turn_001 +iteration: 0 +parentId: evt_parent +spanId: span_tool_001 + +` + var data map[string]interface{} + if err := yaml.Unmarshal([]byte(yamlData), &data); err != nil { + t.Fatalf("Failed to parse YAML: %v", err) + } + + ctx := prompty.NewLoadContext() + instance, err := prompty.LoadTurnEvent(data, ctx) + if err != nil { + t.Fatalf("Failed to load TurnEvent: %v", err) + } + if instance.Id != "evt_abc123" { + t.Errorf(`Expected Id to be "evt_abc123", got %v`, instance.Id) + } + if instance.Timestamp != "2026-06-09T20:00:00Z" { + t.Errorf(`Expected Timestamp to be "2026-06-09T20:00:00Z", got %v`, instance.Timestamp) + } + if instance.TurnId == nil || *instance.TurnId != "turn_001" { + t.Errorf(`Expected TurnId to be "turn_001", got %v`, instance.TurnId) + } + if instance.Iteration == nil || *instance.Iteration != 0 { + t.Errorf(`Expected Iteration to be 0, got %v`, instance.Iteration) + } + if instance.ParentId == nil || *instance.ParentId != "evt_parent" { + t.Errorf(`Expected ParentId to be "evt_parent", got %v`, instance.ParentId) + } + if instance.SpanId == nil || *instance.SpanId != "span_tool_001" { + t.Errorf(`Expected SpanId to be "span_tool_001", got %v`, instance.SpanId) + } +} + +// TestTurnEventFromJSON tests loading TurnEvent through the generated JSON helper +func TestTurnEventFromJSON(t *testing.T) { + jsonData := ` +{ + "id": "evt_abc123", + "timestamp": "2026-06-09T20:00:00Z", + "turnId": "turn_001", + "iteration": 0, + "parentId": "evt_parent", + "spanId": "span_tool_001" +} +` + + instance, err := prompty.TurnEventFromJSON(jsonData) + if err != nil { + t.Fatalf("Failed to load TurnEvent from JSON helper: %v", err) + } + if instance.Id != "evt_abc123" { + t.Errorf(`Expected Id to be "evt_abc123", got %v`, instance.Id) + } + if instance.Timestamp != "2026-06-09T20:00:00Z" { + t.Errorf(`Expected Timestamp to be "2026-06-09T20:00:00Z", got %v`, instance.Timestamp) + } + if instance.TurnId == nil || *instance.TurnId != "turn_001" { + t.Errorf(`Expected TurnId to be "turn_001", got %v`, instance.TurnId) + } + if instance.Iteration == nil || *instance.Iteration != 0 { + t.Errorf(`Expected Iteration to be 0, got %v`, instance.Iteration) + } + if instance.ParentId == nil || *instance.ParentId != "evt_parent" { + t.Errorf(`Expected ParentId to be "evt_parent", got %v`, instance.ParentId) + } + if instance.SpanId == nil || *instance.SpanId != "span_tool_001" { + t.Errorf(`Expected SpanId to be "span_tool_001", got %v`, instance.SpanId) + } +} + +// TestTurnEventFromYAML tests loading TurnEvent through the generated YAML helper +func TestTurnEventFromYAML(t *testing.T) { + yamlData := ` +id: evt_abc123 +timestamp: "2026-06-09T20:00:00Z" +turnId: turn_001 +iteration: 0 +parentId: evt_parent +spanId: span_tool_001 + +` + + instance, err := prompty.TurnEventFromYAML(yamlData) + if err != nil { + t.Fatalf("Failed to load TurnEvent from YAML helper: %v", err) + } + if instance.Id != "evt_abc123" { + t.Errorf(`Expected Id to be "evt_abc123", got %v`, instance.Id) + } + if instance.Timestamp != "2026-06-09T20:00:00Z" { + t.Errorf(`Expected Timestamp to be "2026-06-09T20:00:00Z", got %v`, instance.Timestamp) + } + if instance.TurnId == nil || *instance.TurnId != "turn_001" { + t.Errorf(`Expected TurnId to be "turn_001", got %v`, instance.TurnId) + } + if instance.Iteration == nil || *instance.Iteration != 0 { + t.Errorf(`Expected Iteration to be 0, got %v`, instance.Iteration) + } + if instance.ParentId == nil || *instance.ParentId != "evt_parent" { + t.Errorf(`Expected ParentId to be "evt_parent", got %v`, instance.ParentId) + } + if instance.SpanId == nil || *instance.SpanId != "span_tool_001" { + t.Errorf(`Expected SpanId to be "span_tool_001", got %v`, instance.SpanId) + } +} + +// TestTurnEventRoundtrip tests load -> save -> load produces equivalent data +func TestTurnEventRoundtrip(t *testing.T) { + jsonData := ` +{ + "id": "evt_abc123", + "timestamp": "2026-06-09T20:00:00Z", + "turnId": "turn_001", + "iteration": 0, + "parentId": "evt_parent", + "spanId": "span_tool_001" +} +` + var data map[string]interface{} + if err := json.Unmarshal([]byte(jsonData), &data); err != nil { + t.Fatalf("Failed to parse JSON: %v", err) + } + + loadCtx := prompty.NewLoadContext() + instance, err := prompty.LoadTurnEvent(data, loadCtx) + if err != nil { + t.Fatalf("Failed to load TurnEvent: %v", err) + } + saveCtx := prompty.NewSaveContext() + savedData := instance.Save(saveCtx) + + reloaded, err := prompty.LoadTurnEvent(savedData, loadCtx) + if err != nil { + t.Fatalf("Failed to reload TurnEvent: %v", err) + } + if reloaded.Id != "evt_abc123" { + t.Errorf(`Expected Id to be "evt_abc123", got %v`, reloaded.Id) + } + if reloaded.Timestamp != "2026-06-09T20:00:00Z" { + t.Errorf(`Expected Timestamp to be "2026-06-09T20:00:00Z", got %v`, reloaded.Timestamp) + } + if reloaded.TurnId == nil || *reloaded.TurnId != "turn_001" { + t.Errorf(`Expected TurnId to be "turn_001", got %v`, reloaded.TurnId) + } + if reloaded.Iteration == nil || *reloaded.Iteration != 0 { + t.Errorf(`Expected Iteration to be 0, got %v`, reloaded.Iteration) + } + if reloaded.ParentId == nil || *reloaded.ParentId != "evt_parent" { + t.Errorf(`Expected ParentId to be "evt_parent", got %v`, reloaded.ParentId) + } + if reloaded.SpanId == nil || *reloaded.SpanId != "span_tool_001" { + t.Errorf(`Expected SpanId to be "span_tool_001", got %v`, reloaded.SpanId) + } +} + +// TestTurnEventToJSON tests that ToJSON produces valid JSON +func TestTurnEventToJSON(t *testing.T) { + jsonData := ` +{ + "id": "evt_abc123", + "timestamp": "2026-06-09T20:00:00Z", + "turnId": "turn_001", + "iteration": 0, + "parentId": "evt_parent", + "spanId": "span_tool_001" +} +` + var data map[string]interface{} + if err := json.Unmarshal([]byte(jsonData), &data); err != nil { + t.Fatalf("Failed to parse JSON: %v", err) + } + + ctx := prompty.NewLoadContext() + instance, err := prompty.LoadTurnEvent(data, ctx) + if err != nil { + t.Fatalf("Failed to load TurnEvent: %v", err) + } + jsonOutput, err := instance.ToJSON() + if err != nil { + t.Fatalf("Failed to convert to JSON: %v", err) + } + + var parsed map[string]interface{} + if err := json.Unmarshal([]byte(jsonOutput), &parsed); err != nil { + t.Fatalf("Failed to parse generated JSON: %v", err) + } + + reloaded, err := prompty.LoadTurnEvent(parsed, ctx) + if err != nil { + t.Fatalf("Failed to reload generated JSON: %v", err) + } + if reloaded.Id != "evt_abc123" { + t.Errorf(`Expected Id to be "evt_abc123", got %v`, reloaded.Id) + } + if reloaded.Timestamp != "2026-06-09T20:00:00Z" { + t.Errorf(`Expected Timestamp to be "2026-06-09T20:00:00Z", got %v`, reloaded.Timestamp) + } + if reloaded.TurnId == nil || *reloaded.TurnId != "turn_001" { + t.Errorf(`Expected TurnId to be "turn_001", got %v`, reloaded.TurnId) + } + if reloaded.Iteration == nil || *reloaded.Iteration != 0 { + t.Errorf(`Expected Iteration to be 0, got %v`, reloaded.Iteration) + } + if reloaded.ParentId == nil || *reloaded.ParentId != "evt_parent" { + t.Errorf(`Expected ParentId to be "evt_parent", got %v`, reloaded.ParentId) + } + if reloaded.SpanId == nil || *reloaded.SpanId != "span_tool_001" { + t.Errorf(`Expected SpanId to be "span_tool_001", got %v`, reloaded.SpanId) + } +} + +// TestTurnEventToYAML tests that ToYAML produces valid YAML +func TestTurnEventToYAML(t *testing.T) { + jsonData := ` +{ + "id": "evt_abc123", + "timestamp": "2026-06-09T20:00:00Z", + "turnId": "turn_001", + "iteration": 0, + "parentId": "evt_parent", + "spanId": "span_tool_001" +} +` + var data map[string]interface{} + if err := json.Unmarshal([]byte(jsonData), &data); err != nil { + t.Fatalf("Failed to parse JSON: %v", err) + } + + ctx := prompty.NewLoadContext() + instance, err := prompty.LoadTurnEvent(data, ctx) + if err != nil { + t.Fatalf("Failed to load TurnEvent: %v", err) + } + yamlOutput, err := instance.ToYAML() + if err != nil { + t.Fatalf("Failed to convert to YAML: %v", err) + } + + var parsed map[string]interface{} + if err := yaml.Unmarshal([]byte(yamlOutput), &parsed); err != nil { + t.Fatalf("Failed to parse generated YAML: %v", err) + } + + reloaded, err := prompty.LoadTurnEvent(parsed, ctx) + if err != nil { + t.Fatalf("Failed to reload generated YAML: %v", err) + } + if reloaded.Id != "evt_abc123" { + t.Errorf(`Expected Id to be "evt_abc123", got %v`, reloaded.Id) + } + if reloaded.Timestamp != "2026-06-09T20:00:00Z" { + t.Errorf(`Expected Timestamp to be "2026-06-09T20:00:00Z", got %v`, reloaded.Timestamp) + } + if reloaded.TurnId == nil || *reloaded.TurnId != "turn_001" { + t.Errorf(`Expected TurnId to be "turn_001", got %v`, reloaded.TurnId) + } + if reloaded.Iteration == nil || *reloaded.Iteration != 0 { + t.Errorf(`Expected Iteration to be 0, got %v`, reloaded.Iteration) + } + if reloaded.ParentId == nil || *reloaded.ParentId != "evt_parent" { + t.Errorf(`Expected ParentId to be "evt_parent", got %v`, reloaded.ParentId) + } + if reloaded.SpanId == nil || *reloaded.SpanId != "span_tool_001" { + t.Errorf(`Expected SpanId to be "span_tool_001", got %v`, reloaded.SpanId) + } +} + +// TestTurnEventFromJSONInvalid rejects malformed JSON instead of silently defaulting +func TestTurnEventFromJSONInvalid(t *testing.T) { + if _, err := prompty.TurnEventFromJSON("{"); err == nil { + t.Fatalf("Expected malformed JSON to fail") + } +} diff --git a/runtime/go/prompty/model/turn_model_request.go b/runtime/go/prompty/model/turn_model_request.go new file mode 100644 index 00000000..675df2ff --- /dev/null +++ b/runtime/go/prompty/model/turn_model_request.go @@ -0,0 +1,139 @@ +// +// Code generated by Typra emitter; DO NOT EDIT. +// Group: pipeline + +package prompty + +import ( + "encoding/json" + + "gopkg.in/yaml.v3" +) + +// TurnModelRequest represents Request passed by the reference turn runner to the injected model callback. +// +// The runner owns deterministic orchestration semantics; model/provider-specific +// execution stays behind this callback boundary. + +type TurnModelRequest struct { + SessionId string `json:"sessionId" yaml:"sessionId"` + TurnId string `json:"turnId" yaml:"turnId"` + Iteration int32 `json:"iteration" yaml:"iteration"` + Inputs map[string]interface{} `json:"inputs,omitempty" yaml:"inputs,omitempty"` + Options *TurnOptions `json:"options,omitempty" yaml:"options,omitempty"` + ToolResults []HostToolResult `json:"toolResults,omitempty" yaml:"toolResults,omitempty"` +} + +// LoadTurnModelRequest creates a TurnModelRequest from a map[string]interface{} +func LoadTurnModelRequest(data interface{}, ctx *LoadContext) (TurnModelRequest, error) { + result := TurnModelRequest{} + + // Load from map + if m, ok := data.(map[string]interface{}); ok { + if val, ok := m["sessionId"]; ok && val != nil { + result.SessionId = string(val.(string)) + } + if val, ok := m["turnId"]; ok && val != nil { + result.TurnId = string(val.(string)) + } + if val, ok := m["iteration"]; ok && val != nil { // Handle various numeric types from JSON/YAML/roundtrip + var v int32 + switch n := val.(type) { + case int: + v = int32(n) + case int32: + v = int32(n) + case int64: + v = int32(n) + case float64: + v = int32(n) + } + result.Iteration = v + } + if val, ok := m["inputs"]; ok && val != nil { + if m, ok := val.(map[string]interface{}); ok { + result.Inputs = m + } + } + if val, ok := m["options"]; ok && val != nil { + if m, ok := val.(map[string]interface{}); ok { + loaded, _ := LoadTurnOptions(m, ctx) + result.Options = &loaded + } + } + if val, ok := m["toolResults"]; ok && val != nil { + if arr, ok := val.([]interface{}); ok { + result.ToolResults = make([]HostToolResult, len(arr)) + for i, v := range arr { + if item, ok := v.(map[string]interface{}); ok { + loaded, _ := LoadHostToolResult(item, ctx) + result.ToolResults[i] = loaded + } + } + } + } + } + + return result, nil +} + +// Save serializes TurnModelRequest to map[string]interface{} +func (obj TurnModelRequest) Save(ctx *SaveContext) map[string]interface{} { + result := make(map[string]interface{}) + result["sessionId"] = obj.SessionId + result["turnId"] = obj.TurnId + result["iteration"] = obj.Iteration + if obj.Inputs != nil { + result["inputs"] = obj.Inputs + } + if obj.Options != nil { + result["options"] = obj.Options.Save(ctx) + } + if obj.ToolResults != nil { + arr := make([]interface{}, len(obj.ToolResults)) + for i, item := range obj.ToolResults { + arr[i] = item.Save(ctx) + } + result["toolResults"] = arr + } + + return result +} + +// ToJSON serializes TurnModelRequest to JSON string +func (obj *TurnModelRequest) ToJSON() (string, error) { + ctx := NewSaveContext() + data := obj.Save(ctx) + bytes, err := json.Marshal(data) + if err != nil { + return "", err + } + return string(bytes), nil +} + +// ToYAML serializes TurnModelRequest to YAML string +func (obj *TurnModelRequest) ToYAML() (string, error) { + ctx := NewSaveContext() + data := obj.Save(ctx) + return marshalYAMLDocument(data) +} + +// FromJSON creates TurnModelRequest from JSON string +func TurnModelRequestFromJSON(jsonStr string) (TurnModelRequest, error) { + var data map[string]interface{} + if err := json.Unmarshal([]byte(jsonStr), &data); err != nil { + return TurnModelRequest{}, err + } + ctx := NewLoadContext() + return LoadTurnModelRequest(data, ctx) +} + +// FromYAML creates TurnModelRequest from YAML string +func TurnModelRequestFromYAML(yamlStr string) (TurnModelRequest, error) { + var data map[string]interface{} + if err := yaml.Unmarshal([]byte(yamlStr), &data); err != nil { + return TurnModelRequest{}, err + } + ctx := NewLoadContext() + return LoadTurnModelRequest(data, ctx) +} diff --git a/runtime/go/prompty/model/turn_model_request_test.go b/runtime/go/prompty/model/turn_model_request_test.go new file mode 100644 index 00000000..3377cec8 --- /dev/null +++ b/runtime/go/prompty/model/turn_model_request_test.go @@ -0,0 +1,253 @@ +// +// Code generated by Typra emitter; DO NOT EDIT. + +package prompty_test + +import ( + "encoding/json" + "testing" + + "gopkg.in/yaml.v3" + + "prompty/model" +) + +// TestTurnModelRequestLoadJSON tests loading TurnModelRequest from JSON +func TestTurnModelRequestLoadJSON(t *testing.T) { + jsonData := ` +{ + "sessionId": "sess_abc123", + "turnId": "turn_abc123", + "iteration": 0 +} +` + var data map[string]interface{} + if err := json.Unmarshal([]byte(jsonData), &data); err != nil { + t.Fatalf("Failed to parse JSON: %v", err) + } + + ctx := prompty.NewLoadContext() + instance, err := prompty.LoadTurnModelRequest(data, ctx) + if err != nil { + t.Fatalf("Failed to load TurnModelRequest: %v", err) + } + if instance.SessionId != "sess_abc123" { + t.Errorf(`Expected SessionId to be "sess_abc123", got %v`, instance.SessionId) + } + if instance.TurnId != "turn_abc123" { + t.Errorf(`Expected TurnId to be "turn_abc123", got %v`, instance.TurnId) + } + if instance.Iteration != 0 { + t.Errorf(`Expected Iteration to be 0, got %v`, instance.Iteration) + } +} + +// TestTurnModelRequestLoadYAML tests loading TurnModelRequest from YAML +func TestTurnModelRequestLoadYAML(t *testing.T) { + yamlData := ` +sessionId: sess_abc123 +turnId: turn_abc123 +iteration: 0 + +` + var data map[string]interface{} + if err := yaml.Unmarshal([]byte(yamlData), &data); err != nil { + t.Fatalf("Failed to parse YAML: %v", err) + } + + ctx := prompty.NewLoadContext() + instance, err := prompty.LoadTurnModelRequest(data, ctx) + if err != nil { + t.Fatalf("Failed to load TurnModelRequest: %v", err) + } + if instance.SessionId != "sess_abc123" { + t.Errorf(`Expected SessionId to be "sess_abc123", got %v`, instance.SessionId) + } + if instance.TurnId != "turn_abc123" { + t.Errorf(`Expected TurnId to be "turn_abc123", got %v`, instance.TurnId) + } + if instance.Iteration != 0 { + t.Errorf(`Expected Iteration to be 0, got %v`, instance.Iteration) + } +} + +// TestTurnModelRequestFromJSON tests loading TurnModelRequest through the generated JSON helper +func TestTurnModelRequestFromJSON(t *testing.T) { + jsonData := ` +{ + "sessionId": "sess_abc123", + "turnId": "turn_abc123", + "iteration": 0 +} +` + + instance, err := prompty.TurnModelRequestFromJSON(jsonData) + if err != nil { + t.Fatalf("Failed to load TurnModelRequest from JSON helper: %v", err) + } + if instance.SessionId != "sess_abc123" { + t.Errorf(`Expected SessionId to be "sess_abc123", got %v`, instance.SessionId) + } + if instance.TurnId != "turn_abc123" { + t.Errorf(`Expected TurnId to be "turn_abc123", got %v`, instance.TurnId) + } + if instance.Iteration != 0 { + t.Errorf(`Expected Iteration to be 0, got %v`, instance.Iteration) + } +} + +// TestTurnModelRequestFromYAML tests loading TurnModelRequest through the generated YAML helper +func TestTurnModelRequestFromYAML(t *testing.T) { + yamlData := ` +sessionId: sess_abc123 +turnId: turn_abc123 +iteration: 0 + +` + + instance, err := prompty.TurnModelRequestFromYAML(yamlData) + if err != nil { + t.Fatalf("Failed to load TurnModelRequest from YAML helper: %v", err) + } + if instance.SessionId != "sess_abc123" { + t.Errorf(`Expected SessionId to be "sess_abc123", got %v`, instance.SessionId) + } + if instance.TurnId != "turn_abc123" { + t.Errorf(`Expected TurnId to be "turn_abc123", got %v`, instance.TurnId) + } + if instance.Iteration != 0 { + t.Errorf(`Expected Iteration to be 0, got %v`, instance.Iteration) + } +} + +// TestTurnModelRequestRoundtrip tests load -> save -> load produces equivalent data +func TestTurnModelRequestRoundtrip(t *testing.T) { + jsonData := ` +{ + "sessionId": "sess_abc123", + "turnId": "turn_abc123", + "iteration": 0 +} +` + var data map[string]interface{} + if err := json.Unmarshal([]byte(jsonData), &data); err != nil { + t.Fatalf("Failed to parse JSON: %v", err) + } + + loadCtx := prompty.NewLoadContext() + instance, err := prompty.LoadTurnModelRequest(data, loadCtx) + if err != nil { + t.Fatalf("Failed to load TurnModelRequest: %v", err) + } + saveCtx := prompty.NewSaveContext() + savedData := instance.Save(saveCtx) + + reloaded, err := prompty.LoadTurnModelRequest(savedData, loadCtx) + if err != nil { + t.Fatalf("Failed to reload TurnModelRequest: %v", err) + } + if reloaded.SessionId != "sess_abc123" { + t.Errorf(`Expected SessionId to be "sess_abc123", got %v`, reloaded.SessionId) + } + if reloaded.TurnId != "turn_abc123" { + t.Errorf(`Expected TurnId to be "turn_abc123", got %v`, reloaded.TurnId) + } + if reloaded.Iteration != 0 { + t.Errorf(`Expected Iteration to be 0, got %v`, reloaded.Iteration) + } +} + +// TestTurnModelRequestToJSON tests that ToJSON produces valid JSON +func TestTurnModelRequestToJSON(t *testing.T) { + jsonData := ` +{ + "sessionId": "sess_abc123", + "turnId": "turn_abc123", + "iteration": 0 +} +` + var data map[string]interface{} + if err := json.Unmarshal([]byte(jsonData), &data); err != nil { + t.Fatalf("Failed to parse JSON: %v", err) + } + + ctx := prompty.NewLoadContext() + instance, err := prompty.LoadTurnModelRequest(data, ctx) + if err != nil { + t.Fatalf("Failed to load TurnModelRequest: %v", err) + } + jsonOutput, err := instance.ToJSON() + if err != nil { + t.Fatalf("Failed to convert to JSON: %v", err) + } + + var parsed map[string]interface{} + if err := json.Unmarshal([]byte(jsonOutput), &parsed); err != nil { + t.Fatalf("Failed to parse generated JSON: %v", err) + } + + reloaded, err := prompty.LoadTurnModelRequest(parsed, ctx) + if err != nil { + t.Fatalf("Failed to reload generated JSON: %v", err) + } + if reloaded.SessionId != "sess_abc123" { + t.Errorf(`Expected SessionId to be "sess_abc123", got %v`, reloaded.SessionId) + } + if reloaded.TurnId != "turn_abc123" { + t.Errorf(`Expected TurnId to be "turn_abc123", got %v`, reloaded.TurnId) + } + if reloaded.Iteration != 0 { + t.Errorf(`Expected Iteration to be 0, got %v`, reloaded.Iteration) + } +} + +// TestTurnModelRequestToYAML tests that ToYAML produces valid YAML +func TestTurnModelRequestToYAML(t *testing.T) { + jsonData := ` +{ + "sessionId": "sess_abc123", + "turnId": "turn_abc123", + "iteration": 0 +} +` + var data map[string]interface{} + if err := json.Unmarshal([]byte(jsonData), &data); err != nil { + t.Fatalf("Failed to parse JSON: %v", err) + } + + ctx := prompty.NewLoadContext() + instance, err := prompty.LoadTurnModelRequest(data, ctx) + if err != nil { + t.Fatalf("Failed to load TurnModelRequest: %v", err) + } + yamlOutput, err := instance.ToYAML() + if err != nil { + t.Fatalf("Failed to convert to YAML: %v", err) + } + + var parsed map[string]interface{} + if err := yaml.Unmarshal([]byte(yamlOutput), &parsed); err != nil { + t.Fatalf("Failed to parse generated YAML: %v", err) + } + + reloaded, err := prompty.LoadTurnModelRequest(parsed, ctx) + if err != nil { + t.Fatalf("Failed to reload generated YAML: %v", err) + } + if reloaded.SessionId != "sess_abc123" { + t.Errorf(`Expected SessionId to be "sess_abc123", got %v`, reloaded.SessionId) + } + if reloaded.TurnId != "turn_abc123" { + t.Errorf(`Expected TurnId to be "turn_abc123", got %v`, reloaded.TurnId) + } + if reloaded.Iteration != 0 { + t.Errorf(`Expected Iteration to be 0, got %v`, reloaded.Iteration) + } +} + +// TestTurnModelRequestFromJSONInvalid rejects malformed JSON instead of silently defaulting +func TestTurnModelRequestFromJSONInvalid(t *testing.T) { + if _, err := prompty.TurnModelRequestFromJSON("{"); err == nil { + t.Fatalf("Expected malformed JSON to fail") + } +} diff --git a/runtime/go/prompty/model/turn_model_response.go b/runtime/go/prompty/model/turn_model_response.go new file mode 100644 index 00000000..5207d55a --- /dev/null +++ b/runtime/go/prompty/model/turn_model_response.go @@ -0,0 +1,107 @@ +// +// Code generated by Typra emitter; DO NOT EDIT. +// Group: pipeline + +package prompty + +import ( + "encoding/json" + + "gopkg.in/yaml.v3" +) + +// TurnModelResponse represents Response returned by the injected model callback to the reference turn runner. + +type TurnModelResponse struct { + Output *interface{} `json:"output,omitempty" yaml:"output,omitempty"` + ToolRequests []HostToolRequest `json:"toolRequests,omitempty" yaml:"toolRequests,omitempty"` + CheckpointState map[string]interface{} `json:"checkpointState,omitempty" yaml:"checkpointState,omitempty"` +} + +// LoadTurnModelResponse creates a TurnModelResponse from a map[string]interface{} +func LoadTurnModelResponse(data interface{}, ctx *LoadContext) (TurnModelResponse, error) { + result := TurnModelResponse{} + + // Load from map + if m, ok := data.(map[string]interface{}); ok { + if val, ok := m["output"]; ok && val != nil { + result.Output = &val + } + if val, ok := m["toolRequests"]; ok && val != nil { + if arr, ok := val.([]interface{}); ok { + result.ToolRequests = make([]HostToolRequest, len(arr)) + for i, v := range arr { + if item, ok := v.(map[string]interface{}); ok { + loaded, _ := LoadHostToolRequest(item, ctx) + result.ToolRequests[i] = loaded + } + } + } + } + if val, ok := m["checkpointState"]; ok && val != nil { + if m, ok := val.(map[string]interface{}); ok { + result.CheckpointState = m + } + } + } + + return result, nil +} + +// Save serializes TurnModelResponse to map[string]interface{} +func (obj TurnModelResponse) Save(ctx *SaveContext) map[string]interface{} { + result := make(map[string]interface{}) + if obj.Output != nil { + result["output"] = *obj.Output + } + if obj.ToolRequests != nil { + arr := make([]interface{}, len(obj.ToolRequests)) + for i, item := range obj.ToolRequests { + arr[i] = item.Save(ctx) + } + result["toolRequests"] = arr + } + if obj.CheckpointState != nil { + result["checkpointState"] = obj.CheckpointState + } + + return result +} + +// ToJSON serializes TurnModelResponse to JSON string +func (obj *TurnModelResponse) ToJSON() (string, error) { + ctx := NewSaveContext() + data := obj.Save(ctx) + bytes, err := json.Marshal(data) + if err != nil { + return "", err + } + return string(bytes), nil +} + +// ToYAML serializes TurnModelResponse to YAML string +func (obj *TurnModelResponse) ToYAML() (string, error) { + ctx := NewSaveContext() + data := obj.Save(ctx) + return marshalYAMLDocument(data) +} + +// FromJSON creates TurnModelResponse from JSON string +func TurnModelResponseFromJSON(jsonStr string) (TurnModelResponse, error) { + var data map[string]interface{} + if err := json.Unmarshal([]byte(jsonStr), &data); err != nil { + return TurnModelResponse{}, err + } + ctx := NewLoadContext() + return LoadTurnModelResponse(data, ctx) +} + +// FromYAML creates TurnModelResponse from YAML string +func TurnModelResponseFromYAML(yamlStr string) (TurnModelResponse, error) { + var data map[string]interface{} + if err := yaml.Unmarshal([]byte(yamlStr), &data); err != nil { + return TurnModelResponse{}, err + } + ctx := NewLoadContext() + return LoadTurnModelResponse(data, ctx) +} diff --git a/runtime/go/prompty/model/turn_model_response_test.go b/runtime/go/prompty/model/turn_model_response_test.go new file mode 100644 index 00000000..9cc5440c --- /dev/null +++ b/runtime/go/prompty/model/turn_model_response_test.go @@ -0,0 +1,4 @@ +// +// Code generated by Typra emitter; DO NOT EDIT. + +package prompty_test diff --git a/runtime/go/prompty/model/turn_options.go b/runtime/go/prompty/model/turn_options.go index 08f60735..aacb1588 100644 --- a/runtime/go/prompty/model/turn_options.go +++ b/runtime/go/prompty/model/turn_options.go @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. // Group: pipeline package prompty @@ -108,7 +109,7 @@ func LoadTurnOptions(data interface{}, ctx *LoadContext) (TurnOptions, error) { } // Save serializes TurnOptions to map[string]interface{} -func (obj *TurnOptions) Save(ctx *SaveContext) map[string]interface{} { +func (obj TurnOptions) Save(ctx *SaveContext) map[string]interface{} { result := make(map[string]interface{}) if obj.MaxIterations != nil { result["maxIterations"] = *obj.MaxIterations @@ -150,11 +151,7 @@ func (obj *TurnOptions) ToJSON() (string, error) { func (obj *TurnOptions) ToYAML() (string, error) { ctx := NewSaveContext() data := obj.Save(ctx) - bytes, err := yaml.Marshal(data) - if err != nil { - return "", err - } - return string(bytes), nil + return marshalYAMLDocument(data) } // FromJSON creates TurnOptions from JSON string diff --git a/runtime/go/prompty/model/turn_options_test.go b/runtime/go/prompty/model/turn_options_test.go index c448023d..f4017cb6 100644 --- a/runtime/go/prompty/model/turn_options_test.go +++ b/runtime/go/prompty/model/turn_options_test.go @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. package prompty_test @@ -54,6 +55,9 @@ func TestTurnOptionsLoadJSON(t *testing.T) { if instance.Turn == nil || *instance.Turn != 1 { t.Errorf(`Expected Turn to be 1, got %v`, instance.Turn) } + if instance.Compaction.Strategy == nil || *instance.Compaction.Strategy != "summarize" { + t.Errorf(`Expected Compaction.Strategy to be "summarize", got %v`, instance.Compaction.Strategy) + } } // TestTurnOptionsLoadYAML tests loading TurnOptions from YAML @@ -97,6 +101,93 @@ compaction: if instance.Turn == nil || *instance.Turn != 1 { t.Errorf(`Expected Turn to be 1, got %v`, instance.Turn) } + if instance.Compaction.Strategy == nil || *instance.Compaction.Strategy != "summarize" { + t.Errorf(`Expected Compaction.Strategy to be "summarize", got %v`, instance.Compaction.Strategy) + } +} + +// TestTurnOptionsFromJSON tests loading TurnOptions through the generated JSON helper +func TestTurnOptionsFromJSON(t *testing.T) { + jsonData := ` +{ + "maxIterations": 10, + "maxLlmRetries": 3, + "contextBudget": 100000, + "parallelToolCalls": true, + "raw": false, + "turn": 1, + "compaction": { + "strategy": "summarize" + } +} +` + + instance, err := prompty.TurnOptionsFromJSON(jsonData) + if err != nil { + t.Fatalf("Failed to load TurnOptions from JSON helper: %v", err) + } + if instance.MaxIterations == nil || *instance.MaxIterations != 10 { + t.Errorf(`Expected MaxIterations to be 10, got %v`, instance.MaxIterations) + } + if instance.MaxLlmRetries == nil || *instance.MaxLlmRetries != 3 { + t.Errorf(`Expected MaxLlmRetries to be 3, got %v`, instance.MaxLlmRetries) + } + if instance.ContextBudget == nil || *instance.ContextBudget != 100000 { + t.Errorf(`Expected ContextBudget to be 100000, got %v`, instance.ContextBudget) + } + if instance.ParallelToolCalls == nil || *instance.ParallelToolCalls != true { + t.Errorf(`Expected ParallelToolCalls to be true, got %v`, instance.ParallelToolCalls) + } + if instance.Raw == nil || *instance.Raw != false { + t.Errorf(`Expected Raw to be false, got %v`, instance.Raw) + } + if instance.Turn == nil || *instance.Turn != 1 { + t.Errorf(`Expected Turn to be 1, got %v`, instance.Turn) + } + if instance.Compaction.Strategy == nil || *instance.Compaction.Strategy != "summarize" { + t.Errorf(`Expected Compaction.Strategy to be "summarize", got %v`, instance.Compaction.Strategy) + } +} + +// TestTurnOptionsFromYAML tests loading TurnOptions through the generated YAML helper +func TestTurnOptionsFromYAML(t *testing.T) { + yamlData := ` +maxIterations: 10 +maxLlmRetries: 3 +contextBudget: 100000 +parallelToolCalls: true +raw: false +turn: 1 +compaction: + strategy: summarize + +` + + instance, err := prompty.TurnOptionsFromYAML(yamlData) + if err != nil { + t.Fatalf("Failed to load TurnOptions from YAML helper: %v", err) + } + if instance.MaxIterations == nil || *instance.MaxIterations != 10 { + t.Errorf(`Expected MaxIterations to be 10, got %v`, instance.MaxIterations) + } + if instance.MaxLlmRetries == nil || *instance.MaxLlmRetries != 3 { + t.Errorf(`Expected MaxLlmRetries to be 3, got %v`, instance.MaxLlmRetries) + } + if instance.ContextBudget == nil || *instance.ContextBudget != 100000 { + t.Errorf(`Expected ContextBudget to be 100000, got %v`, instance.ContextBudget) + } + if instance.ParallelToolCalls == nil || *instance.ParallelToolCalls != true { + t.Errorf(`Expected ParallelToolCalls to be true, got %v`, instance.ParallelToolCalls) + } + if instance.Raw == nil || *instance.Raw != false { + t.Errorf(`Expected Raw to be false, got %v`, instance.Raw) + } + if instance.Turn == nil || *instance.Turn != 1 { + t.Errorf(`Expected Turn to be 1, got %v`, instance.Turn) + } + if instance.Compaction.Strategy == nil || *instance.Compaction.Strategy != "summarize" { + t.Errorf(`Expected Compaction.Strategy to be "summarize", got %v`, instance.Compaction.Strategy) + } } // TestTurnOptionsRoundtrip tests load -> save -> load produces equivalent data @@ -149,6 +240,9 @@ func TestTurnOptionsRoundtrip(t *testing.T) { if reloaded.Turn == nil || *reloaded.Turn != 1 { t.Errorf(`Expected Turn to be 1, got %v`, reloaded.Turn) } + if reloaded.Compaction.Strategy == nil || *reloaded.Compaction.Strategy != "summarize" { + t.Errorf(`Expected Compaction.Strategy to be "summarize", got %v`, reloaded.Compaction.Strategy) + } } // TestTurnOptionsToJSON tests that ToJSON produces valid JSON @@ -185,6 +279,32 @@ func TestTurnOptionsToJSON(t *testing.T) { if err := json.Unmarshal([]byte(jsonOutput), &parsed); err != nil { t.Fatalf("Failed to parse generated JSON: %v", err) } + + reloaded, err := prompty.LoadTurnOptions(parsed, ctx) + if err != nil { + t.Fatalf("Failed to reload generated JSON: %v", err) + } + if reloaded.MaxIterations == nil || *reloaded.MaxIterations != 10 { + t.Errorf(`Expected MaxIterations to be 10, got %v`, reloaded.MaxIterations) + } + if reloaded.MaxLlmRetries == nil || *reloaded.MaxLlmRetries != 3 { + t.Errorf(`Expected MaxLlmRetries to be 3, got %v`, reloaded.MaxLlmRetries) + } + if reloaded.ContextBudget == nil || *reloaded.ContextBudget != 100000 { + t.Errorf(`Expected ContextBudget to be 100000, got %v`, reloaded.ContextBudget) + } + if reloaded.ParallelToolCalls == nil || *reloaded.ParallelToolCalls != true { + t.Errorf(`Expected ParallelToolCalls to be true, got %v`, reloaded.ParallelToolCalls) + } + if reloaded.Raw == nil || *reloaded.Raw != false { + t.Errorf(`Expected Raw to be false, got %v`, reloaded.Raw) + } + if reloaded.Turn == nil || *reloaded.Turn != 1 { + t.Errorf(`Expected Turn to be 1, got %v`, reloaded.Turn) + } + if reloaded.Compaction.Strategy == nil || *reloaded.Compaction.Strategy != "summarize" { + t.Errorf(`Expected Compaction.Strategy to be "summarize", got %v`, reloaded.Compaction.Strategy) + } } // TestTurnOptionsToYAML tests that ToYAML produces valid YAML @@ -221,4 +341,37 @@ func TestTurnOptionsToYAML(t *testing.T) { if err := yaml.Unmarshal([]byte(yamlOutput), &parsed); err != nil { t.Fatalf("Failed to parse generated YAML: %v", err) } + + reloaded, err := prompty.LoadTurnOptions(parsed, ctx) + if err != nil { + t.Fatalf("Failed to reload generated YAML: %v", err) + } + if reloaded.MaxIterations == nil || *reloaded.MaxIterations != 10 { + t.Errorf(`Expected MaxIterations to be 10, got %v`, reloaded.MaxIterations) + } + if reloaded.MaxLlmRetries == nil || *reloaded.MaxLlmRetries != 3 { + t.Errorf(`Expected MaxLlmRetries to be 3, got %v`, reloaded.MaxLlmRetries) + } + if reloaded.ContextBudget == nil || *reloaded.ContextBudget != 100000 { + t.Errorf(`Expected ContextBudget to be 100000, got %v`, reloaded.ContextBudget) + } + if reloaded.ParallelToolCalls == nil || *reloaded.ParallelToolCalls != true { + t.Errorf(`Expected ParallelToolCalls to be true, got %v`, reloaded.ParallelToolCalls) + } + if reloaded.Raw == nil || *reloaded.Raw != false { + t.Errorf(`Expected Raw to be false, got %v`, reloaded.Raw) + } + if reloaded.Turn == nil || *reloaded.Turn != 1 { + t.Errorf(`Expected Turn to be 1, got %v`, reloaded.Turn) + } + if reloaded.Compaction.Strategy == nil || *reloaded.Compaction.Strategy != "summarize" { + t.Errorf(`Expected Compaction.Strategy to be "summarize", got %v`, reloaded.Compaction.Strategy) + } +} + +// TestTurnOptionsFromJSONInvalid rejects malformed JSON instead of silently defaulting +func TestTurnOptionsFromJSONInvalid(t *testing.T) { + if _, err := prompty.TurnOptionsFromJSON("{"); err == nil { + t.Fatalf("Expected malformed JSON to fail") + } } diff --git a/runtime/go/prompty/model/turn_runner.go b/runtime/go/prompty/model/turn_runner.go new file mode 100644 index 00000000..5c573bfa --- /dev/null +++ b/runtime/go/prompty/model/turn_runner.go @@ -0,0 +1,312 @@ +package prompty + +import ( + "fmt" + "time" +) + +type TurnModelCallback func(request TurnModelRequest) (TurnModelResponse, error) + +type ReferenceTurnRunner struct { + EventSink EventSink + Journal EventJournalWriter + CheckpointStore CheckpointStore + PermissionResolver PermissionResolver + HostToolExecutor HostToolExecutor + InvokeModel TurnModelCallback + Now func() string + NextId func(prefix string) string + sequence int +} + +func (r *ReferenceTurnRunner) Run(request RunTurnRequest) (RunTurnResult, error) { + inputs := request.Inputs + if inputs == nil { + inputs = map[string]interface{}{} + } + options := request.Options + if options == nil { + options = &TurnOptions{} + } + maxIterations := int32(10) + if options.MaxIterations != nil { + maxIterations = *options.MaxIterations + } + checkpoints := []Checkpoint{} + allToolResults := []HostToolResult{} + pendingToolResults := []HostToolResult{} + var output interface{} + status := RunTurnStatusSuccess + iterations := int32(0) + + if err := r.recordSession(SessionEventTypeSessionStart, request.SessionId, request.TurnId, map[string]interface{}{ + "sessionId": request.SessionId, + "schemaVersion": "1", + }); err != nil { + return RunTurnResult{}, err + } + if err := r.recordTurn(TurnEventTypeTurnStart, request.TurnId, 0, map[string]interface{}{ + "inputs": inputs, + "maxIterations": maxIterations, + }); err != nil { + return RunTurnResult{}, err + } + + for iteration := int32(0); iteration < maxIterations; iteration++ { + iterations = iteration + 1 + if err := r.recordTurn(TurnEventTypeLlmStart, request.TurnId, int(iteration), map[string]interface{}{"attempt": 0}); err != nil { + return RunTurnResult{}, err + } + modelResponse, err := r.InvokeModel(TurnModelRequest{ + SessionId: request.SessionId, + TurnId: request.TurnId, + Iteration: iteration, + Inputs: inputs, + Options: options, + ToolResults: pendingToolResults, + }) + if err != nil { + return RunTurnResult{}, err + } + if err := r.recordTurn(TurnEventTypeLlmComplete, request.TurnId, int(iteration), map[string]interface{}{}); err != nil { + return RunTurnResult{}, err + } + checkpoint, err := r.saveCheckpoint(request.SessionId, request.TurnId, int(iteration), modelResponse) + if err != nil { + return RunTurnResult{}, err + } + checkpoints = append(checkpoints, checkpoint) + + if len(modelResponse.ToolRequests) == 0 { + if modelResponse.Output != nil { + output = *modelResponse.Output + } + break + } + pendingToolResults = []HostToolResult{} + for _, toolRequest := range modelResponse.ToolRequests { + toolResult, err := r.resolveAndExecuteTool(request.TurnId, int(iteration), toolRequest) + if err != nil { + return RunTurnResult{}, err + } + pendingToolResults = append(pendingToolResults, toolResult) + allToolResults = append(allToolResults, toolResult) + } + serializedResults := []map[string]interface{}{} + for _, result := range pendingToolResults { + serializedResults = append(serializedResults, result.Save(NewSaveContext())) + } + if err := r.recordTurn(TurnEventTypeMessagesUpdated, request.TurnId, int(iteration), map[string]interface{}{"toolResults": serializedResults}); err != nil { + return RunTurnResult{}, err + } + } + + if output == nil && len(pendingToolResults) > 0 { + status = RunTurnStatusError + output = map[string]interface{}{"message": "Maximum turn iterations reached"} + if err := r.recordTurn(TurnEventTypeError, request.TurnId, int(iterations), map[string]interface{}{ + "errorKind": "max_iterations", + "message": "Maximum turn iterations reached", + }); err != nil { + return RunTurnResult{}, err + } + } + + if err := r.recordTurn(TurnEventTypeTurnEnd, request.TurnId, int(iterations), map[string]interface{}{ + "iterations": iterations, + "status": status, + "response": output, + }); err != nil { + return RunTurnResult{}, err + } + if err := r.recordSession(SessionEventTypeSessionEnd, request.SessionId, request.TurnId, map[string]interface{}{ + "sessionId": request.SessionId, + "status": status, + "reason": "turn_complete", + }); err != nil { + return RunTurnResult{}, err + } + summaryStatus := SessionSummaryStatus(status) + turns := int32(1) + checkpointCount := int32(len(checkpoints)) + if _, err := r.Journal.Close(&SessionSummary{ + SessionId: request.SessionId, + Status: &summaryStatus, + Turns: &turns, + Checkpoints: &checkpointCount, + }); err != nil { + return RunTurnResult{}, err + } + + var outputPtr *interface{} + if output != nil { + outputPtr = &output + } + + return RunTurnResult{ + SessionId: request.SessionId, + TurnId: request.TurnId, + Status: status, + Output: outputPtr, + Iterations: iterations, + ToolResults: allToolResults, + Checkpoints: checkpoints, + }, nil +} + +func (r *ReferenceTurnRunner) saveCheckpoint(sessionId string, turnId string, iteration int, response TurnModelResponse) (Checkpoint, error) { + checkpointId := fmt.Sprintf("%s-checkpoint-%d", turnId, iteration) + checkpointNumber := int32(iteration + 1) + state := map[string]interface{}{ + "iteration": iteration, + "output": dereferenceOutput(response.Output), + "toolRequests": saveToolRequests(response.ToolRequests), + } + for key, value := range response.CheckpointState { + state[key] = value + } + checkpoint := Checkpoint{ + Id: &checkpointId, + SessionId: &sessionId, + TurnId: &turnId, + CheckpointNumber: &checkpointNumber, + Title: fmt.Sprintf("Turn %s iteration %d", turnId, iteration), + State: state, + CreatedAt: stringPtr(r.timestamp()), + } + saved, err := r.CheckpointStore.Save(checkpoint) + if err != nil { + return Checkpoint{}, err + } + if err := r.recordSession(SessionEventTypeCheckpointCreated, sessionId, turnId, map[string]interface{}{ + "checkpointId": saved.Id, + "checkpointNumber": saved.CheckpointNumber, + }); err != nil { + return Checkpoint{}, err + } + return saved, nil +} + +func dereferenceOutput(output *interface{}) interface{} { + if output == nil { + return nil + } + return *output +} + +func (r *ReferenceTurnRunner) resolveAndExecuteTool(turnId string, iteration int, toolRequest HostToolRequest) (HostToolResult, error) { + permissionRequestId := r.id("permission") + if toolRequest.RequestId != nil { + permissionRequestId = *toolRequest.RequestId + "-permission" + } + permission := PermissionRequest{ + RequestId: &permissionRequestId, + ToolCallId: toolRequest.ToolCallId, + Permission: "tool.execute", + Target: &toolRequest.ToolName, + Details: toolRequest.Save(NewSaveContext()), + } + if err := r.recordTurn(TurnEventTypePermissionRequested, turnId, iteration, permission.Save(NewSaveContext())); err != nil { + return HostToolResult{}, err + } + decision, err := r.PermissionResolver.Request(permission) + if err != nil { + return HostToolResult{}, err + } + if err := r.recordTurn(TurnEventTypePermissionCompleted, turnId, iteration, decision.Save(NewSaveContext())); err != nil { + return HostToolResult{}, err + } + if !decision.Approved { + errorKind := "permission_denied" + message := "Permission denied" + if decision.Reason != nil { + message = *decision.Reason + } + result := interface{}(map[string]interface{}{"message": message}) + return HostToolResult{ + RequestId: toolRequest.RequestId, + ToolCallId: toolRequest.ToolCallId, + ToolName: toolRequest.ToolName, + Success: false, + ErrorKind: &errorKind, + Result: &result, + }, nil + } + if err := r.recordTurn(TurnEventTypeToolExecutionStart, turnId, iteration, toolRequest.Save(NewSaveContext())); err != nil { + return HostToolResult{}, err + } + result, err := r.HostToolExecutor.Execute(toolRequest) + if err != nil { + return HostToolResult{}, err + } + if err := r.recordTurn(TurnEventTypeToolExecutionComplete, turnId, iteration, result.Save(NewSaveContext())); err != nil { + return HostToolResult{}, err + } + if err := r.recordTurn(TurnEventTypeToolResult, turnId, iteration, result.Save(NewSaveContext())); err != nil { + return HostToolResult{}, err + } + return result, nil +} + +func (r *ReferenceTurnRunner) recordTurn(eventType TurnEventType, turnId string, iteration int, payload map[string]interface{}) error { + event := TurnEvent{ + Id: r.id("turn-event"), + Type: eventType, + Timestamp: r.timestamp(), + TurnId: &turnId, + Iteration: int32Ptr(int32(iteration)), + Payload: payload, + } + if _, err := r.EventSink.EmitTurn(event); err != nil { + return err + } + _, err := r.Journal.AppendTurn(event) + return err +} + +func (r *ReferenceTurnRunner) recordSession(eventType SessionEventType, sessionId string, turnId string, payload map[string]interface{}) error { + event := SessionEvent{ + Id: r.id("session-event"), + Type: eventType, + Timestamp: r.timestamp(), + SessionId: &sessionId, + TurnId: &turnId, + Payload: payload, + } + if _, err := r.EventSink.EmitSession(event); err != nil { + return err + } + _, err := r.Journal.AppendSession(event) + return err +} + +func (r *ReferenceTurnRunner) timestamp() string { + if r.Now != nil { + return r.Now() + } + return time.Now().UTC().Format(time.RFC3339) +} + +func (r *ReferenceTurnRunner) id(prefix string) string { + if r.NextId != nil { + return r.NextId(prefix) + } + r.sequence++ + return fmt.Sprintf("%s-%d", prefix, r.sequence) +} + +func saveToolRequests(requests []HostToolRequest) []map[string]interface{} { + result := []map[string]interface{}{} + for _, request := range requests { + result = append(result, request.Save(NewSaveContext())) + } + return result +} + +func int32Ptr(value int32) *int32 { + return &value +} + +func stringPtr(value string) *string { + return &value +} diff --git a/runtime/go/prompty/model/turn_runner_test.go b/runtime/go/prompty/model/turn_runner_test.go new file mode 100644 index 00000000..91df53c9 --- /dev/null +++ b/runtime/go/prompty/model/turn_runner_test.go @@ -0,0 +1,467 @@ +package prompty + +import ( + "encoding/json" + "fmt" + "os" + "path/filepath" + "strings" + "testing" +) + +func fixedIds() func(prefix string) string { + index := 0 + return func(prefix string) string { + index++ + return fmt.Sprintf("%s-%d", prefix, index) + } +} + +func outputPtr(value interface{}) *interface{} { + return &value +} + +func readJournalRecords(t *testing.T, path string) []map[string]interface{} { + t.Helper() + content, err := os.ReadFile(path) + if err != nil { + t.Fatal(err) + } + records := []map[string]interface{}{} + for _, line := range splitLines(string(content)) { + var record map[string]interface{} + if err := json.Unmarshal([]byte(line), &record); err != nil { + t.Fatal(err) + } + records = append(records, record) + } + return records +} + +type replayVectors struct { + Version int `json:"version"` + Clock string `json:"clock"` + SessionId string `json:"sessionId"` + TurnId string `json:"turnId"` + Scenarios []replayScenario `json:"scenarios"` +} + +type replayScenario struct { + Name string `json:"name"` + Inputs map[string]interface{} `json:"inputs"` + MaxIterations *int32 `json:"maxIterations"` + Expected []string `json:"expected"` +} + +func loadReplayVectors(t *testing.T) replayVectors { + t.Helper() + path := filepath.Join("..", "..", "..", "..", "spec", "vectors", "harness", "replay_vectors.json") + content, err := os.ReadFile(path) + if err != nil { + t.Fatal(err) + } + var vectors replayVectors + if err := json.Unmarshal(content, &vectors); err != nil { + t.Fatal(err) + } + if vectors.Version != 1 { + t.Fatalf("unsupported replay vector version: %d", vectors.Version) + } + return vectors +} + +func normalizeJournal(records []map[string]interface{}) []string { + normalized := []string{} + for _, record := range records { + if record["kind"] == "summary" { + summary := record["summary"].(map[string]interface{}) + normalized = append(normalized, fmt.Sprintf( + "summary:%s:%s:turns=%v:checkpoints=%v", + summary["sessionId"], + summary["status"], + summary["turns"], + summary["checkpoints"], + )) + continue + } + + event := record["event"].(map[string]interface{}) + eventType := event["type"].(string) + if record["kind"] == "session" { + if eventType == "session_end" { + payload := event["payload"].(map[string]interface{}) + normalized = append(normalized, fmt.Sprintf( + "session:%s:%s:%s:%s", + eventType, + event["sessionId"], + event["turnId"], + payload["status"], + )) + } else { + normalized = append(normalized, fmt.Sprintf("session:%s:%s:%s", eventType, event["sessionId"], event["turnId"])) + } + continue + } + + payload := event["payload"].(map[string]interface{}) + iteration := event["iteration"] + switch eventType { + case "permission_requested": + normalized = append(normalized, fmt.Sprintf("turn:%s:%v:%s", eventType, iteration, payload["requestId"])) + case "permission_completed": + normalized = append(normalized, fmt.Sprintf("turn:%s:%v:%v", eventType, iteration, payload["approved"])) + case "tool_execution_start": + normalized = append(normalized, fmt.Sprintf("turn:%s:%v:%s", eventType, iteration, payload["toolName"])) + case "tool_execution_complete", "tool_result": + value := fmt.Sprintf("turn:%s:%v:%s:%v", eventType, iteration, payload["toolName"], payload["success"]) + if payload["errorKind"] != nil { + value = fmt.Sprintf("%s:%s", value, payload["errorKind"]) + } + normalized = append(normalized, value) + case "error": + normalized = append(normalized, fmt.Sprintf("turn:%s:%v:%s", eventType, iteration, payload["errorKind"])) + case "turn_end": + normalized = append(normalized, fmt.Sprintf("turn:%s:%v:%s", eventType, iteration, payload["status"])) + default: + normalized = append(normalized, fmt.Sprintf("turn:%s:%v", eventType, iteration)) + } + } + return normalized +} + +func modelForScenario(name string) TurnModelCallback { + return func(request TurnModelRequest) (TurnModelResponse, error) { + if name == "no_tool" { + return TurnModelResponse{ + Output: outputPtr(map[string]interface{}{"text": "hello " + request.Inputs["name"].(string)}), + CheckpointState: map[string]interface{}{"stable": true}, + }, nil + } + if request.Iteration == 0 { + requestId := "exec-1" + toolCallId := "call-1" + toolName := "add" + if name == "tool_failure" { + toolName = "fail" + } + return TurnModelResponse{ToolRequests: []HostToolRequest{{ + RequestId: &requestId, + ToolCallId: &toolCallId, + ToolName: toolName, + Arguments: map[string]interface{}{"a": 2, "b": 3}, + }}}, nil + } + return TurnModelResponse{Output: outputPtr(map[string]interface{}{ + "toolResult": *request.ToolResults[0].Result, + "errorKind": request.ToolResults[0].ErrorKind, + })}, nil + } +} + +func numberAsInt(value interface{}) int { + switch typed := value.(type) { + case int: + return typed + case float64: + return int(typed) + default: + return 0 + } +} + +func TestReferenceTurnRunnerEmitsJournalsAndCheckpoints(t *testing.T) { + journalPath := filepath.Join(t.TempDir(), "trace.jsonl") + journal, err := NewJsonlEventJournalWriter(journalPath) + if err != nil { + t.Fatal(err) + } + sink := &CollectingEventSink{} + checkpointStore := NewInMemoryCheckpointStore() + maxIterations := int32(3) + runner := ReferenceTurnRunner{ + EventSink: sink, + Journal: journal, + CheckpointStore: checkpointStore, + PermissionResolver: AllowAllPermissionResolver{}, + HostToolExecutor: FunctionHostToolExecutor{}, + InvokeModel: func(request TurnModelRequest) (TurnModelResponse, error) { + return TurnModelResponse{ + Output: outputPtr(map[string]interface{}{"text": "hello " + request.Inputs["name"].(string)}), + CheckpointState: map[string]interface{}{"stable": true}, + }, nil + }, + Now: func() string { return "2026-06-28T00:00:00Z" }, + NextId: fixedIds(), + } + + result, err := runner.Run(RunTurnRequest{ + SessionId: "session-1", + TurnId: "turn-1", + Inputs: map[string]interface{}{"name": "Ada"}, + Options: &TurnOptions{MaxIterations: &maxIterations}, + }) + if err != nil { + t.Fatal(err) + } + + if result.Status != "success" || result.Iterations != 1 { + t.Fatalf("unexpected result: %#v", result) + } + if (*result.Output).(map[string]interface{})["text"] != "hello Ada" { + t.Fatalf("unexpected output: %#v", result.Output) + } + assertEventTypes(t, turnTypes(sink.TurnEvents), []string{"turn_start", "llm_start", "llm_complete", "turn_end"}) + assertEventTypes(t, sessionTypes(sink.SessionEvents), []string{"session_start", "checkpoint_created", "session_end"}) + checkpoint, err := checkpointStore.Load("session-1", "turn-1-checkpoint-0") + if err != nil { + t.Fatal(err) + } + if checkpoint == nil || checkpoint.State["stable"] != true { + t.Fatalf("unexpected checkpoint: %#v", checkpoint) + } + records := readJournalRecords(t, journalPath) + assertEventTypes(t, recordKinds(records), []string{"session", "turn", "turn", "turn", "session", "turn", "session", "summary"}) +} + +func TestReferenceTurnRunnerExecutesHostTools(t *testing.T) { + journal, err := NewJsonlEventJournalWriter(filepath.Join(t.TempDir(), "trace.jsonl")) + if err != nil { + t.Fatal(err) + } + sink := &CollectingEventSink{} + runner := ReferenceTurnRunner{ + EventSink: sink, + Journal: journal, + CheckpointStore: NewInMemoryCheckpointStore(), + PermissionResolver: AllowAllPermissionResolver{}, + HostToolExecutor: FunctionHostToolExecutor{Handlers: map[string]HostToolHandler{ + "add": func(arguments map[string]interface{}, request HostToolRequest) (interface{}, error) { + return numberAsInt(arguments["a"]) + numberAsInt(arguments["b"]), nil + }, + }}, + InvokeModel: func(request TurnModelRequest) (TurnModelResponse, error) { + if request.Iteration == 0 { + requestId := "exec-1" + toolCallId := "call-1" + return TurnModelResponse{ToolRequests: []HostToolRequest{{ + RequestId: &requestId, + ToolCallId: &toolCallId, + ToolName: "add", + Arguments: map[string]interface{}{"a": 2, "b": 3}, + }}}, nil + } + return TurnModelResponse{Output: outputPtr(map[string]interface{}{"toolResult": *request.ToolResults[0].Result})}, nil + }, + Now: func() string { return "2026-06-28T00:00:00Z" }, + NextId: fixedIds(), + } + + result, err := runner.Run(RunTurnRequest{SessionId: "session-1", TurnId: "turn-1"}) + if err != nil { + t.Fatal(err) + } + + if (*result.Output).(map[string]interface{})["toolResult"] != 5 { + t.Fatalf("unexpected output: %#v", result.Output) + } + if !result.ToolResults[0].Success || *result.ToolResults[0].Result != 5 { + t.Fatalf("unexpected tool result: %#v", result.ToolResults[0]) + } + assertEventTypes(t, turnTypes(sink.TurnEvents), []string{ + "turn_start", "llm_start", "llm_complete", "permission_requested", "permission_completed", + "tool_execution_start", "tool_execution_complete", "tool_result", "messages_updated", + "llm_start", "llm_complete", "turn_end", + }) +} + +func TestReferenceTurnRunnerDeniedPermissionSkipsExecution(t *testing.T) { + journal, err := NewJsonlEventJournalWriter(filepath.Join(t.TempDir(), "trace.jsonl")) + if err != nil { + t.Fatal(err) + } + sink := &CollectingEventSink{} + runner := ReferenceTurnRunner{ + EventSink: sink, + Journal: journal, + CheckpointStore: NewInMemoryCheckpointStore(), + PermissionResolver: DenyAllPermissionResolver{}, + HostToolExecutor: FunctionHostToolExecutor{Handlers: map[string]HostToolHandler{ + "shell": func(arguments map[string]interface{}, request HostToolRequest) (interface{}, error) { + t.Fatal("should not execute") + return nil, nil + }, + }}, + InvokeModel: func(request TurnModelRequest) (TurnModelResponse, error) { + if request.Iteration == 0 { + requestId := "exec-1" + return TurnModelResponse{ToolRequests: []HostToolRequest{{RequestId: &requestId, ToolName: "shell"}}}, nil + } + return TurnModelResponse{Output: outputPtr(map[string]interface{}{"denied": *request.ToolResults[0].ErrorKind})}, nil + }, + Now: func() string { return "2026-06-28T00:00:00Z" }, + NextId: fixedIds(), + } + + result, err := runner.Run(RunTurnRequest{SessionId: "session-1", TurnId: "turn-1"}) + if err != nil { + t.Fatal(err) + } + if (*result.Output).(map[string]interface{})["denied"] != "permission_denied" { + t.Fatalf("unexpected output: %#v", result.Output) + } + for _, event := range sink.TurnEvents { + if event.Type == TurnEventTypeToolExecutionStart { + t.Fatalf("unexpected tool execution event: %#v", event) + } + } +} + +func TestReferenceTurnRunnerHostToolFailure(t *testing.T) { + journal, err := NewJsonlEventJournalWriter(filepath.Join(t.TempDir(), "trace.jsonl")) + if err != nil { + t.Fatal(err) + } + runner := ReferenceTurnRunner{ + EventSink: &CollectingEventSink{}, + Journal: journal, + CheckpointStore: NewInMemoryCheckpointStore(), + PermissionResolver: AllowAllPermissionResolver{}, + HostToolExecutor: FunctionHostToolExecutor{Handlers: map[string]HostToolHandler{ + "fail": func(arguments map[string]interface{}, request HostToolRequest) (interface{}, error) { + return nil, fmt.Errorf("boom") + }, + }}, + InvokeModel: func(request TurnModelRequest) (TurnModelResponse, error) { + if request.Iteration == 0 { + requestId := "exec-1" + return TurnModelResponse{ToolRequests: []HostToolRequest{{RequestId: &requestId, ToolName: "fail"}}}, nil + } + return TurnModelResponse{Output: outputPtr(request.ToolResults[0].Save(NewSaveContext()))}, nil + }, + Now: func() string { return "2026-06-28T00:00:00Z" }, + NextId: fixedIds(), + } + + result, err := runner.Run(RunTurnRequest{SessionId: "session-1", TurnId: "turn-1"}) + if err != nil { + t.Fatal(err) + } + output := (*result.Output).(map[string]interface{}) + if output["success"] != false || output["errorKind"] != "exception" { + t.Fatalf("unexpected output: %#v", output) + } +} + +func TestReferenceTurnRunnerDeterministicJournal(t *testing.T) { + runOnce := func(path string) []map[string]interface{} { + journal, err := NewJsonlEventJournalWriter(path) + if err != nil { + t.Fatal(err) + } + runner := ReferenceTurnRunner{ + EventSink: &CollectingEventSink{}, + Journal: journal, + CheckpointStore: NewInMemoryCheckpointStore(), + PermissionResolver: AllowAllPermissionResolver{}, + HostToolExecutor: FunctionHostToolExecutor{}, + InvokeModel: func(request TurnModelRequest) (TurnModelResponse, error) { + return TurnModelResponse{Output: outputPtr("done")}, nil + }, + Now: func() string { return "2026-06-28T00:00:00Z" }, + NextId: fixedIds(), + } + if _, err := runner.Run(RunTurnRequest{SessionId: "session-1", TurnId: "turn-1"}); err != nil { + t.Fatal(err) + } + return readJournalRecords(t, path) + } + + first := runOnce(filepath.Join(t.TempDir(), "first.jsonl")) + second := runOnce(filepath.Join(t.TempDir(), "second.jsonl")) + firstJSON, _ := json.Marshal(first) + secondJSON, _ := json.Marshal(second) + if string(firstJSON) != string(secondJSON) { + t.Fatalf("journals differ:\n%s\n%s", firstJSON, secondJSON) + } +} + +func TestReferenceTurnRunnerMatchesSharedGoldenReplayVectors(t *testing.T) { + vectors := loadReplayVectors(t) + for _, scenario := range vectors.Scenarios { + t.Run(scenario.Name, func(t *testing.T) { + journalPath := filepath.Join(t.TempDir(), "trace.jsonl") + journal, err := NewJsonlEventJournalWriter(journalPath) + if err != nil { + t.Fatal(err) + } + permissionResolver := PermissionResolver(AllowAllPermissionResolver{}) + if scenario.Name == "permission_denied" { + permissionResolver = DenyAllPermissionResolver{} + } + runner := ReferenceTurnRunner{ + EventSink: &CollectingEventSink{}, + Journal: journal, + CheckpointStore: NewInMemoryCheckpointStore(), + PermissionResolver: permissionResolver, + HostToolExecutor: FunctionHostToolExecutor{Handlers: map[string]HostToolHandler{ + "add": func(arguments map[string]interface{}, request HostToolRequest) (interface{}, error) { + return numberAsInt(arguments["a"]) + numberAsInt(arguments["b"]), nil + }, + "fail": func(arguments map[string]interface{}, request HostToolRequest) (interface{}, error) { + return nil, fmt.Errorf("boom") + }, + }}, + InvokeModel: modelForScenario(scenario.Name), + Now: func() string { return vectors.Clock }, + NextId: fixedIds(), + } + + _, err = runner.Run(RunTurnRequest{ + SessionId: vectors.SessionId, + TurnId: vectors.TurnId, + Inputs: scenario.Inputs, + Options: &TurnOptions{MaxIterations: scenario.MaxIterations}, + }) + if err != nil { + t.Fatal(err) + } + + actual := strings.Join(normalizeJournal(readJournalRecords(t, journalPath)), "\n") + expected := strings.Join(scenario.Expected, "\n") + if actual != expected { + t.Fatalf("normalized journal mismatch:\nexpected:\n%s\nactual:\n%s", expected, actual) + } + }) + } +} + +func turnTypes(events []TurnEvent) []string { + result := []string{} + for _, event := range events { + result = append(result, string(event.Type)) + } + return result +} + +func sessionTypes(events []SessionEvent) []string { + result := []string{} + for _, event := range events { + result = append(result, string(event.Type)) + } + return result +} + +func recordKinds(records []map[string]interface{}) []string { + result := []string{} + for _, record := range records { + result = append(result, record["kind"].(string)) + } + return result +} + +func assertEventTypes(t *testing.T, actual []string, expected []string) { + t.Helper() + if strings.Join(actual, ",") != strings.Join(expected, ",") { + t.Fatalf("expected %v, got %v", expected, actual) + } +} diff --git a/runtime/go/prompty/model/turn_start_payload.go b/runtime/go/prompty/model/turn_start_payload.go new file mode 100644 index 00000000..f9c156d9 --- /dev/null +++ b/runtime/go/prompty/model/turn_start_payload.go @@ -0,0 +1,107 @@ +// +// Code generated by Typra emitter; DO NOT EDIT. +// Group: events + +package prompty + +import ( + "encoding/json" + + "gopkg.in/yaml.v3" +) + +// TurnStartPayload represents Payload for "turn_start" events — a turn is beginning. + +type TurnStartPayload struct { + Agent *string `json:"agent,omitempty" yaml:"agent,omitempty"` + Inputs map[string]interface{} `json:"inputs,omitempty" yaml:"inputs,omitempty"` + MaxIterations *int32 `json:"maxIterations,omitempty" yaml:"maxIterations,omitempty"` +} + +// LoadTurnStartPayload creates a TurnStartPayload from a map[string]interface{} +func LoadTurnStartPayload(data interface{}, ctx *LoadContext) (TurnStartPayload, error) { + result := TurnStartPayload{} + + // Load from map + if m, ok := data.(map[string]interface{}); ok { + if val, ok := m["agent"]; ok && val != nil { + v := string(val.(string)) + result.Agent = &v + } + if val, ok := m["inputs"]; ok && val != nil { + if m, ok := val.(map[string]interface{}); ok { + result.Inputs = m + } + } + if val, ok := m["maxIterations"]; ok && val != nil { // Handle various numeric types from JSON/YAML/roundtrip + var v int32 + switch n := val.(type) { + case int: + v = int32(n) + case int32: + v = int32(n) + case int64: + v = int32(n) + case float64: + v = int32(n) + } + result.MaxIterations = &v + } + } + + return result, nil +} + +// Save serializes TurnStartPayload to map[string]interface{} +func (obj TurnStartPayload) Save(ctx *SaveContext) map[string]interface{} { + result := make(map[string]interface{}) + if obj.Agent != nil { + result["agent"] = *obj.Agent + } + if obj.Inputs != nil { + result["inputs"] = obj.Inputs + } + if obj.MaxIterations != nil { + result["maxIterations"] = *obj.MaxIterations + } + + return result +} + +// ToJSON serializes TurnStartPayload to JSON string +func (obj *TurnStartPayload) ToJSON() (string, error) { + ctx := NewSaveContext() + data := obj.Save(ctx) + bytes, err := json.Marshal(data) + if err != nil { + return "", err + } + return string(bytes), nil +} + +// ToYAML serializes TurnStartPayload to YAML string +func (obj *TurnStartPayload) ToYAML() (string, error) { + ctx := NewSaveContext() + data := obj.Save(ctx) + return marshalYAMLDocument(data) +} + +// FromJSON creates TurnStartPayload from JSON string +func TurnStartPayloadFromJSON(jsonStr string) (TurnStartPayload, error) { + var data map[string]interface{} + if err := json.Unmarshal([]byte(jsonStr), &data); err != nil { + return TurnStartPayload{}, err + } + ctx := NewLoadContext() + return LoadTurnStartPayload(data, ctx) +} + +// FromYAML creates TurnStartPayload from YAML string +func TurnStartPayloadFromYAML(yamlStr string) (TurnStartPayload, error) { + var data map[string]interface{} + if err := yaml.Unmarshal([]byte(yamlStr), &data); err != nil { + return TurnStartPayload{}, err + } + ctx := NewLoadContext() + return LoadTurnStartPayload(data, ctx) +} diff --git a/runtime/go/prompty/model/turn_start_payload_test.go b/runtime/go/prompty/model/turn_start_payload_test.go new file mode 100644 index 00000000..9b23774b --- /dev/null +++ b/runtime/go/prompty/model/turn_start_payload_test.go @@ -0,0 +1,225 @@ +// +// Code generated by Typra emitter; DO NOT EDIT. + +package prompty_test + +import ( + "encoding/json" + "testing" + + "gopkg.in/yaml.v3" + + "prompty/model" +) + +// TestTurnStartPayloadLoadJSON tests loading TurnStartPayload from JSON +func TestTurnStartPayloadLoadJSON(t *testing.T) { + jsonData := ` +{ + "agent": "weather-agent", + "maxIterations": 10 +} +` + var data map[string]interface{} + if err := json.Unmarshal([]byte(jsonData), &data); err != nil { + t.Fatalf("Failed to parse JSON: %v", err) + } + + ctx := prompty.NewLoadContext() + instance, err := prompty.LoadTurnStartPayload(data, ctx) + if err != nil { + t.Fatalf("Failed to load TurnStartPayload: %v", err) + } + if instance.Agent == nil || *instance.Agent != "weather-agent" { + t.Errorf(`Expected Agent to be "weather-agent", got %v`, instance.Agent) + } + if instance.MaxIterations == nil || *instance.MaxIterations != 10 { + t.Errorf(`Expected MaxIterations to be 10, got %v`, instance.MaxIterations) + } +} + +// TestTurnStartPayloadLoadYAML tests loading TurnStartPayload from YAML +func TestTurnStartPayloadLoadYAML(t *testing.T) { + yamlData := ` +agent: weather-agent +maxIterations: 10 + +` + var data map[string]interface{} + if err := yaml.Unmarshal([]byte(yamlData), &data); err != nil { + t.Fatalf("Failed to parse YAML: %v", err) + } + + ctx := prompty.NewLoadContext() + instance, err := prompty.LoadTurnStartPayload(data, ctx) + if err != nil { + t.Fatalf("Failed to load TurnStartPayload: %v", err) + } + if instance.Agent == nil || *instance.Agent != "weather-agent" { + t.Errorf(`Expected Agent to be "weather-agent", got %v`, instance.Agent) + } + if instance.MaxIterations == nil || *instance.MaxIterations != 10 { + t.Errorf(`Expected MaxIterations to be 10, got %v`, instance.MaxIterations) + } +} + +// TestTurnStartPayloadFromJSON tests loading TurnStartPayload through the generated JSON helper +func TestTurnStartPayloadFromJSON(t *testing.T) { + jsonData := ` +{ + "agent": "weather-agent", + "maxIterations": 10 +} +` + + instance, err := prompty.TurnStartPayloadFromJSON(jsonData) + if err != nil { + t.Fatalf("Failed to load TurnStartPayload from JSON helper: %v", err) + } + if instance.Agent == nil || *instance.Agent != "weather-agent" { + t.Errorf(`Expected Agent to be "weather-agent", got %v`, instance.Agent) + } + if instance.MaxIterations == nil || *instance.MaxIterations != 10 { + t.Errorf(`Expected MaxIterations to be 10, got %v`, instance.MaxIterations) + } +} + +// TestTurnStartPayloadFromYAML tests loading TurnStartPayload through the generated YAML helper +func TestTurnStartPayloadFromYAML(t *testing.T) { + yamlData := ` +agent: weather-agent +maxIterations: 10 + +` + + instance, err := prompty.TurnStartPayloadFromYAML(yamlData) + if err != nil { + t.Fatalf("Failed to load TurnStartPayload from YAML helper: %v", err) + } + if instance.Agent == nil || *instance.Agent != "weather-agent" { + t.Errorf(`Expected Agent to be "weather-agent", got %v`, instance.Agent) + } + if instance.MaxIterations == nil || *instance.MaxIterations != 10 { + t.Errorf(`Expected MaxIterations to be 10, got %v`, instance.MaxIterations) + } +} + +// TestTurnStartPayloadRoundtrip tests load -> save -> load produces equivalent data +func TestTurnStartPayloadRoundtrip(t *testing.T) { + jsonData := ` +{ + "agent": "weather-agent", + "maxIterations": 10 +} +` + var data map[string]interface{} + if err := json.Unmarshal([]byte(jsonData), &data); err != nil { + t.Fatalf("Failed to parse JSON: %v", err) + } + + loadCtx := prompty.NewLoadContext() + instance, err := prompty.LoadTurnStartPayload(data, loadCtx) + if err != nil { + t.Fatalf("Failed to load TurnStartPayload: %v", err) + } + saveCtx := prompty.NewSaveContext() + savedData := instance.Save(saveCtx) + + reloaded, err := prompty.LoadTurnStartPayload(savedData, loadCtx) + if err != nil { + t.Fatalf("Failed to reload TurnStartPayload: %v", err) + } + if reloaded.Agent == nil || *reloaded.Agent != "weather-agent" { + t.Errorf(`Expected Agent to be "weather-agent", got %v`, reloaded.Agent) + } + if reloaded.MaxIterations == nil || *reloaded.MaxIterations != 10 { + t.Errorf(`Expected MaxIterations to be 10, got %v`, reloaded.MaxIterations) + } +} + +// TestTurnStartPayloadToJSON tests that ToJSON produces valid JSON +func TestTurnStartPayloadToJSON(t *testing.T) { + jsonData := ` +{ + "agent": "weather-agent", + "maxIterations": 10 +} +` + var data map[string]interface{} + if err := json.Unmarshal([]byte(jsonData), &data); err != nil { + t.Fatalf("Failed to parse JSON: %v", err) + } + + ctx := prompty.NewLoadContext() + instance, err := prompty.LoadTurnStartPayload(data, ctx) + if err != nil { + t.Fatalf("Failed to load TurnStartPayload: %v", err) + } + jsonOutput, err := instance.ToJSON() + if err != nil { + t.Fatalf("Failed to convert to JSON: %v", err) + } + + var parsed map[string]interface{} + if err := json.Unmarshal([]byte(jsonOutput), &parsed); err != nil { + t.Fatalf("Failed to parse generated JSON: %v", err) + } + + reloaded, err := prompty.LoadTurnStartPayload(parsed, ctx) + if err != nil { + t.Fatalf("Failed to reload generated JSON: %v", err) + } + if reloaded.Agent == nil || *reloaded.Agent != "weather-agent" { + t.Errorf(`Expected Agent to be "weather-agent", got %v`, reloaded.Agent) + } + if reloaded.MaxIterations == nil || *reloaded.MaxIterations != 10 { + t.Errorf(`Expected MaxIterations to be 10, got %v`, reloaded.MaxIterations) + } +} + +// TestTurnStartPayloadToYAML tests that ToYAML produces valid YAML +func TestTurnStartPayloadToYAML(t *testing.T) { + jsonData := ` +{ + "agent": "weather-agent", + "maxIterations": 10 +} +` + var data map[string]interface{} + if err := json.Unmarshal([]byte(jsonData), &data); err != nil { + t.Fatalf("Failed to parse JSON: %v", err) + } + + ctx := prompty.NewLoadContext() + instance, err := prompty.LoadTurnStartPayload(data, ctx) + if err != nil { + t.Fatalf("Failed to load TurnStartPayload: %v", err) + } + yamlOutput, err := instance.ToYAML() + if err != nil { + t.Fatalf("Failed to convert to YAML: %v", err) + } + + var parsed map[string]interface{} + if err := yaml.Unmarshal([]byte(yamlOutput), &parsed); err != nil { + t.Fatalf("Failed to parse generated YAML: %v", err) + } + + reloaded, err := prompty.LoadTurnStartPayload(parsed, ctx) + if err != nil { + t.Fatalf("Failed to reload generated YAML: %v", err) + } + if reloaded.Agent == nil || *reloaded.Agent != "weather-agent" { + t.Errorf(`Expected Agent to be "weather-agent", got %v`, reloaded.Agent) + } + if reloaded.MaxIterations == nil || *reloaded.MaxIterations != 10 { + t.Errorf(`Expected MaxIterations to be 10, got %v`, reloaded.MaxIterations) + } +} + +// TestTurnStartPayloadFromJSONInvalid rejects malformed JSON instead of silently defaulting +func TestTurnStartPayloadFromJSONInvalid(t *testing.T) { + if _, err := prompty.TurnStartPayloadFromJSON("{"); err == nil { + t.Fatalf("Expected malformed JSON to fail") + } +} diff --git a/runtime/go/prompty/model/turn_summary.go b/runtime/go/prompty/model/turn_summary.go new file mode 100644 index 00000000..9260eee3 --- /dev/null +++ b/runtime/go/prompty/model/turn_summary.go @@ -0,0 +1,182 @@ +// +// Code generated by Typra emitter; DO NOT EDIT. +// Group: events + +package prompty + +import ( + "encoding/json" + + "gopkg.in/yaml.v3" +) + +// TurnSummary represents Summary statistics for a completed turn trace. + +type TurnSummary struct { + TurnId string `json:"turnId" yaml:"turnId"` + Status string `json:"status" yaml:"status"` + Iterations int32 `json:"iterations" yaml:"iterations"` + LlmCalls *int32 `json:"llmCalls,omitempty" yaml:"llmCalls,omitempty"` + ToolCalls *int32 `json:"toolCalls,omitempty" yaml:"toolCalls,omitempty"` + Retries *int32 `json:"retries,omitempty" yaml:"retries,omitempty"` + Usage *TokenUsage `json:"usage,omitempty" yaml:"usage,omitempty"` + DurationMs *float64 `json:"durationMs,omitempty" yaml:"durationMs,omitempty"` +} + +// LoadTurnSummary creates a TurnSummary from a map[string]interface{} +func LoadTurnSummary(data interface{}, ctx *LoadContext) (TurnSummary, error) { + result := TurnSummary{} + + // Load from map + if m, ok := data.(map[string]interface{}); ok { + if val, ok := m["turnId"]; ok && val != nil { + result.TurnId = string(val.(string)) + } + if val, ok := m["status"]; ok && val != nil { + result.Status = string(val.(string)) + } + if val, ok := m["iterations"]; ok && val != nil { // Handle various numeric types from JSON/YAML/roundtrip + var v int32 + switch n := val.(type) { + case int: + v = int32(n) + case int32: + v = int32(n) + case int64: + v = int32(n) + case float64: + v = int32(n) + } + result.Iterations = v + } + if val, ok := m["llmCalls"]; ok && val != nil { // Handle various numeric types from JSON/YAML/roundtrip + var v int32 + switch n := val.(type) { + case int: + v = int32(n) + case int32: + v = int32(n) + case int64: + v = int32(n) + case float64: + v = int32(n) + } + result.LlmCalls = &v + } + if val, ok := m["toolCalls"]; ok && val != nil { // Handle various numeric types from JSON/YAML/roundtrip + var v int32 + switch n := val.(type) { + case int: + v = int32(n) + case int32: + v = int32(n) + case int64: + v = int32(n) + case float64: + v = int32(n) + } + result.ToolCalls = &v + } + if val, ok := m["retries"]; ok && val != nil { // Handle various numeric types from JSON/YAML/roundtrip + var v int32 + switch n := val.(type) { + case int: + v = int32(n) + case int32: + v = int32(n) + case int64: + v = int32(n) + case float64: + v = int32(n) + } + result.Retries = &v + } + if val, ok := m["usage"]; ok && val != nil { + if m, ok := val.(map[string]interface{}); ok { + loaded, _ := LoadTokenUsage(m, ctx) + result.Usage = &loaded + } + } + if val, ok := m["durationMs"]; ok && val != nil { // Handle various numeric types from JSON/YAML/roundtrip + var v float64 + switch n := val.(type) { + case int: + v = float64(n) + case int32: + v = float64(n) + case int64: + v = float64(n) + case float32: + v = float64(n) + case float64: + v = n + } + result.DurationMs = &v + } + } + + return result, nil +} + +// Save serializes TurnSummary to map[string]interface{} +func (obj TurnSummary) Save(ctx *SaveContext) map[string]interface{} { + result := make(map[string]interface{}) + result["turnId"] = obj.TurnId + result["status"] = obj.Status + result["iterations"] = obj.Iterations + if obj.LlmCalls != nil { + result["llmCalls"] = *obj.LlmCalls + } + if obj.ToolCalls != nil { + result["toolCalls"] = *obj.ToolCalls + } + if obj.Retries != nil { + result["retries"] = *obj.Retries + } + if obj.Usage != nil { + result["usage"] = obj.Usage.Save(ctx) + } + if obj.DurationMs != nil { + result["durationMs"] = *obj.DurationMs + } + + return result +} + +// ToJSON serializes TurnSummary to JSON string +func (obj *TurnSummary) ToJSON() (string, error) { + ctx := NewSaveContext() + data := obj.Save(ctx) + bytes, err := json.Marshal(data) + if err != nil { + return "", err + } + return string(bytes), nil +} + +// ToYAML serializes TurnSummary to YAML string +func (obj *TurnSummary) ToYAML() (string, error) { + ctx := NewSaveContext() + data := obj.Save(ctx) + return marshalYAMLDocument(data) +} + +// FromJSON creates TurnSummary from JSON string +func TurnSummaryFromJSON(jsonStr string) (TurnSummary, error) { + var data map[string]interface{} + if err := json.Unmarshal([]byte(jsonStr), &data); err != nil { + return TurnSummary{}, err + } + ctx := NewLoadContext() + return LoadTurnSummary(data, ctx) +} + +// FromYAML creates TurnSummary from YAML string +func TurnSummaryFromYAML(yamlStr string) (TurnSummary, error) { + var data map[string]interface{} + if err := yaml.Unmarshal([]byte(yamlStr), &data); err != nil { + return TurnSummary{}, err + } + ctx := NewLoadContext() + return LoadTurnSummary(data, ctx) +} diff --git a/runtime/go/prompty/model/turn_summary_test.go b/runtime/go/prompty/model/turn_summary_test.go new file mode 100644 index 00000000..9c719d3a --- /dev/null +++ b/runtime/go/prompty/model/turn_summary_test.go @@ -0,0 +1,365 @@ +// +// Code generated by Typra emitter; DO NOT EDIT. + +package prompty_test + +import ( + "encoding/json" + "testing" + + "gopkg.in/yaml.v3" + + "prompty/model" +) + +// TestTurnSummaryLoadJSON tests loading TurnSummary from JSON +func TestTurnSummaryLoadJSON(t *testing.T) { + jsonData := ` +{ + "turnId": "turn_001", + "status": "success", + "iterations": 2, + "llmCalls": 3, + "toolCalls": 2, + "retries": 1, + "durationMs": 2500 +} +` + var data map[string]interface{} + if err := json.Unmarshal([]byte(jsonData), &data); err != nil { + t.Fatalf("Failed to parse JSON: %v", err) + } + + ctx := prompty.NewLoadContext() + instance, err := prompty.LoadTurnSummary(data, ctx) + if err != nil { + t.Fatalf("Failed to load TurnSummary: %v", err) + } + if instance.TurnId != "turn_001" { + t.Errorf(`Expected TurnId to be "turn_001", got %v`, instance.TurnId) + } + if instance.Status != "success" { + t.Errorf(`Expected Status to be "success", got %v`, instance.Status) + } + if instance.Iterations != 2 { + t.Errorf(`Expected Iterations to be 2, got %v`, instance.Iterations) + } + if instance.LlmCalls == nil || *instance.LlmCalls != 3 { + t.Errorf(`Expected LlmCalls to be 3, got %v`, instance.LlmCalls) + } + if instance.ToolCalls == nil || *instance.ToolCalls != 2 { + t.Errorf(`Expected ToolCalls to be 2, got %v`, instance.ToolCalls) + } + if instance.Retries == nil || *instance.Retries != 1 { + t.Errorf(`Expected Retries to be 1, got %v`, instance.Retries) + } + if instance.DurationMs == nil || *instance.DurationMs != 2500 { + t.Errorf(`Expected DurationMs to be 2500, got %v`, instance.DurationMs) + } +} + +// TestTurnSummaryLoadYAML tests loading TurnSummary from YAML +func TestTurnSummaryLoadYAML(t *testing.T) { + yamlData := ` +turnId: turn_001 +status: success +iterations: 2 +llmCalls: 3 +toolCalls: 2 +retries: 1 +durationMs: 2500 + +` + var data map[string]interface{} + if err := yaml.Unmarshal([]byte(yamlData), &data); err != nil { + t.Fatalf("Failed to parse YAML: %v", err) + } + + ctx := prompty.NewLoadContext() + instance, err := prompty.LoadTurnSummary(data, ctx) + if err != nil { + t.Fatalf("Failed to load TurnSummary: %v", err) + } + if instance.TurnId != "turn_001" { + t.Errorf(`Expected TurnId to be "turn_001", got %v`, instance.TurnId) + } + if instance.Status != "success" { + t.Errorf(`Expected Status to be "success", got %v`, instance.Status) + } + if instance.Iterations != 2 { + t.Errorf(`Expected Iterations to be 2, got %v`, instance.Iterations) + } + if instance.LlmCalls == nil || *instance.LlmCalls != 3 { + t.Errorf(`Expected LlmCalls to be 3, got %v`, instance.LlmCalls) + } + if instance.ToolCalls == nil || *instance.ToolCalls != 2 { + t.Errorf(`Expected ToolCalls to be 2, got %v`, instance.ToolCalls) + } + if instance.Retries == nil || *instance.Retries != 1 { + t.Errorf(`Expected Retries to be 1, got %v`, instance.Retries) + } + if instance.DurationMs == nil || *instance.DurationMs != 2500 { + t.Errorf(`Expected DurationMs to be 2500, got %v`, instance.DurationMs) + } +} + +// TestTurnSummaryFromJSON tests loading TurnSummary through the generated JSON helper +func TestTurnSummaryFromJSON(t *testing.T) { + jsonData := ` +{ + "turnId": "turn_001", + "status": "success", + "iterations": 2, + "llmCalls": 3, + "toolCalls": 2, + "retries": 1, + "durationMs": 2500 +} +` + + instance, err := prompty.TurnSummaryFromJSON(jsonData) + if err != nil { + t.Fatalf("Failed to load TurnSummary from JSON helper: %v", err) + } + if instance.TurnId != "turn_001" { + t.Errorf(`Expected TurnId to be "turn_001", got %v`, instance.TurnId) + } + if instance.Status != "success" { + t.Errorf(`Expected Status to be "success", got %v`, instance.Status) + } + if instance.Iterations != 2 { + t.Errorf(`Expected Iterations to be 2, got %v`, instance.Iterations) + } + if instance.LlmCalls == nil || *instance.LlmCalls != 3 { + t.Errorf(`Expected LlmCalls to be 3, got %v`, instance.LlmCalls) + } + if instance.ToolCalls == nil || *instance.ToolCalls != 2 { + t.Errorf(`Expected ToolCalls to be 2, got %v`, instance.ToolCalls) + } + if instance.Retries == nil || *instance.Retries != 1 { + t.Errorf(`Expected Retries to be 1, got %v`, instance.Retries) + } + if instance.DurationMs == nil || *instance.DurationMs != 2500 { + t.Errorf(`Expected DurationMs to be 2500, got %v`, instance.DurationMs) + } +} + +// TestTurnSummaryFromYAML tests loading TurnSummary through the generated YAML helper +func TestTurnSummaryFromYAML(t *testing.T) { + yamlData := ` +turnId: turn_001 +status: success +iterations: 2 +llmCalls: 3 +toolCalls: 2 +retries: 1 +durationMs: 2500 + +` + + instance, err := prompty.TurnSummaryFromYAML(yamlData) + if err != nil { + t.Fatalf("Failed to load TurnSummary from YAML helper: %v", err) + } + if instance.TurnId != "turn_001" { + t.Errorf(`Expected TurnId to be "turn_001", got %v`, instance.TurnId) + } + if instance.Status != "success" { + t.Errorf(`Expected Status to be "success", got %v`, instance.Status) + } + if instance.Iterations != 2 { + t.Errorf(`Expected Iterations to be 2, got %v`, instance.Iterations) + } + if instance.LlmCalls == nil || *instance.LlmCalls != 3 { + t.Errorf(`Expected LlmCalls to be 3, got %v`, instance.LlmCalls) + } + if instance.ToolCalls == nil || *instance.ToolCalls != 2 { + t.Errorf(`Expected ToolCalls to be 2, got %v`, instance.ToolCalls) + } + if instance.Retries == nil || *instance.Retries != 1 { + t.Errorf(`Expected Retries to be 1, got %v`, instance.Retries) + } + if instance.DurationMs == nil || *instance.DurationMs != 2500 { + t.Errorf(`Expected DurationMs to be 2500, got %v`, instance.DurationMs) + } +} + +// TestTurnSummaryRoundtrip tests load -> save -> load produces equivalent data +func TestTurnSummaryRoundtrip(t *testing.T) { + jsonData := ` +{ + "turnId": "turn_001", + "status": "success", + "iterations": 2, + "llmCalls": 3, + "toolCalls": 2, + "retries": 1, + "durationMs": 2500 +} +` + var data map[string]interface{} + if err := json.Unmarshal([]byte(jsonData), &data); err != nil { + t.Fatalf("Failed to parse JSON: %v", err) + } + + loadCtx := prompty.NewLoadContext() + instance, err := prompty.LoadTurnSummary(data, loadCtx) + if err != nil { + t.Fatalf("Failed to load TurnSummary: %v", err) + } + saveCtx := prompty.NewSaveContext() + savedData := instance.Save(saveCtx) + + reloaded, err := prompty.LoadTurnSummary(savedData, loadCtx) + if err != nil { + t.Fatalf("Failed to reload TurnSummary: %v", err) + } + if reloaded.TurnId != "turn_001" { + t.Errorf(`Expected TurnId to be "turn_001", got %v`, reloaded.TurnId) + } + if reloaded.Status != "success" { + t.Errorf(`Expected Status to be "success", got %v`, reloaded.Status) + } + if reloaded.Iterations != 2 { + t.Errorf(`Expected Iterations to be 2, got %v`, reloaded.Iterations) + } + if reloaded.LlmCalls == nil || *reloaded.LlmCalls != 3 { + t.Errorf(`Expected LlmCalls to be 3, got %v`, reloaded.LlmCalls) + } + if reloaded.ToolCalls == nil || *reloaded.ToolCalls != 2 { + t.Errorf(`Expected ToolCalls to be 2, got %v`, reloaded.ToolCalls) + } + if reloaded.Retries == nil || *reloaded.Retries != 1 { + t.Errorf(`Expected Retries to be 1, got %v`, reloaded.Retries) + } + if reloaded.DurationMs == nil || *reloaded.DurationMs != 2500 { + t.Errorf(`Expected DurationMs to be 2500, got %v`, reloaded.DurationMs) + } +} + +// TestTurnSummaryToJSON tests that ToJSON produces valid JSON +func TestTurnSummaryToJSON(t *testing.T) { + jsonData := ` +{ + "turnId": "turn_001", + "status": "success", + "iterations": 2, + "llmCalls": 3, + "toolCalls": 2, + "retries": 1, + "durationMs": 2500 +} +` + var data map[string]interface{} + if err := json.Unmarshal([]byte(jsonData), &data); err != nil { + t.Fatalf("Failed to parse JSON: %v", err) + } + + ctx := prompty.NewLoadContext() + instance, err := prompty.LoadTurnSummary(data, ctx) + if err != nil { + t.Fatalf("Failed to load TurnSummary: %v", err) + } + jsonOutput, err := instance.ToJSON() + if err != nil { + t.Fatalf("Failed to convert to JSON: %v", err) + } + + var parsed map[string]interface{} + if err := json.Unmarshal([]byte(jsonOutput), &parsed); err != nil { + t.Fatalf("Failed to parse generated JSON: %v", err) + } + + reloaded, err := prompty.LoadTurnSummary(parsed, ctx) + if err != nil { + t.Fatalf("Failed to reload generated JSON: %v", err) + } + if reloaded.TurnId != "turn_001" { + t.Errorf(`Expected TurnId to be "turn_001", got %v`, reloaded.TurnId) + } + if reloaded.Status != "success" { + t.Errorf(`Expected Status to be "success", got %v`, reloaded.Status) + } + if reloaded.Iterations != 2 { + t.Errorf(`Expected Iterations to be 2, got %v`, reloaded.Iterations) + } + if reloaded.LlmCalls == nil || *reloaded.LlmCalls != 3 { + t.Errorf(`Expected LlmCalls to be 3, got %v`, reloaded.LlmCalls) + } + if reloaded.ToolCalls == nil || *reloaded.ToolCalls != 2 { + t.Errorf(`Expected ToolCalls to be 2, got %v`, reloaded.ToolCalls) + } + if reloaded.Retries == nil || *reloaded.Retries != 1 { + t.Errorf(`Expected Retries to be 1, got %v`, reloaded.Retries) + } + if reloaded.DurationMs == nil || *reloaded.DurationMs != 2500 { + t.Errorf(`Expected DurationMs to be 2500, got %v`, reloaded.DurationMs) + } +} + +// TestTurnSummaryToYAML tests that ToYAML produces valid YAML +func TestTurnSummaryToYAML(t *testing.T) { + jsonData := ` +{ + "turnId": "turn_001", + "status": "success", + "iterations": 2, + "llmCalls": 3, + "toolCalls": 2, + "retries": 1, + "durationMs": 2500 +} +` + var data map[string]interface{} + if err := json.Unmarshal([]byte(jsonData), &data); err != nil { + t.Fatalf("Failed to parse JSON: %v", err) + } + + ctx := prompty.NewLoadContext() + instance, err := prompty.LoadTurnSummary(data, ctx) + if err != nil { + t.Fatalf("Failed to load TurnSummary: %v", err) + } + yamlOutput, err := instance.ToYAML() + if err != nil { + t.Fatalf("Failed to convert to YAML: %v", err) + } + + var parsed map[string]interface{} + if err := yaml.Unmarshal([]byte(yamlOutput), &parsed); err != nil { + t.Fatalf("Failed to parse generated YAML: %v", err) + } + + reloaded, err := prompty.LoadTurnSummary(parsed, ctx) + if err != nil { + t.Fatalf("Failed to reload generated YAML: %v", err) + } + if reloaded.TurnId != "turn_001" { + t.Errorf(`Expected TurnId to be "turn_001", got %v`, reloaded.TurnId) + } + if reloaded.Status != "success" { + t.Errorf(`Expected Status to be "success", got %v`, reloaded.Status) + } + if reloaded.Iterations != 2 { + t.Errorf(`Expected Iterations to be 2, got %v`, reloaded.Iterations) + } + if reloaded.LlmCalls == nil || *reloaded.LlmCalls != 3 { + t.Errorf(`Expected LlmCalls to be 3, got %v`, reloaded.LlmCalls) + } + if reloaded.ToolCalls == nil || *reloaded.ToolCalls != 2 { + t.Errorf(`Expected ToolCalls to be 2, got %v`, reloaded.ToolCalls) + } + if reloaded.Retries == nil || *reloaded.Retries != 1 { + t.Errorf(`Expected Retries to be 1, got %v`, reloaded.Retries) + } + if reloaded.DurationMs == nil || *reloaded.DurationMs != 2500 { + t.Errorf(`Expected DurationMs to be 2500, got %v`, reloaded.DurationMs) + } +} + +// TestTurnSummaryFromJSONInvalid rejects malformed JSON instead of silently defaulting +func TestTurnSummaryFromJSONInvalid(t *testing.T) { + if _, err := prompty.TurnSummaryFromJSON("{"); err == nil { + t.Fatalf("Expected malformed JSON to fail") + } +} diff --git a/runtime/go/prompty/model/turn_trace.go b/runtime/go/prompty/model/turn_trace.go new file mode 100644 index 00000000..8c409389 --- /dev/null +++ b/runtime/go/prompty/model/turn_trace.go @@ -0,0 +1,122 @@ +// +// Code generated by Typra emitter; DO NOT EDIT. +// Group: events + +package prompty + +import ( + "encoding/json" + + "gopkg.in/yaml.v3" +) + +// TurnTrace represents Portable JSONL/replay container for a recorded turn harness run. + +type TurnTrace struct { + Version string `json:"version" yaml:"version"` + Runtime *string `json:"runtime,omitempty" yaml:"runtime,omitempty"` + PromptyVersion *string `json:"promptyVersion,omitempty" yaml:"promptyVersion,omitempty"` + Events []TurnEvent `json:"events" yaml:"events"` + Summary *TurnSummary `json:"summary,omitempty" yaml:"summary,omitempty"` +} + +// LoadTurnTrace creates a TurnTrace from a map[string]interface{} +func LoadTurnTrace(data interface{}, ctx *LoadContext) (TurnTrace, error) { + result := TurnTrace{} + + // Load from map + if m, ok := data.(map[string]interface{}); ok { + if val, ok := m["version"]; ok && val != nil { + result.Version = string(val.(string)) + } + if val, ok := m["runtime"]; ok && val != nil { + v := string(val.(string)) + result.Runtime = &v + } + if val, ok := m["promptyVersion"]; ok && val != nil { + v := string(val.(string)) + result.PromptyVersion = &v + } + if val, ok := m["events"]; ok && val != nil { + if arr, ok := val.([]interface{}); ok { + result.Events = make([]TurnEvent, len(arr)) + for i, v := range arr { + if item, ok := v.(map[string]interface{}); ok { + loaded, _ := LoadTurnEvent(item, ctx) + result.Events[i] = loaded + } + } + } + } + if val, ok := m["summary"]; ok && val != nil { + if m, ok := val.(map[string]interface{}); ok { + loaded, _ := LoadTurnSummary(m, ctx) + result.Summary = &loaded + } + } + } + + return result, nil +} + +// Save serializes TurnTrace to map[string]interface{} +func (obj TurnTrace) Save(ctx *SaveContext) map[string]interface{} { + result := make(map[string]interface{}) + result["version"] = obj.Version + if obj.Runtime != nil { + result["runtime"] = *obj.Runtime + } + if obj.PromptyVersion != nil { + result["promptyVersion"] = *obj.PromptyVersion + } + if obj.Events != nil { + arr := make([]interface{}, len(obj.Events)) + for i, item := range obj.Events { + arr[i] = item.Save(ctx) + } + result["events"] = arr + } + if obj.Summary != nil { + result["summary"] = obj.Summary.Save(ctx) + } + + return result +} + +// ToJSON serializes TurnTrace to JSON string +func (obj *TurnTrace) ToJSON() (string, error) { + ctx := NewSaveContext() + data := obj.Save(ctx) + bytes, err := json.Marshal(data) + if err != nil { + return "", err + } + return string(bytes), nil +} + +// ToYAML serializes TurnTrace to YAML string +func (obj *TurnTrace) ToYAML() (string, error) { + ctx := NewSaveContext() + data := obj.Save(ctx) + return marshalYAMLDocument(data) +} + +// FromJSON creates TurnTrace from JSON string +func TurnTraceFromJSON(jsonStr string) (TurnTrace, error) { + var data map[string]interface{} + if err := json.Unmarshal([]byte(jsonStr), &data); err != nil { + return TurnTrace{}, err + } + ctx := NewLoadContext() + return LoadTurnTrace(data, ctx) +} + +// FromYAML creates TurnTrace from YAML string +func TurnTraceFromYAML(yamlStr string) (TurnTrace, error) { + var data map[string]interface{} + if err := yaml.Unmarshal([]byte(yamlStr), &data); err != nil { + return TurnTrace{}, err + } + ctx := NewLoadContext() + return LoadTurnTrace(data, ctx) +} diff --git a/runtime/go/prompty/model/turn_trace_test.go b/runtime/go/prompty/model/turn_trace_test.go new file mode 100644 index 00000000..6a3816a8 --- /dev/null +++ b/runtime/go/prompty/model/turn_trace_test.go @@ -0,0 +1,253 @@ +// +// Code generated by Typra emitter; DO NOT EDIT. + +package prompty_test + +import ( + "encoding/json" + "testing" + + "gopkg.in/yaml.v3" + + "prompty/model" +) + +// TestTurnTraceLoadJSON tests loading TurnTrace from JSON +func TestTurnTraceLoadJSON(t *testing.T) { + jsonData := ` +{ + "version": "1", + "runtime": "typescript", + "promptyVersion": "2.0.0" +} +` + var data map[string]interface{} + if err := json.Unmarshal([]byte(jsonData), &data); err != nil { + t.Fatalf("Failed to parse JSON: %v", err) + } + + ctx := prompty.NewLoadContext() + instance, err := prompty.LoadTurnTrace(data, ctx) + if err != nil { + t.Fatalf("Failed to load TurnTrace: %v", err) + } + if instance.Version != "1" { + t.Errorf(`Expected Version to be "1", got %v`, instance.Version) + } + if instance.Runtime == nil || *instance.Runtime != "typescript" { + t.Errorf(`Expected Runtime to be "typescript", got %v`, instance.Runtime) + } + if instance.PromptyVersion == nil || *instance.PromptyVersion != "2.0.0" { + t.Errorf(`Expected PromptyVersion to be "2.0.0", got %v`, instance.PromptyVersion) + } +} + +// TestTurnTraceLoadYAML tests loading TurnTrace from YAML +func TestTurnTraceLoadYAML(t *testing.T) { + yamlData := ` +version: "1" +runtime: typescript +promptyVersion: 2.0.0 + +` + var data map[string]interface{} + if err := yaml.Unmarshal([]byte(yamlData), &data); err != nil { + t.Fatalf("Failed to parse YAML: %v", err) + } + + ctx := prompty.NewLoadContext() + instance, err := prompty.LoadTurnTrace(data, ctx) + if err != nil { + t.Fatalf("Failed to load TurnTrace: %v", err) + } + if instance.Version != "1" { + t.Errorf(`Expected Version to be "1", got %v`, instance.Version) + } + if instance.Runtime == nil || *instance.Runtime != "typescript" { + t.Errorf(`Expected Runtime to be "typescript", got %v`, instance.Runtime) + } + if instance.PromptyVersion == nil || *instance.PromptyVersion != "2.0.0" { + t.Errorf(`Expected PromptyVersion to be "2.0.0", got %v`, instance.PromptyVersion) + } +} + +// TestTurnTraceFromJSON tests loading TurnTrace through the generated JSON helper +func TestTurnTraceFromJSON(t *testing.T) { + jsonData := ` +{ + "version": "1", + "runtime": "typescript", + "promptyVersion": "2.0.0" +} +` + + instance, err := prompty.TurnTraceFromJSON(jsonData) + if err != nil { + t.Fatalf("Failed to load TurnTrace from JSON helper: %v", err) + } + if instance.Version != "1" { + t.Errorf(`Expected Version to be "1", got %v`, instance.Version) + } + if instance.Runtime == nil || *instance.Runtime != "typescript" { + t.Errorf(`Expected Runtime to be "typescript", got %v`, instance.Runtime) + } + if instance.PromptyVersion == nil || *instance.PromptyVersion != "2.0.0" { + t.Errorf(`Expected PromptyVersion to be "2.0.0", got %v`, instance.PromptyVersion) + } +} + +// TestTurnTraceFromYAML tests loading TurnTrace through the generated YAML helper +func TestTurnTraceFromYAML(t *testing.T) { + yamlData := ` +version: "1" +runtime: typescript +promptyVersion: 2.0.0 + +` + + instance, err := prompty.TurnTraceFromYAML(yamlData) + if err != nil { + t.Fatalf("Failed to load TurnTrace from YAML helper: %v", err) + } + if instance.Version != "1" { + t.Errorf(`Expected Version to be "1", got %v`, instance.Version) + } + if instance.Runtime == nil || *instance.Runtime != "typescript" { + t.Errorf(`Expected Runtime to be "typescript", got %v`, instance.Runtime) + } + if instance.PromptyVersion == nil || *instance.PromptyVersion != "2.0.0" { + t.Errorf(`Expected PromptyVersion to be "2.0.0", got %v`, instance.PromptyVersion) + } +} + +// TestTurnTraceRoundtrip tests load -> save -> load produces equivalent data +func TestTurnTraceRoundtrip(t *testing.T) { + jsonData := ` +{ + "version": "1", + "runtime": "typescript", + "promptyVersion": "2.0.0" +} +` + var data map[string]interface{} + if err := json.Unmarshal([]byte(jsonData), &data); err != nil { + t.Fatalf("Failed to parse JSON: %v", err) + } + + loadCtx := prompty.NewLoadContext() + instance, err := prompty.LoadTurnTrace(data, loadCtx) + if err != nil { + t.Fatalf("Failed to load TurnTrace: %v", err) + } + saveCtx := prompty.NewSaveContext() + savedData := instance.Save(saveCtx) + + reloaded, err := prompty.LoadTurnTrace(savedData, loadCtx) + if err != nil { + t.Fatalf("Failed to reload TurnTrace: %v", err) + } + if reloaded.Version != "1" { + t.Errorf(`Expected Version to be "1", got %v`, reloaded.Version) + } + if reloaded.Runtime == nil || *reloaded.Runtime != "typescript" { + t.Errorf(`Expected Runtime to be "typescript", got %v`, reloaded.Runtime) + } + if reloaded.PromptyVersion == nil || *reloaded.PromptyVersion != "2.0.0" { + t.Errorf(`Expected PromptyVersion to be "2.0.0", got %v`, reloaded.PromptyVersion) + } +} + +// TestTurnTraceToJSON tests that ToJSON produces valid JSON +func TestTurnTraceToJSON(t *testing.T) { + jsonData := ` +{ + "version": "1", + "runtime": "typescript", + "promptyVersion": "2.0.0" +} +` + var data map[string]interface{} + if err := json.Unmarshal([]byte(jsonData), &data); err != nil { + t.Fatalf("Failed to parse JSON: %v", err) + } + + ctx := prompty.NewLoadContext() + instance, err := prompty.LoadTurnTrace(data, ctx) + if err != nil { + t.Fatalf("Failed to load TurnTrace: %v", err) + } + jsonOutput, err := instance.ToJSON() + if err != nil { + t.Fatalf("Failed to convert to JSON: %v", err) + } + + var parsed map[string]interface{} + if err := json.Unmarshal([]byte(jsonOutput), &parsed); err != nil { + t.Fatalf("Failed to parse generated JSON: %v", err) + } + + reloaded, err := prompty.LoadTurnTrace(parsed, ctx) + if err != nil { + t.Fatalf("Failed to reload generated JSON: %v", err) + } + if reloaded.Version != "1" { + t.Errorf(`Expected Version to be "1", got %v`, reloaded.Version) + } + if reloaded.Runtime == nil || *reloaded.Runtime != "typescript" { + t.Errorf(`Expected Runtime to be "typescript", got %v`, reloaded.Runtime) + } + if reloaded.PromptyVersion == nil || *reloaded.PromptyVersion != "2.0.0" { + t.Errorf(`Expected PromptyVersion to be "2.0.0", got %v`, reloaded.PromptyVersion) + } +} + +// TestTurnTraceToYAML tests that ToYAML produces valid YAML +func TestTurnTraceToYAML(t *testing.T) { + jsonData := ` +{ + "version": "1", + "runtime": "typescript", + "promptyVersion": "2.0.0" +} +` + var data map[string]interface{} + if err := json.Unmarshal([]byte(jsonData), &data); err != nil { + t.Fatalf("Failed to parse JSON: %v", err) + } + + ctx := prompty.NewLoadContext() + instance, err := prompty.LoadTurnTrace(data, ctx) + if err != nil { + t.Fatalf("Failed to load TurnTrace: %v", err) + } + yamlOutput, err := instance.ToYAML() + if err != nil { + t.Fatalf("Failed to convert to YAML: %v", err) + } + + var parsed map[string]interface{} + if err := yaml.Unmarshal([]byte(yamlOutput), &parsed); err != nil { + t.Fatalf("Failed to parse generated YAML: %v", err) + } + + reloaded, err := prompty.LoadTurnTrace(parsed, ctx) + if err != nil { + t.Fatalf("Failed to reload generated YAML: %v", err) + } + if reloaded.Version != "1" { + t.Errorf(`Expected Version to be "1", got %v`, reloaded.Version) + } + if reloaded.Runtime == nil || *reloaded.Runtime != "typescript" { + t.Errorf(`Expected Runtime to be "typescript", got %v`, reloaded.Runtime) + } + if reloaded.PromptyVersion == nil || *reloaded.PromptyVersion != "2.0.0" { + t.Errorf(`Expected PromptyVersion to be "2.0.0", got %v`, reloaded.PromptyVersion) + } +} + +// TestTurnTraceFromJSONInvalid rejects malformed JSON instead of silently defaulting +func TestTurnTraceFromJSONInvalid(t *testing.T) { + if _, err := prompty.TurnTraceFromJSON("{"); err == nil { + t.Fatalf("Expected malformed JSON to fail") + } +} diff --git a/runtime/go/prompty/model/validation_error.go b/runtime/go/prompty/model/validation_error.go index d20b1e12..25386ef3 100644 --- a/runtime/go/prompty/model/validation_error.go +++ b/runtime/go/prompty/model/validation_error.go @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. // Group: core package prompty @@ -39,7 +40,7 @@ func LoadValidationError(data interface{}, ctx *LoadContext) (ValidationError, e } // Save serializes ValidationError to map[string]interface{} -func (obj *ValidationError) Save(ctx *SaveContext) map[string]interface{} { +func (obj ValidationError) Save(ctx *SaveContext) map[string]interface{} { result := make(map[string]interface{}) result["message"] = obj.Message result["property"] = obj.Property @@ -63,11 +64,7 @@ func (obj *ValidationError) ToJSON() (string, error) { func (obj *ValidationError) ToYAML() (string, error) { ctx := NewSaveContext() data := obj.Save(ctx) - bytes, err := yaml.Marshal(data) - if err != nil { - return "", err - } - return string(bytes), nil + return marshalYAMLDocument(data) } // FromJSON creates ValidationError from JSON string diff --git a/runtime/go/prompty/model/validation_error_test.go b/runtime/go/prompty/model/validation_error_test.go index 530c25a0..4e1baaa5 100644 --- a/runtime/go/prompty/model/validation_error_test.go +++ b/runtime/go/prompty/model/validation_error_test.go @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. package prompty_test @@ -70,6 +71,55 @@ constraint: required } } +// TestValidationErrorFromJSON tests loading ValidationError through the generated JSON helper +func TestValidationErrorFromJSON(t *testing.T) { + jsonData := ` +{ + "message": "Missing required input: firstName", + "property": "firstName", + "constraint": "required" +} +` + + instance, err := prompty.ValidationErrorFromJSON(jsonData) + if err != nil { + t.Fatalf("Failed to load ValidationError from JSON helper: %v", err) + } + if instance.Message != "Missing required input: firstName" { + t.Errorf(`Expected Message to be "Missing required input: firstName", got %v`, instance.Message) + } + if instance.Property != "firstName" { + t.Errorf(`Expected Property to be "firstName", got %v`, instance.Property) + } + if instance.Constraint != "required" { + t.Errorf(`Expected Constraint to be "required", got %v`, instance.Constraint) + } +} + +// TestValidationErrorFromYAML tests loading ValidationError through the generated YAML helper +func TestValidationErrorFromYAML(t *testing.T) { + yamlData := ` +message: "Missing required input: firstName" +property: firstName +constraint: required + +` + + instance, err := prompty.ValidationErrorFromYAML(yamlData) + if err != nil { + t.Fatalf("Failed to load ValidationError from YAML helper: %v", err) + } + if instance.Message != "Missing required input: firstName" { + t.Errorf(`Expected Message to be "Missing required input: firstName", got %v`, instance.Message) + } + if instance.Property != "firstName" { + t.Errorf(`Expected Property to be "firstName", got %v`, instance.Property) + } + if instance.Constraint != "required" { + t.Errorf(`Expected Constraint to be "required", got %v`, instance.Constraint) + } +} + // TestValidationErrorRoundtrip tests load -> save -> load produces equivalent data func TestValidationErrorRoundtrip(t *testing.T) { jsonData := ` @@ -135,6 +185,20 @@ func TestValidationErrorToJSON(t *testing.T) { if err := json.Unmarshal([]byte(jsonOutput), &parsed); err != nil { t.Fatalf("Failed to parse generated JSON: %v", err) } + + reloaded, err := prompty.LoadValidationError(parsed, ctx) + if err != nil { + t.Fatalf("Failed to reload generated JSON: %v", err) + } + if reloaded.Message != "Missing required input: firstName" { + t.Errorf(`Expected Message to be "Missing required input: firstName", got %v`, reloaded.Message) + } + if reloaded.Property != "firstName" { + t.Errorf(`Expected Property to be "firstName", got %v`, reloaded.Property) + } + if reloaded.Constraint != "required" { + t.Errorf(`Expected Constraint to be "required", got %v`, reloaded.Constraint) + } } // TestValidationErrorToYAML tests that ToYAML produces valid YAML @@ -165,4 +229,25 @@ func TestValidationErrorToYAML(t *testing.T) { if err := yaml.Unmarshal([]byte(yamlOutput), &parsed); err != nil { t.Fatalf("Failed to parse generated YAML: %v", err) } + + reloaded, err := prompty.LoadValidationError(parsed, ctx) + if err != nil { + t.Fatalf("Failed to reload generated YAML: %v", err) + } + if reloaded.Message != "Missing required input: firstName" { + t.Errorf(`Expected Message to be "Missing required input: firstName", got %v`, reloaded.Message) + } + if reloaded.Property != "firstName" { + t.Errorf(`Expected Property to be "firstName", got %v`, reloaded.Property) + } + if reloaded.Constraint != "required" { + t.Errorf(`Expected Constraint to be "required", got %v`, reloaded.Constraint) + } +} + +// TestValidationErrorFromJSONInvalid rejects malformed JSON instead of silently defaulting +func TestValidationErrorFromJSONInvalid(t *testing.T) { + if _, err := prompty.ValidationErrorFromJSON("{"); err == nil { + t.Fatalf("Expected malformed JSON to fail") + } } diff --git a/runtime/go/prompty/model/validation_result.go b/runtime/go/prompty/model/validation_result.go index ab7a5701..09ab9b6c 100644 --- a/runtime/go/prompty/model/validation_result.go +++ b/runtime/go/prompty/model/validation_result.go @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. // Group: core package prompty @@ -44,7 +45,7 @@ func LoadValidationResult(data interface{}, ctx *LoadContext) (ValidationResult, } // Save serializes ValidationResult to map[string]interface{} -func (obj *ValidationResult) Save(ctx *SaveContext) map[string]interface{} { +func (obj ValidationResult) Save(ctx *SaveContext) map[string]interface{} { result := make(map[string]interface{}) result["valid"] = obj.Valid if obj.Errors != nil { @@ -73,11 +74,7 @@ func (obj *ValidationResult) ToJSON() (string, error) { func (obj *ValidationResult) ToYAML() (string, error) { ctx := NewSaveContext() data := obj.Save(ctx) - bytes, err := yaml.Marshal(data) - if err != nil { - return "", err - } - return string(bytes), nil + return marshalYAMLDocument(data) } // FromJSON creates ValidationResult from JSON string diff --git a/runtime/go/prompty/model/validation_result_test.go b/runtime/go/prompty/model/validation_result_test.go index 9467217a..b102496f 100644 --- a/runtime/go/prompty/model/validation_result_test.go +++ b/runtime/go/prompty/model/validation_result_test.go @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. package prompty_test @@ -32,6 +33,9 @@ func TestValidationResultLoadJSON(t *testing.T) { if instance.Valid != true { t.Errorf(`Expected Valid to be true, got %v`, instance.Valid) } + if len(instance.Errors) != 0 { + t.Fatalf("Expected Errors length to be 0, got %d", len(instance.Errors)) + } } // TestValidationResultLoadYAML tests loading ValidationResult from YAML @@ -54,6 +58,50 @@ errors: [] if instance.Valid != true { t.Errorf(`Expected Valid to be true, got %v`, instance.Valid) } + if len(instance.Errors) != 0 { + t.Fatalf("Expected Errors length to be 0, got %d", len(instance.Errors)) + } +} + +// TestValidationResultFromJSON tests loading ValidationResult through the generated JSON helper +func TestValidationResultFromJSON(t *testing.T) { + jsonData := ` +{ + "valid": true, + "errors": [] +} +` + + instance, err := prompty.ValidationResultFromJSON(jsonData) + if err != nil { + t.Fatalf("Failed to load ValidationResult from JSON helper: %v", err) + } + if instance.Valid != true { + t.Errorf(`Expected Valid to be true, got %v`, instance.Valid) + } + if len(instance.Errors) != 0 { + t.Fatalf("Expected Errors length to be 0, got %d", len(instance.Errors)) + } +} + +// TestValidationResultFromYAML tests loading ValidationResult through the generated YAML helper +func TestValidationResultFromYAML(t *testing.T) { + yamlData := ` +valid: true +errors: [] + +` + + instance, err := prompty.ValidationResultFromYAML(yamlData) + if err != nil { + t.Fatalf("Failed to load ValidationResult from YAML helper: %v", err) + } + if instance.Valid != true { + t.Errorf(`Expected Valid to be true, got %v`, instance.Valid) + } + if len(instance.Errors) != 0 { + t.Fatalf("Expected Errors length to be 0, got %d", len(instance.Errors)) + } } // TestValidationResultRoundtrip tests load -> save -> load produces equivalent data @@ -84,6 +132,9 @@ func TestValidationResultRoundtrip(t *testing.T) { if reloaded.Valid != true { t.Errorf(`Expected Valid to be true, got %v`, reloaded.Valid) } + if len(reloaded.Errors) != 0 { + t.Fatalf("Expected Errors length to be 0, got %d", len(reloaded.Errors)) + } } // TestValidationResultToJSON tests that ToJSON produces valid JSON @@ -113,6 +164,17 @@ func TestValidationResultToJSON(t *testing.T) { if err := json.Unmarshal([]byte(jsonOutput), &parsed); err != nil { t.Fatalf("Failed to parse generated JSON: %v", err) } + + reloaded, err := prompty.LoadValidationResult(parsed, ctx) + if err != nil { + t.Fatalf("Failed to reload generated JSON: %v", err) + } + if reloaded.Valid != true { + t.Errorf(`Expected Valid to be true, got %v`, reloaded.Valid) + } + if len(reloaded.Errors) != 0 { + t.Fatalf("Expected Errors length to be 0, got %d", len(reloaded.Errors)) + } } // TestValidationResultToYAML tests that ToYAML produces valid YAML @@ -142,4 +204,22 @@ func TestValidationResultToYAML(t *testing.T) { if err := yaml.Unmarshal([]byte(yamlOutput), &parsed); err != nil { t.Fatalf("Failed to parse generated YAML: %v", err) } + + reloaded, err := prompty.LoadValidationResult(parsed, ctx) + if err != nil { + t.Fatalf("Failed to reload generated YAML: %v", err) + } + if reloaded.Valid != true { + t.Errorf(`Expected Valid to be true, got %v`, reloaded.Valid) + } + if len(reloaded.Errors) != 0 { + t.Fatalf("Expected Errors length to be 0, got %d", len(reloaded.Errors)) + } +} + +// TestValidationResultFromJSONInvalid rejects malformed JSON instead of silently defaulting +func TestValidationResultFromJSONInvalid(t *testing.T) { + if _, err := prompty.ValidationResultFromJSON("{"); err == nil { + t.Fatalf("Expected malformed JSON to fail") + } } diff --git a/runtime/python/prompty/prompty/__init__.py b/runtime/python/prompty/prompty/__init__.py index b87df1b9..8d42a18f 100644 --- a/runtime/python/prompty/prompty/__init__.py +++ b/runtime/python/prompty/prompty/__init__.py @@ -110,8 +110,20 @@ "GuardrailError", "GuardrailResult", "Guardrails", + "AllowAllPermissionResolver", + "CollectingEventSink", + "DenyAllPermissionResolver", + "FunctionHostToolExecutor", + "InMemoryCheckpointStore", + "JsonlEventJournalWriter", + "ReferenceReplayVerifier", + "ReferenceTurnRunner", + "RunTurnRequest", + "RunTurnResult", "Steering", "StructuredResult", + "TurnModelRequest", + "TurnModelResponse", "cast", "bind_tools", "tool", @@ -154,6 +166,20 @@ TextPart, ThreadMarker, ) +from .harness import ( + AllowAllPermissionResolver, + CollectingEventSink, + DenyAllPermissionResolver, + FunctionHostToolExecutor, + InMemoryCheckpointStore, + JsonlEventJournalWriter, + ReferenceReplayVerifier, + ReferenceTurnRunner, + RunTurnRequest, + RunTurnResult, + TurnModelRequest, + TurnModelResponse, +) # Pipeline (via backward-compat shim) from .invoker import ( diff --git a/runtime/python/prompty/prompty/core/agent_events.py b/runtime/python/prompty/prompty/core/agent_events.py index de344751..41643daa 100644 --- a/runtime/python/prompty/prompty/core/agent_events.py +++ b/runtime/python/prompty/prompty/core/agent_events.py @@ -8,7 +8,9 @@ from collections.abc import Callable from dataclasses import dataclass +from datetime import UTC, datetime from typing import Any +from uuid import uuid4 __all__ = [ "AgentEvent", @@ -52,7 +54,19 @@ def emit_event( if callback is None: return try: - callback(event_type, data or {}) + payload = data or {} + event_data = dict(payload) + event_data.setdefault( + "turnEvent", + { + "id": f"evt_{uuid4().hex}", + "type": event_type, + "timestamp": datetime.now(UTC).isoformat().replace("+00:00", "Z"), + "iteration": payload.get("iteration") if isinstance(payload.get("iteration"), int) else None, + "payload": payload, + }, + ) + callback(event_type, event_data) except Exception as exc: # noqa: BLE001 — spec says log and continue import logging diff --git a/runtime/python/prompty/prompty/core/pipeline.py b/runtime/python/prompty/prompty/core/pipeline.py index c3f61ede..ba508ff6 100644 --- a/runtime/python/prompty/prompty/core/pipeline.py +++ b/runtime/python/prompty/prompty/core/pipeline.py @@ -654,6 +654,16 @@ def _invoke_with_retry( "status", {"message": f"LLM call failed, retrying (attempt {attempts + 1}/{max_retries})..."}, ) + emit_event( + on_event, + "retry", + { + "operation": "llm", + "attempt": attempts + 1, + "maxAttempts": max_retries, + "reason": str(e), + }, + ) # Exponential backoff with jitter, capped at 60s backoff = min(2**attempts + random.random(), 60) # Check cancellation during backoff @@ -686,6 +696,16 @@ async def _invoke_with_retry_async( "status", {"message": f"LLM call failed, retrying (attempt {attempts + 1}/{max_retries})..."}, ) + emit_event( + on_event, + "retry", + { + "operation": "llm", + "attempt": attempts + 1, + "maxAttempts": max_retries, + "reason": str(e), + }, + ) backoff = min(2**attempts + random.random(), 60) if cancel is not None and cancel.is_cancelled: raise @@ -868,6 +888,20 @@ async def invoke_async( _DEFAULT_MAX_LLM_RETRIES = 3 +def _emit_failed_turn_end( + on_event: EventCallback | None, + exc: BaseException, + *, + iterations: int, + response: Any = None, +) -> None: + status = "cancelled" if isinstance(exc, CancelledError) else "error" + payload: dict[str, Any] = {"iterations": iterations, "status": status} + if response is not None: + payload["response"] = response + emit_event(on_event, "turn_end", payload) + + @trace def turn( prompt: str | Prompty, @@ -955,6 +989,15 @@ def turn( tools = tools or {} parent_inputs = inputs or {} messages = prepare(agent, inputs) + emit_event( + on_event, + "turn_start", + { + "agent": getattr(agent, "name", None), + "inputs": inputs or {}, + "maxIterations": max_iterations, + }, + ) # Fast path: no tools and no loop extensions — single executor + process call (not via run) _has_extensions = ( @@ -965,10 +1008,26 @@ def turn( or steering is not None ) if not tools and not _has_extensions: - response = _invoke_executor(agent, messages) + emit_event( + on_event, + "llm_start", + {"provider": agent.model.provider, "modelId": agent.model.id, "messageCount": len(messages), "attempt": 0}, + ) + try: + response = _invoke_executor(agent, messages) + except Exception as exc: + _emit_failed_turn_end(on_event, exc, iterations=0) + raise + emit_event(on_event, "llm_complete", {}) if raw: + emit_event(on_event, "turn_end", {"iterations": 0, "status": "success", "response": response}) return response - result = process(agent, response) + try: + result = process(agent, response) + except Exception as exc: + _emit_failed_turn_end(on_event, exc, iterations=0, response=response) + raise + emit_event(on_event, "turn_end", {"iterations": 0, "status": "success", "response": result}) if target_type is not None: return cast(result, target_type) return result @@ -984,6 +1043,7 @@ def turn( while True: if cancel is not None and cancel.is_cancelled: emit_event(on_event, "cancelled", {}) + _emit_failed_turn_end(on_event, CancelledError(), iterations=iteration) raise CancelledError() if steering is not None: @@ -1005,6 +1065,9 @@ def turn( gr_input = guardrails.check_input(messages) if not gr_input.allowed: emit_event(on_event, "error", {"message": f"Input guardrail denied: {gr_input.reason}"}) + _emit_failed_turn_end( + on_event, GuardrailError(gr_input.reason or "Input guardrail denied"), iterations=iteration + ) raise GuardrailError(gr_input.reason or "Input guardrail denied") if gr_input.rewrite is not None: messages = gr_input.rewrite @@ -1012,14 +1075,35 @@ def turn( if cancel is not None and cancel.is_cancelled: emit_event(on_event, "cancelled", {}) + _emit_failed_turn_end(on_event, CancelledError(), iterations=iteration) raise CancelledError() # Call LLM (with retry per §9.10 in agent loop) - response = _invoke_with_retry(agent, messages, max_llm_retries, on_event, cancel) + emit_event( + on_event, + "llm_start", + { + "provider": agent.model.provider, + "modelId": agent.model.id, + "messageCount": len(messages), + "attempt": 0, + "iteration": iteration, + }, + ) + try: + response = _invoke_with_retry(agent, messages, max_llm_retries, on_event, cancel) + except Exception as exc: + _emit_failed_turn_end(on_event, exc, iterations=iteration) + raise + emit_event(on_event, "llm_complete", {"iteration": iteration}) # Streaming: consume through processor, extract tool calls if _is_stream(response): - streamed_tool_calls, content = _consume_stream(agent, response, on_event) + try: + streamed_tool_calls, content = _consume_stream(agent, response, on_event) + except Exception as exc: + _emit_failed_turn_end(on_event, exc, iterations=iteration, response=response) + raise if not streamed_tool_calls: if guardrails is not None and content: @@ -1027,12 +1111,21 @@ def turn( gr = guardrails.check_output(assistant_msg) if not gr.allowed: emit_event(on_event, "error", {"message": f"Output guardrail denied: {gr.reason}"}) + _emit_failed_turn_end( + on_event, + GuardrailError(gr.reason or "Output guardrail denied"), + iterations=iteration, + response=content, + ) raise GuardrailError(gr.reason or "Output guardrail denied") if gr.rewrite is not None: content = gr.rewrite t("iterations", iteration) t("result", content) emit_event(on_event, "done", {"response": content, "messages": messages}) + emit_event( + on_event, "turn_end", {"iterations": iteration, "status": "success", "response": content} + ) return content if guardrails is not None and content: @@ -1040,28 +1133,41 @@ def turn( gr = guardrails.check_output(assistant_msg) if not gr.allowed: emit_event(on_event, "error", {"message": f"Output guardrail denied: {gr.reason}"}) + _emit_failed_turn_end( + on_event, + GuardrailError(gr.reason or "Output guardrail denied"), + iterations=iteration, + response=content, + ) raise GuardrailError(gr.reason or "Output guardrail denied") if gr.rewrite is not None: content = gr.rewrite iteration += 1 if iteration > max_iterations: + _emit_failed_turn_end( + on_event, ValueError("Agent loop exceeded max_iterations"), iterations=iteration + ) raise ValueError( f"Agent loop exceeded max_iterations ({max_iterations}). " f"The model kept requesting tool calls. Increase max_iterations or check your tools." ) - tool_messages = _build_tool_messages_from_calls_with_extensions( - streamed_tool_calls, - content, - tools, - agent, - parent_inputs, - on_event=on_event, - cancel=cancel, - guardrails=guardrails, - parallel=parallel_tool_calls, - ) + try: + tool_messages = _build_tool_messages_from_calls_with_extensions( + streamed_tool_calls, + content, + tools, + agent, + parent_inputs, + on_event=on_event, + cancel=cancel, + guardrails=guardrails, + parallel=parallel_tool_calls, + ) + except Exception as exc: + _emit_failed_turn_end(on_event, exc, iterations=iteration, response=content) + raise messages.extend(tool_messages) emit_event(on_event, "messages_updated", {"messages": messages}) continue @@ -1077,27 +1183,38 @@ def turn( gr = guardrails.check_output(assistant_msg) if not gr.allowed: emit_event(on_event, "error", {"message": f"Output guardrail denied: {gr.reason}"}) + _emit_failed_turn_end( + on_event, + GuardrailError(gr.reason or "Output guardrail denied"), + iterations=iteration, + response=text_content, + ) raise GuardrailError(gr.reason or "Output guardrail denied") if gr.rewrite is not None: text_content = gr.rewrite iteration += 1 if iteration > max_iterations: + _emit_failed_turn_end(on_event, ValueError("Agent loop exceeded max_iterations"), iterations=iteration) raise ValueError( f"Agent loop exceeded max_iterations ({max_iterations}). " f"The model kept requesting tool calls. Increase max_iterations or check your tools." ) - tool_messages, _ = _build_tool_result_messages_with_extensions( - response, - tools, - agent, - parent_inputs, - on_event=on_event, - cancel=cancel, - guardrails=guardrails, - parallel=parallel_tool_calls, - ) + try: + tool_messages, _ = _build_tool_result_messages_with_extensions( + response, + tools, + agent, + parent_inputs, + on_event=on_event, + cancel=cancel, + guardrails=guardrails, + parallel=parallel_tool_calls, + ) + except Exception as exc: + _emit_failed_turn_end(on_event, exc, iterations=iteration, response=response) + raise messages.extend(tool_messages) emit_event(on_event, "messages_updated", {"messages": messages}) @@ -1107,17 +1224,29 @@ def turn( # Process final response (directly, not via run) if raw: emit_event(on_event, "done", {"response": response, "messages": messages}) + emit_event(on_event, "turn_end", {"iterations": iteration, "status": "success", "response": response}) return response - processed_result = process(agent, response) + try: + processed_result = process(agent, response) + except Exception as exc: + _emit_failed_turn_end(on_event, exc, iterations=iteration, response=response) + raise if guardrails is not None and isinstance(processed_result, str): assistant_msg = Message(role="assistant", parts=[TextPart(value=processed_result)]) gr = guardrails.check_output(assistant_msg) if not gr.allowed: emit_event(on_event, "error", {"message": f"Output guardrail denied: {gr.reason}"}) + _emit_failed_turn_end( + on_event, + GuardrailError(gr.reason or "Output guardrail denied"), + iterations=iteration, + response=processed_result, + ) raise GuardrailError(gr.reason or "Output guardrail denied") if gr.rewrite is not None: processed_result = gr.rewrite emit_event(on_event, "done", {"response": processed_result, "messages": messages}) + emit_event(on_event, "turn_end", {"iterations": iteration, "status": "success", "response": processed_result}) if target_type is not None: return cast(processed_result, target_type) return processed_result @@ -1152,6 +1281,15 @@ async def turn_async( tools = tools or {} parent_inputs = inputs or {} messages = await prepare_async(agent, inputs) + emit_event( + on_event, + "turn_start", + { + "agent": getattr(agent, "name", None), + "inputs": inputs or {}, + "maxIterations": max_iterations, + }, + ) # Fast path: no tools and no loop extensions — single executor + process call (not via run) _has_extensions = ( @@ -1162,10 +1300,26 @@ async def turn_async( or steering is not None ) if not tools and not _has_extensions: - response = await _invoke_executor_async(agent, messages) + emit_event( + on_event, + "llm_start", + {"provider": agent.model.provider, "modelId": agent.model.id, "messageCount": len(messages), "attempt": 0}, + ) + try: + response = await _invoke_executor_async(agent, messages) + except Exception as exc: + _emit_failed_turn_end(on_event, exc, iterations=0) + raise + emit_event(on_event, "llm_complete", {}) if raw: + emit_event(on_event, "turn_end", {"iterations": 0, "status": "success", "response": response}) return response - result = await process_async(agent, response) + try: + result = await process_async(agent, response) + except Exception as exc: + _emit_failed_turn_end(on_event, exc, iterations=0, response=response) + raise + emit_event(on_event, "turn_end", {"iterations": 0, "status": "success", "response": result}) if target_type is not None: return cast(result, target_type) return result @@ -1181,6 +1335,7 @@ async def turn_async( while True: if cancel is not None and cancel.is_cancelled: emit_event(on_event, "cancelled", {}) + _emit_failed_turn_end(on_event, CancelledError(), iterations=iteration) raise CancelledError() if steering is not None: @@ -1202,6 +1357,9 @@ async def turn_async( gr_input = guardrails.check_input(messages) if not gr_input.allowed: emit_event(on_event, "error", {"message": f"Input guardrail denied: {gr_input.reason}"}) + _emit_failed_turn_end( + on_event, GuardrailError(gr_input.reason or "Input guardrail denied"), iterations=iteration + ) raise GuardrailError(gr_input.reason or "Input guardrail denied") if gr_input.rewrite is not None: messages = gr_input.rewrite @@ -1209,14 +1367,35 @@ async def turn_async( if cancel is not None and cancel.is_cancelled: emit_event(on_event, "cancelled", {}) + _emit_failed_turn_end(on_event, CancelledError(), iterations=iteration) raise CancelledError() # Call LLM (with retry per §9.10 in agent loop) - response = await _invoke_with_retry_async(agent, messages, max_llm_retries, on_event, cancel) + emit_event( + on_event, + "llm_start", + { + "provider": agent.model.provider, + "modelId": agent.model.id, + "messageCount": len(messages), + "attempt": 0, + "iteration": iteration, + }, + ) + try: + response = await _invoke_with_retry_async(agent, messages, max_llm_retries, on_event, cancel) + except Exception as exc: + _emit_failed_turn_end(on_event, exc, iterations=iteration) + raise + emit_event(on_event, "llm_complete", {"iteration": iteration}) # Streaming: consume through processor, extract tool calls if _is_stream(response): - streamed_tool_calls, content = await _consume_stream_async(agent, response, on_event) + try: + streamed_tool_calls, content = await _consume_stream_async(agent, response, on_event) + except Exception as exc: + _emit_failed_turn_end(on_event, exc, iterations=iteration, response=response) + raise if not streamed_tool_calls: if guardrails is not None and content: @@ -1224,12 +1403,21 @@ async def turn_async( gr = guardrails.check_output(assistant_msg) if not gr.allowed: emit_event(on_event, "error", {"message": f"Output guardrail denied: {gr.reason}"}) + _emit_failed_turn_end( + on_event, + GuardrailError(gr.reason or "Output guardrail denied"), + iterations=iteration, + response=content, + ) raise GuardrailError(gr.reason or "Output guardrail denied") if gr.rewrite is not None: content = gr.rewrite t("iterations", iteration) t("result", content) emit_event(on_event, "done", {"response": content, "messages": messages}) + emit_event( + on_event, "turn_end", {"iterations": iteration, "status": "success", "response": content} + ) return content if guardrails is not None and content: @@ -1237,28 +1425,41 @@ async def turn_async( gr = guardrails.check_output(assistant_msg) if not gr.allowed: emit_event(on_event, "error", {"message": f"Output guardrail denied: {gr.reason}"}) + _emit_failed_turn_end( + on_event, + GuardrailError(gr.reason or "Output guardrail denied"), + iterations=iteration, + response=content, + ) raise GuardrailError(gr.reason or "Output guardrail denied") if gr.rewrite is not None: content = gr.rewrite iteration += 1 if iteration > max_iterations: + _emit_failed_turn_end( + on_event, ValueError("Agent loop exceeded max_iterations"), iterations=iteration + ) raise ValueError( f"Agent loop exceeded max_iterations ({max_iterations}). " f"The model kept requesting tool calls. Increase max_iterations or check your tools." ) - tool_messages = await _build_tool_messages_from_calls_with_extensions_async( - streamed_tool_calls, - content, - tools, - agent, - parent_inputs, - on_event=on_event, - cancel=cancel, - guardrails=guardrails, - parallel=parallel_tool_calls, - ) + try: + tool_messages = await _build_tool_messages_from_calls_with_extensions_async( + streamed_tool_calls, + content, + tools, + agent, + parent_inputs, + on_event=on_event, + cancel=cancel, + guardrails=guardrails, + parallel=parallel_tool_calls, + ) + except Exception as exc: + _emit_failed_turn_end(on_event, exc, iterations=iteration, response=content) + raise messages.extend(tool_messages) emit_event(on_event, "messages_updated", {"messages": messages}) continue @@ -1274,27 +1475,38 @@ async def turn_async( gr = guardrails.check_output(assistant_msg) if not gr.allowed: emit_event(on_event, "error", {"message": f"Output guardrail denied: {gr.reason}"}) + _emit_failed_turn_end( + on_event, + GuardrailError(gr.reason or "Output guardrail denied"), + iterations=iteration, + response=text_content, + ) raise GuardrailError(gr.reason or "Output guardrail denied") if gr.rewrite is not None: text_content = gr.rewrite iteration += 1 if iteration > max_iterations: + _emit_failed_turn_end(on_event, ValueError("Agent loop exceeded max_iterations"), iterations=iteration) raise ValueError( f"Agent loop exceeded max_iterations ({max_iterations}). " f"The model kept requesting tool calls. Increase max_iterations or check your tools." ) - tool_messages = await _build_tool_result_messages_with_extensions_async( - response, - tools, - agent, - parent_inputs, - on_event=on_event, - cancel=cancel, - guardrails=guardrails, - parallel=parallel_tool_calls, - ) + try: + tool_messages = await _build_tool_result_messages_with_extensions_async( + response, + tools, + agent, + parent_inputs, + on_event=on_event, + cancel=cancel, + guardrails=guardrails, + parallel=parallel_tool_calls, + ) + except Exception as exc: + _emit_failed_turn_end(on_event, exc, iterations=iteration, response=response) + raise messages.extend(tool_messages) emit_event(on_event, "messages_updated", {"messages": messages}) @@ -1304,17 +1516,29 @@ async def turn_async( # Process final response (directly, not via run) if raw: emit_event(on_event, "done", {"response": response, "messages": messages}) + emit_event(on_event, "turn_end", {"iterations": iteration, "status": "success", "response": response}) return response - processed_result = await process_async(agent, response) + try: + processed_result = await process_async(agent, response) + except Exception as exc: + _emit_failed_turn_end(on_event, exc, iterations=iteration, response=response) + raise if guardrails is not None and isinstance(processed_result, str): assistant_msg = Message(role="assistant", parts=[TextPart(value=processed_result)]) gr = guardrails.check_output(assistant_msg) if not gr.allowed: emit_event(on_event, "error", {"message": f"Output guardrail denied: {gr.reason}"}) + _emit_failed_turn_end( + on_event, + GuardrailError(gr.reason or "Output guardrail denied"), + iterations=iteration, + response=processed_result, + ) raise GuardrailError(gr.reason or "Output guardrail denied") if gr.rewrite is not None: processed_result = gr.rewrite emit_event(on_event, "done", {"response": processed_result, "messages": messages}) + emit_event(on_event, "turn_end", {"iterations": iteration, "status": "success", "response": processed_result}) if target_type is not None: return cast(processed_result, target_type) return processed_result @@ -1872,6 +2096,7 @@ def _dispatch_one(tc: Any) -> str: # §13.1 — Emit tool_call_start emit_event(on_event, "tool_call_start", {"name": name, "arguments": arguments}) + started = time.perf_counter() # §13.4 — Tool guardrail if guardrails is not None: @@ -1880,6 +2105,17 @@ def _dispatch_one(tc: Any) -> str: if not gr.allowed: denied_msg = f"Tool denied by guardrail: {gr.reason}" emit_event(on_event, "tool_result", {"name": name, "result": denied_msg}) + emit_event( + on_event, + "tool_call_complete", + { + "name": name, + "success": False, + "result": denied_msg, + "durationMs": (time.perf_counter() - started) * 1000, + "errorKind": "guardrail_denied", + }, + ) return denied_msg if gr.rewrite is not None: arguments = json.dumps(gr.rewrite) if isinstance(gr.rewrite, dict) else gr.rewrite @@ -1890,9 +2126,22 @@ def _dispatch_one(tc: Any) -> str: except Exception as e: result = f"Error: Tool '{name}' failed: {type(e).__name__}: {e}" emit_event(on_event, "error", {"tool": name, "error": str(e)}) + success = not result.startswith("Error:") + error_kind = None if success else "tool_error" # §13.1 — Emit tool_result emit_event(on_event, "tool_result", {"name": name, "result": result}) + emit_event( + on_event, + "tool_call_complete", + { + "name": name, + "success": success, + "result": result, + "durationMs": (time.perf_counter() - started) * 1000, + "errorKind": error_kind, + }, + ) return result # §13.6 — Parallel tool execution @@ -1928,6 +2177,7 @@ async def _dispatch_one(tc: Any) -> str: # §13.1 — Emit tool_call_start emit_event(on_event, "tool_call_start", {"name": name, "arguments": arguments}) + started = time.perf_counter() # §13.4 — Tool guardrail if guardrails is not None: @@ -1936,6 +2186,17 @@ async def _dispatch_one(tc: Any) -> str: if not gr.allowed: denied_msg = f"Tool denied by guardrail: {gr.reason}" emit_event(on_event, "tool_result", {"name": name, "result": denied_msg}) + emit_event( + on_event, + "tool_call_complete", + { + "name": name, + "success": False, + "result": denied_msg, + "durationMs": (time.perf_counter() - started) * 1000, + "errorKind": "guardrail_denied", + }, + ) return denied_msg if gr.rewrite is not None: arguments = json.dumps(gr.rewrite) if isinstance(gr.rewrite, dict) else gr.rewrite @@ -1946,9 +2207,22 @@ async def _dispatch_one(tc: Any) -> str: except Exception as e: result = f"Error: Tool '{name}' failed: {type(e).__name__}: {e}" emit_event(on_event, "error", {"tool": name, "error": str(e)}) + success = not result.startswith("Error:") + error_kind = None if success else "tool_error" # §13.1 — Emit tool_result emit_event(on_event, "tool_result", {"name": name, "result": result}) + emit_event( + on_event, + "tool_call_complete", + { + "name": name, + "success": success, + "result": result, + "durationMs": (time.perf_counter() - started) * 1000, + "errorKind": error_kind, + }, + ) return result # §13.6 — Parallel tool execution diff --git a/runtime/python/prompty/prompty/harness/__init__.py b/runtime/python/prompty/prompty/harness/__init__.py new file mode 100644 index 00000000..3b513389 --- /dev/null +++ b/runtime/python/prompty/prompty/harness/__init__.py @@ -0,0 +1,33 @@ +"""Provide reference harness adapters for event, trace, permission, and tool protocols.""" + +from .adapters import ( + AllowAllPermissionResolver, + CollectingEventSink, + DenyAllPermissionResolver, + FunctionHostToolExecutor, + InMemoryCheckpointStore, + JsonlEventJournalWriter, +) +from .replay_verifier import ReferenceReplayVerifier +from .turn_runner import ( + ReferenceTurnRunner, + RunTurnRequest, + RunTurnResult, + TurnModelRequest, + TurnModelResponse, +) + +__all__ = [ + "AllowAllPermissionResolver", + "CollectingEventSink", + "DenyAllPermissionResolver", + "FunctionHostToolExecutor", + "InMemoryCheckpointStore", + "JsonlEventJournalWriter", + "ReferenceReplayVerifier", + "ReferenceTurnRunner", + "RunTurnRequest", + "RunTurnResult", + "TurnModelRequest", + "TurnModelResponse", +] diff --git a/runtime/python/prompty/prompty/harness/adapters.py b/runtime/python/prompty/prompty/harness/adapters.py new file mode 100644 index 00000000..da2957c5 --- /dev/null +++ b/runtime/python/prompty/prompty/harness/adapters.py @@ -0,0 +1,183 @@ +"""Implement dependency-free reference adapters for harness protocols.""" + +from __future__ import annotations + +import inspect +import json +from collections.abc import Awaitable, Callable +from pathlib import Path +from time import perf_counter +from typing import Any + +from ..model import ( + Checkpoint, + HostToolRequest, + HostToolResult, + PermissionDecision, + PermissionRequest, + SessionEvent, + SessionSummary, + TurnEvent, +) + +ToolHandler = Callable[[dict[str, Any], HostToolRequest], Any | Awaitable[Any]] + + +def _checkpoint_key(session_id: str, checkpoint_id: str) -> tuple[str, str]: + return (session_id, checkpoint_id) + + +def _require_checkpoint_key(checkpoint: Checkpoint) -> tuple[str, str]: + if not checkpoint.session_id: + raise ValueError("Checkpoint session_id is required") + if not checkpoint.id: + raise ValueError("Checkpoint id is required") + return _checkpoint_key(checkpoint.session_id, checkpoint.id) + + +def _error_message(error: BaseException) -> str: + return str(error) + + +class CollectingEventSink: + """Capture emitted turn and session events in memory.""" + + def __init__(self) -> None: + self.turn_events: list[TurnEvent] = [] + self.session_events: list[SessionEvent] = [] + + def emit_turn(self, turn_event: TurnEvent) -> bool: + """Append a turn event.""" + self.turn_events.append(turn_event) + return True + + def emit_session(self, session_event: SessionEvent) -> bool: + """Append a session event.""" + self.session_events.append(session_event) + return True + + +class JsonlEventJournalWriter: + """Append replayable event journal records as newline-delimited JSON.""" + + def __init__(self, path: str | Path) -> None: + self.path = Path(path) + self.path.parent.mkdir(parents=True, exist_ok=True) + self._closed = False + + def append_turn(self, turn_event: TurnEvent) -> bool: + """Append a turn event record.""" + return self._write({"kind": "turn", "event": turn_event.save()}) + + def append_session(self, session_event: SessionEvent) -> bool: + """Append a session event record.""" + return self._write({"kind": "session", "event": session_event.save()}) + + def close(self, summary: SessionSummary | None) -> bool: + """Append an optional summary and close the writer.""" + if summary is not None: + if not self._write({"kind": "summary", "summary": summary.save()}): + return False + self._closed = True + return True + + def _write(self, record: dict[str, Any]) -> bool: + if self._closed: + return False + with self.path.open("a", encoding="utf-8") as file: + file.write(json.dumps(record, separators=(",", ":"))) + file.write("\n") + return True + + +class InMemoryCheckpointStore: + """Store checkpoints in memory by session and checkpoint identifier.""" + + def __init__(self) -> None: + self._checkpoints: dict[tuple[str, str], Checkpoint] = {} + + async def save(self, checkpoint: Checkpoint) -> Checkpoint: + """Persist a checkpoint in memory.""" + self._checkpoints[_require_checkpoint_key(checkpoint)] = checkpoint + return checkpoint + + async def load(self, session_id: str, checkpoint_id: str) -> Checkpoint | None: + """Load a checkpoint by identifiers.""" + return self._checkpoints.get(_checkpoint_key(session_id, checkpoint_id)) + + async def list_checkpoints(self, session_id: str) -> list[Checkpoint]: + """List checkpoints for a session.""" + return [checkpoint for checkpoint in self._checkpoints.values() if checkpoint.session_id == session_id] + + +class AllowAllPermissionResolver: + """Resolve every permission request as approved.""" + + async def request(self, request: PermissionRequest) -> PermissionDecision: + """Return an approved permission decision.""" + return PermissionDecision( + request_id=request.request_id, + tool_call_id=request.tool_call_id, + permission=request.permission, + approved=True, + reason="allow_all", + ) + + +class DenyAllPermissionResolver: + """Resolve every permission request as denied.""" + + async def request(self, request: PermissionRequest) -> PermissionDecision: + """Return a denied permission decision.""" + return PermissionDecision( + request_id=request.request_id, + tool_call_id=request.tool_call_id, + permission=request.permission, + approved=False, + reason="deny_all", + ) + + +class FunctionHostToolExecutor: + """Dispatch host tool requests to registered local callables.""" + + def __init__(self, handlers: dict[str, ToolHandler]) -> None: + self.handlers = handlers + + async def execute(self, request: HostToolRequest) -> HostToolResult: + """Execute a registered host tool callable.""" + started = perf_counter() + handler = self.handlers.get(request.tool_name) + if handler is None: + return HostToolResult( + request_id=request.request_id, + tool_call_id=request.tool_call_id, + tool_name=request.tool_name, + success=False, + error_kind="not_found", + result={"message": f"No host tool registered for '{request.tool_name}'"}, + duration_ms=(perf_counter() - started) * 1000, + ) + + try: + result = handler(request.arguments or {}, request) + if inspect.isawaitable(result): + result = await result + return HostToolResult( + request_id=request.request_id, + tool_call_id=request.tool_call_id, + tool_name=request.tool_name, + success=True, + result=result, + duration_ms=(perf_counter() - started) * 1000, + ) + except Exception as error: + return HostToolResult( + request_id=request.request_id, + tool_call_id=request.tool_call_id, + tool_name=request.tool_name, + success=False, + error_kind="exception", + result={"message": _error_message(error)}, + duration_ms=(perf_counter() - started) * 1000, + ) diff --git a/runtime/python/prompty/prompty/harness/replay_verifier.py b/runtime/python/prompty/prompty/harness/replay_verifier.py new file mode 100644 index 00000000..5d5a3644 --- /dev/null +++ b/runtime/python/prompty/prompty/harness/replay_verifier.py @@ -0,0 +1,50 @@ +"""Verify normalized replay journals against generated harness contracts.""" + +from __future__ import annotations + +from ..model import ( + ReplayJournalRecord, + ReplayMismatch, + ReplayVerificationRequest, + ReplayVerificationResult, +) + + +def _comparable(record: ReplayJournalRecord | None) -> dict | None: + return None if record is None else record.save() + + +class ReferenceReplayVerifier: + """Compare expected and actual normalized replay journal records.""" + + def verify(self, request: ReplayVerificationRequest) -> ReplayVerificationResult: + """Return generated replay verification result data.""" + expected = request.expected or [] + actual = request.actual or [] + mismatches: list[ReplayMismatch] = [] + + for index in range(max(len(expected), len(actual))): + expected_record = expected[index] if index < len(expected) else None + actual_record = actual[index] if index < len(actual) else None + if _comparable(expected_record) != _comparable(actual_record): + if expected_record is None: + message = "Unexpected extra replay record" + elif actual_record is None: + message = "Missing replay record" + else: + message = "Replay record mismatch" + mismatches.append( + ReplayMismatch( + index=index, + expected=expected_record, + actual=actual_record, + message=message, + ) + ) + + return ReplayVerificationResult( + status="passed" if not mismatches else "failed", + mismatches=mismatches, + expected_count=len(expected), + actual_count=len(actual), + ) diff --git a/runtime/python/prompty/prompty/harness/turn_runner.py b/runtime/python/prompty/prompty/harness/turn_runner.py new file mode 100644 index 00000000..0b9ecdef --- /dev/null +++ b/runtime/python/prompty/prompty/harness/turn_runner.py @@ -0,0 +1,294 @@ +"""Run one deterministic harness turn using host-provided model callbacks.""" + +from __future__ import annotations + +import inspect +from collections.abc import Awaitable, Callable +from datetime import UTC, datetime +from typing import Any, Literal, Protocol + +from ..model import ( + Checkpoint, + HostToolRequest, + HostToolResult, + PermissionDecision, + PermissionRequest, + RunTurnRequest, + RunTurnResult, + SessionEvent, + SessionSummary, + TurnEvent, + TurnModelRequest, + TurnModelResponse, + TurnOptions, +) + +JsonRecord = dict[str, Any] +TurnStatus = Literal["success", "error"] + + +class EventSinkProtocol(Protocol): + """Subset of EventSink used by the reference turn runner.""" + + def emit_turn(self, turn_event: TurnEvent) -> bool: ... + + def emit_session(self, session_event: SessionEvent) -> bool: ... + + +class EventJournalWriterProtocol(Protocol): + """Subset of EventJournalWriter used by the reference turn runner.""" + + def append_turn(self, turn_event: TurnEvent) -> bool: ... + + def append_session(self, session_event: SessionEvent) -> bool: ... + + def close(self, summary: SessionSummary | None) -> bool: ... + + +class CheckpointStoreProtocol(Protocol): + """Subset of CheckpointStore used by the reference turn runner.""" + + async def save(self, checkpoint: Checkpoint) -> Checkpoint: ... + + +class PermissionResolverProtocol(Protocol): + """Subset of PermissionResolver used by the reference turn runner.""" + + async def request(self, request: PermissionRequest) -> PermissionDecision: ... + + +class HostToolExecutorProtocol(Protocol): + """Subset of HostToolExecutor used by the reference turn runner.""" + + async def execute(self, request: HostToolRequest) -> HostToolResult: ... + + +TurnModelCallback = Callable[[TurnModelRequest], TurnModelResponse | Awaitable[TurnModelResponse]] + + +class ReferenceTurnRunner: + """Compose harness contracts to run one deterministic single-turn loop.""" + + def __init__( + self, + *, + event_sink: EventSinkProtocol, + journal: EventJournalWriterProtocol, + checkpoint_store: CheckpointStoreProtocol, + permission_resolver: PermissionResolverProtocol, + host_tool_executor: HostToolExecutorProtocol, + invoke_model: TurnModelCallback, + now: Callable[[], str] | None = None, + next_id: Callable[[str], str] | None = None, + ) -> None: + self.event_sink = event_sink + self.journal = journal + self.checkpoint_store = checkpoint_store + self.permission_resolver = permission_resolver + self.host_tool_executor = host_tool_executor + self.invoke_model = invoke_model + self.now = now + self.next_id = next_id + self._sequence = 0 + + async def run(self, request: RunTurnRequest) -> RunTurnResult: + """Run one turn and return its deterministic, replayable result.""" + options = request.options or TurnOptions() + inputs = request.inputs or {} + max_iterations = options.max_iterations if options.max_iterations is not None else 10 + checkpoints: list[Checkpoint] = [] + all_tool_results: list[HostToolResult] = [] + pending_tool_results: list[HostToolResult] = [] + output: Any | None = None + status: TurnStatus = "success" + iterations = 0 + + self._record_session( + "session_start", + request.session_id, + request.turn_id, + {"sessionId": request.session_id, "schemaVersion": "1"}, + ) + self._record_turn( + "turn_start", + request.turn_id, + 0, + {"inputs": inputs, "maxIterations": max_iterations}, + ) + + for iteration in range(max_iterations): + iterations = iteration + 1 + self._record_turn("llm_start", request.turn_id, iteration, {"attempt": 0}) + response = self.invoke_model( + TurnModelRequest( + session_id=request.session_id, + turn_id=request.turn_id, + iteration=iteration, + inputs=inputs, + options=options, + tool_results=pending_tool_results, + ) + ) + if inspect.isawaitable(response): + response = await response + self._record_turn("llm_complete", request.turn_id, iteration, {}) + + checkpoint = await self._save_checkpoint(request.session_id, request.turn_id, iteration, response) + checkpoints.append(checkpoint) + + tool_requests = response.tool_requests or [] + if not tool_requests: + output = response.output + break + + pending_tool_results = [] + for tool_request in tool_requests: + tool_result = await self._resolve_and_execute_tool(request.turn_id, iteration, tool_request) + pending_tool_results.append(tool_result) + all_tool_results.append(tool_result) + + self._record_turn( + "messages_updated", + request.turn_id, + iteration, + {"toolResults": [result.save() for result in pending_tool_results]}, + ) + + if output is None and pending_tool_results: + status = "error" + output = {"message": "Maximum turn iterations reached"} + self._record_turn( + "error", + request.turn_id, + iterations, + {"errorKind": "max_iterations", "message": "Maximum turn iterations reached"}, + ) + + self._record_turn( + "turn_end", + request.turn_id, + iterations, + {"iterations": iterations, "status": status, "response": output}, + ) + self._record_session( + "session_end", + request.session_id, + request.turn_id, + {"sessionId": request.session_id, "status": status, "reason": "turn_complete"}, + ) + self.journal.close( + SessionSummary( + session_id=request.session_id, + status=status, + turns=1, + checkpoints=len(checkpoints), + ) + ) + + return RunTurnResult( + session_id=request.session_id, + turn_id=request.turn_id, + status=status, + output=output, + iterations=iterations, + tool_results=all_tool_results, + checkpoints=checkpoints, + ) + + async def _save_checkpoint( + self, + session_id: str, + turn_id: str, + iteration: int, + response: TurnModelResponse, + ) -> Checkpoint: + checkpoint = Checkpoint( + id=f"{turn_id}-checkpoint-{iteration}", + session_id=session_id, + turn_id=turn_id, + checkpoint_number=iteration + 1, + title=f"Turn {turn_id} iteration {iteration}", + state={ + "iteration": iteration, + "output": response.output, + "toolRequests": [tool_request.save() for tool_request in response.tool_requests or []], + **(response.checkpoint_state or {}), + }, + created_at=self._timestamp(), + ) + saved = await self.checkpoint_store.save(checkpoint) + self._record_session( + "checkpoint_created", + session_id, + turn_id, + {"checkpointId": saved.id, "checkpointNumber": saved.checkpoint_number}, + ) + return saved + + async def _resolve_and_execute_tool( + self, + turn_id: str, + iteration: int, + tool_request: HostToolRequest, + ) -> HostToolResult: + permission = PermissionRequest( + request_id=f"{tool_request.request_id}-permission" if tool_request.request_id else self._id("permission"), + tool_call_id=tool_request.tool_call_id, + permission="tool.execute", + target=tool_request.tool_name, + details=tool_request.save(), + ) + self._record_turn("permission_requested", turn_id, iteration, permission.save()) + decision = await self.permission_resolver.request(permission) + self._record_turn("permission_completed", turn_id, iteration, decision.save()) + + if not decision.approved: + return HostToolResult( + request_id=tool_request.request_id, + tool_call_id=tool_request.tool_call_id, + tool_name=tool_request.tool_name, + success=False, + error_kind="permission_denied", + result={"message": decision.reason or "Permission denied"}, + ) + + self._record_turn("tool_execution_start", turn_id, iteration, tool_request.save()) + result = await self.host_tool_executor.execute(tool_request) + self._record_turn("tool_execution_complete", turn_id, iteration, result.save()) + self._record_turn("tool_result", turn_id, iteration, result.save()) + return result + + def _record_turn(self, event_type: Any, turn_id: str, iteration: int, payload: JsonRecord) -> None: + event = TurnEvent( + id=self._id("turn-event"), + type=event_type, + timestamp=self._timestamp(), + turn_id=turn_id, + iteration=iteration, + payload=payload, + ) + self.event_sink.emit_turn(event) + self.journal.append_turn(event) + + def _record_session(self, event_type: Any, session_id: str, turn_id: str, payload: JsonRecord) -> None: + event = SessionEvent( + id=self._id("session-event"), + type=event_type, + timestamp=self._timestamp(), + session_id=session_id, + turn_id=turn_id, + payload=payload, + ) + self.event_sink.emit_session(event) + self.journal.append_session(event) + + def _timestamp(self) -> str: + if self.now is not None: + return self.now() + return datetime.now(UTC).isoformat().replace("+00:00", "Z") + + def _id(self, prefix: str) -> str: + if self.next_id is not None: + return self.next_id(prefix) + self._sequence += 1 + return f"{prefix}-{self._sequence}" diff --git a/runtime/python/prompty/prompty/model/__init__.py b/runtime/python/prompty/prompty/model/__init__.py index c207597f..d8b682fc 100644 --- a/runtime/python/prompty/prompty/model/__init__.py +++ b/runtime/python/prompty/prompty/model/__init__.py @@ -1,3 +1,4 @@ +# ########################################## # WARNING: This is an auto-generated file. # DO NOT EDIT THIS FILE DIRECTLY @@ -40,21 +41,54 @@ ValidationResult, ) from .events import ( + Checkpoint, CompactionCompletePayload, CompactionFailedPayload, + CompactionStartPayload, DoneEventPayload, ErrorChunk, ErrorEventPayload, + HarnessContext, + HookEndPayload, + HookStartPayload, + HostToolRequest, + HostToolResult, + LlmCompletePayload, + LlmStartPayload, MessagesUpdatedPayload, + PermissionCompletedPayload, + PermissionDecision, + PermissionRequest, + PermissionRequestedPayload, + RedactedField, + RedactionMetadata, + RetryPayload, + SessionEndPayload, + SessionEvent, + SessionFileRef, + SessionRef, + SessionStartPayload, + SessionSummary, + SessionTrace, + SessionWarningPayload, StatusEventPayload, StreamChunk, TextChunk, ThinkingChunk, ThinkingEventPayload, TokenEventPayload, + ToolCallCompletePayload, ToolCallStartPayload, ToolChunk, + ToolExecutionCompletePayload, + ToolExecutionStartPayload, ToolResultPayload, + TrajectoryEvent, + TurnEndPayload, + TurnEvent, + TurnStartPayload, + TurnSummary, + TurnTrace, ) from .model import ( Model, @@ -63,11 +97,24 @@ TokenUsage, ) from .pipeline import ( + CheckpointStore, CompactionConfig, + EventJournalWriter, + EventSink, Executor, + HostToolExecutor, Parser, + PermissionResolver, Processor, Renderer, + ReplayJournalRecord, + ReplayMismatch, + ReplayVerificationRequest, + ReplayVerificationResult, + RunTurnRequest, + RunTurnResult, + TurnModelRequest, + TurnModelResponse, TurnOptions, ) from .streaming import ( @@ -157,20 +204,66 @@ "ModelInfo", "CompactionConfig", "TurnOptions", - "Renderer", - "Parser", - "Executor", - "Processor", + "HostToolResult", + "TurnModelRequest", + "HostToolRequest", + "TurnModelResponse", + "RunTurnRequest", + "RedactedField", + "RedactionMetadata", + "Checkpoint", + "RunTurnResult", + "ReplayJournalRecord", + "ReplayVerificationRequest", + "ReplayMismatch", + "ReplayVerificationResult", + "TurnEvent", + "TurnStartPayload", + "TurnEndPayload", + "LlmStartPayload", + "LlmCompletePayload", + "RetryPayload", + "PermissionRequestedPayload", + "PermissionCompletedPayload", + "PermissionRequest", + "PermissionDecision", "TokenEventPayload", "ThinkingEventPayload", "ToolCallStartPayload", + "ToolCallCompletePayload", + "ToolExecutionStartPayload", + "ToolExecutionCompletePayload", + "HookStartPayload", + "HookEndPayload", "ToolResultPayload", "StatusEventPayload", "MessagesUpdatedPayload", "DoneEventPayload", "ErrorEventPayload", + "CompactionStartPayload", "CompactionCompletePayload", "CompactionFailedPayload", + "TurnSummary", + "TurnTrace", + "HarnessContext", + "SessionStartPayload", + "SessionEndPayload", + "SessionWarningPayload", + "SessionEvent", + "TrajectoryEvent", + "SessionFileRef", + "SessionRef", + "SessionSummary", + "SessionTrace", + "Renderer", + "Parser", + "Executor", + "Processor", + "EventSink", + "EventJournalWriter", + "PermissionResolver", + "CheckpointStore", + "HostToolExecutor", "StreamChunk", "TextChunk", "ThinkingChunk", diff --git a/runtime/python/prompty/prompty/model/_context.py b/runtime/python/prompty/prompty/model/_context.py index 961fd708..c29e102c 100644 --- a/runtime/python/prompty/prompty/model/_context.py +++ b/runtime/python/prompty/prompty/model/_context.py @@ -1,4 +1,5 @@ -# Prompty LoadContext +# +# Typra LoadContext import json from collections.abc import Callable from dataclasses import dataclass diff --git a/runtime/python/prompty/prompty/model/agent/_GuardrailResult.py b/runtime/python/prompty/prompty/model/agent/_GuardrailResult.py index 6fe41041..ed297f00 100644 --- a/runtime/python/prompty/prompty/model/agent/_GuardrailResult.py +++ b/runtime/python/prompty/prompty/model/agent/_GuardrailResult.py @@ -1,3 +1,4 @@ +# ########################################## # WARNING: This is an auto-generated file. # DO NOT EDIT THIS FILE DIRECTLY diff --git a/runtime/python/prompty/prompty/model/agent/_Prompty.py b/runtime/python/prompty/prompty/model/agent/_Prompty.py index 517e7bdc..bdd60c33 100644 --- a/runtime/python/prompty/prompty/model/agent/_Prompty.py +++ b/runtime/python/prompty/prompty/model/agent/_Prompty.py @@ -1,3 +1,4 @@ +# ########################################## # WARNING: This is an auto-generated file. # DO NOT EDIT THIS FILE DIRECTLY @@ -23,6 +24,12 @@ class Prompty: This is the single root type for the Prompty schema — there is no abstract base class or kind discriminator. A .prompty file always produces a Prompty instance. + Runtime loaders may resolve frontmatter references such as `${env:VAR}` and + `${file:relative/path}`. File references must be treated as a host-controlled + capability: by default they are scoped to the containing .prompty file's + directory tree after canonicalization, and any additional allowed roots must + be supplied by the host application's load options rather than frontmatter. + Attributes ---------- name : str diff --git a/runtime/python/prompty/prompty/model/agent/__init__.py b/runtime/python/prompty/prompty/model/agent/__init__.py index ca1d0971..c475977a 100644 --- a/runtime/python/prompty/prompty/model/agent/__init__.py +++ b/runtime/python/prompty/prompty/model/agent/__init__.py @@ -1,3 +1,4 @@ +# ########################################## # WARNING: This is an auto-generated file. # DO NOT EDIT THIS FILE DIRECTLY diff --git a/runtime/python/prompty/prompty/model/connection/_Connection.py b/runtime/python/prompty/prompty/model/connection/_Connection.py index 64f91a85..9df3d76f 100644 --- a/runtime/python/prompty/prompty/model/connection/_Connection.py +++ b/runtime/python/prompty/prompty/model/connection/_Connection.py @@ -1,3 +1,4 @@ +# ########################################## # WARNING: This is an auto-generated file. # DO NOT EDIT THIS FILE DIRECTLY diff --git a/runtime/python/prompty/prompty/model/connection/__init__.py b/runtime/python/prompty/prompty/model/connection/__init__.py index 21677c89..344b6de4 100644 --- a/runtime/python/prompty/prompty/model/connection/__init__.py +++ b/runtime/python/prompty/prompty/model/connection/__init__.py @@ -1,3 +1,4 @@ +# ########################################## # WARNING: This is an auto-generated file. # DO NOT EDIT THIS FILE DIRECTLY diff --git a/runtime/python/prompty/prompty/model/conversation/_ContentPart.py b/runtime/python/prompty/prompty/model/conversation/_ContentPart.py index b4c12736..af4e9004 100644 --- a/runtime/python/prompty/prompty/model/conversation/_ContentPart.py +++ b/runtime/python/prompty/prompty/model/conversation/_ContentPart.py @@ -1,3 +1,4 @@ +# ########################################## # WARNING: This is an auto-generated file. # DO NOT EDIT THIS FILE DIRECTLY diff --git a/runtime/python/prompty/prompty/model/conversation/_Message.py b/runtime/python/prompty/prompty/model/conversation/_Message.py index 4f4a4342..8ee2fee0 100644 --- a/runtime/python/prompty/prompty/model/conversation/_Message.py +++ b/runtime/python/prompty/prompty/model/conversation/_Message.py @@ -1,3 +1,4 @@ +# ########################################## # WARNING: This is an auto-generated file. # DO NOT EDIT THIS FILE DIRECTLY diff --git a/runtime/python/prompty/prompty/model/conversation/_ThreadMarker.py b/runtime/python/prompty/prompty/model/conversation/_ThreadMarker.py index 02f8728c..fc0d905b 100644 --- a/runtime/python/prompty/prompty/model/conversation/_ThreadMarker.py +++ b/runtime/python/prompty/prompty/model/conversation/_ThreadMarker.py @@ -1,3 +1,4 @@ +# ########################################## # WARNING: This is an auto-generated file. # DO NOT EDIT THIS FILE DIRECTLY diff --git a/runtime/python/prompty/prompty/model/conversation/_ToolCall.py b/runtime/python/prompty/prompty/model/conversation/_ToolCall.py index 5c4526c8..08e44bad 100644 --- a/runtime/python/prompty/prompty/model/conversation/_ToolCall.py +++ b/runtime/python/prompty/prompty/model/conversation/_ToolCall.py @@ -1,3 +1,4 @@ +# ########################################## # WARNING: This is an auto-generated file. # DO NOT EDIT THIS FILE DIRECTLY diff --git a/runtime/python/prompty/prompty/model/conversation/_ToolResult.py b/runtime/python/prompty/prompty/model/conversation/_ToolResult.py index 48cf022c..3586a1ee 100644 --- a/runtime/python/prompty/prompty/model/conversation/_ToolResult.py +++ b/runtime/python/prompty/prompty/model/conversation/_ToolResult.py @@ -1,3 +1,4 @@ +# ########################################## # WARNING: This is an auto-generated file. # DO NOT EDIT THIS FILE DIRECTLY @@ -5,11 +6,13 @@ ########################################## from dataclasses import dataclass, field -from typing import Any, ClassVar, Protocol, runtime_checkable +from typing import Any, ClassVar, Literal, Protocol, runtime_checkable from .._context import LoadContext, SaveContext from ._ContentPart import ContentPart, TextPart +ToolResultStatus = Literal["success", "error", "cancelled", "timeout"] + @dataclass class ToolResult: @@ -23,11 +26,23 @@ class ToolResult: ---------- parts : list[ContentPart] The content parts of the tool result + status : Optional[str] + Semantic execution status for the tool result + error_kind : Optional[str] + Stable machine-readable error category when status is not success + error_message : Optional[str] + Human-readable error message when status is not success + duration_ms : Optional[float] + Tool execution duration in milliseconds """ _shorthand_property: ClassVar[str | None] = None parts: list[ContentPart] = field(default_factory=list) + status: ToolResultStatus | None = None + error_kind: str | None = None + error_message: str | None = None + duration_ms: float | None = None @staticmethod def load(data: Any, context: LoadContext | None = None) -> "ToolResult": @@ -51,6 +66,14 @@ def load(data: Any, context: LoadContext | None = None) -> "ToolResult": if data is not None and "parts" in data: instance.parts = ToolResult.load_parts(data["parts"], context) + if data is not None and "status" in data: + instance.status = data["status"] + if data is not None and "errorKind" in data: + instance.error_kind = data["errorKind"] + if data is not None and "errorMessage" in data: + instance.error_message = data["errorMessage"] + if data is not None and "durationMs" in data: + instance.duration_ms = data["durationMs"] if context is not None: instance = context.process_output(instance) return instance @@ -94,6 +117,14 @@ def save(self, context: SaveContext | None = None) -> dict[str, Any]: if obj.parts is not None: result["parts"] = ToolResult.save_parts(obj.parts, context) + if obj.status is not None: + result["status"] = obj.status + if obj.error_kind is not None: + result["errorKind"] = obj.error_kind + if obj.error_message is not None: + result["errorMessage"] = obj.error_message + if obj.duration_ms is not None: + result["durationMs"] = obj.duration_ms if context is not None: result = context.process_dict(result) diff --git a/runtime/python/prompty/prompty/model/conversation/__init__.py b/runtime/python/prompty/prompty/model/conversation/__init__.py index f9113dfb..33695a04 100644 --- a/runtime/python/prompty/prompty/model/conversation/__init__.py +++ b/runtime/python/prompty/prompty/model/conversation/__init__.py @@ -1,3 +1,4 @@ +# ########################################## # WARNING: This is an auto-generated file. # DO NOT EDIT THIS FILE DIRECTLY diff --git a/runtime/python/prompty/prompty/model/core/_FileNotFoundError.py b/runtime/python/prompty/prompty/model/core/_FileNotFoundError.py index c663be66..47e95a79 100644 --- a/runtime/python/prompty/prompty/model/core/_FileNotFoundError.py +++ b/runtime/python/prompty/prompty/model/core/_FileNotFoundError.py @@ -1,3 +1,4 @@ +# ########################################## # WARNING: This is an auto-generated file. # DO NOT EDIT THIS FILE DIRECTLY diff --git a/runtime/python/prompty/prompty/model/core/_InvokerError.py b/runtime/python/prompty/prompty/model/core/_InvokerError.py index 4b3d89f6..74bdd871 100644 --- a/runtime/python/prompty/prompty/model/core/_InvokerError.py +++ b/runtime/python/prompty/prompty/model/core/_InvokerError.py @@ -1,3 +1,4 @@ +# ########################################## # WARNING: This is an auto-generated file. # DO NOT EDIT THIS FILE DIRECTLY diff --git a/runtime/python/prompty/prompty/model/core/_Property.py b/runtime/python/prompty/prompty/model/core/_Property.py index efa07454..13087bea 100644 --- a/runtime/python/prompty/prompty/model/core/_Property.py +++ b/runtime/python/prompty/prompty/model/core/_Property.py @@ -1,3 +1,4 @@ +# ########################################## # WARNING: This is an auto-generated file. # DO NOT EDIT THIS FILE DIRECTLY diff --git a/runtime/python/prompty/prompty/model/core/_ValidationError.py b/runtime/python/prompty/prompty/model/core/_ValidationError.py index 2d1ee4cc..9efc6540 100644 --- a/runtime/python/prompty/prompty/model/core/_ValidationError.py +++ b/runtime/python/prompty/prompty/model/core/_ValidationError.py @@ -1,3 +1,4 @@ +# ########################################## # WARNING: This is an auto-generated file. # DO NOT EDIT THIS FILE DIRECTLY diff --git a/runtime/python/prompty/prompty/model/core/_ValidationResult.py b/runtime/python/prompty/prompty/model/core/_ValidationResult.py index c603770b..d28edd55 100644 --- a/runtime/python/prompty/prompty/model/core/_ValidationResult.py +++ b/runtime/python/prompty/prompty/model/core/_ValidationResult.py @@ -1,3 +1,4 @@ +# ########################################## # WARNING: This is an auto-generated file. # DO NOT EDIT THIS FILE DIRECTLY diff --git a/runtime/python/prompty/prompty/model/core/__init__.py b/runtime/python/prompty/prompty/model/core/__init__.py index 66a70e34..9f77200d 100644 --- a/runtime/python/prompty/prompty/model/core/__init__.py +++ b/runtime/python/prompty/prompty/model/core/__init__.py @@ -1,3 +1,4 @@ +# ########################################## # WARNING: This is an auto-generated file. # DO NOT EDIT THIS FILE DIRECTLY diff --git a/runtime/python/prompty/prompty/model/events/_Checkpoint.py b/runtime/python/prompty/prompty/model/events/_Checkpoint.py new file mode 100644 index 00000000..7977d5ec --- /dev/null +++ b/runtime/python/prompty/prompty/model/events/_Checkpoint.py @@ -0,0 +1,169 @@ +# +########################################## +# WARNING: This is an auto-generated file. +# DO NOT EDIT THIS FILE DIRECTLY +# ANY EDITS WILL BE LOST +########################################## + +from dataclasses import dataclass, field +from typing import Any, ClassVar + +from .._context import LoadContext, SaveContext +from ._RedactionMetadata import RedactionMetadata + + +@dataclass +class Checkpoint: + """A persisted handoff point for a harness session. + + Attributes + ---------- + id : Optional[str] + Stable checkpoint identifier + session_id : Optional[str] + Stable session identifier + turn_id : Optional[str] + Associated turn identifier, when the checkpoint was created inside a turn + checkpoint_number : Optional[int] + Monotonic checkpoint number within the session + title : str + Short checkpoint title + overview : Optional[str] + Short human-readable overview + state : Optional[dict[str, Any]] + Portable checkpoint state needed to resume or hand off the session + summary : Optional[str] + Optional host-authored summary or handoff note + metadata : Optional[dict[str, Any]] + Host-defined checkpoint metadata + created_at : Optional[str] + ISO 8601 UTC timestamp when the checkpoint was created + redaction : Optional[RedactionMetadata] + Redaction state for sensitive checkpoint fields + """ + + _shorthand_property: ClassVar[str | None] = None + + id: str | None = None + session_id: str | None = None + turn_id: str | None = None + checkpoint_number: int | None = None + title: str = field(default="") + overview: str | None = None + state: dict[str, Any] | None = None + summary: str | None = None + metadata: dict[str, Any] | None = None + created_at: str | None = None + redaction: RedactionMetadata | None = None + + @staticmethod + def load(data: Any, context: LoadContext | None = None) -> "Checkpoint": + """Load a Checkpoint instance. + Args: + data (Any): The data to load the instance from. + context (Optional[LoadContext]): Optional context with pre/post processing callbacks. + Returns: + Checkpoint: The loaded Checkpoint instance. + + """ + + if context is not None: + data = context.process_input(data) + + if not isinstance(data, dict): + raise ValueError(f"Invalid data for Checkpoint: {data}") + + # create new instance + instance = Checkpoint() + + if data is not None and "id" in data: + instance.id = data["id"] + if data is not None and "sessionId" in data: + instance.session_id = data["sessionId"] + if data is not None and "turnId" in data: + instance.turn_id = data["turnId"] + if data is not None and "checkpointNumber" in data: + instance.checkpoint_number = data["checkpointNumber"] + if data is not None and "title" in data: + instance.title = data["title"] + if data is not None and "overview" in data: + instance.overview = data["overview"] + if data is not None and "state" in data: + instance.state = data["state"] + if data is not None and "summary" in data: + instance.summary = data["summary"] + if data is not None and "metadata" in data: + instance.metadata = data["metadata"] + if data is not None and "createdAt" in data: + instance.created_at = data["createdAt"] + if data is not None and "redaction" in data: + instance.redaction = RedactionMetadata.load(data["redaction"], context) + if context is not None: + instance = context.process_output(instance) + return instance + + def save(self, context: SaveContext | None = None) -> dict[str, Any]: + """Save the Checkpoint instance to a dictionary. + Args: + context (Optional[SaveContext]): Optional context with pre/post processing callbacks. + Returns: + dict[str, Any]: The dictionary representation of this instance. + + """ + obj = self + if context is not None: + obj = context.process_object(obj) + + result: dict[str, Any] = {} + + if obj.id is not None: + result["id"] = obj.id + if obj.session_id is not None: + result["sessionId"] = obj.session_id + if obj.turn_id is not None: + result["turnId"] = obj.turn_id + if obj.checkpoint_number is not None: + result["checkpointNumber"] = obj.checkpoint_number + if obj.title is not None: + result["title"] = obj.title + if obj.overview is not None: + result["overview"] = obj.overview + if obj.state is not None: + result["state"] = obj.state + if obj.summary is not None: + result["summary"] = obj.summary + if obj.metadata is not None: + result["metadata"] = obj.metadata + if obj.created_at is not None: + result["createdAt"] = obj.created_at + if obj.redaction is not None: + result["redaction"] = obj.redaction.save(context) + + if context is not None: + result = context.process_dict(result) + return result + + def to_yaml(self, context: SaveContext | None = None) -> str: + """Convert the Checkpoint instance to a YAML string. + Args: + context (Optional[SaveContext]): Optional context with pre/post processing callbacks. + Returns: + str: The YAML string representation of this instance. + + """ + if context is None: + context = SaveContext() + return context.to_yaml(self.save(context)) + + def to_json(self, context: SaveContext | None = None, indent: int = 2) -> str: + """Convert the Checkpoint instance to a JSON string. + Args: + context (Optional[SaveContext]): Optional context with pre/post processing callbacks. + indent (int): Number of spaces for indentation. Defaults to 2. + Returns: + str: The JSON string representation of this instance. + + """ + if context is None: + context = SaveContext() + return context.to_json(self.save(context), indent) diff --git a/runtime/python/prompty/prompty/model/events/_CompactionCompletePayload.py b/runtime/python/prompty/prompty/model/events/_CompactionCompletePayload.py index 0ac1385b..ae387785 100644 --- a/runtime/python/prompty/prompty/model/events/_CompactionCompletePayload.py +++ b/runtime/python/prompty/prompty/model/events/_CompactionCompletePayload.py @@ -1,3 +1,4 @@ +# ########################################## # WARNING: This is an auto-generated file. # DO NOT EDIT THIS FILE DIRECTLY @@ -20,12 +21,15 @@ class CompactionCompletePayload: Number of messages removed during compaction remaining : int Number of messages remaining after compaction + summary_length : Optional[int] + Length of the generated summary, when a summarization strategy is used """ _shorthand_property: ClassVar[str | None] = None removed: int = field(default=0) remaining: int = field(default=0) + summary_length: int | None = None @staticmethod def load(data: Any, context: LoadContext | None = None) -> "CompactionCompletePayload": @@ -51,6 +55,8 @@ def load(data: Any, context: LoadContext | None = None) -> "CompactionCompletePa instance.removed = data["removed"] if data is not None and "remaining" in data: instance.remaining = data["remaining"] + if data is not None and "summaryLength" in data: + instance.summary_length = data["summaryLength"] if context is not None: instance = context.process_output(instance) return instance @@ -73,6 +79,8 @@ def save(self, context: SaveContext | None = None) -> dict[str, Any]: result["removed"] = obj.removed if obj.remaining is not None: result["remaining"] = obj.remaining + if obj.summary_length is not None: + result["summaryLength"] = obj.summary_length if context is not None: result = context.process_dict(result) diff --git a/runtime/python/prompty/prompty/model/events/_CompactionFailedPayload.py b/runtime/python/prompty/prompty/model/events/_CompactionFailedPayload.py index a8332923..e7d6f200 100644 --- a/runtime/python/prompty/prompty/model/events/_CompactionFailedPayload.py +++ b/runtime/python/prompty/prompty/model/events/_CompactionFailedPayload.py @@ -1,3 +1,4 @@ +# ########################################## # WARNING: This is an auto-generated file. # DO NOT EDIT THIS FILE DIRECTLY diff --git a/runtime/python/prompty/prompty/model/events/_CompactionStartPayload.py b/runtime/python/prompty/prompty/model/events/_CompactionStartPayload.py new file mode 100644 index 00000000..574b01b8 --- /dev/null +++ b/runtime/python/prompty/prompty/model/events/_CompactionStartPayload.py @@ -0,0 +1,98 @@ +# +########################################## +# WARNING: This is an auto-generated file. +# DO NOT EDIT THIS FILE DIRECTLY +# ANY EDITS WILL BE LOST +########################################## + +from dataclasses import dataclass, field +from typing import Any, ClassVar + +from .._context import LoadContext, SaveContext + + +@dataclass +class CompactionStartPayload: + """Payload for "compaction_start" events — context compaction is beginning. + + Attributes + ---------- + dropped_count : int + Number of messages selected for compaction + """ + + _shorthand_property: ClassVar[str | None] = None + + dropped_count: int = field(default=0) + + @staticmethod + def load(data: Any, context: LoadContext | None = None) -> "CompactionStartPayload": + """Load a CompactionStartPayload instance. + Args: + data (Any): The data to load the instance from. + context (Optional[LoadContext]): Optional context with pre/post processing callbacks. + Returns: + CompactionStartPayload: The loaded CompactionStartPayload instance. + + """ + + if context is not None: + data = context.process_input(data) + + if not isinstance(data, dict): + raise ValueError(f"Invalid data for CompactionStartPayload: {data}") + + # create new instance + instance = CompactionStartPayload() + + if data is not None and "droppedCount" in data: + instance.dropped_count = data["droppedCount"] + if context is not None: + instance = context.process_output(instance) + return instance + + def save(self, context: SaveContext | None = None) -> dict[str, Any]: + """Save the CompactionStartPayload instance to a dictionary. + Args: + context (Optional[SaveContext]): Optional context with pre/post processing callbacks. + Returns: + dict[str, Any]: The dictionary representation of this instance. + + """ + obj = self + if context is not None: + obj = context.process_object(obj) + + result: dict[str, Any] = {} + + if obj.dropped_count is not None: + result["droppedCount"] = obj.dropped_count + + if context is not None: + result = context.process_dict(result) + return result + + def to_yaml(self, context: SaveContext | None = None) -> str: + """Convert the CompactionStartPayload instance to a YAML string. + Args: + context (Optional[SaveContext]): Optional context with pre/post processing callbacks. + Returns: + str: The YAML string representation of this instance. + + """ + if context is None: + context = SaveContext() + return context.to_yaml(self.save(context)) + + def to_json(self, context: SaveContext | None = None, indent: int = 2) -> str: + """Convert the CompactionStartPayload instance to a JSON string. + Args: + context (Optional[SaveContext]): Optional context with pre/post processing callbacks. + indent (int): Number of spaces for indentation. Defaults to 2. + Returns: + str: The JSON string representation of this instance. + + """ + if context is None: + context = SaveContext() + return context.to_json(self.save(context), indent) diff --git a/runtime/python/prompty/prompty/model/events/_DoneEventPayload.py b/runtime/python/prompty/prompty/model/events/_DoneEventPayload.py index 4465a498..cb577223 100644 --- a/runtime/python/prompty/prompty/model/events/_DoneEventPayload.py +++ b/runtime/python/prompty/prompty/model/events/_DoneEventPayload.py @@ -1,3 +1,4 @@ +# ########################################## # WARNING: This is an auto-generated file. # DO NOT EDIT THIS FILE DIRECTLY @@ -17,15 +18,15 @@ class DoneEventPayload: Attributes ---------- - response : str - The final text response from the LLM + response : Any + The final response from the LLM after processing messages : list[Message] The final conversation state including all messages """ _shorthand_property: ClassVar[str | None] = None - response: str = field(default="") + response: Any = field(default=None) messages: list[Message] = field(default_factory=list) @staticmethod diff --git a/runtime/python/prompty/prompty/model/events/_ErrorEventPayload.py b/runtime/python/prompty/prompty/model/events/_ErrorEventPayload.py index 88c5bab5..8524888c 100644 --- a/runtime/python/prompty/prompty/model/events/_ErrorEventPayload.py +++ b/runtime/python/prompty/prompty/model/events/_ErrorEventPayload.py @@ -1,3 +1,4 @@ +# ########################################## # WARNING: This is an auto-generated file. # DO NOT EDIT THIS FILE DIRECTLY @@ -18,11 +19,17 @@ class ErrorEventPayload: ---------- message : str Human-readable error description + error_kind : Optional[str] + Stable machine-readable error category + phase : Optional[str] + Operation or phase where the error occurred """ _shorthand_property: ClassVar[str | None] = None message: str = field(default="") + error_kind: str | None = None + phase: str | None = None @staticmethod def load(data: Any, context: LoadContext | None = None) -> "ErrorEventPayload": @@ -46,6 +53,10 @@ def load(data: Any, context: LoadContext | None = None) -> "ErrorEventPayload": if data is not None and "message" in data: instance.message = data["message"] + if data is not None and "errorKind" in data: + instance.error_kind = data["errorKind"] + if data is not None and "phase" in data: + instance.phase = data["phase"] if context is not None: instance = context.process_output(instance) return instance @@ -66,6 +77,10 @@ def save(self, context: SaveContext | None = None) -> dict[str, Any]: if obj.message is not None: result["message"] = obj.message + if obj.error_kind is not None: + result["errorKind"] = obj.error_kind + if obj.phase is not None: + result["phase"] = obj.phase if context is not None: result = context.process_dict(result) diff --git a/runtime/python/prompty/prompty/model/events/_HarnessContext.py b/runtime/python/prompty/prompty/model/events/_HarnessContext.py new file mode 100644 index 00000000..5480523f --- /dev/null +++ b/runtime/python/prompty/prompty/model/events/_HarnessContext.py @@ -0,0 +1,114 @@ +# +########################################## +# WARNING: This is an auto-generated file. +# DO NOT EDIT THIS FILE DIRECTLY +# ANY EDITS WILL BE LOST +########################################## + +from dataclasses import dataclass +from typing import Any, ClassVar + +from .._context import LoadContext, SaveContext + + +@dataclass +class HarnessContext: + """Execution context associated with a harness session. Host-specific + environments can store detailed profiles in metadata without making the core + contract depend on one source-control provider or workspace shape. + + Attributes + ---------- + cwd : Optional[str] + Current working directory for the harness + git_root : Optional[str] + Git repository root, when known + metadata : Optional[dict[str, Any]] + Host-defined context metadata, such as source-control or sandbox details + """ + + _shorthand_property: ClassVar[str | None] = None + + cwd: str | None = None + git_root: str | None = None + metadata: dict[str, Any] | None = None + + @staticmethod + def load(data: Any, context: LoadContext | None = None) -> "HarnessContext": + """Load a HarnessContext instance. + Args: + data (Any): The data to load the instance from. + context (Optional[LoadContext]): Optional context with pre/post processing callbacks. + Returns: + HarnessContext: The loaded HarnessContext instance. + + """ + + if context is not None: + data = context.process_input(data) + + if not isinstance(data, dict): + raise ValueError(f"Invalid data for HarnessContext: {data}") + + # create new instance + instance = HarnessContext() + + if data is not None and "cwd" in data: + instance.cwd = data["cwd"] + if data is not None and "gitRoot" in data: + instance.git_root = data["gitRoot"] + if data is not None and "metadata" in data: + instance.metadata = data["metadata"] + if context is not None: + instance = context.process_output(instance) + return instance + + def save(self, context: SaveContext | None = None) -> dict[str, Any]: + """Save the HarnessContext instance to a dictionary. + Args: + context (Optional[SaveContext]): Optional context with pre/post processing callbacks. + Returns: + dict[str, Any]: The dictionary representation of this instance. + + """ + obj = self + if context is not None: + obj = context.process_object(obj) + + result: dict[str, Any] = {} + + if obj.cwd is not None: + result["cwd"] = obj.cwd + if obj.git_root is not None: + result["gitRoot"] = obj.git_root + if obj.metadata is not None: + result["metadata"] = obj.metadata + + if context is not None: + result = context.process_dict(result) + return result + + def to_yaml(self, context: SaveContext | None = None) -> str: + """Convert the HarnessContext instance to a YAML string. + Args: + context (Optional[SaveContext]): Optional context with pre/post processing callbacks. + Returns: + str: The YAML string representation of this instance. + + """ + if context is None: + context = SaveContext() + return context.to_yaml(self.save(context)) + + def to_json(self, context: SaveContext | None = None, indent: int = 2) -> str: + """Convert the HarnessContext instance to a JSON string. + Args: + context (Optional[SaveContext]): Optional context with pre/post processing callbacks. + indent (int): Number of spaces for indentation. Defaults to 2. + Returns: + str: The JSON string representation of this instance. + + """ + if context is None: + context = SaveContext() + return context.to_json(self.save(context), indent) diff --git a/runtime/python/prompty/prompty/model/events/_HookEndPayload.py b/runtime/python/prompty/prompty/model/events/_HookEndPayload.py new file mode 100644 index 00000000..f913b870 --- /dev/null +++ b/runtime/python/prompty/prompty/model/events/_HookEndPayload.py @@ -0,0 +1,150 @@ +# +########################################## +# WARNING: This is an auto-generated file. +# DO NOT EDIT THIS FILE DIRECTLY +# ANY EDITS WILL BE LOST +########################################## + +from dataclasses import dataclass, field +from typing import Any, ClassVar, Literal + +from .._context import LoadContext, SaveContext +from ._RedactionMetadata import RedactionMetadata + +HookEndScope = Literal["turn", "session"] + + +@dataclass +class HookEndPayload: + """Payload for "hook_end" events — a host lifecycle hook finished. + + Attributes + ---------- + hook_invocation_id : str + Stable hook invocation identifier + hook_type : str + Host-defined hook type + scope : Optional[str] + Whether the hook is scoped to a turn or the outer session + success : bool + Whether the hook completed successfully + output : Optional[dict[str, Any]] + Hook output after host-side sanitization + duration_ms : Optional[float] + Hook execution duration in milliseconds + error : Optional[str] + Human-readable error when success is false + redaction : Optional[RedactionMetadata] + Redaction state for sensitive hook output fields + """ + + _shorthand_property: ClassVar[str | None] = None + + hook_invocation_id: str = field(default="") + hook_type: str = field(default="") + scope: HookEndScope | None = None + success: bool = field(default=False) + output: dict[str, Any] | None = None + duration_ms: float | None = None + error: str | None = None + redaction: RedactionMetadata | None = None + + @staticmethod + def load(data: Any, context: LoadContext | None = None) -> "HookEndPayload": + """Load a HookEndPayload instance. + Args: + data (Any): The data to load the instance from. + context (Optional[LoadContext]): Optional context with pre/post processing callbacks. + Returns: + HookEndPayload: The loaded HookEndPayload instance. + + """ + + if context is not None: + data = context.process_input(data) + + if not isinstance(data, dict): + raise ValueError(f"Invalid data for HookEndPayload: {data}") + + # create new instance + instance = HookEndPayload() + + if data is not None and "hookInvocationId" in data: + instance.hook_invocation_id = data["hookInvocationId"] + if data is not None and "hookType" in data: + instance.hook_type = data["hookType"] + if data is not None and "scope" in data: + instance.scope = data["scope"] + if data is not None and "success" in data: + instance.success = data["success"] + if data is not None and "output" in data: + instance.output = data["output"] + if data is not None and "durationMs" in data: + instance.duration_ms = data["durationMs"] + if data is not None and "error" in data: + instance.error = data["error"] + if data is not None and "redaction" in data: + instance.redaction = RedactionMetadata.load(data["redaction"], context) + if context is not None: + instance = context.process_output(instance) + return instance + + def save(self, context: SaveContext | None = None) -> dict[str, Any]: + """Save the HookEndPayload instance to a dictionary. + Args: + context (Optional[SaveContext]): Optional context with pre/post processing callbacks. + Returns: + dict[str, Any]: The dictionary representation of this instance. + + """ + obj = self + if context is not None: + obj = context.process_object(obj) + + result: dict[str, Any] = {} + + if obj.hook_invocation_id is not None: + result["hookInvocationId"] = obj.hook_invocation_id + if obj.hook_type is not None: + result["hookType"] = obj.hook_type + if obj.scope is not None: + result["scope"] = obj.scope + if obj.success is not None: + result["success"] = obj.success + if obj.output is not None: + result["output"] = obj.output + if obj.duration_ms is not None: + result["durationMs"] = obj.duration_ms + if obj.error is not None: + result["error"] = obj.error + if obj.redaction is not None: + result["redaction"] = obj.redaction.save(context) + + if context is not None: + result = context.process_dict(result) + return result + + def to_yaml(self, context: SaveContext | None = None) -> str: + """Convert the HookEndPayload instance to a YAML string. + Args: + context (Optional[SaveContext]): Optional context with pre/post processing callbacks. + Returns: + str: The YAML string representation of this instance. + + """ + if context is None: + context = SaveContext() + return context.to_yaml(self.save(context)) + + def to_json(self, context: SaveContext | None = None, indent: int = 2) -> str: + """Convert the HookEndPayload instance to a JSON string. + Args: + context (Optional[SaveContext]): Optional context with pre/post processing callbacks. + indent (int): Number of spaces for indentation. Defaults to 2. + Returns: + str: The JSON string representation of this instance. + + """ + if context is None: + context = SaveContext() + return context.to_json(self.save(context), indent) diff --git a/runtime/python/prompty/prompty/model/events/_HookStartPayload.py b/runtime/python/prompty/prompty/model/events/_HookStartPayload.py new file mode 100644 index 00000000..b1e6171b --- /dev/null +++ b/runtime/python/prompty/prompty/model/events/_HookStartPayload.py @@ -0,0 +1,129 @@ +# +########################################## +# WARNING: This is an auto-generated file. +# DO NOT EDIT THIS FILE DIRECTLY +# ANY EDITS WILL BE LOST +########################################## + +from dataclasses import dataclass, field +from typing import Any, ClassVar, Literal + +from .._context import LoadContext, SaveContext +from ._RedactionMetadata import RedactionMetadata + +HookStartScope = Literal["turn", "session"] + + +@dataclass +class HookStartPayload: + """Payload for "hook_start" events — a host lifecycle hook is beginning. + + Attributes + ---------- + hook_invocation_id : str + Stable hook invocation identifier + hook_type : str + Host-defined hook type + scope : Optional[str] + Whether the hook is scoped to a turn or the outer session + input : Optional[dict[str, Any]] + Hook input after host-side sanitization + redaction : Optional[RedactionMetadata] + Redaction state for sensitive hook input fields + """ + + _shorthand_property: ClassVar[str | None] = None + + hook_invocation_id: str = field(default="") + hook_type: str = field(default="") + scope: HookStartScope | None = None + input: dict[str, Any] | None = None + redaction: RedactionMetadata | None = None + + @staticmethod + def load(data: Any, context: LoadContext | None = None) -> "HookStartPayload": + """Load a HookStartPayload instance. + Args: + data (Any): The data to load the instance from. + context (Optional[LoadContext]): Optional context with pre/post processing callbacks. + Returns: + HookStartPayload: The loaded HookStartPayload instance. + + """ + + if context is not None: + data = context.process_input(data) + + if not isinstance(data, dict): + raise ValueError(f"Invalid data for HookStartPayload: {data}") + + # create new instance + instance = HookStartPayload() + + if data is not None and "hookInvocationId" in data: + instance.hook_invocation_id = data["hookInvocationId"] + if data is not None and "hookType" in data: + instance.hook_type = data["hookType"] + if data is not None and "scope" in data: + instance.scope = data["scope"] + if data is not None and "input" in data: + instance.input = data["input"] + if data is not None and "redaction" in data: + instance.redaction = RedactionMetadata.load(data["redaction"], context) + if context is not None: + instance = context.process_output(instance) + return instance + + def save(self, context: SaveContext | None = None) -> dict[str, Any]: + """Save the HookStartPayload instance to a dictionary. + Args: + context (Optional[SaveContext]): Optional context with pre/post processing callbacks. + Returns: + dict[str, Any]: The dictionary representation of this instance. + + """ + obj = self + if context is not None: + obj = context.process_object(obj) + + result: dict[str, Any] = {} + + if obj.hook_invocation_id is not None: + result["hookInvocationId"] = obj.hook_invocation_id + if obj.hook_type is not None: + result["hookType"] = obj.hook_type + if obj.scope is not None: + result["scope"] = obj.scope + if obj.input is not None: + result["input"] = obj.input + if obj.redaction is not None: + result["redaction"] = obj.redaction.save(context) + + if context is not None: + result = context.process_dict(result) + return result + + def to_yaml(self, context: SaveContext | None = None) -> str: + """Convert the HookStartPayload instance to a YAML string. + Args: + context (Optional[SaveContext]): Optional context with pre/post processing callbacks. + Returns: + str: The YAML string representation of this instance. + + """ + if context is None: + context = SaveContext() + return context.to_yaml(self.save(context)) + + def to_json(self, context: SaveContext | None = None, indent: int = 2) -> str: + """Convert the HookStartPayload instance to a JSON string. + Args: + context (Optional[SaveContext]): Optional context with pre/post processing callbacks. + indent (int): Number of spaces for indentation. Defaults to 2. + Returns: + str: The JSON string representation of this instance. + + """ + if context is None: + context = SaveContext() + return context.to_json(self.save(context), indent) diff --git a/runtime/python/prompty/prompty/model/events/_HostToolRequest.py b/runtime/python/prompty/prompty/model/events/_HostToolRequest.py new file mode 100644 index 00000000..24f9c717 --- /dev/null +++ b/runtime/python/prompty/prompty/model/events/_HostToolRequest.py @@ -0,0 +1,126 @@ +# +########################################## +# WARNING: This is an auto-generated file. +# DO NOT EDIT THIS FILE DIRECTLY +# ANY EDITS WILL BE LOST +########################################## + +from dataclasses import dataclass, field +from typing import Any, ClassVar + +from .._context import LoadContext, SaveContext + + +@dataclass +class HostToolRequest: + """Request passed to a host tool executor after policy and permission checks. + + Attributes + ---------- + request_id : Optional[str] + Stable host execution request identifier + tool_call_id : Optional[str] + Associated model tool call identifier, when available + tool_name : str + Name of the host tool being executed + arguments : Optional[dict[str, Any]] + Tool arguments after host-side sanitization + working_directory : Optional[str] + Working directory or execution scope for the tool + """ + + _shorthand_property: ClassVar[str | None] = None + + request_id: str | None = None + tool_call_id: str | None = None + tool_name: str = field(default="") + arguments: dict[str, Any] | None = None + working_directory: str | None = None + + @staticmethod + def load(data: Any, context: LoadContext | None = None) -> "HostToolRequest": + """Load a HostToolRequest instance. + Args: + data (Any): The data to load the instance from. + context (Optional[LoadContext]): Optional context with pre/post processing callbacks. + Returns: + HostToolRequest: The loaded HostToolRequest instance. + + """ + + if context is not None: + data = context.process_input(data) + + if not isinstance(data, dict): + raise ValueError(f"Invalid data for HostToolRequest: {data}") + + # create new instance + instance = HostToolRequest() + + if data is not None and "requestId" in data: + instance.request_id = data["requestId"] + if data is not None and "toolCallId" in data: + instance.tool_call_id = data["toolCallId"] + if data is not None and "toolName" in data: + instance.tool_name = data["toolName"] + if data is not None and "arguments" in data: + instance.arguments = data["arguments"] + if data is not None and "workingDirectory" in data: + instance.working_directory = data["workingDirectory"] + if context is not None: + instance = context.process_output(instance) + return instance + + def save(self, context: SaveContext | None = None) -> dict[str, Any]: + """Save the HostToolRequest instance to a dictionary. + Args: + context (Optional[SaveContext]): Optional context with pre/post processing callbacks. + Returns: + dict[str, Any]: The dictionary representation of this instance. + + """ + obj = self + if context is not None: + obj = context.process_object(obj) + + result: dict[str, Any] = {} + + if obj.request_id is not None: + result["requestId"] = obj.request_id + if obj.tool_call_id is not None: + result["toolCallId"] = obj.tool_call_id + if obj.tool_name is not None: + result["toolName"] = obj.tool_name + if obj.arguments is not None: + result["arguments"] = obj.arguments + if obj.working_directory is not None: + result["workingDirectory"] = obj.working_directory + + if context is not None: + result = context.process_dict(result) + return result + + def to_yaml(self, context: SaveContext | None = None) -> str: + """Convert the HostToolRequest instance to a YAML string. + Args: + context (Optional[SaveContext]): Optional context with pre/post processing callbacks. + Returns: + str: The YAML string representation of this instance. + + """ + if context is None: + context = SaveContext() + return context.to_yaml(self.save(context)) + + def to_json(self, context: SaveContext | None = None, indent: int = 2) -> str: + """Convert the HostToolRequest instance to a JSON string. + Args: + context (Optional[SaveContext]): Optional context with pre/post processing callbacks. + indent (int): Number of spaces for indentation. Defaults to 2. + Returns: + str: The JSON string representation of this instance. + + """ + if context is None: + context = SaveContext() + return context.to_json(self.save(context), indent) diff --git a/runtime/python/prompty/prompty/model/events/_HostToolResult.py b/runtime/python/prompty/prompty/model/events/_HostToolResult.py new file mode 100644 index 00000000..c54872f5 --- /dev/null +++ b/runtime/python/prompty/prompty/model/events/_HostToolResult.py @@ -0,0 +1,154 @@ +# +########################################## +# WARNING: This is an auto-generated file. +# DO NOT EDIT THIS FILE DIRECTLY +# ANY EDITS WILL BE LOST +########################################## + +from dataclasses import dataclass, field +from typing import Any, ClassVar + +from .._context import LoadContext, SaveContext + + +@dataclass +class HostToolResult: + """Result returned by a host tool executor. + + Attributes + ---------- + request_id : Optional[str] + Stable host execution request identifier + tool_call_id : Optional[str] + Associated model tool call identifier, when available + tool_name : str + Name of the host tool that executed + success : bool + Whether the host execution completed successfully + result : Optional[Any] + Host-normalized execution result + exit_code : Optional[int] + Process or host exit code, when applicable + duration_ms : Optional[float] + Tool execution duration in milliseconds + error_kind : Optional[str] + Machine-readable error category when success is false + telemetry : Optional[dict[str, Any]] + Host-specific telemetry for the execution + """ + + _shorthand_property: ClassVar[str | None] = None + + request_id: str | None = None + tool_call_id: str | None = None + tool_name: str = field(default="") + success: bool = field(default=False) + result: Any | None = None + exit_code: int | None = None + duration_ms: float | None = None + error_kind: str | None = None + telemetry: dict[str, Any] | None = None + + @staticmethod + def load(data: Any, context: LoadContext | None = None) -> "HostToolResult": + """Load a HostToolResult instance. + Args: + data (Any): The data to load the instance from. + context (Optional[LoadContext]): Optional context with pre/post processing callbacks. + Returns: + HostToolResult: The loaded HostToolResult instance. + + """ + + if context is not None: + data = context.process_input(data) + + if not isinstance(data, dict): + raise ValueError(f"Invalid data for HostToolResult: {data}") + + # create new instance + instance = HostToolResult() + + if data is not None and "requestId" in data: + instance.request_id = data["requestId"] + if data is not None and "toolCallId" in data: + instance.tool_call_id = data["toolCallId"] + if data is not None and "toolName" in data: + instance.tool_name = data["toolName"] + if data is not None and "success" in data: + instance.success = data["success"] + if data is not None and "result" in data: + instance.result = data["result"] + if data is not None and "exitCode" in data: + instance.exit_code = data["exitCode"] + if data is not None and "durationMs" in data: + instance.duration_ms = data["durationMs"] + if data is not None and "errorKind" in data: + instance.error_kind = data["errorKind"] + if data is not None and "telemetry" in data: + instance.telemetry = data["telemetry"] + if context is not None: + instance = context.process_output(instance) + return instance + + def save(self, context: SaveContext | None = None) -> dict[str, Any]: + """Save the HostToolResult instance to a dictionary. + Args: + context (Optional[SaveContext]): Optional context with pre/post processing callbacks. + Returns: + dict[str, Any]: The dictionary representation of this instance. + + """ + obj = self + if context is not None: + obj = context.process_object(obj) + + result: dict[str, Any] = {} + + if obj.request_id is not None: + result["requestId"] = obj.request_id + if obj.tool_call_id is not None: + result["toolCallId"] = obj.tool_call_id + if obj.tool_name is not None: + result["toolName"] = obj.tool_name + if obj.success is not None: + result["success"] = obj.success + if obj.result is not None: + result["result"] = obj.result + if obj.exit_code is not None: + result["exitCode"] = obj.exit_code + if obj.duration_ms is not None: + result["durationMs"] = obj.duration_ms + if obj.error_kind is not None: + result["errorKind"] = obj.error_kind + if obj.telemetry is not None: + result["telemetry"] = obj.telemetry + + if context is not None: + result = context.process_dict(result) + return result + + def to_yaml(self, context: SaveContext | None = None) -> str: + """Convert the HostToolResult instance to a YAML string. + Args: + context (Optional[SaveContext]): Optional context with pre/post processing callbacks. + Returns: + str: The YAML string representation of this instance. + + """ + if context is None: + context = SaveContext() + return context.to_yaml(self.save(context)) + + def to_json(self, context: SaveContext | None = None, indent: int = 2) -> str: + """Convert the HostToolResult instance to a JSON string. + Args: + context (Optional[SaveContext]): Optional context with pre/post processing callbacks. + indent (int): Number of spaces for indentation. Defaults to 2. + Returns: + str: The JSON string representation of this instance. + + """ + if context is None: + context = SaveContext() + return context.to_json(self.save(context), indent) diff --git a/runtime/python/prompty/prompty/model/events/_LlmCompletePayload.py b/runtime/python/prompty/prompty/model/events/_LlmCompletePayload.py new file mode 100644 index 00000000..d59608f4 --- /dev/null +++ b/runtime/python/prompty/prompty/model/events/_LlmCompletePayload.py @@ -0,0 +1,120 @@ +# +########################################## +# WARNING: This is an auto-generated file. +# DO NOT EDIT THIS FILE DIRECTLY +# ANY EDITS WILL BE LOST +########################################## + +from dataclasses import dataclass +from typing import Any, ClassVar + +from .._context import LoadContext, SaveContext +from ..model._TokenUsage import TokenUsage + + +@dataclass +class LlmCompletePayload: + """Payload for "llm_complete" events — an LLM request completed. + + Attributes + ---------- + request_id : Optional[str] + Provider request identifier, when supplied by the SDK/API + service_request_id : Optional[str] + Service request identifier, when supplied by the SDK/API + usage : Optional[TokenUsage] + Token usage reported by the provider + duration_ms : Optional[float] + LLM call duration in milliseconds + """ + + _shorthand_property: ClassVar[str | None] = None + + request_id: str | None = None + service_request_id: str | None = None + usage: TokenUsage | None = None + duration_ms: float | None = None + + @staticmethod + def load(data: Any, context: LoadContext | None = None) -> "LlmCompletePayload": + """Load a LlmCompletePayload instance. + Args: + data (Any): The data to load the instance from. + context (Optional[LoadContext]): Optional context with pre/post processing callbacks. + Returns: + LlmCompletePayload: The loaded LlmCompletePayload instance. + + """ + + if context is not None: + data = context.process_input(data) + + if not isinstance(data, dict): + raise ValueError(f"Invalid data for LlmCompletePayload: {data}") + + # create new instance + instance = LlmCompletePayload() + + if data is not None and "requestId" in data: + instance.request_id = data["requestId"] + if data is not None and "serviceRequestId" in data: + instance.service_request_id = data["serviceRequestId"] + if data is not None and "usage" in data: + instance.usage = TokenUsage.load(data["usage"], context) + if data is not None and "durationMs" in data: + instance.duration_ms = data["durationMs"] + if context is not None: + instance = context.process_output(instance) + return instance + + def save(self, context: SaveContext | None = None) -> dict[str, Any]: + """Save the LlmCompletePayload instance to a dictionary. + Args: + context (Optional[SaveContext]): Optional context with pre/post processing callbacks. + Returns: + dict[str, Any]: The dictionary representation of this instance. + + """ + obj = self + if context is not None: + obj = context.process_object(obj) + + result: dict[str, Any] = {} + + if obj.request_id is not None: + result["requestId"] = obj.request_id + if obj.service_request_id is not None: + result["serviceRequestId"] = obj.service_request_id + if obj.usage is not None: + result["usage"] = obj.usage.save(context) + if obj.duration_ms is not None: + result["durationMs"] = obj.duration_ms + + if context is not None: + result = context.process_dict(result) + return result + + def to_yaml(self, context: SaveContext | None = None) -> str: + """Convert the LlmCompletePayload instance to a YAML string. + Args: + context (Optional[SaveContext]): Optional context with pre/post processing callbacks. + Returns: + str: The YAML string representation of this instance. + + """ + if context is None: + context = SaveContext() + return context.to_yaml(self.save(context)) + + def to_json(self, context: SaveContext | None = None, indent: int = 2) -> str: + """Convert the LlmCompletePayload instance to a JSON string. + Args: + context (Optional[SaveContext]): Optional context with pre/post processing callbacks. + indent (int): Number of spaces for indentation. Defaults to 2. + Returns: + str: The JSON string representation of this instance. + + """ + if context is None: + context = SaveContext() + return context.to_json(self.save(context), indent) diff --git a/runtime/python/prompty/prompty/model/events/_LlmStartPayload.py b/runtime/python/prompty/prompty/model/events/_LlmStartPayload.py new file mode 100644 index 00000000..719dd935 --- /dev/null +++ b/runtime/python/prompty/prompty/model/events/_LlmStartPayload.py @@ -0,0 +1,119 @@ +# +########################################## +# WARNING: This is an auto-generated file. +# DO NOT EDIT THIS FILE DIRECTLY +# ANY EDITS WILL BE LOST +########################################## + +from dataclasses import dataclass +from typing import Any, ClassVar + +from .._context import LoadContext, SaveContext + + +@dataclass +class LlmStartPayload: + """Payload for "llm_start" events — an LLM request is about to be sent. + + Attributes + ---------- + provider : Optional[str] + Provider identifier used for the request + model_id : Optional[str] + Model or deployment identifier used for the request + message_count : Optional[int] + Number of messages sent to the provider + attempt : Optional[int] + Retry attempt number, zero for the initial attempt + """ + + _shorthand_property: ClassVar[str | None] = None + + provider: str | None = None + model_id: str | None = None + message_count: int | None = None + attempt: int | None = None + + @staticmethod + def load(data: Any, context: LoadContext | None = None) -> "LlmStartPayload": + """Load a LlmStartPayload instance. + Args: + data (Any): The data to load the instance from. + context (Optional[LoadContext]): Optional context with pre/post processing callbacks. + Returns: + LlmStartPayload: The loaded LlmStartPayload instance. + + """ + + if context is not None: + data = context.process_input(data) + + if not isinstance(data, dict): + raise ValueError(f"Invalid data for LlmStartPayload: {data}") + + # create new instance + instance = LlmStartPayload() + + if data is not None and "provider" in data: + instance.provider = data["provider"] + if data is not None and "modelId" in data: + instance.model_id = data["modelId"] + if data is not None and "messageCount" in data: + instance.message_count = data["messageCount"] + if data is not None and "attempt" in data: + instance.attempt = data["attempt"] + if context is not None: + instance = context.process_output(instance) + return instance + + def save(self, context: SaveContext | None = None) -> dict[str, Any]: + """Save the LlmStartPayload instance to a dictionary. + Args: + context (Optional[SaveContext]): Optional context with pre/post processing callbacks. + Returns: + dict[str, Any]: The dictionary representation of this instance. + + """ + obj = self + if context is not None: + obj = context.process_object(obj) + + result: dict[str, Any] = {} + + if obj.provider is not None: + result["provider"] = obj.provider + if obj.model_id is not None: + result["modelId"] = obj.model_id + if obj.message_count is not None: + result["messageCount"] = obj.message_count + if obj.attempt is not None: + result["attempt"] = obj.attempt + + if context is not None: + result = context.process_dict(result) + return result + + def to_yaml(self, context: SaveContext | None = None) -> str: + """Convert the LlmStartPayload instance to a YAML string. + Args: + context (Optional[SaveContext]): Optional context with pre/post processing callbacks. + Returns: + str: The YAML string representation of this instance. + + """ + if context is None: + context = SaveContext() + return context.to_yaml(self.save(context)) + + def to_json(self, context: SaveContext | None = None, indent: int = 2) -> str: + """Convert the LlmStartPayload instance to a JSON string. + Args: + context (Optional[SaveContext]): Optional context with pre/post processing callbacks. + indent (int): Number of spaces for indentation. Defaults to 2. + Returns: + str: The JSON string representation of this instance. + + """ + if context is None: + context = SaveContext() + return context.to_json(self.save(context), indent) diff --git a/runtime/python/prompty/prompty/model/events/_MessagesUpdatedPayload.py b/runtime/python/prompty/prompty/model/events/_MessagesUpdatedPayload.py index f3de4c6e..42f8d612 100644 --- a/runtime/python/prompty/prompty/model/events/_MessagesUpdatedPayload.py +++ b/runtime/python/prompty/prompty/model/events/_MessagesUpdatedPayload.py @@ -1,3 +1,4 @@ +# ########################################## # WARNING: This is an auto-generated file. # DO NOT EDIT THIS FILE DIRECTLY @@ -17,13 +18,22 @@ class MessagesUpdatedPayload: Attributes ---------- - messages : list[Message] + messages : Optional[list[Message]] The current full message list after the update + reason : Optional[str] + Why the message list changed + appended : Optional[list[Message]] + Messages appended by this update, when available + removed : Optional[int] + Number of messages removed by this update, when available """ _shorthand_property: ClassVar[str | None] = None messages: list[Message] = field(default_factory=list) + reason: str | None = None + appended: list[Message] = field(default_factory=list) + removed: int | None = None @staticmethod def load(data: Any, context: LoadContext | None = None) -> "MessagesUpdatedPayload": @@ -47,6 +57,12 @@ def load(data: Any, context: LoadContext | None = None) -> "MessagesUpdatedPaylo if data is not None and "messages" in data: instance.messages = MessagesUpdatedPayload.load_messages(data["messages"], context) + if data is not None and "reason" in data: + instance.reason = data["reason"] + if data is not None and "appended" in data: + instance.appended = MessagesUpdatedPayload.load_appended(data["appended"], context) + if data is not None and "removed" in data: + instance.removed = data["removed"] if context is not None: instance = context.process_output(instance) return instance @@ -74,6 +90,29 @@ def save_messages(items: list[Message], context: SaveContext | None) -> dict[str # This type doesn't have a 'name' property, so always use array format return [item.save(context) for item in items] + @staticmethod + def load_appended(data: dict | list, context: LoadContext | None) -> list[Message]: + if isinstance(data, dict): + # convert simple named appended to list of Message + result = [] + for k, v in data.items(): + if isinstance(v, dict): + # value is an object, spread its properties + result.append({"name": k, **v}) + else: + # value is a scalar, use it as the primary property + result.append({"name": k, "role": v}) + data = result + return [Message.load(item, context) for item in data] + + @staticmethod + def save_appended(items: list[Message], context: SaveContext | None) -> dict[str, Any] | list[dict[str, Any]]: + if context is None: + context = SaveContext() + + # This type doesn't have a 'name' property, so always use array format + return [item.save(context) for item in items] + def save(self, context: SaveContext | None = None) -> dict[str, Any]: """Save the MessagesUpdatedPayload instance to a dictionary. Args: @@ -90,6 +129,12 @@ def save(self, context: SaveContext | None = None) -> dict[str, Any]: if obj.messages is not None: result["messages"] = MessagesUpdatedPayload.save_messages(obj.messages, context) + if obj.reason is not None: + result["reason"] = obj.reason + if obj.appended is not None: + result["appended"] = MessagesUpdatedPayload.save_appended(obj.appended, context) + if obj.removed is not None: + result["removed"] = obj.removed if context is not None: result = context.process_dict(result) diff --git a/runtime/python/prompty/prompty/model/events/_PermissionCompletedPayload.py b/runtime/python/prompty/prompty/model/events/_PermissionCompletedPayload.py new file mode 100644 index 00000000..9215718a --- /dev/null +++ b/runtime/python/prompty/prompty/model/events/_PermissionCompletedPayload.py @@ -0,0 +1,141 @@ +# +########################################## +# WARNING: This is an auto-generated file. +# DO NOT EDIT THIS FILE DIRECTLY +# ANY EDITS WILL BE LOST +########################################## + +from dataclasses import dataclass, field +from typing import Any, ClassVar + +from .._context import LoadContext, SaveContext +from ._RedactionMetadata import RedactionMetadata + + +@dataclass +class PermissionCompletedPayload: + """Payload for permission completion events — an approval decision was made. + + Attributes + ---------- + request_id : Optional[str] + Stable permission request identifier + tool_call_id : Optional[str] + Associated tool call identifier, when the permission gated a tool call + permission : str + Permission/action name that was decided + approved : bool + Whether the requested permission was approved + reason : Optional[str] + Decision reason, if available + result : Optional[dict[str, Any]] + Host-specific decision result, such as a durable approval token or denial details + redaction : Optional[RedactionMetadata] + Redaction state for sensitive decision fields + """ + + _shorthand_property: ClassVar[str | None] = None + + request_id: str | None = None + tool_call_id: str | None = None + permission: str = field(default="") + approved: bool = field(default=False) + reason: str | None = None + result: dict[str, Any] | None = None + redaction: RedactionMetadata | None = None + + @staticmethod + def load(data: Any, context: LoadContext | None = None) -> "PermissionCompletedPayload": + """Load a PermissionCompletedPayload instance. + Args: + data (Any): The data to load the instance from. + context (Optional[LoadContext]): Optional context with pre/post processing callbacks. + Returns: + PermissionCompletedPayload: The loaded PermissionCompletedPayload instance. + + """ + + if context is not None: + data = context.process_input(data) + + if not isinstance(data, dict): + raise ValueError(f"Invalid data for PermissionCompletedPayload: {data}") + + # create new instance + instance = PermissionCompletedPayload() + + if data is not None and "requestId" in data: + instance.request_id = data["requestId"] + if data is not None and "toolCallId" in data: + instance.tool_call_id = data["toolCallId"] + if data is not None and "permission" in data: + instance.permission = data["permission"] + if data is not None and "approved" in data: + instance.approved = data["approved"] + if data is not None and "reason" in data: + instance.reason = data["reason"] + if data is not None and "result" in data: + instance.result = data["result"] + if data is not None and "redaction" in data: + instance.redaction = RedactionMetadata.load(data["redaction"], context) + if context is not None: + instance = context.process_output(instance) + return instance + + def save(self, context: SaveContext | None = None) -> dict[str, Any]: + """Save the PermissionCompletedPayload instance to a dictionary. + Args: + context (Optional[SaveContext]): Optional context with pre/post processing callbacks. + Returns: + dict[str, Any]: The dictionary representation of this instance. + + """ + obj = self + if context is not None: + obj = context.process_object(obj) + + result: dict[str, Any] = {} + + if obj.request_id is not None: + result["requestId"] = obj.request_id + if obj.tool_call_id is not None: + result["toolCallId"] = obj.tool_call_id + if obj.permission is not None: + result["permission"] = obj.permission + if obj.approved is not None: + result["approved"] = obj.approved + if obj.reason is not None: + result["reason"] = obj.reason + if obj.result is not None: + result["result"] = obj.result + if obj.redaction is not None: + result["redaction"] = obj.redaction.save(context) + + if context is not None: + result = context.process_dict(result) + return result + + def to_yaml(self, context: SaveContext | None = None) -> str: + """Convert the PermissionCompletedPayload instance to a YAML string. + Args: + context (Optional[SaveContext]): Optional context with pre/post processing callbacks. + Returns: + str: The YAML string representation of this instance. + + """ + if context is None: + context = SaveContext() + return context.to_yaml(self.save(context)) + + def to_json(self, context: SaveContext | None = None, indent: int = 2) -> str: + """Convert the PermissionCompletedPayload instance to a JSON string. + Args: + context (Optional[SaveContext]): Optional context with pre/post processing callbacks. + indent (int): Number of spaces for indentation. Defaults to 2. + Returns: + str: The JSON string representation of this instance. + + """ + if context is None: + context = SaveContext() + return context.to_json(self.save(context), indent) diff --git a/runtime/python/prompty/prompty/model/events/_PermissionDecision.py b/runtime/python/prompty/prompty/model/events/_PermissionDecision.py new file mode 100644 index 00000000..aaa61632 --- /dev/null +++ b/runtime/python/prompty/prompty/model/events/_PermissionDecision.py @@ -0,0 +1,133 @@ +# +########################################## +# WARNING: This is an auto-generated file. +# DO NOT EDIT THIS FILE DIRECTLY +# ANY EDITS WILL BE LOST +########################################## + +from dataclasses import dataclass, field +from typing import Any, ClassVar + +from .._context import LoadContext, SaveContext + + +@dataclass +class PermissionDecision: + """Decision returned by a permission resolver. + + Attributes + ---------- + request_id : Optional[str] + Stable permission request identifier + tool_call_id : Optional[str] + Associated tool call identifier, when the permission gated a tool call + permission : str + Permission/action name that was decided + approved : bool + Whether the requested permission was approved + reason : Optional[str] + Decision reason, if available + result : Optional[dict[str, Any]] + Host-specific decision result, such as a durable approval token or denial details + """ + + _shorthand_property: ClassVar[str | None] = None + + request_id: str | None = None + tool_call_id: str | None = None + permission: str = field(default="") + approved: bool = field(default=False) + reason: str | None = None + result: dict[str, Any] | None = None + + @staticmethod + def load(data: Any, context: LoadContext | None = None) -> "PermissionDecision": + """Load a PermissionDecision instance. + Args: + data (Any): The data to load the instance from. + context (Optional[LoadContext]): Optional context with pre/post processing callbacks. + Returns: + PermissionDecision: The loaded PermissionDecision instance. + + """ + + if context is not None: + data = context.process_input(data) + + if not isinstance(data, dict): + raise ValueError(f"Invalid data for PermissionDecision: {data}") + + # create new instance + instance = PermissionDecision() + + if data is not None and "requestId" in data: + instance.request_id = data["requestId"] + if data is not None and "toolCallId" in data: + instance.tool_call_id = data["toolCallId"] + if data is not None and "permission" in data: + instance.permission = data["permission"] + if data is not None and "approved" in data: + instance.approved = data["approved"] + if data is not None and "reason" in data: + instance.reason = data["reason"] + if data is not None and "result" in data: + instance.result = data["result"] + if context is not None: + instance = context.process_output(instance) + return instance + + def save(self, context: SaveContext | None = None) -> dict[str, Any]: + """Save the PermissionDecision instance to a dictionary. + Args: + context (Optional[SaveContext]): Optional context with pre/post processing callbacks. + Returns: + dict[str, Any]: The dictionary representation of this instance. + + """ + obj = self + if context is not None: + obj = context.process_object(obj) + + result: dict[str, Any] = {} + + if obj.request_id is not None: + result["requestId"] = obj.request_id + if obj.tool_call_id is not None: + result["toolCallId"] = obj.tool_call_id + if obj.permission is not None: + result["permission"] = obj.permission + if obj.approved is not None: + result["approved"] = obj.approved + if obj.reason is not None: + result["reason"] = obj.reason + if obj.result is not None: + result["result"] = obj.result + + if context is not None: + result = context.process_dict(result) + return result + + def to_yaml(self, context: SaveContext | None = None) -> str: + """Convert the PermissionDecision instance to a YAML string. + Args: + context (Optional[SaveContext]): Optional context with pre/post processing callbacks. + Returns: + str: The YAML string representation of this instance. + + """ + if context is None: + context = SaveContext() + return context.to_yaml(self.save(context)) + + def to_json(self, context: SaveContext | None = None, indent: int = 2) -> str: + """Convert the PermissionDecision instance to a JSON string. + Args: + context (Optional[SaveContext]): Optional context with pre/post processing callbacks. + indent (int): Number of spaces for indentation. Defaults to 2. + Returns: + str: The JSON string representation of this instance. + + """ + if context is None: + context = SaveContext() + return context.to_json(self.save(context), indent) diff --git a/runtime/python/prompty/prompty/model/events/_PermissionRequest.py b/runtime/python/prompty/prompty/model/events/_PermissionRequest.py new file mode 100644 index 00000000..85963dc0 --- /dev/null +++ b/runtime/python/prompty/prompty/model/events/_PermissionRequest.py @@ -0,0 +1,141 @@ +# +########################################## +# WARNING: This is an auto-generated file. +# DO NOT EDIT THIS FILE DIRECTLY +# ANY EDITS WILL BE LOST +########################################## + +from dataclasses import dataclass, field +from typing import Any, ClassVar + +from .._context import LoadContext, SaveContext + + +@dataclass +class PermissionRequest: + """Request passed to a permission resolver. This is the live protocol shape; the + event payloads above can include trace-only metadata such as redaction state. + + Attributes + ---------- + request_id : Optional[str] + Stable permission request identifier + tool_call_id : Optional[str] + Associated tool call identifier, when the permission gates a tool call + permission : str + Permission/action name being requested + target : Optional[str] + Resource or tool the permission applies to + details : Optional[dict[str, Any]] + Additional host-specific permission details + prompt_request : Optional[str] + Human-readable prompt or rationale that can be shown to an approval UI + policy : Optional[dict[str, Any]] + Policy metadata used to evaluate or explain the permission request + """ + + _shorthand_property: ClassVar[str | None] = None + + request_id: str | None = None + tool_call_id: str | None = None + permission: str = field(default="") + target: str | None = None + details: dict[str, Any] | None = None + prompt_request: str | None = None + policy: dict[str, Any] | None = None + + @staticmethod + def load(data: Any, context: LoadContext | None = None) -> "PermissionRequest": + """Load a PermissionRequest instance. + Args: + data (Any): The data to load the instance from. + context (Optional[LoadContext]): Optional context with pre/post processing callbacks. + Returns: + PermissionRequest: The loaded PermissionRequest instance. + + """ + + if context is not None: + data = context.process_input(data) + + if not isinstance(data, dict): + raise ValueError(f"Invalid data for PermissionRequest: {data}") + + # create new instance + instance = PermissionRequest() + + if data is not None and "requestId" in data: + instance.request_id = data["requestId"] + if data is not None and "toolCallId" in data: + instance.tool_call_id = data["toolCallId"] + if data is not None and "permission" in data: + instance.permission = data["permission"] + if data is not None and "target" in data: + instance.target = data["target"] + if data is not None and "details" in data: + instance.details = data["details"] + if data is not None and "promptRequest" in data: + instance.prompt_request = data["promptRequest"] + if data is not None and "policy" in data: + instance.policy = data["policy"] + if context is not None: + instance = context.process_output(instance) + return instance + + def save(self, context: SaveContext | None = None) -> dict[str, Any]: + """Save the PermissionRequest instance to a dictionary. + Args: + context (Optional[SaveContext]): Optional context with pre/post processing callbacks. + Returns: + dict[str, Any]: The dictionary representation of this instance. + + """ + obj = self + if context is not None: + obj = context.process_object(obj) + + result: dict[str, Any] = {} + + if obj.request_id is not None: + result["requestId"] = obj.request_id + if obj.tool_call_id is not None: + result["toolCallId"] = obj.tool_call_id + if obj.permission is not None: + result["permission"] = obj.permission + if obj.target is not None: + result["target"] = obj.target + if obj.details is not None: + result["details"] = obj.details + if obj.prompt_request is not None: + result["promptRequest"] = obj.prompt_request + if obj.policy is not None: + result["policy"] = obj.policy + + if context is not None: + result = context.process_dict(result) + return result + + def to_yaml(self, context: SaveContext | None = None) -> str: + """Convert the PermissionRequest instance to a YAML string. + Args: + context (Optional[SaveContext]): Optional context with pre/post processing callbacks. + Returns: + str: The YAML string representation of this instance. + + """ + if context is None: + context = SaveContext() + return context.to_yaml(self.save(context)) + + def to_json(self, context: SaveContext | None = None, indent: int = 2) -> str: + """Convert the PermissionRequest instance to a JSON string. + Args: + context (Optional[SaveContext]): Optional context with pre/post processing callbacks. + indent (int): Number of spaces for indentation. Defaults to 2. + Returns: + str: The JSON string representation of this instance. + + """ + if context is None: + context = SaveContext() + return context.to_json(self.save(context), indent) diff --git a/runtime/python/prompty/prompty/model/events/_PermissionRequestedPayload.py b/runtime/python/prompty/prompty/model/events/_PermissionRequestedPayload.py new file mode 100644 index 00000000..6125d53b --- /dev/null +++ b/runtime/python/prompty/prompty/model/events/_PermissionRequestedPayload.py @@ -0,0 +1,148 @@ +# +########################################## +# WARNING: This is an auto-generated file. +# DO NOT EDIT THIS FILE DIRECTLY +# ANY EDITS WILL BE LOST +########################################## + +from dataclasses import dataclass, field +from typing import Any, ClassVar + +from .._context import LoadContext, SaveContext +from ._RedactionMetadata import RedactionMetadata + + +@dataclass +class PermissionRequestedPayload: + """Payload for permission request events — a host is asked to approve an action. + + Attributes + ---------- + request_id : Optional[str] + Stable permission request identifier + tool_call_id : Optional[str] + Associated tool call identifier, when the permission gates a tool call + permission : str + Permission/action name being requested + target : Optional[str] + Resource or tool the permission applies to + details : Optional[dict[str, Any]] + Additional host-specific permission details + prompt_request : Optional[str] + Human-readable prompt or rationale that can be shown to an approval UI + policy : Optional[dict[str, Any]] + Policy metadata used to evaluate or explain the permission request + redaction : Optional[RedactionMetadata] + Redaction state for sensitive request fields + """ + + _shorthand_property: ClassVar[str | None] = None + + request_id: str | None = None + tool_call_id: str | None = None + permission: str = field(default="") + target: str | None = None + details: dict[str, Any] | None = None + prompt_request: str | None = None + policy: dict[str, Any] | None = None + redaction: RedactionMetadata | None = None + + @staticmethod + def load(data: Any, context: LoadContext | None = None) -> "PermissionRequestedPayload": + """Load a PermissionRequestedPayload instance. + Args: + data (Any): The data to load the instance from. + context (Optional[LoadContext]): Optional context with pre/post processing callbacks. + Returns: + PermissionRequestedPayload: The loaded PermissionRequestedPayload instance. + + """ + + if context is not None: + data = context.process_input(data) + + if not isinstance(data, dict): + raise ValueError(f"Invalid data for PermissionRequestedPayload: {data}") + + # create new instance + instance = PermissionRequestedPayload() + + if data is not None and "requestId" in data: + instance.request_id = data["requestId"] + if data is not None and "toolCallId" in data: + instance.tool_call_id = data["toolCallId"] + if data is not None and "permission" in data: + instance.permission = data["permission"] + if data is not None and "target" in data: + instance.target = data["target"] + if data is not None and "details" in data: + instance.details = data["details"] + if data is not None and "promptRequest" in data: + instance.prompt_request = data["promptRequest"] + if data is not None and "policy" in data: + instance.policy = data["policy"] + if data is not None and "redaction" in data: + instance.redaction = RedactionMetadata.load(data["redaction"], context) + if context is not None: + instance = context.process_output(instance) + return instance + + def save(self, context: SaveContext | None = None) -> dict[str, Any]: + """Save the PermissionRequestedPayload instance to a dictionary. + Args: + context (Optional[SaveContext]): Optional context with pre/post processing callbacks. + Returns: + dict[str, Any]: The dictionary representation of this instance. + + """ + obj = self + if context is not None: + obj = context.process_object(obj) + + result: dict[str, Any] = {} + + if obj.request_id is not None: + result["requestId"] = obj.request_id + if obj.tool_call_id is not None: + result["toolCallId"] = obj.tool_call_id + if obj.permission is not None: + result["permission"] = obj.permission + if obj.target is not None: + result["target"] = obj.target + if obj.details is not None: + result["details"] = obj.details + if obj.prompt_request is not None: + result["promptRequest"] = obj.prompt_request + if obj.policy is not None: + result["policy"] = obj.policy + if obj.redaction is not None: + result["redaction"] = obj.redaction.save(context) + + if context is not None: + result = context.process_dict(result) + return result + + def to_yaml(self, context: SaveContext | None = None) -> str: + """Convert the PermissionRequestedPayload instance to a YAML string. + Args: + context (Optional[SaveContext]): Optional context with pre/post processing callbacks. + Returns: + str: The YAML string representation of this instance. + + """ + if context is None: + context = SaveContext() + return context.to_yaml(self.save(context)) + + def to_json(self, context: SaveContext | None = None, indent: int = 2) -> str: + """Convert the PermissionRequestedPayload instance to a JSON string. + Args: + context (Optional[SaveContext]): Optional context with pre/post processing callbacks. + indent (int): Number of spaces for indentation. Defaults to 2. + Returns: + str: The JSON string representation of this instance. + + """ + if context is None: + context = SaveContext() + return context.to_json(self.save(context), indent) diff --git a/runtime/python/prompty/prompty/model/events/_RedactedField.py b/runtime/python/prompty/prompty/model/events/_RedactedField.py new file mode 100644 index 00000000..59d929e6 --- /dev/null +++ b/runtime/python/prompty/prompty/model/events/_RedactedField.py @@ -0,0 +1,114 @@ +# +########################################## +# WARNING: This is an auto-generated file. +# DO NOT EDIT THIS FILE DIRECTLY +# ANY EDITS WILL BE LOST +########################################## + +from dataclasses import dataclass, field +from typing import Any, ClassVar, Literal + +from .._context import LoadContext, SaveContext + +RedactionMode = Literal["none", "redacted", "hashed", "summary", "reference"] + + +@dataclass +class RedactedField: + """Redaction handling for one JSON-shaped field path. + + Attributes + ---------- + path : str + JSONPath-like field path, relative to the containing payload + mode : str + How the field was represented + reason : Optional[str] + Human-readable reason or policy that caused this handling + """ + + _shorthand_property: ClassVar[str | None] = None + + path: str = field(default="") + mode: RedactionMode = field(default="none") + reason: str | None = None + + @staticmethod + def load(data: Any, context: LoadContext | None = None) -> "RedactedField": + """Load a RedactedField instance. + Args: + data (Any): The data to load the instance from. + context (Optional[LoadContext]): Optional context with pre/post processing callbacks. + Returns: + RedactedField: The loaded RedactedField instance. + + """ + + if context is not None: + data = context.process_input(data) + + if not isinstance(data, dict): + raise ValueError(f"Invalid data for RedactedField: {data}") + + # create new instance + instance = RedactedField() + + if data is not None and "path" in data: + instance.path = data["path"] + if data is not None and "mode" in data: + instance.mode = data["mode"] + if data is not None and "reason" in data: + instance.reason = data["reason"] + if context is not None: + instance = context.process_output(instance) + return instance + + def save(self, context: SaveContext | None = None) -> dict[str, Any]: + """Save the RedactedField instance to a dictionary. + Args: + context (Optional[SaveContext]): Optional context with pre/post processing callbacks. + Returns: + dict[str, Any]: The dictionary representation of this instance. + + """ + obj = self + if context is not None: + obj = context.process_object(obj) + + result: dict[str, Any] = {} + + if obj.path is not None: + result["path"] = obj.path + if obj.mode is not None: + result["mode"] = obj.mode + if obj.reason is not None: + result["reason"] = obj.reason + + if context is not None: + result = context.process_dict(result) + return result + + def to_yaml(self, context: SaveContext | None = None) -> str: + """Convert the RedactedField instance to a YAML string. + Args: + context (Optional[SaveContext]): Optional context with pre/post processing callbacks. + Returns: + str: The YAML string representation of this instance. + + """ + if context is None: + context = SaveContext() + return context.to_yaml(self.save(context)) + + def to_json(self, context: SaveContext | None = None, indent: int = 2) -> str: + """Convert the RedactedField instance to a JSON string. + Args: + context (Optional[SaveContext]): Optional context with pre/post processing callbacks. + indent (int): Number of spaces for indentation. Defaults to 2. + Returns: + str: The JSON string representation of this instance. + + """ + if context is None: + context = SaveContext() + return context.to_json(self.save(context), indent) diff --git a/runtime/python/prompty/prompty/model/events/_RedactionMetadata.py b/runtime/python/prompty/prompty/model/events/_RedactionMetadata.py new file mode 100644 index 00000000..0c325b01 --- /dev/null +++ b/runtime/python/prompty/prompty/model/events/_RedactionMetadata.py @@ -0,0 +1,136 @@ +# +########################################## +# WARNING: This is an auto-generated file. +# DO NOT EDIT THIS FILE DIRECTLY +# ANY EDITS WILL BE LOST +########################################## + +from dataclasses import dataclass, field +from typing import Any, ClassVar + +from .._context import LoadContext, SaveContext +from ._RedactedField import RedactedField + + +@dataclass +class RedactionMetadata: + """Metadata describing whether and how a payload was sanitized. + + Attributes + ---------- + sanitized : Optional[bool] + Whether the payload has been sanitized for persistence or external display + fields : Optional[list[RedactedField]] + Field-level redaction details + policy : Optional[str] + Host policy or sanitizer version that produced this metadata + """ + + _shorthand_property: ClassVar[str | None] = None + + sanitized: bool | None = None + fields: list[RedactedField] = field(default_factory=list) + policy: str | None = None + + @staticmethod + def load(data: Any, context: LoadContext | None = None) -> "RedactionMetadata": + """Load a RedactionMetadata instance. + Args: + data (Any): The data to load the instance from. + context (Optional[LoadContext]): Optional context with pre/post processing callbacks. + Returns: + RedactionMetadata: The loaded RedactionMetadata instance. + + """ + + if context is not None: + data = context.process_input(data) + + if not isinstance(data, dict): + raise ValueError(f"Invalid data for RedactionMetadata: {data}") + + # create new instance + instance = RedactionMetadata() + + if data is not None and "sanitized" in data: + instance.sanitized = data["sanitized"] + if data is not None and "fields" in data: + instance.fields = RedactionMetadata.load_fields(data["fields"], context) + if data is not None and "policy" in data: + instance.policy = data["policy"] + if context is not None: + instance = context.process_output(instance) + return instance + + @staticmethod + def load_fields(data: dict | list, context: LoadContext | None) -> list[RedactedField]: + if isinstance(data, dict): + # convert simple named fields to list of RedactedField + result = [] + for k, v in data.items(): + if isinstance(v, dict): + # value is an object, spread its properties + result.append({"name": k, **v}) + else: + # value is a scalar, use it as the primary property + result.append({"name": k, "path": v}) + data = result + return [RedactedField.load(item, context) for item in data] + + @staticmethod + def save_fields(items: list[RedactedField], context: SaveContext | None) -> dict[str, Any] | list[dict[str, Any]]: + if context is None: + context = SaveContext() + + # This type doesn't have a 'name' property, so always use array format + return [item.save(context) for item in items] + + def save(self, context: SaveContext | None = None) -> dict[str, Any]: + """Save the RedactionMetadata instance to a dictionary. + Args: + context (Optional[SaveContext]): Optional context with pre/post processing callbacks. + Returns: + dict[str, Any]: The dictionary representation of this instance. + + """ + obj = self + if context is not None: + obj = context.process_object(obj) + + result: dict[str, Any] = {} + + if obj.sanitized is not None: + result["sanitized"] = obj.sanitized + if obj.fields is not None: + result["fields"] = RedactionMetadata.save_fields(obj.fields, context) + if obj.policy is not None: + result["policy"] = obj.policy + + if context is not None: + result = context.process_dict(result) + return result + + def to_yaml(self, context: SaveContext | None = None) -> str: + """Convert the RedactionMetadata instance to a YAML string. + Args: + context (Optional[SaveContext]): Optional context with pre/post processing callbacks. + Returns: + str: The YAML string representation of this instance. + + """ + if context is None: + context = SaveContext() + return context.to_yaml(self.save(context)) + + def to_json(self, context: SaveContext | None = None, indent: int = 2) -> str: + """Convert the RedactionMetadata instance to a JSON string. + Args: + context (Optional[SaveContext]): Optional context with pre/post processing callbacks. + indent (int): Number of spaces for indentation. Defaults to 2. + Returns: + str: The JSON string representation of this instance. + + """ + if context is None: + context = SaveContext() + return context.to_json(self.save(context), indent) diff --git a/runtime/python/prompty/prompty/model/events/_RetryPayload.py b/runtime/python/prompty/prompty/model/events/_RetryPayload.py new file mode 100644 index 00000000..215706aa --- /dev/null +++ b/runtime/python/prompty/prompty/model/events/_RetryPayload.py @@ -0,0 +1,126 @@ +# +########################################## +# WARNING: This is an auto-generated file. +# DO NOT EDIT THIS FILE DIRECTLY +# ANY EDITS WILL BE LOST +########################################## + +from dataclasses import dataclass, field +from typing import Any, ClassVar + +from .._context import LoadContext, SaveContext + + +@dataclass +class RetryPayload: + """Payload for "retry" events — a transient operation will be retried. + + Attributes + ---------- + operation : str + Operation being retried + attempt : int + Attempt number about to run + max_attempts : Optional[int] + Maximum configured attempts + delay_ms : Optional[float] + Backoff delay before the next attempt in milliseconds + reason : Optional[str] + Reason for the retry + """ + + _shorthand_property: ClassVar[str | None] = None + + operation: str = field(default="") + attempt: int = field(default=0) + max_attempts: int | None = None + delay_ms: float | None = None + reason: str | None = None + + @staticmethod + def load(data: Any, context: LoadContext | None = None) -> "RetryPayload": + """Load a RetryPayload instance. + Args: + data (Any): The data to load the instance from. + context (Optional[LoadContext]): Optional context with pre/post processing callbacks. + Returns: + RetryPayload: The loaded RetryPayload instance. + + """ + + if context is not None: + data = context.process_input(data) + + if not isinstance(data, dict): + raise ValueError(f"Invalid data for RetryPayload: {data}") + + # create new instance + instance = RetryPayload() + + if data is not None and "operation" in data: + instance.operation = data["operation"] + if data is not None and "attempt" in data: + instance.attempt = data["attempt"] + if data is not None and "maxAttempts" in data: + instance.max_attempts = data["maxAttempts"] + if data is not None and "delayMs" in data: + instance.delay_ms = data["delayMs"] + if data is not None and "reason" in data: + instance.reason = data["reason"] + if context is not None: + instance = context.process_output(instance) + return instance + + def save(self, context: SaveContext | None = None) -> dict[str, Any]: + """Save the RetryPayload instance to a dictionary. + Args: + context (Optional[SaveContext]): Optional context with pre/post processing callbacks. + Returns: + dict[str, Any]: The dictionary representation of this instance. + + """ + obj = self + if context is not None: + obj = context.process_object(obj) + + result: dict[str, Any] = {} + + if obj.operation is not None: + result["operation"] = obj.operation + if obj.attempt is not None: + result["attempt"] = obj.attempt + if obj.max_attempts is not None: + result["maxAttempts"] = obj.max_attempts + if obj.delay_ms is not None: + result["delayMs"] = obj.delay_ms + if obj.reason is not None: + result["reason"] = obj.reason + + if context is not None: + result = context.process_dict(result) + return result + + def to_yaml(self, context: SaveContext | None = None) -> str: + """Convert the RetryPayload instance to a YAML string. + Args: + context (Optional[SaveContext]): Optional context with pre/post processing callbacks. + Returns: + str: The YAML string representation of this instance. + + """ + if context is None: + context = SaveContext() + return context.to_yaml(self.save(context)) + + def to_json(self, context: SaveContext | None = None, indent: int = 2) -> str: + """Convert the RetryPayload instance to a JSON string. + Args: + context (Optional[SaveContext]): Optional context with pre/post processing callbacks. + indent (int): Number of spaces for indentation. Defaults to 2. + Returns: + str: The JSON string representation of this instance. + + """ + if context is None: + context = SaveContext() + return context.to_json(self.save(context), indent) diff --git a/runtime/python/prompty/prompty/model/events/_SessionEndPayload.py b/runtime/python/prompty/prompty/model/events/_SessionEndPayload.py new file mode 100644 index 00000000..aec2e435 --- /dev/null +++ b/runtime/python/prompty/prompty/model/events/_SessionEndPayload.py @@ -0,0 +1,121 @@ +# +########################################## +# WARNING: This is an auto-generated file. +# DO NOT EDIT THIS FILE DIRECTLY +# ANY EDITS WILL BE LOST +########################################## + +from dataclasses import dataclass +from typing import Any, ClassVar, Literal + +from .._context import LoadContext, SaveContext + +SessionEndStatus = Literal["success", "error", "cancelled", "interrupted"] + + +@dataclass +class SessionEndPayload: + """Payload for "session_end" events. + + Attributes + ---------- + session_id : Optional[str] + Stable session identifier + status : Optional[str] + Final session status + reason : Optional[str] + Host-specific reason the session ended + duration_ms : Optional[float] + Total elapsed session duration in milliseconds + """ + + _shorthand_property: ClassVar[str | None] = None + + session_id: str | None = None + status: SessionEndStatus | None = None + reason: str | None = None + duration_ms: float | None = None + + @staticmethod + def load(data: Any, context: LoadContext | None = None) -> "SessionEndPayload": + """Load a SessionEndPayload instance. + Args: + data (Any): The data to load the instance from. + context (Optional[LoadContext]): Optional context with pre/post processing callbacks. + Returns: + SessionEndPayload: The loaded SessionEndPayload instance. + + """ + + if context is not None: + data = context.process_input(data) + + if not isinstance(data, dict): + raise ValueError(f"Invalid data for SessionEndPayload: {data}") + + # create new instance + instance = SessionEndPayload() + + if data is not None and "sessionId" in data: + instance.session_id = data["sessionId"] + if data is not None and "status" in data: + instance.status = data["status"] + if data is not None and "reason" in data: + instance.reason = data["reason"] + if data is not None and "durationMs" in data: + instance.duration_ms = data["durationMs"] + if context is not None: + instance = context.process_output(instance) + return instance + + def save(self, context: SaveContext | None = None) -> dict[str, Any]: + """Save the SessionEndPayload instance to a dictionary. + Args: + context (Optional[SaveContext]): Optional context with pre/post processing callbacks. + Returns: + dict[str, Any]: The dictionary representation of this instance. + + """ + obj = self + if context is not None: + obj = context.process_object(obj) + + result: dict[str, Any] = {} + + if obj.session_id is not None: + result["sessionId"] = obj.session_id + if obj.status is not None: + result["status"] = obj.status + if obj.reason is not None: + result["reason"] = obj.reason + if obj.duration_ms is not None: + result["durationMs"] = obj.duration_ms + + if context is not None: + result = context.process_dict(result) + return result + + def to_yaml(self, context: SaveContext | None = None) -> str: + """Convert the SessionEndPayload instance to a YAML string. + Args: + context (Optional[SaveContext]): Optional context with pre/post processing callbacks. + Returns: + str: The YAML string representation of this instance. + + """ + if context is None: + context = SaveContext() + return context.to_yaml(self.save(context)) + + def to_json(self, context: SaveContext | None = None, indent: int = 2) -> str: + """Convert the SessionEndPayload instance to a JSON string. + Args: + context (Optional[SaveContext]): Optional context with pre/post processing callbacks. + indent (int): Number of spaces for indentation. Defaults to 2. + Returns: + str: The JSON string representation of this instance. + + """ + if context is None: + context = SaveContext() + return context.to_json(self.save(context), indent) diff --git a/runtime/python/prompty/prompty/model/events/_SessionEvent.py b/runtime/python/prompty/prompty/model/events/_SessionEvent.py new file mode 100644 index 00000000..663e64b3 --- /dev/null +++ b/runtime/python/prompty/prompty/model/events/_SessionEvent.py @@ -0,0 +1,165 @@ +# +########################################## +# WARNING: This is an auto-generated file. +# DO NOT EDIT THIS FILE DIRECTLY +# ANY EDITS WILL BE LOST +########################################## + +from dataclasses import dataclass, field +from typing import Any, ClassVar, Literal + +from .._context import LoadContext, SaveContext +from ._RedactionMetadata import RedactionMetadata + +SessionEventType = Literal[ + "session_start", + "session_end", + "session_warning", + "session_hook_start", + "session_hook_end", + "checkpoint_created", + "trajectory_event", +] + + +@dataclass +class SessionEvent: + """A canonical event envelope emitted by an outer harness session. + + Attributes + ---------- + id : str + Unique identifier for this event + type : str + Event type discriminator + timestamp : str + ISO 8601 UTC timestamp when the event was emitted + session_id : Optional[str] + Stable identifier for the outer session + turn_id : Optional[str] + Associated turn identifier, when this session event is linked to a turn + parent_id : Optional[str] + Parent event or span identifier for reconstructing event hierarchy + span_id : Optional[str] + Trace span identifier associated with this event + payload : dict[str, Any] + Event-specific payload. Use the typed payload model matching 'type'. + redaction : Optional[RedactionMetadata] + Redaction state for sensitive payload fields + """ + + _shorthand_property: ClassVar[str | None] = None + + id: str = field(default="") + type: SessionEventType = field(default="session_start") + timestamp: str = field(default="") + session_id: str | None = None + turn_id: str | None = None + parent_id: str | None = None + span_id: str | None = None + payload: dict[str, Any] = field(default_factory=dict) + redaction: RedactionMetadata | None = None + + @staticmethod + def load(data: Any, context: LoadContext | None = None) -> "SessionEvent": + """Load a SessionEvent instance. + Args: + data (Any): The data to load the instance from. + context (Optional[LoadContext]): Optional context with pre/post processing callbacks. + Returns: + SessionEvent: The loaded SessionEvent instance. + + """ + + if context is not None: + data = context.process_input(data) + + if not isinstance(data, dict): + raise ValueError(f"Invalid data for SessionEvent: {data}") + + # create new instance + instance = SessionEvent() + + if data is not None and "id" in data: + instance.id = data["id"] + if data is not None and "type" in data: + instance.type = data["type"] + if data is not None and "timestamp" in data: + instance.timestamp = data["timestamp"] + if data is not None and "sessionId" in data: + instance.session_id = data["sessionId"] + if data is not None and "turnId" in data: + instance.turn_id = data["turnId"] + if data is not None and "parentId" in data: + instance.parent_id = data["parentId"] + if data is not None and "spanId" in data: + instance.span_id = data["spanId"] + if data is not None and "payload" in data: + instance.payload = data["payload"] + if data is not None and "redaction" in data: + instance.redaction = RedactionMetadata.load(data["redaction"], context) + if context is not None: + instance = context.process_output(instance) + return instance + + def save(self, context: SaveContext | None = None) -> dict[str, Any]: + """Save the SessionEvent instance to a dictionary. + Args: + context (Optional[SaveContext]): Optional context with pre/post processing callbacks. + Returns: + dict[str, Any]: The dictionary representation of this instance. + + """ + obj = self + if context is not None: + obj = context.process_object(obj) + + result: dict[str, Any] = {} + + if obj.id is not None: + result["id"] = obj.id + if obj.type is not None: + result["type"] = obj.type + if obj.timestamp is not None: + result["timestamp"] = obj.timestamp + if obj.session_id is not None: + result["sessionId"] = obj.session_id + if obj.turn_id is not None: + result["turnId"] = obj.turn_id + if obj.parent_id is not None: + result["parentId"] = obj.parent_id + if obj.span_id is not None: + result["spanId"] = obj.span_id + if obj.payload is not None: + result["payload"] = obj.payload + if obj.redaction is not None: + result["redaction"] = obj.redaction.save(context) + + if context is not None: + result = context.process_dict(result) + return result + + def to_yaml(self, context: SaveContext | None = None) -> str: + """Convert the SessionEvent instance to a YAML string. + Args: + context (Optional[SaveContext]): Optional context with pre/post processing callbacks. + Returns: + str: The YAML string representation of this instance. + + """ + if context is None: + context = SaveContext() + return context.to_yaml(self.save(context)) + + def to_json(self, context: SaveContext | None = None, indent: int = 2) -> str: + """Convert the SessionEvent instance to a JSON string. + Args: + context (Optional[SaveContext]): Optional context with pre/post processing callbacks. + indent (int): Number of spaces for indentation. Defaults to 2. + Returns: + str: The JSON string representation of this instance. + + """ + if context is None: + context = SaveContext() + return context.to_json(self.save(context), indent) diff --git a/runtime/python/prompty/prompty/model/events/_SessionFileRef.py b/runtime/python/prompty/prompty/model/events/_SessionFileRef.py new file mode 100644 index 00000000..b932f69f --- /dev/null +++ b/runtime/python/prompty/prompty/model/events/_SessionFileRef.py @@ -0,0 +1,126 @@ +# +########################################## +# WARNING: This is an auto-generated file. +# DO NOT EDIT THIS FILE DIRECTLY +# ANY EDITS WILL BE LOST +########################################## + +from dataclasses import dataclass, field +from typing import Any, ClassVar + +from .._context import LoadContext, SaveContext + + +@dataclass +class SessionFileRef: + """A file observed or touched by a harness session. + + Attributes + ---------- + session_id : Optional[str] + Stable session identifier + path : str + File path, relative to the harness workspace when possible + tool_name : Optional[str] + Tool that first observed the file, when known + turn_index : Optional[int] + Zero-based turn index where the file was first observed + first_seen_at : Optional[str] + ISO 8601 UTC timestamp when the file was first observed + """ + + _shorthand_property: ClassVar[str | None] = None + + session_id: str | None = None + path: str = field(default="") + tool_name: str | None = None + turn_index: int | None = None + first_seen_at: str | None = None + + @staticmethod + def load(data: Any, context: LoadContext | None = None) -> "SessionFileRef": + """Load a SessionFileRef instance. + Args: + data (Any): The data to load the instance from. + context (Optional[LoadContext]): Optional context with pre/post processing callbacks. + Returns: + SessionFileRef: The loaded SessionFileRef instance. + + """ + + if context is not None: + data = context.process_input(data) + + if not isinstance(data, dict): + raise ValueError(f"Invalid data for SessionFileRef: {data}") + + # create new instance + instance = SessionFileRef() + + if data is not None and "sessionId" in data: + instance.session_id = data["sessionId"] + if data is not None and "path" in data: + instance.path = data["path"] + if data is not None and "toolName" in data: + instance.tool_name = data["toolName"] + if data is not None and "turnIndex" in data: + instance.turn_index = data["turnIndex"] + if data is not None and "firstSeenAt" in data: + instance.first_seen_at = data["firstSeenAt"] + if context is not None: + instance = context.process_output(instance) + return instance + + def save(self, context: SaveContext | None = None) -> dict[str, Any]: + """Save the SessionFileRef instance to a dictionary. + Args: + context (Optional[SaveContext]): Optional context with pre/post processing callbacks. + Returns: + dict[str, Any]: The dictionary representation of this instance. + + """ + obj = self + if context is not None: + obj = context.process_object(obj) + + result: dict[str, Any] = {} + + if obj.session_id is not None: + result["sessionId"] = obj.session_id + if obj.path is not None: + result["path"] = obj.path + if obj.tool_name is not None: + result["toolName"] = obj.tool_name + if obj.turn_index is not None: + result["turnIndex"] = obj.turn_index + if obj.first_seen_at is not None: + result["firstSeenAt"] = obj.first_seen_at + + if context is not None: + result = context.process_dict(result) + return result + + def to_yaml(self, context: SaveContext | None = None) -> str: + """Convert the SessionFileRef instance to a YAML string. + Args: + context (Optional[SaveContext]): Optional context with pre/post processing callbacks. + Returns: + str: The YAML string representation of this instance. + + """ + if context is None: + context = SaveContext() + return context.to_yaml(self.save(context)) + + def to_json(self, context: SaveContext | None = None, indent: int = 2) -> str: + """Convert the SessionFileRef instance to a JSON string. + Args: + context (Optional[SaveContext]): Optional context with pre/post processing callbacks. + indent (int): Number of spaces for indentation. Defaults to 2. + Returns: + str: The JSON string representation of this instance. + + """ + if context is None: + context = SaveContext() + return context.to_json(self.save(context), indent) diff --git a/runtime/python/prompty/prompty/model/events/_SessionRef.py b/runtime/python/prompty/prompty/model/events/_SessionRef.py new file mode 100644 index 00000000..9c7d22f1 --- /dev/null +++ b/runtime/python/prompty/prompty/model/events/_SessionRef.py @@ -0,0 +1,126 @@ +# +########################################## +# WARNING: This is an auto-generated file. +# DO NOT EDIT THIS FILE DIRECTLY +# ANY EDITS WILL BE LOST +########################################## + +from dataclasses import dataclass, field +from typing import Any, ClassVar + +from .._context import LoadContext, SaveContext + + +@dataclass +class SessionRef: + """A non-file reference observed by a harness session. + + Attributes + ---------- + session_id : Optional[str] + Stable session identifier + ref_type : str + Reference category + ref_value : str + Reference value + turn_index : Optional[int] + Zero-based turn index where the reference was first observed + created_at : Optional[str] + ISO 8601 UTC timestamp when the reference was recorded + """ + + _shorthand_property: ClassVar[str | None] = None + + session_id: str | None = None + ref_type: str = field(default="") + ref_value: str = field(default="") + turn_index: int | None = None + created_at: str | None = None + + @staticmethod + def load(data: Any, context: LoadContext | None = None) -> "SessionRef": + """Load a SessionRef instance. + Args: + data (Any): The data to load the instance from. + context (Optional[LoadContext]): Optional context with pre/post processing callbacks. + Returns: + SessionRef: The loaded SessionRef instance. + + """ + + if context is not None: + data = context.process_input(data) + + if not isinstance(data, dict): + raise ValueError(f"Invalid data for SessionRef: {data}") + + # create new instance + instance = SessionRef() + + if data is not None and "sessionId" in data: + instance.session_id = data["sessionId"] + if data is not None and "refType" in data: + instance.ref_type = data["refType"] + if data is not None and "refValue" in data: + instance.ref_value = data["refValue"] + if data is not None and "turnIndex" in data: + instance.turn_index = data["turnIndex"] + if data is not None and "createdAt" in data: + instance.created_at = data["createdAt"] + if context is not None: + instance = context.process_output(instance) + return instance + + def save(self, context: SaveContext | None = None) -> dict[str, Any]: + """Save the SessionRef instance to a dictionary. + Args: + context (Optional[SaveContext]): Optional context with pre/post processing callbacks. + Returns: + dict[str, Any]: The dictionary representation of this instance. + + """ + obj = self + if context is not None: + obj = context.process_object(obj) + + result: dict[str, Any] = {} + + if obj.session_id is not None: + result["sessionId"] = obj.session_id + if obj.ref_type is not None: + result["refType"] = obj.ref_type + if obj.ref_value is not None: + result["refValue"] = obj.ref_value + if obj.turn_index is not None: + result["turnIndex"] = obj.turn_index + if obj.created_at is not None: + result["createdAt"] = obj.created_at + + if context is not None: + result = context.process_dict(result) + return result + + def to_yaml(self, context: SaveContext | None = None) -> str: + """Convert the SessionRef instance to a YAML string. + Args: + context (Optional[SaveContext]): Optional context with pre/post processing callbacks. + Returns: + str: The YAML string representation of this instance. + + """ + if context is None: + context = SaveContext() + return context.to_yaml(self.save(context)) + + def to_json(self, context: SaveContext | None = None, indent: int = 2) -> str: + """Convert the SessionRef instance to a JSON string. + Args: + context (Optional[SaveContext]): Optional context with pre/post processing callbacks. + indent (int): Number of spaces for indentation. Defaults to 2. + Returns: + str: The JSON string representation of this instance. + + """ + if context is None: + context = SaveContext() + return context.to_json(self.save(context), indent) diff --git a/runtime/python/prompty/prompty/model/events/_SessionStartPayload.py b/runtime/python/prompty/prompty/model/events/_SessionStartPayload.py new file mode 100644 index 00000000..bc13ed0f --- /dev/null +++ b/runtime/python/prompty/prompty/model/events/_SessionStartPayload.py @@ -0,0 +1,155 @@ +# +########################################## +# WARNING: This is an auto-generated file. +# DO NOT EDIT THIS FILE DIRECTLY +# ANY EDITS WILL BE LOST +########################################## + +from dataclasses import dataclass, field +from typing import Any, ClassVar + +from .._context import LoadContext, SaveContext +from ._HarnessContext import HarnessContext + + +@dataclass +class SessionStartPayload: + """Payload for "session_start" events. + + Attributes + ---------- + session_id : str + Stable session identifier + schema_version : Optional[str] + Session event schema version + producer : Optional[str] + Producer that started the session + runtime : Optional[str] + Runtime that produced the session + prompty_version : Optional[str] + Prompty library version + start_time : Optional[str] + ISO 8601 UTC timestamp when the session started + selected_model : Optional[str] + Selected model identifier, when known + reasoning_effort : Optional[str] + Selected reasoning effort or equivalent model setting, when known + context : Optional[HarnessContext] + Repository and execution context + """ + + _shorthand_property: ClassVar[str | None] = None + + session_id: str = field(default="") + schema_version: str | None = None + producer: str | None = None + runtime: str | None = None + prompty_version: str | None = None + start_time: str | None = None + selected_model: str | None = None + reasoning_effort: str | None = None + context: HarnessContext | None = None + + @staticmethod + def load(data: Any, context: LoadContext | None = None) -> "SessionStartPayload": + """Load a SessionStartPayload instance. + Args: + data (Any): The data to load the instance from. + context (Optional[LoadContext]): Optional context with pre/post processing callbacks. + Returns: + SessionStartPayload: The loaded SessionStartPayload instance. + + """ + + if context is not None: + data = context.process_input(data) + + if not isinstance(data, dict): + raise ValueError(f"Invalid data for SessionStartPayload: {data}") + + # create new instance + instance = SessionStartPayload() + + if data is not None and "sessionId" in data: + instance.session_id = data["sessionId"] + if data is not None and "schemaVersion" in data: + instance.schema_version = data["schemaVersion"] + if data is not None and "producer" in data: + instance.producer = data["producer"] + if data is not None and "runtime" in data: + instance.runtime = data["runtime"] + if data is not None and "promptyVersion" in data: + instance.prompty_version = data["promptyVersion"] + if data is not None and "startTime" in data: + instance.start_time = data["startTime"] + if data is not None and "selectedModel" in data: + instance.selected_model = data["selectedModel"] + if data is not None and "reasoningEffort" in data: + instance.reasoning_effort = data["reasoningEffort"] + if data is not None and "context" in data: + instance.context = HarnessContext.load(data["context"], context) + if context is not None: + instance = context.process_output(instance) + return instance + + def save(self, context: SaveContext | None = None) -> dict[str, Any]: + """Save the SessionStartPayload instance to a dictionary. + Args: + context (Optional[SaveContext]): Optional context with pre/post processing callbacks. + Returns: + dict[str, Any]: The dictionary representation of this instance. + + """ + obj = self + if context is not None: + obj = context.process_object(obj) + + result: dict[str, Any] = {} + + if obj.session_id is not None: + result["sessionId"] = obj.session_id + if obj.schema_version is not None: + result["schemaVersion"] = obj.schema_version + if obj.producer is not None: + result["producer"] = obj.producer + if obj.runtime is not None: + result["runtime"] = obj.runtime + if obj.prompty_version is not None: + result["promptyVersion"] = obj.prompty_version + if obj.start_time is not None: + result["startTime"] = obj.start_time + if obj.selected_model is not None: + result["selectedModel"] = obj.selected_model + if obj.reasoning_effort is not None: + result["reasoningEffort"] = obj.reasoning_effort + if obj.context is not None: + result["context"] = obj.context.save(context) + + if context is not None: + result = context.process_dict(result) + return result + + def to_yaml(self, context: SaveContext | None = None) -> str: + """Convert the SessionStartPayload instance to a YAML string. + Args: + context (Optional[SaveContext]): Optional context with pre/post processing callbacks. + Returns: + str: The YAML string representation of this instance. + + """ + if context is None: + context = SaveContext() + return context.to_yaml(self.save(context)) + + def to_json(self, context: SaveContext | None = None, indent: int = 2) -> str: + """Convert the SessionStartPayload instance to a JSON string. + Args: + context (Optional[SaveContext]): Optional context with pre/post processing callbacks. + indent (int): Number of spaces for indentation. Defaults to 2. + Returns: + str: The JSON string representation of this instance. + + """ + if context is None: + context = SaveContext() + return context.to_json(self.save(context), indent) diff --git a/runtime/python/prompty/prompty/model/events/_SessionSummary.py b/runtime/python/prompty/prompty/model/events/_SessionSummary.py new file mode 100644 index 00000000..f3d7bb58 --- /dev/null +++ b/runtime/python/prompty/prompty/model/events/_SessionSummary.py @@ -0,0 +1,136 @@ +# +########################################## +# WARNING: This is an auto-generated file. +# DO NOT EDIT THIS FILE DIRECTLY +# ANY EDITS WILL BE LOST +########################################## + +from dataclasses import dataclass, field +from typing import Any, ClassVar, Literal + +from .._context import LoadContext, SaveContext +from ..model._TokenUsage import TokenUsage + +SessionSummaryStatus = Literal["success", "error", "cancelled", "interrupted"] + + +@dataclass +class SessionSummary: + """Summary statistics for a completed session trace. + + Attributes + ---------- + session_id : str + Stable session identifier + status : Optional[str] + Final session status + turns : Optional[int] + Number of user/assistant turns in the session + checkpoints : Optional[int] + Number of checkpoints created + usage : Optional[TokenUsage] + Aggregated token usage for the session + duration_ms : Optional[float] + Total elapsed session duration in milliseconds + """ + + _shorthand_property: ClassVar[str | None] = None + + session_id: str = field(default="") + status: SessionSummaryStatus | None = None + turns: int | None = None + checkpoints: int | None = None + usage: TokenUsage | None = None + duration_ms: float | None = None + + @staticmethod + def load(data: Any, context: LoadContext | None = None) -> "SessionSummary": + """Load a SessionSummary instance. + Args: + data (Any): The data to load the instance from. + context (Optional[LoadContext]): Optional context with pre/post processing callbacks. + Returns: + SessionSummary: The loaded SessionSummary instance. + + """ + + if context is not None: + data = context.process_input(data) + + if not isinstance(data, dict): + raise ValueError(f"Invalid data for SessionSummary: {data}") + + # create new instance + instance = SessionSummary() + + if data is not None and "sessionId" in data: + instance.session_id = data["sessionId"] + if data is not None and "status" in data: + instance.status = data["status"] + if data is not None and "turns" in data: + instance.turns = data["turns"] + if data is not None and "checkpoints" in data: + instance.checkpoints = data["checkpoints"] + if data is not None and "usage" in data: + instance.usage = TokenUsage.load(data["usage"], context) + if data is not None and "durationMs" in data: + instance.duration_ms = data["durationMs"] + if context is not None: + instance = context.process_output(instance) + return instance + + def save(self, context: SaveContext | None = None) -> dict[str, Any]: + """Save the SessionSummary instance to a dictionary. + Args: + context (Optional[SaveContext]): Optional context with pre/post processing callbacks. + Returns: + dict[str, Any]: The dictionary representation of this instance. + + """ + obj = self + if context is not None: + obj = context.process_object(obj) + + result: dict[str, Any] = {} + + if obj.session_id is not None: + result["sessionId"] = obj.session_id + if obj.status is not None: + result["status"] = obj.status + if obj.turns is not None: + result["turns"] = obj.turns + if obj.checkpoints is not None: + result["checkpoints"] = obj.checkpoints + if obj.usage is not None: + result["usage"] = obj.usage.save(context) + if obj.duration_ms is not None: + result["durationMs"] = obj.duration_ms + + if context is not None: + result = context.process_dict(result) + return result + + def to_yaml(self, context: SaveContext | None = None) -> str: + """Convert the SessionSummary instance to a YAML string. + Args: + context (Optional[SaveContext]): Optional context with pre/post processing callbacks. + Returns: + str: The YAML string representation of this instance. + + """ + if context is None: + context = SaveContext() + return context.to_yaml(self.save(context)) + + def to_json(self, context: SaveContext | None = None, indent: int = 2) -> str: + """Convert the SessionSummary instance to a JSON string. + Args: + context (Optional[SaveContext]): Optional context with pre/post processing callbacks. + indent (int): Number of spaces for indentation. Defaults to 2. + Returns: + str: The JSON string representation of this instance. + + """ + if context is None: + context = SaveContext() + return context.to_json(self.save(context), indent) diff --git a/runtime/python/prompty/prompty/model/events/_SessionTrace.py b/runtime/python/prompty/prompty/model/events/_SessionTrace.py new file mode 100644 index 00000000..70bae77e --- /dev/null +++ b/runtime/python/prompty/prompty/model/events/_SessionTrace.py @@ -0,0 +1,315 @@ +# +########################################## +# WARNING: This is an auto-generated file. +# DO NOT EDIT THIS FILE DIRECTLY +# ANY EDITS WILL BE LOST +########################################## + +from dataclasses import dataclass, field +from typing import Any, ClassVar + +from .._context import LoadContext, SaveContext +from ._Checkpoint import Checkpoint +from ._SessionEvent import SessionEvent +from ._SessionFileRef import SessionFileRef +from ._SessionRef import SessionRef +from ._SessionSummary import SessionSummary +from ._TrajectoryEvent import TrajectoryEvent +from ._TurnTrace import TurnTrace + + +@dataclass +class SessionTrace: + """Portable replay container for an outer harness session. + + Attributes + ---------- + version : str + Trace schema version + runtime : Optional[str] + Runtime name that produced the trace + prompty_version : Optional[str] + Prompty library version that produced the trace + session_id : Optional[str] + Stable session identifier + events : list[SessionEvent] + Recorded session events in emission order + turns : Optional[list[TurnTrace]] + Recorded turn traces associated with the session + checkpoints : Optional[list[Checkpoint]] + Checkpoints created during the session + trajectory : Optional[list[TrajectoryEvent]] + Compact trajectory records associated with the session + files : Optional[list[SessionFileRef]] + Files observed or touched during the session + refs : Optional[list[SessionRef]] + Non-file references observed during the session + summary : Optional[SessionSummary] + Optional summary computed from the event stream + """ + + _shorthand_property: ClassVar[str | None] = None + + version: str = field(default="1") + runtime: str | None = None + prompty_version: str | None = None + session_id: str | None = None + events: list[SessionEvent] = field(default_factory=list) + turns: list[TurnTrace] = field(default_factory=list) + checkpoints: list[Checkpoint] = field(default_factory=list) + trajectory: list[TrajectoryEvent] = field(default_factory=list) + files: list[SessionFileRef] = field(default_factory=list) + refs: list[SessionRef] = field(default_factory=list) + summary: SessionSummary | None = None + + @staticmethod + def load(data: Any, context: LoadContext | None = None) -> "SessionTrace": + """Load a SessionTrace instance. + Args: + data (Any): The data to load the instance from. + context (Optional[LoadContext]): Optional context with pre/post processing callbacks. + Returns: + SessionTrace: The loaded SessionTrace instance. + + """ + + if context is not None: + data = context.process_input(data) + + if not isinstance(data, dict): + raise ValueError(f"Invalid data for SessionTrace: {data}") + + # create new instance + instance = SessionTrace() + + if data is not None and "version" in data: + instance.version = data["version"] + if data is not None and "runtime" in data: + instance.runtime = data["runtime"] + if data is not None and "promptyVersion" in data: + instance.prompty_version = data["promptyVersion"] + if data is not None and "sessionId" in data: + instance.session_id = data["sessionId"] + if data is not None and "events" in data: + instance.events = SessionTrace.load_events(data["events"], context) + if data is not None and "turns" in data: + instance.turns = SessionTrace.load_turns(data["turns"], context) + if data is not None and "checkpoints" in data: + instance.checkpoints = SessionTrace.load_checkpoints(data["checkpoints"], context) + if data is not None and "trajectory" in data: + instance.trajectory = SessionTrace.load_trajectory(data["trajectory"], context) + if data is not None and "files" in data: + instance.files = SessionTrace.load_files(data["files"], context) + if data is not None and "refs" in data: + instance.refs = SessionTrace.load_refs(data["refs"], context) + if data is not None and "summary" in data: + instance.summary = SessionSummary.load(data["summary"], context) + if context is not None: + instance = context.process_output(instance) + return instance + + @staticmethod + def load_events(data: dict | list, context: LoadContext | None) -> list[SessionEvent]: + if isinstance(data, dict): + # convert simple named events to list of SessionEvent + result = [] + for k, v in data.items(): + if isinstance(v, dict): + # value is an object, spread its properties + result.append({"name": k, **v}) + else: + # value is a scalar, use it as the primary property + result.append({"name": k, "id": v}) + data = result + return [SessionEvent.load(item, context) for item in data] + + @staticmethod + def save_events(items: list[SessionEvent], context: SaveContext | None) -> dict[str, Any] | list[dict[str, Any]]: + if context is None: + context = SaveContext() + + # This type doesn't have a 'name' property, so always use array format + return [item.save(context) for item in items] + + @staticmethod + def load_turns(data: dict | list, context: LoadContext | None) -> list[TurnTrace]: + if isinstance(data, dict): + # convert simple named turns to list of TurnTrace + result = [] + for k, v in data.items(): + if isinstance(v, dict): + # value is an object, spread its properties + result.append({"name": k, **v}) + else: + # value is a scalar, use it as the primary property + result.append({"name": k, "version": v}) + data = result + return [TurnTrace.load(item, context) for item in data] + + @staticmethod + def save_turns(items: list[TurnTrace], context: SaveContext | None) -> dict[str, Any] | list[dict[str, Any]]: + if context is None: + context = SaveContext() + + # This type doesn't have a 'name' property, so always use array format + return [item.save(context) for item in items] + + @staticmethod + def load_checkpoints(data: dict | list, context: LoadContext | None) -> list[Checkpoint]: + if isinstance(data, dict): + # convert simple named checkpoints to list of Checkpoint + result = [] + for k, v in data.items(): + if isinstance(v, dict): + # value is an object, spread its properties + result.append({"name": k, **v}) + else: + # value is a scalar, use it as the primary property + result.append({"name": k, "id": v}) + data = result + return [Checkpoint.load(item, context) for item in data] + + @staticmethod + def save_checkpoints(items: list[Checkpoint], context: SaveContext | None) -> dict[str, Any] | list[dict[str, Any]]: + if context is None: + context = SaveContext() + + # This type doesn't have a 'name' property, so always use array format + return [item.save(context) for item in items] + + @staticmethod + def load_trajectory(data: dict | list, context: LoadContext | None) -> list[TrajectoryEvent]: + if isinstance(data, dict): + # convert simple named trajectory to list of TrajectoryEvent + result = [] + for k, v in data.items(): + if isinstance(v, dict): + # value is an object, spread its properties + result.append({"name": k, **v}) + else: + # value is a scalar, use it as the primary property + result.append({"name": k, "id": v}) + data = result + return [TrajectoryEvent.load(item, context) for item in data] + + @staticmethod + def save_trajectory( + items: list[TrajectoryEvent], context: SaveContext | None + ) -> dict[str, Any] | list[dict[str, Any]]: + if context is None: + context = SaveContext() + + # This type doesn't have a 'name' property, so always use array format + return [item.save(context) for item in items] + + @staticmethod + def load_files(data: dict | list, context: LoadContext | None) -> list[SessionFileRef]: + if isinstance(data, dict): + # convert simple named files to list of SessionFileRef + result = [] + for k, v in data.items(): + if isinstance(v, dict): + # value is an object, spread its properties + result.append({"name": k, **v}) + else: + # value is a scalar, use it as the primary property + result.append({"name": k, "sessionId": v}) + data = result + return [SessionFileRef.load(item, context) for item in data] + + @staticmethod + def save_files(items: list[SessionFileRef], context: SaveContext | None) -> dict[str, Any] | list[dict[str, Any]]: + if context is None: + context = SaveContext() + + # This type doesn't have a 'name' property, so always use array format + return [item.save(context) for item in items] + + @staticmethod + def load_refs(data: dict | list, context: LoadContext | None) -> list[SessionRef]: + if isinstance(data, dict): + # convert simple named refs to list of SessionRef + result = [] + for k, v in data.items(): + if isinstance(v, dict): + # value is an object, spread its properties + result.append({"name": k, **v}) + else: + # value is a scalar, use it as the primary property + result.append({"name": k, "sessionId": v}) + data = result + return [SessionRef.load(item, context) for item in data] + + @staticmethod + def save_refs(items: list[SessionRef], context: SaveContext | None) -> dict[str, Any] | list[dict[str, Any]]: + if context is None: + context = SaveContext() + + # This type doesn't have a 'name' property, so always use array format + return [item.save(context) for item in items] + + def save(self, context: SaveContext | None = None) -> dict[str, Any]: + """Save the SessionTrace instance to a dictionary. + Args: + context (Optional[SaveContext]): Optional context with pre/post processing callbacks. + Returns: + dict[str, Any]: The dictionary representation of this instance. + + """ + obj = self + if context is not None: + obj = context.process_object(obj) + + result: dict[str, Any] = {} + + if obj.version is not None: + result["version"] = obj.version + if obj.runtime is not None: + result["runtime"] = obj.runtime + if obj.prompty_version is not None: + result["promptyVersion"] = obj.prompty_version + if obj.session_id is not None: + result["sessionId"] = obj.session_id + if obj.events is not None: + result["events"] = SessionTrace.save_events(obj.events, context) + if obj.turns is not None: + result["turns"] = SessionTrace.save_turns(obj.turns, context) + if obj.checkpoints is not None: + result["checkpoints"] = SessionTrace.save_checkpoints(obj.checkpoints, context) + if obj.trajectory is not None: + result["trajectory"] = SessionTrace.save_trajectory(obj.trajectory, context) + if obj.files is not None: + result["files"] = SessionTrace.save_files(obj.files, context) + if obj.refs is not None: + result["refs"] = SessionTrace.save_refs(obj.refs, context) + if obj.summary is not None: + result["summary"] = obj.summary.save(context) + + if context is not None: + result = context.process_dict(result) + return result + + def to_yaml(self, context: SaveContext | None = None) -> str: + """Convert the SessionTrace instance to a YAML string. + Args: + context (Optional[SaveContext]): Optional context with pre/post processing callbacks. + Returns: + str: The YAML string representation of this instance. + + """ + if context is None: + context = SaveContext() + return context.to_yaml(self.save(context)) + + def to_json(self, context: SaveContext | None = None, indent: int = 2) -> str: + """Convert the SessionTrace instance to a JSON string. + Args: + context (Optional[SaveContext]): Optional context with pre/post processing callbacks. + indent (int): Number of spaces for indentation. Defaults to 2. + Returns: + str: The JSON string representation of this instance. + + """ + if context is None: + context = SaveContext() + return context.to_json(self.save(context), indent) diff --git a/runtime/python/prompty/prompty/model/events/_SessionWarningPayload.py b/runtime/python/prompty/prompty/model/events/_SessionWarningPayload.py new file mode 100644 index 00000000..6b597c35 --- /dev/null +++ b/runtime/python/prompty/prompty/model/events/_SessionWarningPayload.py @@ -0,0 +1,112 @@ +# +########################################## +# WARNING: This is an auto-generated file. +# DO NOT EDIT THIS FILE DIRECTLY +# ANY EDITS WILL BE LOST +########################################## + +from dataclasses import dataclass, field +from typing import Any, ClassVar + +from .._context import LoadContext, SaveContext + + +@dataclass +class SessionWarningPayload: + """Payload for "session_warning" events. + + Attributes + ---------- + warning_type : str + Stable machine-readable warning category + message : str + Human-readable warning message + details : Optional[dict[str, Any]] + Additional host-specific warning details + """ + + _shorthand_property: ClassVar[str | None] = None + + warning_type: str = field(default="") + message: str = field(default="") + details: dict[str, Any] | None = None + + @staticmethod + def load(data: Any, context: LoadContext | None = None) -> "SessionWarningPayload": + """Load a SessionWarningPayload instance. + Args: + data (Any): The data to load the instance from. + context (Optional[LoadContext]): Optional context with pre/post processing callbacks. + Returns: + SessionWarningPayload: The loaded SessionWarningPayload instance. + + """ + + if context is not None: + data = context.process_input(data) + + if not isinstance(data, dict): + raise ValueError(f"Invalid data for SessionWarningPayload: {data}") + + # create new instance + instance = SessionWarningPayload() + + if data is not None and "warningType" in data: + instance.warning_type = data["warningType"] + if data is not None and "message" in data: + instance.message = data["message"] + if data is not None and "details" in data: + instance.details = data["details"] + if context is not None: + instance = context.process_output(instance) + return instance + + def save(self, context: SaveContext | None = None) -> dict[str, Any]: + """Save the SessionWarningPayload instance to a dictionary. + Args: + context (Optional[SaveContext]): Optional context with pre/post processing callbacks. + Returns: + dict[str, Any]: The dictionary representation of this instance. + + """ + obj = self + if context is not None: + obj = context.process_object(obj) + + result: dict[str, Any] = {} + + if obj.warning_type is not None: + result["warningType"] = obj.warning_type + if obj.message is not None: + result["message"] = obj.message + if obj.details is not None: + result["details"] = obj.details + + if context is not None: + result = context.process_dict(result) + return result + + def to_yaml(self, context: SaveContext | None = None) -> str: + """Convert the SessionWarningPayload instance to a YAML string. + Args: + context (Optional[SaveContext]): Optional context with pre/post processing callbacks. + Returns: + str: The YAML string representation of this instance. + + """ + if context is None: + context = SaveContext() + return context.to_yaml(self.save(context)) + + def to_json(self, context: SaveContext | None = None, indent: int = 2) -> str: + """Convert the SessionWarningPayload instance to a JSON string. + Args: + context (Optional[SaveContext]): Optional context with pre/post processing callbacks. + indent (int): Number of spaces for indentation. Defaults to 2. + Returns: + str: The JSON string representation of this instance. + + """ + if context is None: + context = SaveContext() + return context.to_json(self.save(context), indent) diff --git a/runtime/python/prompty/prompty/model/events/_StatusEventPayload.py b/runtime/python/prompty/prompty/model/events/_StatusEventPayload.py index 34a934ad..ad7bf9b8 100644 --- a/runtime/python/prompty/prompty/model/events/_StatusEventPayload.py +++ b/runtime/python/prompty/prompty/model/events/_StatusEventPayload.py @@ -1,3 +1,4 @@ +# ########################################## # WARNING: This is an auto-generated file. # DO NOT EDIT THIS FILE DIRECTLY diff --git a/runtime/python/prompty/prompty/model/events/_StreamChunk.py b/runtime/python/prompty/prompty/model/events/_StreamChunk.py index 5a668fc1..08078506 100644 --- a/runtime/python/prompty/prompty/model/events/_StreamChunk.py +++ b/runtime/python/prompty/prompty/model/events/_StreamChunk.py @@ -1,3 +1,4 @@ +# ########################################## # WARNING: This is an auto-generated file. # DO NOT EDIT THIS FILE DIRECTLY diff --git a/runtime/python/prompty/prompty/model/events/_ThinkingEventPayload.py b/runtime/python/prompty/prompty/model/events/_ThinkingEventPayload.py index f8732ca3..0639fcaf 100644 --- a/runtime/python/prompty/prompty/model/events/_ThinkingEventPayload.py +++ b/runtime/python/prompty/prompty/model/events/_ThinkingEventPayload.py @@ -1,3 +1,4 @@ +# ########################################## # WARNING: This is an auto-generated file. # DO NOT EDIT THIS FILE DIRECTLY diff --git a/runtime/python/prompty/prompty/model/events/_TokenEventPayload.py b/runtime/python/prompty/prompty/model/events/_TokenEventPayload.py index 59e3ea4d..e1ab3921 100644 --- a/runtime/python/prompty/prompty/model/events/_TokenEventPayload.py +++ b/runtime/python/prompty/prompty/model/events/_TokenEventPayload.py @@ -1,3 +1,4 @@ +# ########################################## # WARNING: This is an auto-generated file. # DO NOT EDIT THIS FILE DIRECTLY diff --git a/runtime/python/prompty/prompty/model/events/_ToolCallCompletePayload.py b/runtime/python/prompty/prompty/model/events/_ToolCallCompletePayload.py new file mode 100644 index 00000000..736fd44f --- /dev/null +++ b/runtime/python/prompty/prompty/model/events/_ToolCallCompletePayload.py @@ -0,0 +1,134 @@ +# +########################################## +# WARNING: This is an auto-generated file. +# DO NOT EDIT THIS FILE DIRECTLY +# ANY EDITS WILL BE LOST +########################################## + +from dataclasses import dataclass, field +from typing import Any, ClassVar + +from .._context import LoadContext, SaveContext +from ..conversation._ToolResult import ToolResult + + +@dataclass +class ToolCallCompletePayload: + """Payload for "tool_call_complete" events — a tool dispatch finished. + + Attributes + ---------- + id : Optional[str] + The unique identifier of the tool call + name : str + The name of the tool that completed + success : bool + Whether the tool dispatch succeeded semantically + result : Optional[ToolResult] + Normalized tool result + duration_ms : Optional[float] + Tool execution duration in milliseconds + error_kind : Optional[str] + Machine-readable error category when success is false + """ + + _shorthand_property: ClassVar[str | None] = None + + id: str | None = None + name: str = field(default="") + success: bool = field(default=False) + result: ToolResult | None = None + duration_ms: float | None = None + error_kind: str | None = None + + @staticmethod + def load(data: Any, context: LoadContext | None = None) -> "ToolCallCompletePayload": + """Load a ToolCallCompletePayload instance. + Args: + data (Any): The data to load the instance from. + context (Optional[LoadContext]): Optional context with pre/post processing callbacks. + Returns: + ToolCallCompletePayload: The loaded ToolCallCompletePayload instance. + + """ + + if context is not None: + data = context.process_input(data) + + if not isinstance(data, dict): + raise ValueError(f"Invalid data for ToolCallCompletePayload: {data}") + + # create new instance + instance = ToolCallCompletePayload() + + if data is not None and "id" in data: + instance.id = data["id"] + if data is not None and "name" in data: + instance.name = data["name"] + if data is not None and "success" in data: + instance.success = data["success"] + if data is not None and "result" in data: + instance.result = ToolResult.load(data["result"], context) + if data is not None and "durationMs" in data: + instance.duration_ms = data["durationMs"] + if data is not None and "errorKind" in data: + instance.error_kind = data["errorKind"] + if context is not None: + instance = context.process_output(instance) + return instance + + def save(self, context: SaveContext | None = None) -> dict[str, Any]: + """Save the ToolCallCompletePayload instance to a dictionary. + Args: + context (Optional[SaveContext]): Optional context with pre/post processing callbacks. + Returns: + dict[str, Any]: The dictionary representation of this instance. + + """ + obj = self + if context is not None: + obj = context.process_object(obj) + + result: dict[str, Any] = {} + + if obj.id is not None: + result["id"] = obj.id + if obj.name is not None: + result["name"] = obj.name + if obj.success is not None: + result["success"] = obj.success + if obj.result is not None: + result["result"] = obj.result.save(context) + if obj.duration_ms is not None: + result["durationMs"] = obj.duration_ms + if obj.error_kind is not None: + result["errorKind"] = obj.error_kind + + if context is not None: + result = context.process_dict(result) + return result + + def to_yaml(self, context: SaveContext | None = None) -> str: + """Convert the ToolCallCompletePayload instance to a YAML string. + Args: + context (Optional[SaveContext]): Optional context with pre/post processing callbacks. + Returns: + str: The YAML string representation of this instance. + + """ + if context is None: + context = SaveContext() + return context.to_yaml(self.save(context)) + + def to_json(self, context: SaveContext | None = None, indent: int = 2) -> str: + """Convert the ToolCallCompletePayload instance to a JSON string. + Args: + context (Optional[SaveContext]): Optional context with pre/post processing callbacks. + indent (int): Number of spaces for indentation. Defaults to 2. + Returns: + str: The JSON string representation of this instance. + + """ + if context is None: + context = SaveContext() + return context.to_json(self.save(context), indent) diff --git a/runtime/python/prompty/prompty/model/events/_ToolCallStartPayload.py b/runtime/python/prompty/prompty/model/events/_ToolCallStartPayload.py index a4489731..8870c6e6 100644 --- a/runtime/python/prompty/prompty/model/events/_ToolCallStartPayload.py +++ b/runtime/python/prompty/prompty/model/events/_ToolCallStartPayload.py @@ -1,3 +1,4 @@ +# ########################################## # WARNING: This is an auto-generated file. # DO NOT EDIT THIS FILE DIRECTLY @@ -16,6 +17,8 @@ class ToolCallStartPayload: Attributes ---------- + id : Optional[str] + The unique identifier of the tool call name : str The name of the tool being called arguments : str @@ -24,6 +27,7 @@ class ToolCallStartPayload: _shorthand_property: ClassVar[str | None] = None + id: str | None = None name: str = field(default="") arguments: str = field(default="") @@ -47,6 +51,8 @@ def load(data: Any, context: LoadContext | None = None) -> "ToolCallStartPayload # create new instance instance = ToolCallStartPayload() + if data is not None and "id" in data: + instance.id = data["id"] if data is not None and "name" in data: instance.name = data["name"] if data is not None and "arguments" in data: @@ -69,6 +75,8 @@ def save(self, context: SaveContext | None = None) -> dict[str, Any]: result: dict[str, Any] = {} + if obj.id is not None: + result["id"] = obj.id if obj.name is not None: result["name"] = obj.name if obj.arguments is not None: diff --git a/runtime/python/prompty/prompty/model/events/_ToolExecutionCompletePayload.py b/runtime/python/prompty/prompty/model/events/_ToolExecutionCompletePayload.py new file mode 100644 index 00000000..dbb9de24 --- /dev/null +++ b/runtime/python/prompty/prompty/model/events/_ToolExecutionCompletePayload.py @@ -0,0 +1,162 @@ +# +########################################## +# WARNING: This is an auto-generated file. +# DO NOT EDIT THIS FILE DIRECTLY +# ANY EDITS WILL BE LOST +########################################## + +from dataclasses import dataclass, field +from typing import Any, ClassVar + +from .._context import LoadContext, SaveContext +from ._RedactionMetadata import RedactionMetadata + + +@dataclass +class ToolExecutionCompletePayload: + """Payload for "tool_execution_complete" events — a concrete host tool execution finished. + + Attributes + ---------- + request_id : Optional[str] + Stable host execution request identifier + tool_call_id : Optional[str] + Associated model tool call identifier, when available + tool_name : str + Name of the host tool that executed + success : bool + Whether the host execution completed successfully + result : Optional[Any] + Host-normalized execution result + exit_code : Optional[int] + Process or host exit code, when applicable + duration_ms : Optional[float] + Tool execution duration in milliseconds + error_kind : Optional[str] + Machine-readable error category when success is false + telemetry : Optional[dict[str, Any]] + Host-specific telemetry for the execution + redaction : Optional[RedactionMetadata] + Redaction state for sensitive result fields + """ + + _shorthand_property: ClassVar[str | None] = None + + request_id: str | None = None + tool_call_id: str | None = None + tool_name: str = field(default="") + success: bool = field(default=False) + result: Any | None = None + exit_code: int | None = None + duration_ms: float | None = None + error_kind: str | None = None + telemetry: dict[str, Any] | None = None + redaction: RedactionMetadata | None = None + + @staticmethod + def load(data: Any, context: LoadContext | None = None) -> "ToolExecutionCompletePayload": + """Load a ToolExecutionCompletePayload instance. + Args: + data (Any): The data to load the instance from. + context (Optional[LoadContext]): Optional context with pre/post processing callbacks. + Returns: + ToolExecutionCompletePayload: The loaded ToolExecutionCompletePayload instance. + + """ + + if context is not None: + data = context.process_input(data) + + if not isinstance(data, dict): + raise ValueError(f"Invalid data for ToolExecutionCompletePayload: {data}") + + # create new instance + instance = ToolExecutionCompletePayload() + + if data is not None and "requestId" in data: + instance.request_id = data["requestId"] + if data is not None and "toolCallId" in data: + instance.tool_call_id = data["toolCallId"] + if data is not None and "toolName" in data: + instance.tool_name = data["toolName"] + if data is not None and "success" in data: + instance.success = data["success"] + if data is not None and "result" in data: + instance.result = data["result"] + if data is not None and "exitCode" in data: + instance.exit_code = data["exitCode"] + if data is not None and "durationMs" in data: + instance.duration_ms = data["durationMs"] + if data is not None and "errorKind" in data: + instance.error_kind = data["errorKind"] + if data is not None and "telemetry" in data: + instance.telemetry = data["telemetry"] + if data is not None and "redaction" in data: + instance.redaction = RedactionMetadata.load(data["redaction"], context) + if context is not None: + instance = context.process_output(instance) + return instance + + def save(self, context: SaveContext | None = None) -> dict[str, Any]: + """Save the ToolExecutionCompletePayload instance to a dictionary. + Args: + context (Optional[SaveContext]): Optional context with pre/post processing callbacks. + Returns: + dict[str, Any]: The dictionary representation of this instance. + + """ + obj = self + if context is not None: + obj = context.process_object(obj) + + result: dict[str, Any] = {} + + if obj.request_id is not None: + result["requestId"] = obj.request_id + if obj.tool_call_id is not None: + result["toolCallId"] = obj.tool_call_id + if obj.tool_name is not None: + result["toolName"] = obj.tool_name + if obj.success is not None: + result["success"] = obj.success + if obj.result is not None: + result["result"] = obj.result + if obj.exit_code is not None: + result["exitCode"] = obj.exit_code + if obj.duration_ms is not None: + result["durationMs"] = obj.duration_ms + if obj.error_kind is not None: + result["errorKind"] = obj.error_kind + if obj.telemetry is not None: + result["telemetry"] = obj.telemetry + if obj.redaction is not None: + result["redaction"] = obj.redaction.save(context) + + if context is not None: + result = context.process_dict(result) + return result + + def to_yaml(self, context: SaveContext | None = None) -> str: + """Convert the ToolExecutionCompletePayload instance to a YAML string. + Args: + context (Optional[SaveContext]): Optional context with pre/post processing callbacks. + Returns: + str: The YAML string representation of this instance. + + """ + if context is None: + context = SaveContext() + return context.to_yaml(self.save(context)) + + def to_json(self, context: SaveContext | None = None, indent: int = 2) -> str: + """Convert the ToolExecutionCompletePayload instance to a JSON string. + Args: + context (Optional[SaveContext]): Optional context with pre/post processing callbacks. + indent (int): Number of spaces for indentation. Defaults to 2. + Returns: + str: The JSON string representation of this instance. + + """ + if context is None: + context = SaveContext() + return context.to_json(self.save(context), indent) diff --git a/runtime/python/prompty/prompty/model/events/_ToolExecutionStartPayload.py b/runtime/python/prompty/prompty/model/events/_ToolExecutionStartPayload.py new file mode 100644 index 00000000..5421c4ac --- /dev/null +++ b/runtime/python/prompty/prompty/model/events/_ToolExecutionStartPayload.py @@ -0,0 +1,137 @@ +# +########################################## +# WARNING: This is an auto-generated file. +# DO NOT EDIT THIS FILE DIRECTLY +# ANY EDITS WILL BE LOST +########################################## + +from dataclasses import dataclass, field +from typing import Any, ClassVar + +from .._context import LoadContext, SaveContext +from ._RedactionMetadata import RedactionMetadata + + +@dataclass +class ToolExecutionStartPayload: + """Payload for "tool_execution_start" events — the host is about to execute a concrete tool request. + + This is distinct from "tool_call_start", which records the model requesting a tool. + Tool execution events capture the harness-side action after policy and permission checks. + + Attributes + ---------- + request_id : Optional[str] + Stable host execution request identifier + tool_call_id : Optional[str] + Associated model tool call identifier, when available + tool_name : str + Name of the host tool being executed + arguments : Optional[dict[str, Any]] + Tool arguments after host-side sanitization + working_directory : Optional[str] + Working directory or execution scope for the tool + redaction : Optional[RedactionMetadata] + Redaction state for sensitive argument fields + """ + + _shorthand_property: ClassVar[str | None] = None + + request_id: str | None = None + tool_call_id: str | None = None + tool_name: str = field(default="") + arguments: dict[str, Any] | None = None + working_directory: str | None = None + redaction: RedactionMetadata | None = None + + @staticmethod + def load(data: Any, context: LoadContext | None = None) -> "ToolExecutionStartPayload": + """Load a ToolExecutionStartPayload instance. + Args: + data (Any): The data to load the instance from. + context (Optional[LoadContext]): Optional context with pre/post processing callbacks. + Returns: + ToolExecutionStartPayload: The loaded ToolExecutionStartPayload instance. + + """ + + if context is not None: + data = context.process_input(data) + + if not isinstance(data, dict): + raise ValueError(f"Invalid data for ToolExecutionStartPayload: {data}") + + # create new instance + instance = ToolExecutionStartPayload() + + if data is not None and "requestId" in data: + instance.request_id = data["requestId"] + if data is not None and "toolCallId" in data: + instance.tool_call_id = data["toolCallId"] + if data is not None and "toolName" in data: + instance.tool_name = data["toolName"] + if data is not None and "arguments" in data: + instance.arguments = data["arguments"] + if data is not None and "workingDirectory" in data: + instance.working_directory = data["workingDirectory"] + if data is not None and "redaction" in data: + instance.redaction = RedactionMetadata.load(data["redaction"], context) + if context is not None: + instance = context.process_output(instance) + return instance + + def save(self, context: SaveContext | None = None) -> dict[str, Any]: + """Save the ToolExecutionStartPayload instance to a dictionary. + Args: + context (Optional[SaveContext]): Optional context with pre/post processing callbacks. + Returns: + dict[str, Any]: The dictionary representation of this instance. + + """ + obj = self + if context is not None: + obj = context.process_object(obj) + + result: dict[str, Any] = {} + + if obj.request_id is not None: + result["requestId"] = obj.request_id + if obj.tool_call_id is not None: + result["toolCallId"] = obj.tool_call_id + if obj.tool_name is not None: + result["toolName"] = obj.tool_name + if obj.arguments is not None: + result["arguments"] = obj.arguments + if obj.working_directory is not None: + result["workingDirectory"] = obj.working_directory + if obj.redaction is not None: + result["redaction"] = obj.redaction.save(context) + + if context is not None: + result = context.process_dict(result) + return result + + def to_yaml(self, context: SaveContext | None = None) -> str: + """Convert the ToolExecutionStartPayload instance to a YAML string. + Args: + context (Optional[SaveContext]): Optional context with pre/post processing callbacks. + Returns: + str: The YAML string representation of this instance. + + """ + if context is None: + context = SaveContext() + return context.to_yaml(self.save(context)) + + def to_json(self, context: SaveContext | None = None, indent: int = 2) -> str: + """Convert the ToolExecutionStartPayload instance to a JSON string. + Args: + context (Optional[SaveContext]): Optional context with pre/post processing callbacks. + indent (int): Number of spaces for indentation. Defaults to 2. + Returns: + str: The JSON string representation of this instance. + + """ + if context is None: + context = SaveContext() + return context.to_json(self.save(context), indent) diff --git a/runtime/python/prompty/prompty/model/events/_ToolResultPayload.py b/runtime/python/prompty/prompty/model/events/_ToolResultPayload.py index f621a884..d184dd71 100644 --- a/runtime/python/prompty/prompty/model/events/_ToolResultPayload.py +++ b/runtime/python/prompty/prompty/model/events/_ToolResultPayload.py @@ -1,3 +1,4 @@ +# ########################################## # WARNING: This is an auto-generated file. # DO NOT EDIT THIS FILE DIRECTLY diff --git a/runtime/python/prompty/prompty/model/events/_TrajectoryEvent.py b/runtime/python/prompty/prompty/model/events/_TrajectoryEvent.py new file mode 100644 index 00000000..aca0073f --- /dev/null +++ b/runtime/python/prompty/prompty/model/events/_TrajectoryEvent.py @@ -0,0 +1,155 @@ +# +########################################## +# WARNING: This is an auto-generated file. +# DO NOT EDIT THIS FILE DIRECTLY +# ANY EDITS WILL BE LOST +########################################## + +from dataclasses import dataclass, field +from typing import Any, ClassVar + +from .._context import LoadContext, SaveContext +from ._RedactionMetadata import RedactionMetadata + + +@dataclass +class TrajectoryEvent: + """A compact, replay-oriented record of one harness-side action or observation. + + Attributes + ---------- + id : Optional[str] + Stable trajectory event identifier + session_id : Optional[str] + Stable session identifier + turn_id : Optional[str] + Associated turn identifier, when available + tool_call_id : Optional[str] + Associated tool call identifier, when available + turn_index : Optional[int] + Zero-based turn index in the session + event_type : str + Host-defined trajectory event category + data : Optional[dict[str, Any]] + Sanitized event data + created_at : Optional[str] + ISO 8601 UTC timestamp when the trajectory event was recorded + redaction : Optional[RedactionMetadata] + Redaction state for sensitive trajectory fields + """ + + _shorthand_property: ClassVar[str | None] = None + + id: str | None = None + session_id: str | None = None + turn_id: str | None = None + tool_call_id: str | None = None + turn_index: int | None = None + event_type: str = field(default="") + data: dict[str, Any] | None = None + created_at: str | None = None + redaction: RedactionMetadata | None = None + + @staticmethod + def load(data: Any, context: LoadContext | None = None) -> "TrajectoryEvent": + """Load a TrajectoryEvent instance. + Args: + data (Any): The data to load the instance from. + context (Optional[LoadContext]): Optional context with pre/post processing callbacks. + Returns: + TrajectoryEvent: The loaded TrajectoryEvent instance. + + """ + + if context is not None: + data = context.process_input(data) + + if not isinstance(data, dict): + raise ValueError(f"Invalid data for TrajectoryEvent: {data}") + + # create new instance + instance = TrajectoryEvent() + + if data is not None and "id" in data: + instance.id = data["id"] + if data is not None and "sessionId" in data: + instance.session_id = data["sessionId"] + if data is not None and "turnId" in data: + instance.turn_id = data["turnId"] + if data is not None and "toolCallId" in data: + instance.tool_call_id = data["toolCallId"] + if data is not None and "turnIndex" in data: + instance.turn_index = data["turnIndex"] + if data is not None and "eventType" in data: + instance.event_type = data["eventType"] + if data is not None and "data" in data: + instance.data = data["data"] + if data is not None and "createdAt" in data: + instance.created_at = data["createdAt"] + if data is not None and "redaction" in data: + instance.redaction = RedactionMetadata.load(data["redaction"], context) + if context is not None: + instance = context.process_output(instance) + return instance + + def save(self, context: SaveContext | None = None) -> dict[str, Any]: + """Save the TrajectoryEvent instance to a dictionary. + Args: + context (Optional[SaveContext]): Optional context with pre/post processing callbacks. + Returns: + dict[str, Any]: The dictionary representation of this instance. + + """ + obj = self + if context is not None: + obj = context.process_object(obj) + + result: dict[str, Any] = {} + + if obj.id is not None: + result["id"] = obj.id + if obj.session_id is not None: + result["sessionId"] = obj.session_id + if obj.turn_id is not None: + result["turnId"] = obj.turn_id + if obj.tool_call_id is not None: + result["toolCallId"] = obj.tool_call_id + if obj.turn_index is not None: + result["turnIndex"] = obj.turn_index + if obj.event_type is not None: + result["eventType"] = obj.event_type + if obj.data is not None: + result["data"] = obj.data + if obj.created_at is not None: + result["createdAt"] = obj.created_at + if obj.redaction is not None: + result["redaction"] = obj.redaction.save(context) + + if context is not None: + result = context.process_dict(result) + return result + + def to_yaml(self, context: SaveContext | None = None) -> str: + """Convert the TrajectoryEvent instance to a YAML string. + Args: + context (Optional[SaveContext]): Optional context with pre/post processing callbacks. + Returns: + str: The YAML string representation of this instance. + + """ + if context is None: + context = SaveContext() + return context.to_yaml(self.save(context)) + + def to_json(self, context: SaveContext | None = None, indent: int = 2) -> str: + """Convert the TrajectoryEvent instance to a JSON string. + Args: + context (Optional[SaveContext]): Optional context with pre/post processing callbacks. + indent (int): Number of spaces for indentation. Defaults to 2. + Returns: + str: The JSON string representation of this instance. + + """ + if context is None: + context = SaveContext() + return context.to_json(self.save(context), indent) diff --git a/runtime/python/prompty/prompty/model/events/_TurnEndPayload.py b/runtime/python/prompty/prompty/model/events/_TurnEndPayload.py new file mode 100644 index 00000000..9c8b1dfd --- /dev/null +++ b/runtime/python/prompty/prompty/model/events/_TurnEndPayload.py @@ -0,0 +1,121 @@ +# +########################################## +# WARNING: This is an auto-generated file. +# DO NOT EDIT THIS FILE DIRECTLY +# ANY EDITS WILL BE LOST +########################################## + +from dataclasses import dataclass +from typing import Any, ClassVar, Literal + +from .._context import LoadContext, SaveContext + +TurnStatus = Literal["success", "error", "cancelled"] + + +@dataclass +class TurnEndPayload: + """Payload for "turn_end" events — a turn has completed. + + Attributes + ---------- + iterations : Optional[int] + Number of tool-call iterations performed + status : Optional[str] + Final semantic status of the turn + response : Optional[Any] + Final response after processing, if available + duration_ms : Optional[float] + Total elapsed turn duration in milliseconds + """ + + _shorthand_property: ClassVar[str | None] = None + + iterations: int | None = None + status: TurnStatus | None = None + response: Any | None = None + duration_ms: float | None = None + + @staticmethod + def load(data: Any, context: LoadContext | None = None) -> "TurnEndPayload": + """Load a TurnEndPayload instance. + Args: + data (Any): The data to load the instance from. + context (Optional[LoadContext]): Optional context with pre/post processing callbacks. + Returns: + TurnEndPayload: The loaded TurnEndPayload instance. + + """ + + if context is not None: + data = context.process_input(data) + + if not isinstance(data, dict): + raise ValueError(f"Invalid data for TurnEndPayload: {data}") + + # create new instance + instance = TurnEndPayload() + + if data is not None and "iterations" in data: + instance.iterations = data["iterations"] + if data is not None and "status" in data: + instance.status = data["status"] + if data is not None and "response" in data: + instance.response = data["response"] + if data is not None and "durationMs" in data: + instance.duration_ms = data["durationMs"] + if context is not None: + instance = context.process_output(instance) + return instance + + def save(self, context: SaveContext | None = None) -> dict[str, Any]: + """Save the TurnEndPayload instance to a dictionary. + Args: + context (Optional[SaveContext]): Optional context with pre/post processing callbacks. + Returns: + dict[str, Any]: The dictionary representation of this instance. + + """ + obj = self + if context is not None: + obj = context.process_object(obj) + + result: dict[str, Any] = {} + + if obj.iterations is not None: + result["iterations"] = obj.iterations + if obj.status is not None: + result["status"] = obj.status + if obj.response is not None: + result["response"] = obj.response + if obj.duration_ms is not None: + result["durationMs"] = obj.duration_ms + + if context is not None: + result = context.process_dict(result) + return result + + def to_yaml(self, context: SaveContext | None = None) -> str: + """Convert the TurnEndPayload instance to a YAML string. + Args: + context (Optional[SaveContext]): Optional context with pre/post processing callbacks. + Returns: + str: The YAML string representation of this instance. + + """ + if context is None: + context = SaveContext() + return context.to_yaml(self.save(context)) + + def to_json(self, context: SaveContext | None = None, indent: int = 2) -> str: + """Convert the TurnEndPayload instance to a JSON string. + Args: + context (Optional[SaveContext]): Optional context with pre/post processing callbacks. + indent (int): Number of spaces for indentation. Defaults to 2. + Returns: + str: The JSON string representation of this instance. + + """ + if context is None: + context = SaveContext() + return context.to_json(self.save(context), indent) diff --git a/runtime/python/prompty/prompty/model/events/_TurnEvent.py b/runtime/python/prompty/prompty/model/events/_TurnEvent.py new file mode 100644 index 00000000..4e447d8d --- /dev/null +++ b/runtime/python/prompty/prompty/model/events/_TurnEvent.py @@ -0,0 +1,176 @@ +# +########################################## +# WARNING: This is an auto-generated file. +# DO NOT EDIT THIS FILE DIRECTLY +# ANY EDITS WILL BE LOST +########################################## + +from dataclasses import dataclass, field +from typing import Any, ClassVar, Literal + +from .._context import LoadContext, SaveContext + +TurnEventType = Literal[ + "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", +] + + +@dataclass +class TurnEvent: + """A canonical event envelope emitted by the turn harness. The payload is kept + JSON-shaped so runtimes can load all events even when newer payload types are + added; event-specific typed payload models below define the canonical shapes. + + Attributes + ---------- + id : str + Unique identifier for this event + type : str + Event type discriminator + timestamp : str + ISO 8601 UTC timestamp when the event was emitted + turn_id : Optional[str] + Stable identifier for the outer turn + iteration : Optional[int] + Zero-based agent-loop iteration associated with the event + parent_id : Optional[str] + Parent event or span identifier for reconstructing event hierarchy + span_id : Optional[str] + Trace span identifier associated with this event + payload : dict[str, Any] + Event-specific payload. Use the typed payload model matching 'type'. + """ + + _shorthand_property: ClassVar[str | None] = None + + id: str = field(default="") + type: TurnEventType = field(default="turn_start") + timestamp: str = field(default="") + turn_id: str | None = None + iteration: int | None = None + parent_id: str | None = None + span_id: str | None = None + payload: dict[str, Any] = field(default_factory=dict) + + @staticmethod + def load(data: Any, context: LoadContext | None = None) -> "TurnEvent": + """Load a TurnEvent instance. + Args: + data (Any): The data to load the instance from. + context (Optional[LoadContext]): Optional context with pre/post processing callbacks. + Returns: + TurnEvent: The loaded TurnEvent instance. + + """ + + if context is not None: + data = context.process_input(data) + + if not isinstance(data, dict): + raise ValueError(f"Invalid data for TurnEvent: {data}") + + # create new instance + instance = TurnEvent() + + if data is not None and "id" in data: + instance.id = data["id"] + if data is not None and "type" in data: + instance.type = data["type"] + if data is not None and "timestamp" in data: + instance.timestamp = data["timestamp"] + if data is not None and "turnId" in data: + instance.turn_id = data["turnId"] + if data is not None and "iteration" in data: + instance.iteration = data["iteration"] + if data is not None and "parentId" in data: + instance.parent_id = data["parentId"] + if data is not None and "spanId" in data: + instance.span_id = data["spanId"] + if data is not None and "payload" in data: + instance.payload = data["payload"] + if context is not None: + instance = context.process_output(instance) + return instance + + def save(self, context: SaveContext | None = None) -> dict[str, Any]: + """Save the TurnEvent instance to a dictionary. + Args: + context (Optional[SaveContext]): Optional context with pre/post processing callbacks. + Returns: + dict[str, Any]: The dictionary representation of this instance. + + """ + obj = self + if context is not None: + obj = context.process_object(obj) + + result: dict[str, Any] = {} + + if obj.id is not None: + result["id"] = obj.id + if obj.type is not None: + result["type"] = obj.type + if obj.timestamp is not None: + result["timestamp"] = obj.timestamp + if obj.turn_id is not None: + result["turnId"] = obj.turn_id + if obj.iteration is not None: + result["iteration"] = obj.iteration + if obj.parent_id is not None: + result["parentId"] = obj.parent_id + if obj.span_id is not None: + result["spanId"] = obj.span_id + if obj.payload is not None: + result["payload"] = obj.payload + + if context is not None: + result = context.process_dict(result) + return result + + def to_yaml(self, context: SaveContext | None = None) -> str: + """Convert the TurnEvent instance to a YAML string. + Args: + context (Optional[SaveContext]): Optional context with pre/post processing callbacks. + Returns: + str: The YAML string representation of this instance. + + """ + if context is None: + context = SaveContext() + return context.to_yaml(self.save(context)) + + def to_json(self, context: SaveContext | None = None, indent: int = 2) -> str: + """Convert the TurnEvent instance to a JSON string. + Args: + context (Optional[SaveContext]): Optional context with pre/post processing callbacks. + indent (int): Number of spaces for indentation. Defaults to 2. + Returns: + str: The JSON string representation of this instance. + + """ + if context is None: + context = SaveContext() + return context.to_json(self.save(context), indent) diff --git a/runtime/python/prompty/prompty/model/events/_TurnStartPayload.py b/runtime/python/prompty/prompty/model/events/_TurnStartPayload.py new file mode 100644 index 00000000..bb45b0e1 --- /dev/null +++ b/runtime/python/prompty/prompty/model/events/_TurnStartPayload.py @@ -0,0 +1,112 @@ +# +########################################## +# WARNING: This is an auto-generated file. +# DO NOT EDIT THIS FILE DIRECTLY +# ANY EDITS WILL BE LOST +########################################## + +from dataclasses import dataclass +from typing import Any, ClassVar + +from .._context import LoadContext, SaveContext + + +@dataclass +class TurnStartPayload: + """Payload for "turn_start" events — a turn is beginning. + + Attributes + ---------- + agent : Optional[str] + Name of the loaded prompt/agent, when available + inputs : Optional[dict[str, Any]] + Input values supplied to the turn after host-side sanitization + max_iterations : Optional[int] + Configured maximum tool-call iterations + """ + + _shorthand_property: ClassVar[str | None] = None + + agent: str | None = None + inputs: dict[str, Any] | None = None + max_iterations: int | None = None + + @staticmethod + def load(data: Any, context: LoadContext | None = None) -> "TurnStartPayload": + """Load a TurnStartPayload instance. + Args: + data (Any): The data to load the instance from. + context (Optional[LoadContext]): Optional context with pre/post processing callbacks. + Returns: + TurnStartPayload: The loaded TurnStartPayload instance. + + """ + + if context is not None: + data = context.process_input(data) + + if not isinstance(data, dict): + raise ValueError(f"Invalid data for TurnStartPayload: {data}") + + # create new instance + instance = TurnStartPayload() + + if data is not None and "agent" in data: + instance.agent = data["agent"] + if data is not None and "inputs" in data: + instance.inputs = data["inputs"] + if data is not None and "maxIterations" in data: + instance.max_iterations = data["maxIterations"] + if context is not None: + instance = context.process_output(instance) + return instance + + def save(self, context: SaveContext | None = None) -> dict[str, Any]: + """Save the TurnStartPayload instance to a dictionary. + Args: + context (Optional[SaveContext]): Optional context with pre/post processing callbacks. + Returns: + dict[str, Any]: The dictionary representation of this instance. + + """ + obj = self + if context is not None: + obj = context.process_object(obj) + + result: dict[str, Any] = {} + + if obj.agent is not None: + result["agent"] = obj.agent + if obj.inputs is not None: + result["inputs"] = obj.inputs + if obj.max_iterations is not None: + result["maxIterations"] = obj.max_iterations + + if context is not None: + result = context.process_dict(result) + return result + + def to_yaml(self, context: SaveContext | None = None) -> str: + """Convert the TurnStartPayload instance to a YAML string. + Args: + context (Optional[SaveContext]): Optional context with pre/post processing callbacks. + Returns: + str: The YAML string representation of this instance. + + """ + if context is None: + context = SaveContext() + return context.to_yaml(self.save(context)) + + def to_json(self, context: SaveContext | None = None, indent: int = 2) -> str: + """Convert the TurnStartPayload instance to a JSON string. + Args: + context (Optional[SaveContext]): Optional context with pre/post processing callbacks. + indent (int): Number of spaces for indentation. Defaults to 2. + Returns: + str: The JSON string representation of this instance. + + """ + if context is None: + context = SaveContext() + return context.to_json(self.save(context), indent) diff --git a/runtime/python/prompty/prompty/model/events/_TurnSummary.py b/runtime/python/prompty/prompty/model/events/_TurnSummary.py new file mode 100644 index 00000000..03bc0c37 --- /dev/null +++ b/runtime/python/prompty/prompty/model/events/_TurnSummary.py @@ -0,0 +1,148 @@ +# +########################################## +# WARNING: This is an auto-generated file. +# DO NOT EDIT THIS FILE DIRECTLY +# ANY EDITS WILL BE LOST +########################################## + +from dataclasses import dataclass, field +from typing import Any, ClassVar + +from .._context import LoadContext, SaveContext +from ..model._TokenUsage import TokenUsage + + +@dataclass +class TurnSummary: + """Summary statistics for a completed turn trace. + + Attributes + ---------- + turn_id : str + Stable identifier for the outer turn + status : str + Final turn status: 'success', 'error', or 'cancelled' + iterations : int + Number of agent-loop iterations + llm_calls : Optional[int] + Number of LLM calls made during the turn + tool_calls : Optional[int] + Number of tool calls dispatched during the turn + retries : Optional[int] + Number of retry events during the turn + usage : Optional[TokenUsage] + Aggregated token usage for the turn + duration_ms : Optional[float] + Total elapsed turn duration in milliseconds + """ + + _shorthand_property: ClassVar[str | None] = None + + turn_id: str = field(default="") + status: str = field(default="") + iterations: int = field(default=0) + llm_calls: int | None = None + tool_calls: int | None = None + retries: int | None = None + usage: TokenUsage | None = None + duration_ms: float | None = None + + @staticmethod + def load(data: Any, context: LoadContext | None = None) -> "TurnSummary": + """Load a TurnSummary instance. + Args: + data (Any): The data to load the instance from. + context (Optional[LoadContext]): Optional context with pre/post processing callbacks. + Returns: + TurnSummary: The loaded TurnSummary instance. + + """ + + if context is not None: + data = context.process_input(data) + + if not isinstance(data, dict): + raise ValueError(f"Invalid data for TurnSummary: {data}") + + # create new instance + instance = TurnSummary() + + if data is not None and "turnId" in data: + instance.turn_id = data["turnId"] + if data is not None and "status" in data: + instance.status = data["status"] + if data is not None and "iterations" in data: + instance.iterations = data["iterations"] + if data is not None and "llmCalls" in data: + instance.llm_calls = data["llmCalls"] + if data is not None and "toolCalls" in data: + instance.tool_calls = data["toolCalls"] + if data is not None and "retries" in data: + instance.retries = data["retries"] + if data is not None and "usage" in data: + instance.usage = TokenUsage.load(data["usage"], context) + if data is not None and "durationMs" in data: + instance.duration_ms = data["durationMs"] + if context is not None: + instance = context.process_output(instance) + return instance + + def save(self, context: SaveContext | None = None) -> dict[str, Any]: + """Save the TurnSummary instance to a dictionary. + Args: + context (Optional[SaveContext]): Optional context with pre/post processing callbacks. + Returns: + dict[str, Any]: The dictionary representation of this instance. + + """ + obj = self + if context is not None: + obj = context.process_object(obj) + + result: dict[str, Any] = {} + + if obj.turn_id is not None: + result["turnId"] = obj.turn_id + if obj.status is not None: + result["status"] = obj.status + if obj.iterations is not None: + result["iterations"] = obj.iterations + if obj.llm_calls is not None: + result["llmCalls"] = obj.llm_calls + if obj.tool_calls is not None: + result["toolCalls"] = obj.tool_calls + if obj.retries is not None: + result["retries"] = obj.retries + if obj.usage is not None: + result["usage"] = obj.usage.save(context) + if obj.duration_ms is not None: + result["durationMs"] = obj.duration_ms + + if context is not None: + result = context.process_dict(result) + return result + + def to_yaml(self, context: SaveContext | None = None) -> str: + """Convert the TurnSummary instance to a YAML string. + Args: + context (Optional[SaveContext]): Optional context with pre/post processing callbacks. + Returns: + str: The YAML string representation of this instance. + + """ + if context is None: + context = SaveContext() + return context.to_yaml(self.save(context)) + + def to_json(self, context: SaveContext | None = None, indent: int = 2) -> str: + """Convert the TurnSummary instance to a JSON string. + Args: + context (Optional[SaveContext]): Optional context with pre/post processing callbacks. + indent (int): Number of spaces for indentation. Defaults to 2. + Returns: + str: The JSON string representation of this instance. + + """ + if context is None: + context = SaveContext() + return context.to_json(self.save(context), indent) diff --git a/runtime/python/prompty/prompty/model/events/_TurnTrace.py b/runtime/python/prompty/prompty/model/events/_TurnTrace.py new file mode 100644 index 00000000..99798bab --- /dev/null +++ b/runtime/python/prompty/prompty/model/events/_TurnTrace.py @@ -0,0 +1,151 @@ +# +########################################## +# WARNING: This is an auto-generated file. +# DO NOT EDIT THIS FILE DIRECTLY +# ANY EDITS WILL BE LOST +########################################## + +from dataclasses import dataclass, field +from typing import Any, ClassVar + +from .._context import LoadContext, SaveContext +from ._TurnEvent import TurnEvent +from ._TurnSummary import TurnSummary + + +@dataclass +class TurnTrace: + """Portable JSONL/replay container for a recorded turn harness run. + + Attributes + ---------- + version : str + Trace schema version + runtime : Optional[str] + Runtime name that produced the trace + prompty_version : Optional[str] + Prompty library version that produced the trace + events : list[TurnEvent] + Recorded turn events in emission order + summary : Optional[TurnSummary] + Optional summary computed from the event stream + """ + + _shorthand_property: ClassVar[str | None] = None + + version: str = field(default="1") + runtime: str | None = None + prompty_version: str | None = None + events: list[TurnEvent] = field(default_factory=list) + summary: TurnSummary | None = None + + @staticmethod + def load(data: Any, context: LoadContext | None = None) -> "TurnTrace": + """Load a TurnTrace instance. + Args: + data (Any): The data to load the instance from. + context (Optional[LoadContext]): Optional context with pre/post processing callbacks. + Returns: + TurnTrace: The loaded TurnTrace instance. + + """ + + if context is not None: + data = context.process_input(data) + + if not isinstance(data, dict): + raise ValueError(f"Invalid data for TurnTrace: {data}") + + # create new instance + instance = TurnTrace() + + if data is not None and "version" in data: + instance.version = data["version"] + if data is not None and "runtime" in data: + instance.runtime = data["runtime"] + if data is not None and "promptyVersion" in data: + instance.prompty_version = data["promptyVersion"] + if data is not None and "events" in data: + instance.events = TurnTrace.load_events(data["events"], context) + if data is not None and "summary" in data: + instance.summary = TurnSummary.load(data["summary"], context) + if context is not None: + instance = context.process_output(instance) + return instance + + @staticmethod + def load_events(data: dict | list, context: LoadContext | None) -> list[TurnEvent]: + if isinstance(data, dict): + # convert simple named events to list of TurnEvent + result = [] + for k, v in data.items(): + if isinstance(v, dict): + # value is an object, spread its properties + result.append({"name": k, **v}) + else: + # value is a scalar, use it as the primary property + result.append({"name": k, "id": v}) + data = result + return [TurnEvent.load(item, context) for item in data] + + @staticmethod + def save_events(items: list[TurnEvent], context: SaveContext | None) -> dict[str, Any] | list[dict[str, Any]]: + if context is None: + context = SaveContext() + + # This type doesn't have a 'name' property, so always use array format + return [item.save(context) for item in items] + + def save(self, context: SaveContext | None = None) -> dict[str, Any]: + """Save the TurnTrace instance to a dictionary. + Args: + context (Optional[SaveContext]): Optional context with pre/post processing callbacks. + Returns: + dict[str, Any]: The dictionary representation of this instance. + + """ + obj = self + if context is not None: + obj = context.process_object(obj) + + result: dict[str, Any] = {} + + if obj.version is not None: + result["version"] = obj.version + if obj.runtime is not None: + result["runtime"] = obj.runtime + if obj.prompty_version is not None: + result["promptyVersion"] = obj.prompty_version + if obj.events is not None: + result["events"] = TurnTrace.save_events(obj.events, context) + if obj.summary is not None: + result["summary"] = obj.summary.save(context) + + if context is not None: + result = context.process_dict(result) + return result + + def to_yaml(self, context: SaveContext | None = None) -> str: + """Convert the TurnTrace instance to a YAML string. + Args: + context (Optional[SaveContext]): Optional context with pre/post processing callbacks. + Returns: + str: The YAML string representation of this instance. + + """ + if context is None: + context = SaveContext() + return context.to_yaml(self.save(context)) + + def to_json(self, context: SaveContext | None = None, indent: int = 2) -> str: + """Convert the TurnTrace instance to a JSON string. + Args: + context (Optional[SaveContext]): Optional context with pre/post processing callbacks. + indent (int): Number of spaces for indentation. Defaults to 2. + Returns: + str: The JSON string representation of this instance. + + """ + if context is None: + context = SaveContext() + return context.to_json(self.save(context), indent) diff --git a/runtime/python/prompty/prompty/model/events/__init__.py b/runtime/python/prompty/prompty/model/events/__init__.py index a2404644..357d7590 100644 --- a/runtime/python/prompty/prompty/model/events/__init__.py +++ b/runtime/python/prompty/prompty/model/events/__init__.py @@ -1,13 +1,38 @@ +# ########################################## # WARNING: This is an auto-generated file. # DO NOT EDIT THIS FILE DIRECTLY # ANY EDITS WILL BE LOST ########################################## +from ._Checkpoint import Checkpoint from ._CompactionCompletePayload import CompactionCompletePayload from ._CompactionFailedPayload import CompactionFailedPayload +from ._CompactionStartPayload import CompactionStartPayload from ._DoneEventPayload import DoneEventPayload from ._ErrorEventPayload import ErrorEventPayload +from ._HarnessContext import HarnessContext +from ._HookEndPayload import HookEndPayload +from ._HookStartPayload import HookStartPayload +from ._HostToolRequest import HostToolRequest +from ._HostToolResult import HostToolResult +from ._LlmCompletePayload import LlmCompletePayload +from ._LlmStartPayload import LlmStartPayload from ._MessagesUpdatedPayload import MessagesUpdatedPayload +from ._PermissionCompletedPayload import PermissionCompletedPayload +from ._PermissionDecision import PermissionDecision +from ._PermissionRequest import PermissionRequest +from ._PermissionRequestedPayload import PermissionRequestedPayload +from ._RedactedField import RedactedField +from ._RedactionMetadata import RedactionMetadata +from ._RetryPayload import RetryPayload +from ._SessionEndPayload import SessionEndPayload +from ._SessionEvent import SessionEvent +from ._SessionFileRef import SessionFileRef +from ._SessionRef import SessionRef +from ._SessionStartPayload import SessionStartPayload +from ._SessionSummary import SessionSummary +from ._SessionTrace import SessionTrace +from ._SessionWarningPayload import SessionWarningPayload from ._StatusEventPayload import StatusEventPayload from ._StreamChunk import ( ErrorChunk, @@ -18,20 +43,62 @@ ) from ._ThinkingEventPayload import ThinkingEventPayload from ._TokenEventPayload import TokenEventPayload +from ._ToolCallCompletePayload import ToolCallCompletePayload from ._ToolCallStartPayload import ToolCallStartPayload +from ._ToolExecutionCompletePayload import ToolExecutionCompletePayload +from ._ToolExecutionStartPayload import ToolExecutionStartPayload from ._ToolResultPayload import ToolResultPayload +from ._TrajectoryEvent import TrajectoryEvent +from ._TurnEndPayload import TurnEndPayload +from ._TurnEvent import TurnEvent +from ._TurnStartPayload import TurnStartPayload +from ._TurnSummary import TurnSummary +from ._TurnTrace import TurnTrace __all__ = [ + "HostToolResult", + "HostToolRequest", + "RedactedField", + "RedactionMetadata", + "Checkpoint", + "TurnEvent", + "TurnStartPayload", + "TurnEndPayload", + "LlmStartPayload", + "LlmCompletePayload", + "RetryPayload", + "PermissionRequestedPayload", + "PermissionCompletedPayload", + "PermissionRequest", + "PermissionDecision", "TokenEventPayload", "ThinkingEventPayload", "ToolCallStartPayload", + "ToolCallCompletePayload", + "ToolExecutionStartPayload", + "ToolExecutionCompletePayload", + "HookStartPayload", + "HookEndPayload", "ToolResultPayload", "StatusEventPayload", "MessagesUpdatedPayload", "DoneEventPayload", "ErrorEventPayload", + "CompactionStartPayload", "CompactionCompletePayload", "CompactionFailedPayload", + "TurnSummary", + "TurnTrace", + "HarnessContext", + "SessionStartPayload", + "SessionEndPayload", + "SessionWarningPayload", + "SessionEvent", + "TrajectoryEvent", + "SessionFileRef", + "SessionRef", + "SessionSummary", + "SessionTrace", "StreamChunk", "TextChunk", "ThinkingChunk", diff --git a/runtime/python/prompty/prompty/model/model/_Model.py b/runtime/python/prompty/prompty/model/model/_Model.py index 42633ca3..aa0341c5 100644 --- a/runtime/python/prompty/prompty/model/model/_Model.py +++ b/runtime/python/prompty/prompty/model/model/_Model.py @@ -1,3 +1,4 @@ +# ########################################## # WARNING: This is an auto-generated file. # DO NOT EDIT THIS FILE DIRECTLY diff --git a/runtime/python/prompty/prompty/model/model/_ModelInfo.py b/runtime/python/prompty/prompty/model/model/_ModelInfo.py index 75a0c9e1..d1c5a9f5 100644 --- a/runtime/python/prompty/prompty/model/model/_ModelInfo.py +++ b/runtime/python/prompty/prompty/model/model/_ModelInfo.py @@ -1,3 +1,4 @@ +# ########################################## # WARNING: This is an auto-generated file. # DO NOT EDIT THIS FILE DIRECTLY diff --git a/runtime/python/prompty/prompty/model/model/_ModelOptions.py b/runtime/python/prompty/prompty/model/model/_ModelOptions.py index 9326c2e2..a73310d7 100644 --- a/runtime/python/prompty/prompty/model/model/_ModelOptions.py +++ b/runtime/python/prompty/prompty/model/model/_ModelOptions.py @@ -1,3 +1,4 @@ +# ########################################## # WARNING: This is an auto-generated file. # DO NOT EDIT THIS FILE DIRECTLY diff --git a/runtime/python/prompty/prompty/model/model/_TokenUsage.py b/runtime/python/prompty/prompty/model/model/_TokenUsage.py index 22d02c9d..e4ecd1ae 100644 --- a/runtime/python/prompty/prompty/model/model/_TokenUsage.py +++ b/runtime/python/prompty/prompty/model/model/_TokenUsage.py @@ -1,3 +1,4 @@ +# ########################################## # WARNING: This is an auto-generated file. # DO NOT EDIT THIS FILE DIRECTLY diff --git a/runtime/python/prompty/prompty/model/model/__init__.py b/runtime/python/prompty/prompty/model/model/__init__.py index 672e31b9..1e4ae9e4 100644 --- a/runtime/python/prompty/prompty/model/model/__init__.py +++ b/runtime/python/prompty/prompty/model/model/__init__.py @@ -1,3 +1,4 @@ +# ########################################## # WARNING: This is an auto-generated file. # DO NOT EDIT THIS FILE DIRECTLY diff --git a/runtime/python/prompty/prompty/model/pipeline/_CheckpointStore.py b/runtime/python/prompty/prompty/model/pipeline/_CheckpointStore.py new file mode 100644 index 00000000..89681cac --- /dev/null +++ b/runtime/python/prompty/prompty/model/pipeline/_CheckpointStore.py @@ -0,0 +1,39 @@ +# +########################################## +# WARNING: This is an auto-generated file. +# DO NOT EDIT THIS FILE DIRECTLY +# ANY EDITS WILL BE LOST +########################################## + +from typing import Protocol, runtime_checkable + +from ..events._Checkpoint import Checkpoint + + +@runtime_checkable +class CheckpointStore(Protocol): + """Stores and retrieves resumable session checkpoints.""" + + def save(self, checkpoint: Checkpoint) -> Checkpoint: + """Persist a session checkpoint and return the stored checkpoint""" + ... + + async def save_async(self, checkpoint: Checkpoint) -> Checkpoint: + """Persist a session checkpoint and return the stored checkpoint (async variant)""" + ... + + def load(self, session_id: str, checkpoint_id: str) -> Checkpoint | None: + """Load a checkpoint by session and checkpoint identifier""" + ... + + async def load_async(self, session_id: str, checkpoint_id: str) -> Checkpoint | None: + """Load a checkpoint by session and checkpoint identifier (async variant)""" + ... + + def list_checkpoints(self, session_id: str) -> list[Checkpoint]: + """List checkpoints for a session""" + ... + + async def list_checkpoints_async(self, session_id: str) -> list[Checkpoint]: + """List checkpoints for a session (async variant)""" + ... diff --git a/runtime/python/prompty/prompty/model/pipeline/_CompactionConfig.py b/runtime/python/prompty/prompty/model/pipeline/_CompactionConfig.py index 087b08b0..112bf9d0 100644 --- a/runtime/python/prompty/prompty/model/pipeline/_CompactionConfig.py +++ b/runtime/python/prompty/prompty/model/pipeline/_CompactionConfig.py @@ -1,3 +1,4 @@ +# ########################################## # WARNING: This is an auto-generated file. # DO NOT EDIT THIS FILE DIRECTLY diff --git a/runtime/python/prompty/prompty/model/pipeline/_EventJournalWriter.py b/runtime/python/prompty/prompty/model/pipeline/_EventJournalWriter.py new file mode 100644 index 00000000..21960aeb --- /dev/null +++ b/runtime/python/prompty/prompty/model/pipeline/_EventJournalWriter.py @@ -0,0 +1,29 @@ +# +########################################## +# WARNING: This is an auto-generated file. +# DO NOT EDIT THIS FILE DIRECTLY +# ANY EDITS WILL BE LOST +########################################## + +from typing import Protocol, runtime_checkable + +from ..events._SessionEvent import SessionEvent +from ..events._SessionSummary import SessionSummary +from ..events._TurnEvent import TurnEvent + + +@runtime_checkable +class EventJournalWriter(Protocol): + """Persists typed events to a durable replay journal.""" + + def append_turn(self, turn_event: TurnEvent) -> bool: + """Append a turn event to a durable replay journal""" + ... + + def append_session(self, session_event: SessionEvent) -> bool: + """Append a session event to a durable replay journal""" + ... + + def close(self, summary: SessionSummary | None) -> bool: + """Finalize the journal with an optional session summary""" + ... diff --git a/runtime/python/prompty/prompty/model/pipeline/_EventSink.py b/runtime/python/prompty/prompty/model/pipeline/_EventSink.py new file mode 100644 index 00000000..a78bbe1d --- /dev/null +++ b/runtime/python/prompty/prompty/model/pipeline/_EventSink.py @@ -0,0 +1,24 @@ +# +########################################## +# WARNING: This is an auto-generated file. +# DO NOT EDIT THIS FILE DIRECTLY +# ANY EDITS WILL BE LOST +########################################## + +from typing import Protocol, runtime_checkable + +from ..events._SessionEvent import SessionEvent +from ..events._TurnEvent import TurnEvent + + +@runtime_checkable +class EventSink(Protocol): + """Receives typed turn and session events from a harness.""" + + def emit_turn(self, turn_event: TurnEvent) -> bool: + """Emit a typed turn event to a host sink""" + ... + + def emit_session(self, session_event: SessionEvent) -> bool: + """Emit a typed session event to a host sink""" + ... diff --git a/runtime/python/prompty/prompty/model/pipeline/_Executor.py b/runtime/python/prompty/prompty/model/pipeline/_Executor.py index 4b18564f..2ec3313a 100644 --- a/runtime/python/prompty/prompty/model/pipeline/_Executor.py +++ b/runtime/python/prompty/prompty/model/pipeline/_Executor.py @@ -1,3 +1,4 @@ +# ########################################## # WARNING: This is an auto-generated file. # DO NOT EDIT THIS FILE DIRECTLY diff --git a/runtime/python/prompty/prompty/model/pipeline/_HostToolExecutor.py b/runtime/python/prompty/prompty/model/pipeline/_HostToolExecutor.py new file mode 100644 index 00000000..46f451e3 --- /dev/null +++ b/runtime/python/prompty/prompty/model/pipeline/_HostToolExecutor.py @@ -0,0 +1,24 @@ +# +########################################## +# WARNING: This is an auto-generated file. +# DO NOT EDIT THIS FILE DIRECTLY +# ANY EDITS WILL BE LOST +########################################## + +from typing import Protocol, runtime_checkable + +from ..events._HostToolRequest import HostToolRequest +from ..events._HostToolResult import HostToolResult + + +@runtime_checkable +class HostToolExecutor(Protocol): + """Executes host tools after policy and permission checks.""" + + def execute(self, request: HostToolRequest) -> HostToolResult: + """Execute a concrete host tool request and return its completion payload""" + ... + + async def execute_async(self, request: HostToolRequest) -> HostToolResult: + """Execute a concrete host tool request and return its completion payload (async variant)""" + ... diff --git a/runtime/python/prompty/prompty/model/pipeline/_Parser.py b/runtime/python/prompty/prompty/model/pipeline/_Parser.py index 4ee14088..b9aebf0e 100644 --- a/runtime/python/prompty/prompty/model/pipeline/_Parser.py +++ b/runtime/python/prompty/prompty/model/pipeline/_Parser.py @@ -1,3 +1,4 @@ +# ########################################## # WARNING: This is an auto-generated file. # DO NOT EDIT THIS FILE DIRECTLY diff --git a/runtime/python/prompty/prompty/model/pipeline/_PermissionResolver.py b/runtime/python/prompty/prompty/model/pipeline/_PermissionResolver.py new file mode 100644 index 00000000..306b6907 --- /dev/null +++ b/runtime/python/prompty/prompty/model/pipeline/_PermissionResolver.py @@ -0,0 +1,24 @@ +# +########################################## +# WARNING: This is an auto-generated file. +# DO NOT EDIT THIS FILE DIRECTLY +# ANY EDITS WILL BE LOST +########################################## + +from typing import Protocol, runtime_checkable + +from ..events._PermissionDecision import PermissionDecision +from ..events._PermissionRequest import PermissionRequest + + +@runtime_checkable +class PermissionResolver(Protocol): + """Resolves host permission requests for potentially sensitive actions.""" + + def request(self, request: PermissionRequest) -> PermissionDecision: + """Resolve a host permission request""" + ... + + async def request_async(self, request: PermissionRequest) -> PermissionDecision: + """Resolve a host permission request (async variant)""" + ... diff --git a/runtime/python/prompty/prompty/model/pipeline/_Processor.py b/runtime/python/prompty/prompty/model/pipeline/_Processor.py index 78dea4d5..110e71c6 100644 --- a/runtime/python/prompty/prompty/model/pipeline/_Processor.py +++ b/runtime/python/prompty/prompty/model/pipeline/_Processor.py @@ -1,3 +1,4 @@ +# ########################################## # WARNING: This is an auto-generated file. # DO NOT EDIT THIS FILE DIRECTLY diff --git a/runtime/python/prompty/prompty/model/pipeline/_Renderer.py b/runtime/python/prompty/prompty/model/pipeline/_Renderer.py index 02e0cf5e..c0794c6d 100644 --- a/runtime/python/prompty/prompty/model/pipeline/_Renderer.py +++ b/runtime/python/prompty/prompty/model/pipeline/_Renderer.py @@ -1,3 +1,4 @@ +# ########################################## # WARNING: This is an auto-generated file. # DO NOT EDIT THIS FILE DIRECTLY diff --git a/runtime/python/prompty/prompty/model/pipeline/_ReplayJournalRecord.py b/runtime/python/prompty/prompty/model/pipeline/_ReplayJournalRecord.py new file mode 100644 index 00000000..0ad265f0 --- /dev/null +++ b/runtime/python/prompty/prompty/model/pipeline/_ReplayJournalRecord.py @@ -0,0 +1,182 @@ +# +########################################## +# WARNING: This is an auto-generated file. +# DO NOT EDIT THIS FILE DIRECTLY +# ANY EDITS WILL BE LOST +########################################## + +from dataclasses import dataclass, field +from typing import Any, ClassVar, Literal + +from .._context import LoadContext, SaveContext + +ReplayRecordKind = Literal["session", "turn", "summary"] +ReplayRecordStatus = Literal["success", "error", "cancelled"] + + +@dataclass +class ReplayJournalRecord: + """Stable, replay-comparable projection of a journal record. + + Runtime journal records may carry additional payload fields, durations, telemetry, + or provider-specific data. Replay verification compares this normalized shape so + deterministic orchestration semantics are mechanically shared across runtimes. + + Attributes + ---------- + kind : str + Journal record kind + type : Optional[str] + Turn or session event type, when kind is not summary + session_id : Optional[str] + Stable harness session identifier + turn_id : Optional[str] + Stable turn identifier within the session + iteration : Optional[int] + Zero-based model loop iteration for turn records + status : Optional[str] + Final semantic status for turn/session/summary records + request_id : Optional[str] + Permission request identifier for permission request records + tool_name : Optional[str] + Host tool name for tool execution/result records + success : Optional[bool] + Whether a permission or host tool operation succeeded + error_kind : Optional[str] + Stable error discriminator for failed records + turns : Optional[int] + Number of turns represented by a summary record + checkpoints : Optional[int] + Number of checkpoints represented by a summary record + """ + + _shorthand_property: ClassVar[str | None] = None + + kind: ReplayRecordKind = field(default="session") + type: str | None = None + session_id: str | None = None + turn_id: str | None = None + iteration: int | None = None + status: ReplayRecordStatus | None = None + request_id: str | None = None + tool_name: str | None = None + success: bool | None = None + error_kind: str | None = None + turns: int | None = None + checkpoints: int | None = None + + @staticmethod + def load(data: Any, context: LoadContext | None = None) -> "ReplayJournalRecord": + """Load a ReplayJournalRecord instance. + Args: + data (Any): The data to load the instance from. + context (Optional[LoadContext]): Optional context with pre/post processing callbacks. + Returns: + ReplayJournalRecord: The loaded ReplayJournalRecord instance. + + """ + + if context is not None: + data = context.process_input(data) + + if not isinstance(data, dict): + raise ValueError(f"Invalid data for ReplayJournalRecord: {data}") + + # create new instance + instance = ReplayJournalRecord() + + if data is not None and "kind" in data: + instance.kind = data["kind"] + if data is not None and "type" in data: + instance.type = data["type"] + if data is not None and "sessionId" in data: + instance.session_id = data["sessionId"] + if data is not None and "turnId" in data: + instance.turn_id = data["turnId"] + if data is not None and "iteration" in data: + instance.iteration = data["iteration"] + if data is not None and "status" in data: + instance.status = data["status"] + if data is not None and "requestId" in data: + instance.request_id = data["requestId"] + if data is not None and "toolName" in data: + instance.tool_name = data["toolName"] + if data is not None and "success" in data: + instance.success = data["success"] + if data is not None and "errorKind" in data: + instance.error_kind = data["errorKind"] + if data is not None and "turns" in data: + instance.turns = data["turns"] + if data is not None and "checkpoints" in data: + instance.checkpoints = data["checkpoints"] + if context is not None: + instance = context.process_output(instance) + return instance + + def save(self, context: SaveContext | None = None) -> dict[str, Any]: + """Save the ReplayJournalRecord instance to a dictionary. + Args: + context (Optional[SaveContext]): Optional context with pre/post processing callbacks. + Returns: + dict[str, Any]: The dictionary representation of this instance. + + """ + obj = self + if context is not None: + obj = context.process_object(obj) + + result: dict[str, Any] = {} + + if obj.kind is not None: + result["kind"] = obj.kind + if obj.type is not None: + result["type"] = obj.type + if obj.session_id is not None: + result["sessionId"] = obj.session_id + if obj.turn_id is not None: + result["turnId"] = obj.turn_id + if obj.iteration is not None: + result["iteration"] = obj.iteration + if obj.status is not None: + result["status"] = obj.status + if obj.request_id is not None: + result["requestId"] = obj.request_id + if obj.tool_name is not None: + result["toolName"] = obj.tool_name + if obj.success is not None: + result["success"] = obj.success + if obj.error_kind is not None: + result["errorKind"] = obj.error_kind + if obj.turns is not None: + result["turns"] = obj.turns + if obj.checkpoints is not None: + result["checkpoints"] = obj.checkpoints + + if context is not None: + result = context.process_dict(result) + return result + + def to_yaml(self, context: SaveContext | None = None) -> str: + """Convert the ReplayJournalRecord instance to a YAML string. + Args: + context (Optional[SaveContext]): Optional context with pre/post processing callbacks. + Returns: + str: The YAML string representation of this instance. + + """ + if context is None: + context = SaveContext() + return context.to_yaml(self.save(context)) + + def to_json(self, context: SaveContext | None = None, indent: int = 2) -> str: + """Convert the ReplayJournalRecord instance to a JSON string. + Args: + context (Optional[SaveContext]): Optional context with pre/post processing callbacks. + indent (int): Number of spaces for indentation. Defaults to 2. + Returns: + str: The JSON string representation of this instance. + + """ + if context is None: + context = SaveContext() + return context.to_json(self.save(context), indent) diff --git a/runtime/python/prompty/prompty/model/pipeline/_ReplayMismatch.py b/runtime/python/prompty/prompty/model/pipeline/_ReplayMismatch.py new file mode 100644 index 00000000..da5b7e55 --- /dev/null +++ b/runtime/python/prompty/prompty/model/pipeline/_ReplayMismatch.py @@ -0,0 +1,120 @@ +# +########################################## +# WARNING: This is an auto-generated file. +# DO NOT EDIT THIS FILE DIRECTLY +# ANY EDITS WILL BE LOST +########################################## + +from dataclasses import dataclass, field +from typing import Any, ClassVar + +from .._context import LoadContext, SaveContext +from ._ReplayJournalRecord import ReplayJournalRecord + + +@dataclass +class ReplayMismatch: + """A single mismatch produced by replay verification. + + Attributes + ---------- + index : int + Zero-based record index where the mismatch was found + expected : Optional[ReplayJournalRecord] + Expected record at this index, when present + actual : Optional[ReplayJournalRecord] + Actual record at this index, when present + message : str + Human-readable mismatch explanation + """ + + _shorthand_property: ClassVar[str | None] = None + + index: int = field(default=0) + expected: ReplayJournalRecord | None = None + actual: ReplayJournalRecord | None = None + message: str = field(default="") + + @staticmethod + def load(data: Any, context: LoadContext | None = None) -> "ReplayMismatch": + """Load a ReplayMismatch instance. + Args: + data (Any): The data to load the instance from. + context (Optional[LoadContext]): Optional context with pre/post processing callbacks. + Returns: + ReplayMismatch: The loaded ReplayMismatch instance. + + """ + + if context is not None: + data = context.process_input(data) + + if not isinstance(data, dict): + raise ValueError(f"Invalid data for ReplayMismatch: {data}") + + # create new instance + instance = ReplayMismatch() + + if data is not None and "index" in data: + instance.index = data["index"] + if data is not None and "expected" in data: + instance.expected = ReplayJournalRecord.load(data["expected"], context) + if data is not None and "actual" in data: + instance.actual = ReplayJournalRecord.load(data["actual"], context) + if data is not None and "message" in data: + instance.message = data["message"] + if context is not None: + instance = context.process_output(instance) + return instance + + def save(self, context: SaveContext | None = None) -> dict[str, Any]: + """Save the ReplayMismatch instance to a dictionary. + Args: + context (Optional[SaveContext]): Optional context with pre/post processing callbacks. + Returns: + dict[str, Any]: The dictionary representation of this instance. + + """ + obj = self + if context is not None: + obj = context.process_object(obj) + + result: dict[str, Any] = {} + + if obj.index is not None: + result["index"] = obj.index + if obj.expected is not None: + result["expected"] = obj.expected.save(context) + if obj.actual is not None: + result["actual"] = obj.actual.save(context) + if obj.message is not None: + result["message"] = obj.message + + if context is not None: + result = context.process_dict(result) + return result + + def to_yaml(self, context: SaveContext | None = None) -> str: + """Convert the ReplayMismatch instance to a YAML string. + Args: + context (Optional[SaveContext]): Optional context with pre/post processing callbacks. + Returns: + str: The YAML string representation of this instance. + + """ + if context is None: + context = SaveContext() + return context.to_yaml(self.save(context)) + + def to_json(self, context: SaveContext | None = None, indent: int = 2) -> str: + """Convert the ReplayMismatch instance to a JSON string. + Args: + context (Optional[SaveContext]): Optional context with pre/post processing callbacks. + indent (int): Number of spaces for indentation. Defaults to 2. + Returns: + str: The JSON string representation of this instance. + + """ + if context is None: + context = SaveContext() + return context.to_json(self.save(context), indent) diff --git a/runtime/python/prompty/prompty/model/pipeline/_ReplayVerificationRequest.py b/runtime/python/prompty/prompty/model/pipeline/_ReplayVerificationRequest.py new file mode 100644 index 00000000..04d54dd3 --- /dev/null +++ b/runtime/python/prompty/prompty/model/pipeline/_ReplayVerificationRequest.py @@ -0,0 +1,156 @@ +# +########################################## +# WARNING: This is an auto-generated file. +# DO NOT EDIT THIS FILE DIRECTLY +# ANY EDITS WILL BE LOST +########################################## + +from dataclasses import dataclass, field +from typing import Any, ClassVar + +from .._context import LoadContext, SaveContext +from ._ReplayJournalRecord import ReplayJournalRecord + + +@dataclass +class ReplayVerificationRequest: + """Request accepted by a replay verifier implementation. + + Attributes + ---------- + expected : list[ReplayJournalRecord] + Expected normalized replay records + actual : list[ReplayJournalRecord] + Actual normalized replay records + """ + + _shorthand_property: ClassVar[str | None] = None + + expected: list[ReplayJournalRecord] = field(default_factory=list) + actual: list[ReplayJournalRecord] = field(default_factory=list) + + @staticmethod + def load(data: Any, context: LoadContext | None = None) -> "ReplayVerificationRequest": + """Load a ReplayVerificationRequest instance. + Args: + data (Any): The data to load the instance from. + context (Optional[LoadContext]): Optional context with pre/post processing callbacks. + Returns: + ReplayVerificationRequest: The loaded ReplayVerificationRequest instance. + + """ + + if context is not None: + data = context.process_input(data) + + if not isinstance(data, dict): + raise ValueError(f"Invalid data for ReplayVerificationRequest: {data}") + + # create new instance + instance = ReplayVerificationRequest() + + if data is not None and "expected" in data: + instance.expected = ReplayVerificationRequest.load_expected(data["expected"], context) + if data is not None and "actual" in data: + instance.actual = ReplayVerificationRequest.load_actual(data["actual"], context) + if context is not None: + instance = context.process_output(instance) + return instance + + @staticmethod + def load_expected(data: dict | list, context: LoadContext | None) -> list[ReplayJournalRecord]: + if isinstance(data, dict): + # convert simple named expected to list of ReplayJournalRecord + result = [] + for k, v in data.items(): + if isinstance(v, dict): + # value is an object, spread its properties + result.append({"name": k, **v}) + else: + # value is a scalar, use it as the primary property + result.append({"name": k, "kind": v}) + data = result + return [ReplayJournalRecord.load(item, context) for item in data] + + @staticmethod + def save_expected( + items: list[ReplayJournalRecord], context: SaveContext | None + ) -> dict[str, Any] | list[dict[str, Any]]: + if context is None: + context = SaveContext() + + # This type doesn't have a 'name' property, so always use array format + return [item.save(context) for item in items] + + @staticmethod + def load_actual(data: dict | list, context: LoadContext | None) -> list[ReplayJournalRecord]: + if isinstance(data, dict): + # convert simple named actual to list of ReplayJournalRecord + result = [] + for k, v in data.items(): + if isinstance(v, dict): + # value is an object, spread its properties + result.append({"name": k, **v}) + else: + # value is a scalar, use it as the primary property + result.append({"name": k, "kind": v}) + data = result + return [ReplayJournalRecord.load(item, context) for item in data] + + @staticmethod + def save_actual( + items: list[ReplayJournalRecord], context: SaveContext | None + ) -> dict[str, Any] | list[dict[str, Any]]: + if context is None: + context = SaveContext() + + # This type doesn't have a 'name' property, so always use array format + return [item.save(context) for item in items] + + def save(self, context: SaveContext | None = None) -> dict[str, Any]: + """Save the ReplayVerificationRequest instance to a dictionary. + Args: + context (Optional[SaveContext]): Optional context with pre/post processing callbacks. + Returns: + dict[str, Any]: The dictionary representation of this instance. + + """ + obj = self + if context is not None: + obj = context.process_object(obj) + + result: dict[str, Any] = {} + + if obj.expected is not None: + result["expected"] = ReplayVerificationRequest.save_expected(obj.expected, context) + if obj.actual is not None: + result["actual"] = ReplayVerificationRequest.save_actual(obj.actual, context) + + if context is not None: + result = context.process_dict(result) + return result + + def to_yaml(self, context: SaveContext | None = None) -> str: + """Convert the ReplayVerificationRequest instance to a YAML string. + Args: + context (Optional[SaveContext]): Optional context with pre/post processing callbacks. + Returns: + str: The YAML string representation of this instance. + + """ + if context is None: + context = SaveContext() + return context.to_yaml(self.save(context)) + + def to_json(self, context: SaveContext | None = None, indent: int = 2) -> str: + """Convert the ReplayVerificationRequest instance to a JSON string. + Args: + context (Optional[SaveContext]): Optional context with pre/post processing callbacks. + indent (int): Number of spaces for indentation. Defaults to 2. + Returns: + str: The JSON string representation of this instance. + + """ + if context is None: + context = SaveContext() + return context.to_json(self.save(context), indent) diff --git a/runtime/python/prompty/prompty/model/pipeline/_ReplayVerificationResult.py b/runtime/python/prompty/prompty/model/pipeline/_ReplayVerificationResult.py new file mode 100644 index 00000000..00fffe09 --- /dev/null +++ b/runtime/python/prompty/prompty/model/pipeline/_ReplayVerificationResult.py @@ -0,0 +1,147 @@ +# +########################################## +# WARNING: This is an auto-generated file. +# DO NOT EDIT THIS FILE DIRECTLY +# ANY EDITS WILL BE LOST +########################################## + +from dataclasses import dataclass, field +from typing import Any, ClassVar, Literal + +from .._context import LoadContext, SaveContext +from ._ReplayMismatch import ReplayMismatch + +ReplayVerificationStatus = Literal["passed", "failed"] + + +@dataclass +class ReplayVerificationResult: + """Result returned by a replay verifier implementation. + + Attributes + ---------- + status : str + Replay verification status + mismatches : Optional[list[ReplayMismatch]] + Record mismatches, empty when verification passed + expected_count : int + Number of expected records + actual_count : int + Number of actual records + """ + + _shorthand_property: ClassVar[str | None] = None + + status: ReplayVerificationStatus = field(default="passed") + mismatches: list[ReplayMismatch] = field(default_factory=list) + expected_count: int = field(default=0) + actual_count: int = field(default=0) + + @staticmethod + def load(data: Any, context: LoadContext | None = None) -> "ReplayVerificationResult": + """Load a ReplayVerificationResult instance. + Args: + data (Any): The data to load the instance from. + context (Optional[LoadContext]): Optional context with pre/post processing callbacks. + Returns: + ReplayVerificationResult: The loaded ReplayVerificationResult instance. + + """ + + if context is not None: + data = context.process_input(data) + + if not isinstance(data, dict): + raise ValueError(f"Invalid data for ReplayVerificationResult: {data}") + + # create new instance + instance = ReplayVerificationResult() + + if data is not None and "status" in data: + instance.status = data["status"] + if data is not None and "mismatches" in data: + instance.mismatches = ReplayVerificationResult.load_mismatches(data["mismatches"], context) + if data is not None and "expectedCount" in data: + instance.expected_count = data["expectedCount"] + if data is not None and "actualCount" in data: + instance.actual_count = data["actualCount"] + if context is not None: + instance = context.process_output(instance) + return instance + + @staticmethod + def load_mismatches(data: dict | list, context: LoadContext | None) -> list[ReplayMismatch]: + if isinstance(data, dict): + # convert simple named mismatches to list of ReplayMismatch + result = [] + for k, v in data.items(): + if isinstance(v, dict): + # value is an object, spread its properties + result.append({"name": k, **v}) + else: + # value is a scalar, use it as the primary property + result.append({"name": k, "index": v}) + data = result + return [ReplayMismatch.load(item, context) for item in data] + + @staticmethod + def save_mismatches( + items: list[ReplayMismatch], context: SaveContext | None + ) -> dict[str, Any] | list[dict[str, Any]]: + if context is None: + context = SaveContext() + + # This type doesn't have a 'name' property, so always use array format + return [item.save(context) for item in items] + + def save(self, context: SaveContext | None = None) -> dict[str, Any]: + """Save the ReplayVerificationResult instance to a dictionary. + Args: + context (Optional[SaveContext]): Optional context with pre/post processing callbacks. + Returns: + dict[str, Any]: The dictionary representation of this instance. + + """ + obj = self + if context is not None: + obj = context.process_object(obj) + + result: dict[str, Any] = {} + + if obj.status is not None: + result["status"] = obj.status + if obj.mismatches is not None: + result["mismatches"] = ReplayVerificationResult.save_mismatches(obj.mismatches, context) + if obj.expected_count is not None: + result["expectedCount"] = obj.expected_count + if obj.actual_count is not None: + result["actualCount"] = obj.actual_count + + if context is not None: + result = context.process_dict(result) + return result + + def to_yaml(self, context: SaveContext | None = None) -> str: + """Convert the ReplayVerificationResult instance to a YAML string. + Args: + context (Optional[SaveContext]): Optional context with pre/post processing callbacks. + Returns: + str: The YAML string representation of this instance. + + """ + if context is None: + context = SaveContext() + return context.to_yaml(self.save(context)) + + def to_json(self, context: SaveContext | None = None, indent: int = 2) -> str: + """Convert the ReplayVerificationResult instance to a JSON string. + Args: + context (Optional[SaveContext]): Optional context with pre/post processing callbacks. + indent (int): Number of spaces for indentation. Defaults to 2. + Returns: + str: The JSON string representation of this instance. + + """ + if context is None: + context = SaveContext() + return context.to_json(self.save(context), indent) diff --git a/runtime/python/prompty/prompty/model/pipeline/_RunTurnRequest.py b/runtime/python/prompty/prompty/model/pipeline/_RunTurnRequest.py new file mode 100644 index 00000000..4e124b5b --- /dev/null +++ b/runtime/python/prompty/prompty/model/pipeline/_RunTurnRequest.py @@ -0,0 +1,120 @@ +# +########################################## +# WARNING: This is an auto-generated file. +# DO NOT EDIT THIS FILE DIRECTLY +# ANY EDITS WILL BE LOST +########################################## + +from dataclasses import dataclass, field +from typing import Any, ClassVar + +from .._context import LoadContext, SaveContext +from ._TurnOptions import TurnOptions + + +@dataclass +class RunTurnRequest: + """Request accepted by a reference turn runner implementation. + + Attributes + ---------- + session_id : str + Stable harness session identifier + turn_id : str + Stable turn identifier within the session + inputs : Optional[dict[str, Any]] + Inputs supplied to the deterministic single-turn run + options : Optional[TurnOptions] + Canonical turn execution options + """ + + _shorthand_property: ClassVar[str | None] = None + + session_id: str = field(default="") + turn_id: str = field(default="") + inputs: dict[str, Any] | None = None + options: TurnOptions | None = None + + @staticmethod + def load(data: Any, context: LoadContext | None = None) -> "RunTurnRequest": + """Load a RunTurnRequest instance. + Args: + data (Any): The data to load the instance from. + context (Optional[LoadContext]): Optional context with pre/post processing callbacks. + Returns: + RunTurnRequest: The loaded RunTurnRequest instance. + + """ + + if context is not None: + data = context.process_input(data) + + if not isinstance(data, dict): + raise ValueError(f"Invalid data for RunTurnRequest: {data}") + + # create new instance + instance = RunTurnRequest() + + if data is not None and "sessionId" in data: + instance.session_id = data["sessionId"] + if data is not None and "turnId" in data: + instance.turn_id = data["turnId"] + if data is not None and "inputs" in data: + instance.inputs = data["inputs"] + if data is not None and "options" in data: + instance.options = TurnOptions.load(data["options"], context) + if context is not None: + instance = context.process_output(instance) + return instance + + def save(self, context: SaveContext | None = None) -> dict[str, Any]: + """Save the RunTurnRequest instance to a dictionary. + Args: + context (Optional[SaveContext]): Optional context with pre/post processing callbacks. + Returns: + dict[str, Any]: The dictionary representation of this instance. + + """ + obj = self + if context is not None: + obj = context.process_object(obj) + + result: dict[str, Any] = {} + + if obj.session_id is not None: + result["sessionId"] = obj.session_id + if obj.turn_id is not None: + result["turnId"] = obj.turn_id + if obj.inputs is not None: + result["inputs"] = obj.inputs + if obj.options is not None: + result["options"] = obj.options.save(context) + + if context is not None: + result = context.process_dict(result) + return result + + def to_yaml(self, context: SaveContext | None = None) -> str: + """Convert the RunTurnRequest instance to a YAML string. + Args: + context (Optional[SaveContext]): Optional context with pre/post processing callbacks. + Returns: + str: The YAML string representation of this instance. + + """ + if context is None: + context = SaveContext() + return context.to_yaml(self.save(context)) + + def to_json(self, context: SaveContext | None = None, indent: int = 2) -> str: + """Convert the RunTurnRequest instance to a JSON string. + Args: + context (Optional[SaveContext]): Optional context with pre/post processing callbacks. + indent (int): Number of spaces for indentation. Defaults to 2. + Returns: + str: The JSON string representation of this instance. + + """ + if context is None: + context = SaveContext() + return context.to_json(self.save(context), indent) diff --git a/runtime/python/prompty/prompty/model/pipeline/_RunTurnResult.py b/runtime/python/prompty/prompty/model/pipeline/_RunTurnResult.py new file mode 100644 index 00000000..706caa83 --- /dev/null +++ b/runtime/python/prompty/prompty/model/pipeline/_RunTurnResult.py @@ -0,0 +1,192 @@ +# +########################################## +# WARNING: This is an auto-generated file. +# DO NOT EDIT THIS FILE DIRECTLY +# ANY EDITS WILL BE LOST +########################################## + +from dataclasses import dataclass, field +from typing import Any, ClassVar, Literal + +from .._context import LoadContext, SaveContext +from ..events._Checkpoint import Checkpoint +from ..events._HostToolResult import HostToolResult + +RunTurnStatus = Literal["success", "error", "cancelled"] + + +@dataclass +class RunTurnResult: + """Result returned by a reference turn runner implementation. + + Attributes + ---------- + session_id : str + Stable harness session identifier + turn_id : str + Stable turn identifier within the session + status : str + Final semantic status for the deterministic turn + output : Optional[Any] + Provider-neutral final output returned by the injected model callback + iterations : int + Number of model loop iterations executed + tool_results : Optional[list[HostToolResult]] + Host tool results produced during the turn + checkpoints : Optional[list[Checkpoint]] + Checkpoints created during the turn + """ + + _shorthand_property: ClassVar[str | None] = None + + session_id: str = field(default="") + turn_id: str = field(default="") + status: RunTurnStatus = field(default="success") + output: Any | None = None + iterations: int = field(default=0) + tool_results: list[HostToolResult] = field(default_factory=list) + checkpoints: list[Checkpoint] = field(default_factory=list) + + @staticmethod + def load(data: Any, context: LoadContext | None = None) -> "RunTurnResult": + """Load a RunTurnResult instance. + Args: + data (Any): The data to load the instance from. + context (Optional[LoadContext]): Optional context with pre/post processing callbacks. + Returns: + RunTurnResult: The loaded RunTurnResult instance. + + """ + + if context is not None: + data = context.process_input(data) + + if not isinstance(data, dict): + raise ValueError(f"Invalid data for RunTurnResult: {data}") + + # create new instance + instance = RunTurnResult() + + if data is not None and "sessionId" in data: + instance.session_id = data["sessionId"] + if data is not None and "turnId" in data: + instance.turn_id = data["turnId"] + if data is not None and "status" in data: + instance.status = data["status"] + if data is not None and "output" in data: + instance.output = data["output"] + if data is not None and "iterations" in data: + instance.iterations = data["iterations"] + if data is not None and "toolResults" in data: + instance.tool_results = RunTurnResult.load_tool_results(data["toolResults"], context) + if data is not None and "checkpoints" in data: + instance.checkpoints = RunTurnResult.load_checkpoints(data["checkpoints"], context) + if context is not None: + instance = context.process_output(instance) + return instance + + @staticmethod + def load_tool_results(data: dict | list, context: LoadContext | None) -> list[HostToolResult]: + if isinstance(data, dict): + # convert simple named toolResults to list of HostToolResult + result = [] + for k, v in data.items(): + if isinstance(v, dict): + # value is an object, spread its properties + result.append({"name": k, **v}) + else: + # value is a scalar, use it as the primary property + result.append({"name": k, "requestId": v}) + data = result + return [HostToolResult.load(item, context) for item in data] + + @staticmethod + def save_tool_results( + items: list[HostToolResult], context: SaveContext | None + ) -> dict[str, Any] | list[dict[str, Any]]: + if context is None: + context = SaveContext() + + # This type doesn't have a 'name' property, so always use array format + return [item.save(context) for item in items] + + @staticmethod + def load_checkpoints(data: dict | list, context: LoadContext | None) -> list[Checkpoint]: + if isinstance(data, dict): + # convert simple named checkpoints to list of Checkpoint + result = [] + for k, v in data.items(): + if isinstance(v, dict): + # value is an object, spread its properties + result.append({"name": k, **v}) + else: + # value is a scalar, use it as the primary property + result.append({"name": k, "id": v}) + data = result + return [Checkpoint.load(item, context) for item in data] + + @staticmethod + def save_checkpoints(items: list[Checkpoint], context: SaveContext | None) -> dict[str, Any] | list[dict[str, Any]]: + if context is None: + context = SaveContext() + + # This type doesn't have a 'name' property, so always use array format + return [item.save(context) for item in items] + + def save(self, context: SaveContext | None = None) -> dict[str, Any]: + """Save the RunTurnResult instance to a dictionary. + Args: + context (Optional[SaveContext]): Optional context with pre/post processing callbacks. + Returns: + dict[str, Any]: The dictionary representation of this instance. + + """ + obj = self + if context is not None: + obj = context.process_object(obj) + + result: dict[str, Any] = {} + + if obj.session_id is not None: + result["sessionId"] = obj.session_id + if obj.turn_id is not None: + result["turnId"] = obj.turn_id + if obj.status is not None: + result["status"] = obj.status + if obj.output is not None: + result["output"] = obj.output + if obj.iterations is not None: + result["iterations"] = obj.iterations + if obj.tool_results is not None: + result["toolResults"] = RunTurnResult.save_tool_results(obj.tool_results, context) + if obj.checkpoints is not None: + result["checkpoints"] = RunTurnResult.save_checkpoints(obj.checkpoints, context) + + if context is not None: + result = context.process_dict(result) + return result + + def to_yaml(self, context: SaveContext | None = None) -> str: + """Convert the RunTurnResult instance to a YAML string. + Args: + context (Optional[SaveContext]): Optional context with pre/post processing callbacks. + Returns: + str: The YAML string representation of this instance. + + """ + if context is None: + context = SaveContext() + return context.to_yaml(self.save(context)) + + def to_json(self, context: SaveContext | None = None, indent: int = 2) -> str: + """Convert the RunTurnResult instance to a JSON string. + Args: + context (Optional[SaveContext]): Optional context with pre/post processing callbacks. + indent (int): Number of spaces for indentation. Defaults to 2. + Returns: + str: The JSON string representation of this instance. + + """ + if context is None: + context = SaveContext() + return context.to_json(self.save(context), indent) diff --git a/runtime/python/prompty/prompty/model/pipeline/_TurnModelRequest.py b/runtime/python/prompty/prompty/model/pipeline/_TurnModelRequest.py new file mode 100644 index 00000000..2bb23453 --- /dev/null +++ b/runtime/python/prompty/prompty/model/pipeline/_TurnModelRequest.py @@ -0,0 +1,163 @@ +# +########################################## +# WARNING: This is an auto-generated file. +# DO NOT EDIT THIS FILE DIRECTLY +# ANY EDITS WILL BE LOST +########################################## + +from dataclasses import dataclass, field +from typing import Any, ClassVar + +from .._context import LoadContext, SaveContext +from ..events._HostToolResult import HostToolResult +from ._TurnOptions import TurnOptions + + +@dataclass +class TurnModelRequest: + """Request passed by the reference turn runner to the injected model callback. + + The runner owns deterministic orchestration semantics; model/provider-specific + execution stays behind this callback boundary. + + Attributes + ---------- + session_id : str + Stable harness session identifier + turn_id : str + Stable turn identifier within the session + iteration : int + Zero-based model loop iteration + inputs : Optional[dict[str, Any]] + Inputs supplied to the deterministic single-turn run + options : Optional[TurnOptions] + Canonical turn execution options + tool_results : Optional[list[HostToolResult]] + Host tool results produced by the previous iteration + """ + + _shorthand_property: ClassVar[str | None] = None + + session_id: str = field(default="") + turn_id: str = field(default="") + iteration: int = field(default=0) + inputs: dict[str, Any] | None = None + options: TurnOptions | None = None + tool_results: list[HostToolResult] = field(default_factory=list) + + @staticmethod + def load(data: Any, context: LoadContext | None = None) -> "TurnModelRequest": + """Load a TurnModelRequest instance. + Args: + data (Any): The data to load the instance from. + context (Optional[LoadContext]): Optional context with pre/post processing callbacks. + Returns: + TurnModelRequest: The loaded TurnModelRequest instance. + + """ + + if context is not None: + data = context.process_input(data) + + if not isinstance(data, dict): + raise ValueError(f"Invalid data for TurnModelRequest: {data}") + + # create new instance + instance = TurnModelRequest() + + if data is not None and "sessionId" in data: + instance.session_id = data["sessionId"] + if data is not None and "turnId" in data: + instance.turn_id = data["turnId"] + if data is not None and "iteration" in data: + instance.iteration = data["iteration"] + if data is not None and "inputs" in data: + instance.inputs = data["inputs"] + if data is not None and "options" in data: + instance.options = TurnOptions.load(data["options"], context) + if data is not None and "toolResults" in data: + instance.tool_results = TurnModelRequest.load_tool_results(data["toolResults"], context) + if context is not None: + instance = context.process_output(instance) + return instance + + @staticmethod + def load_tool_results(data: dict | list, context: LoadContext | None) -> list[HostToolResult]: + if isinstance(data, dict): + # convert simple named toolResults to list of HostToolResult + result = [] + for k, v in data.items(): + if isinstance(v, dict): + # value is an object, spread its properties + result.append({"name": k, **v}) + else: + # value is a scalar, use it as the primary property + result.append({"name": k, "requestId": v}) + data = result + return [HostToolResult.load(item, context) for item in data] + + @staticmethod + def save_tool_results( + items: list[HostToolResult], context: SaveContext | None + ) -> dict[str, Any] | list[dict[str, Any]]: + if context is None: + context = SaveContext() + + # This type doesn't have a 'name' property, so always use array format + return [item.save(context) for item in items] + + def save(self, context: SaveContext | None = None) -> dict[str, Any]: + """Save the TurnModelRequest instance to a dictionary. + Args: + context (Optional[SaveContext]): Optional context with pre/post processing callbacks. + Returns: + dict[str, Any]: The dictionary representation of this instance. + + """ + obj = self + if context is not None: + obj = context.process_object(obj) + + result: dict[str, Any] = {} + + if obj.session_id is not None: + result["sessionId"] = obj.session_id + if obj.turn_id is not None: + result["turnId"] = obj.turn_id + if obj.iteration is not None: + result["iteration"] = obj.iteration + if obj.inputs is not None: + result["inputs"] = obj.inputs + if obj.options is not None: + result["options"] = obj.options.save(context) + if obj.tool_results is not None: + result["toolResults"] = TurnModelRequest.save_tool_results(obj.tool_results, context) + + if context is not None: + result = context.process_dict(result) + return result + + def to_yaml(self, context: SaveContext | None = None) -> str: + """Convert the TurnModelRequest instance to a YAML string. + Args: + context (Optional[SaveContext]): Optional context with pre/post processing callbacks. + Returns: + str: The YAML string representation of this instance. + + """ + if context is None: + context = SaveContext() + return context.to_yaml(self.save(context)) + + def to_json(self, context: SaveContext | None = None, indent: int = 2) -> str: + """Convert the TurnModelRequest instance to a JSON string. + Args: + context (Optional[SaveContext]): Optional context with pre/post processing callbacks. + indent (int): Number of spaces for indentation. Defaults to 2. + Returns: + str: The JSON string representation of this instance. + + """ + if context is None: + context = SaveContext() + return context.to_json(self.save(context), indent) diff --git a/runtime/python/prompty/prompty/model/pipeline/_TurnModelResponse.py b/runtime/python/prompty/prompty/model/pipeline/_TurnModelResponse.py new file mode 100644 index 00000000..dae5fe9f --- /dev/null +++ b/runtime/python/prompty/prompty/model/pipeline/_TurnModelResponse.py @@ -0,0 +1,138 @@ +# +########################################## +# WARNING: This is an auto-generated file. +# DO NOT EDIT THIS FILE DIRECTLY +# ANY EDITS WILL BE LOST +########################################## + +from dataclasses import dataclass, field +from typing import Any, ClassVar + +from .._context import LoadContext, SaveContext +from ..events._HostToolRequest import HostToolRequest + + +@dataclass +class TurnModelResponse: + """Response returned by the injected model callback to the reference turn runner. + + Attributes + ---------- + output : Optional[Any] + Provider-neutral final model output for the turn when no more tools are requested + tool_requests : Optional[list[HostToolRequest]] + Host tool execution requests emitted by the model callback + checkpoint_state : Optional[dict[str, Any]] + Additional deterministic state to merge into the iteration checkpoint + """ + + _shorthand_property: ClassVar[str | None] = None + + output: Any | None = None + tool_requests: list[HostToolRequest] = field(default_factory=list) + checkpoint_state: dict[str, Any] | None = None + + @staticmethod + def load(data: Any, context: LoadContext | None = None) -> "TurnModelResponse": + """Load a TurnModelResponse instance. + Args: + data (Any): The data to load the instance from. + context (Optional[LoadContext]): Optional context with pre/post processing callbacks. + Returns: + TurnModelResponse: The loaded TurnModelResponse instance. + + """ + + if context is not None: + data = context.process_input(data) + + if not isinstance(data, dict): + raise ValueError(f"Invalid data for TurnModelResponse: {data}") + + # create new instance + instance = TurnModelResponse() + + if data is not None and "output" in data: + instance.output = data["output"] + if data is not None and "toolRequests" in data: + instance.tool_requests = TurnModelResponse.load_tool_requests(data["toolRequests"], context) + if data is not None and "checkpointState" in data: + instance.checkpoint_state = data["checkpointState"] + if context is not None: + instance = context.process_output(instance) + return instance + + @staticmethod + def load_tool_requests(data: dict | list, context: LoadContext | None) -> list[HostToolRequest]: + if isinstance(data, dict): + # convert simple named toolRequests to list of HostToolRequest + result = [] + for k, v in data.items(): + if isinstance(v, dict): + # value is an object, spread its properties + result.append({"name": k, **v}) + else: + # value is a scalar, use it as the primary property + result.append({"name": k, "requestId": v}) + data = result + return [HostToolRequest.load(item, context) for item in data] + + @staticmethod + def save_tool_requests( + items: list[HostToolRequest], context: SaveContext | None + ) -> dict[str, Any] | list[dict[str, Any]]: + if context is None: + context = SaveContext() + + # This type doesn't have a 'name' property, so always use array format + return [item.save(context) for item in items] + + def save(self, context: SaveContext | None = None) -> dict[str, Any]: + """Save the TurnModelResponse instance to a dictionary. + Args: + context (Optional[SaveContext]): Optional context with pre/post processing callbacks. + Returns: + dict[str, Any]: The dictionary representation of this instance. + + """ + obj = self + if context is not None: + obj = context.process_object(obj) + + result: dict[str, Any] = {} + + if obj.output is not None: + result["output"] = obj.output + if obj.tool_requests is not None: + result["toolRequests"] = TurnModelResponse.save_tool_requests(obj.tool_requests, context) + if obj.checkpoint_state is not None: + result["checkpointState"] = obj.checkpoint_state + + if context is not None: + result = context.process_dict(result) + return result + + def to_yaml(self, context: SaveContext | None = None) -> str: + """Convert the TurnModelResponse instance to a YAML string. + Args: + context (Optional[SaveContext]): Optional context with pre/post processing callbacks. + Returns: + str: The YAML string representation of this instance. + + """ + if context is None: + context = SaveContext() + return context.to_yaml(self.save(context)) + + def to_json(self, context: SaveContext | None = None, indent: int = 2) -> str: + """Convert the TurnModelResponse instance to a JSON string. + Args: + context (Optional[SaveContext]): Optional context with pre/post processing callbacks. + indent (int): Number of spaces for indentation. Defaults to 2. + Returns: + str: The JSON string representation of this instance. + + """ + if context is None: + context = SaveContext() + return context.to_json(self.save(context), indent) diff --git a/runtime/python/prompty/prompty/model/pipeline/_TurnOptions.py b/runtime/python/prompty/prompty/model/pipeline/_TurnOptions.py index 1e64dccc..be8fdf9d 100644 --- a/runtime/python/prompty/prompty/model/pipeline/_TurnOptions.py +++ b/runtime/python/prompty/prompty/model/pipeline/_TurnOptions.py @@ -1,3 +1,4 @@ +# ########################################## # WARNING: This is an auto-generated file. # DO NOT EDIT THIS FILE DIRECTLY diff --git a/runtime/python/prompty/prompty/model/pipeline/__init__.py b/runtime/python/prompty/prompty/model/pipeline/__init__.py index 8080ed2a..aed8147b 100644 --- a/runtime/python/prompty/prompty/model/pipeline/__init__.py +++ b/runtime/python/prompty/prompty/model/pipeline/__init__.py @@ -1,20 +1,47 @@ +# ########################################## # WARNING: This is an auto-generated file. # DO NOT EDIT THIS FILE DIRECTLY # ANY EDITS WILL BE LOST ########################################## +from ._CheckpointStore import CheckpointStore from ._CompactionConfig import CompactionConfig +from ._EventJournalWriter import EventJournalWriter +from ._EventSink import EventSink from ._Executor import Executor +from ._HostToolExecutor import HostToolExecutor from ._Parser import Parser +from ._PermissionResolver import PermissionResolver from ._Processor import Processor from ._Renderer import Renderer +from ._ReplayJournalRecord import ReplayJournalRecord +from ._ReplayMismatch import ReplayMismatch +from ._ReplayVerificationRequest import ReplayVerificationRequest +from ._ReplayVerificationResult import ReplayVerificationResult +from ._RunTurnRequest import RunTurnRequest +from ._RunTurnResult import RunTurnResult +from ._TurnModelRequest import TurnModelRequest +from ._TurnModelResponse import TurnModelResponse from ._TurnOptions import TurnOptions __all__ = [ "CompactionConfig", "TurnOptions", + "TurnModelRequest", + "TurnModelResponse", + "RunTurnRequest", + "RunTurnResult", + "ReplayJournalRecord", + "ReplayVerificationRequest", + "ReplayMismatch", + "ReplayVerificationResult", "Renderer", "Parser", "Executor", "Processor", + "EventSink", + "EventJournalWriter", + "PermissionResolver", + "CheckpointStore", + "HostToolExecutor", ] diff --git a/runtime/python/prompty/prompty/model/py.typed b/runtime/python/prompty/prompty/model/py.typed index e69de29b..66ab2838 100644 --- a/runtime/python/prompty/prompty/model/py.typed +++ b/runtime/python/prompty/prompty/model/py.typed @@ -0,0 +1 @@ +// diff --git a/runtime/python/prompty/prompty/model/streaming/_StreamOptions.py b/runtime/python/prompty/prompty/model/streaming/_StreamOptions.py index 36881dc9..6400bbd2 100644 --- a/runtime/python/prompty/prompty/model/streaming/_StreamOptions.py +++ b/runtime/python/prompty/prompty/model/streaming/_StreamOptions.py @@ -1,3 +1,4 @@ +# ########################################## # WARNING: This is an auto-generated file. # DO NOT EDIT THIS FILE DIRECTLY diff --git a/runtime/python/prompty/prompty/model/streaming/__init__.py b/runtime/python/prompty/prompty/model/streaming/__init__.py index 099ef532..14675110 100644 --- a/runtime/python/prompty/prompty/model/streaming/__init__.py +++ b/runtime/python/prompty/prompty/model/streaming/__init__.py @@ -1,3 +1,4 @@ +# ########################################## # WARNING: This is an auto-generated file. # DO NOT EDIT THIS FILE DIRECTLY diff --git a/runtime/python/prompty/prompty/model/template/_FormatConfig.py b/runtime/python/prompty/prompty/model/template/_FormatConfig.py index baf603d1..4f80e68a 100644 --- a/runtime/python/prompty/prompty/model/template/_FormatConfig.py +++ b/runtime/python/prompty/prompty/model/template/_FormatConfig.py @@ -1,3 +1,4 @@ +# ########################################## # WARNING: This is an auto-generated file. # DO NOT EDIT THIS FILE DIRECTLY diff --git a/runtime/python/prompty/prompty/model/template/_ParserConfig.py b/runtime/python/prompty/prompty/model/template/_ParserConfig.py index be926a3e..1bb897c3 100644 --- a/runtime/python/prompty/prompty/model/template/_ParserConfig.py +++ b/runtime/python/prompty/prompty/model/template/_ParserConfig.py @@ -1,3 +1,4 @@ +# ########################################## # WARNING: This is an auto-generated file. # DO NOT EDIT THIS FILE DIRECTLY diff --git a/runtime/python/prompty/prompty/model/template/_Template.py b/runtime/python/prompty/prompty/model/template/_Template.py index 9b85c8ff..577260a8 100644 --- a/runtime/python/prompty/prompty/model/template/_Template.py +++ b/runtime/python/prompty/prompty/model/template/_Template.py @@ -1,3 +1,4 @@ +# ########################################## # WARNING: This is an auto-generated file. # DO NOT EDIT THIS FILE DIRECTLY diff --git a/runtime/python/prompty/prompty/model/template/__init__.py b/runtime/python/prompty/prompty/model/template/__init__.py index 4e65c6c3..b4a49acc 100644 --- a/runtime/python/prompty/prompty/model/template/__init__.py +++ b/runtime/python/prompty/prompty/model/template/__init__.py @@ -1,3 +1,4 @@ +# ########################################## # WARNING: This is an auto-generated file. # DO NOT EDIT THIS FILE DIRECTLY diff --git a/runtime/python/prompty/prompty/model/tools/_Binding.py b/runtime/python/prompty/prompty/model/tools/_Binding.py index f39325da..8b834581 100644 --- a/runtime/python/prompty/prompty/model/tools/_Binding.py +++ b/runtime/python/prompty/prompty/model/tools/_Binding.py @@ -1,3 +1,4 @@ +# ########################################## # WARNING: This is an auto-generated file. # DO NOT EDIT THIS FILE DIRECTLY diff --git a/runtime/python/prompty/prompty/model/tools/_McpApprovalMode.py b/runtime/python/prompty/prompty/model/tools/_McpApprovalMode.py index a48c7ef7..a4f9ba9b 100644 --- a/runtime/python/prompty/prompty/model/tools/_McpApprovalMode.py +++ b/runtime/python/prompty/prompty/model/tools/_McpApprovalMode.py @@ -1,3 +1,4 @@ +# ########################################## # WARNING: This is an auto-generated file. # DO NOT EDIT THIS FILE DIRECTLY diff --git a/runtime/python/prompty/prompty/model/tools/_Tool.py b/runtime/python/prompty/prompty/model/tools/_Tool.py index 6a0a263c..96f666ef 100644 --- a/runtime/python/prompty/prompty/model/tools/_Tool.py +++ b/runtime/python/prompty/prompty/model/tools/_Tool.py @@ -1,3 +1,4 @@ +# ########################################## # WARNING: This is an auto-generated file. # DO NOT EDIT THIS FILE DIRECTLY diff --git a/runtime/python/prompty/prompty/model/tools/_ToolContext.py b/runtime/python/prompty/prompty/model/tools/_ToolContext.py index 2aab5a54..e79292c9 100644 --- a/runtime/python/prompty/prompty/model/tools/_ToolContext.py +++ b/runtime/python/prompty/prompty/model/tools/_ToolContext.py @@ -1,3 +1,4 @@ +# ########################################## # WARNING: This is an auto-generated file. # DO NOT EDIT THIS FILE DIRECTLY diff --git a/runtime/python/prompty/prompty/model/tools/_ToolDispatchResult.py b/runtime/python/prompty/prompty/model/tools/_ToolDispatchResult.py index 23b1a986..cbc16f9d 100644 --- a/runtime/python/prompty/prompty/model/tools/_ToolDispatchResult.py +++ b/runtime/python/prompty/prompty/model/tools/_ToolDispatchResult.py @@ -1,3 +1,4 @@ +# ########################################## # WARNING: This is an auto-generated file. # DO NOT EDIT THIS FILE DIRECTLY diff --git a/runtime/python/prompty/prompty/model/tools/__init__.py b/runtime/python/prompty/prompty/model/tools/__init__.py index 97fb4c71..81352179 100644 --- a/runtime/python/prompty/prompty/model/tools/__init__.py +++ b/runtime/python/prompty/prompty/model/tools/__init__.py @@ -1,3 +1,4 @@ +# ########################################## # WARNING: This is an auto-generated file. # DO NOT EDIT THIS FILE DIRECTLY diff --git a/runtime/python/prompty/prompty/model/tracing/_TraceFile.py b/runtime/python/prompty/prompty/model/tracing/_TraceFile.py index 9967bb0a..ba4a79e7 100644 --- a/runtime/python/prompty/prompty/model/tracing/_TraceFile.py +++ b/runtime/python/prompty/prompty/model/tracing/_TraceFile.py @@ -1,3 +1,4 @@ +# ########################################## # WARNING: This is an auto-generated file. # DO NOT EDIT THIS FILE DIRECTLY diff --git a/runtime/python/prompty/prompty/model/tracing/_TraceSpan.py b/runtime/python/prompty/prompty/model/tracing/_TraceSpan.py index 386fb5aa..5bb42ad5 100644 --- a/runtime/python/prompty/prompty/model/tracing/_TraceSpan.py +++ b/runtime/python/prompty/prompty/model/tracing/_TraceSpan.py @@ -1,3 +1,4 @@ +# ########################################## # WARNING: This is an auto-generated file. # DO NOT EDIT THIS FILE DIRECTLY diff --git a/runtime/python/prompty/prompty/model/tracing/_TraceTime.py b/runtime/python/prompty/prompty/model/tracing/_TraceTime.py index ff208518..85c5fbd3 100644 --- a/runtime/python/prompty/prompty/model/tracing/_TraceTime.py +++ b/runtime/python/prompty/prompty/model/tracing/_TraceTime.py @@ -1,3 +1,4 @@ +# ########################################## # WARNING: This is an auto-generated file. # DO NOT EDIT THIS FILE DIRECTLY diff --git a/runtime/python/prompty/prompty/model/tracing/__init__.py b/runtime/python/prompty/prompty/model/tracing/__init__.py index 7eb0a530..548c34c3 100644 --- a/runtime/python/prompty/prompty/model/tracing/__init__.py +++ b/runtime/python/prompty/prompty/model/tracing/__init__.py @@ -1,3 +1,4 @@ +# ########################################## # WARNING: This is an auto-generated file. # DO NOT EDIT THIS FILE DIRECTLY diff --git a/runtime/python/prompty/prompty/model/wire/_AnthropicImageBlock.py b/runtime/python/prompty/prompty/model/wire/_AnthropicImageBlock.py index 63d6a74c..f2a2a3d1 100644 --- a/runtime/python/prompty/prompty/model/wire/_AnthropicImageBlock.py +++ b/runtime/python/prompty/prompty/model/wire/_AnthropicImageBlock.py @@ -1,3 +1,4 @@ +# ########################################## # WARNING: This is an auto-generated file. # DO NOT EDIT THIS FILE DIRECTLY diff --git a/runtime/python/prompty/prompty/model/wire/_AnthropicImageSource.py b/runtime/python/prompty/prompty/model/wire/_AnthropicImageSource.py index 9b6ba862..4305990a 100644 --- a/runtime/python/prompty/prompty/model/wire/_AnthropicImageSource.py +++ b/runtime/python/prompty/prompty/model/wire/_AnthropicImageSource.py @@ -1,3 +1,4 @@ +# ########################################## # WARNING: This is an auto-generated file. # DO NOT EDIT THIS FILE DIRECTLY diff --git a/runtime/python/prompty/prompty/model/wire/_AnthropicMessagesRequest.py b/runtime/python/prompty/prompty/model/wire/_AnthropicMessagesRequest.py index 6f430618..760dc4be 100644 --- a/runtime/python/prompty/prompty/model/wire/_AnthropicMessagesRequest.py +++ b/runtime/python/prompty/prompty/model/wire/_AnthropicMessagesRequest.py @@ -1,3 +1,4 @@ +# ########################################## # WARNING: This is an auto-generated file. # DO NOT EDIT THIS FILE DIRECTLY diff --git a/runtime/python/prompty/prompty/model/wire/_AnthropicMessagesResponse.py b/runtime/python/prompty/prompty/model/wire/_AnthropicMessagesResponse.py index 8c02b899..5c621a83 100644 --- a/runtime/python/prompty/prompty/model/wire/_AnthropicMessagesResponse.py +++ b/runtime/python/prompty/prompty/model/wire/_AnthropicMessagesResponse.py @@ -1,3 +1,4 @@ +# ########################################## # WARNING: This is an auto-generated file. # DO NOT EDIT THIS FILE DIRECTLY diff --git a/runtime/python/prompty/prompty/model/wire/_AnthropicTextBlock.py b/runtime/python/prompty/prompty/model/wire/_AnthropicTextBlock.py index ecf1c6d6..6b7667ee 100644 --- a/runtime/python/prompty/prompty/model/wire/_AnthropicTextBlock.py +++ b/runtime/python/prompty/prompty/model/wire/_AnthropicTextBlock.py @@ -1,3 +1,4 @@ +# ########################################## # WARNING: This is an auto-generated file. # DO NOT EDIT THIS FILE DIRECTLY diff --git a/runtime/python/prompty/prompty/model/wire/_AnthropicToolDefinition.py b/runtime/python/prompty/prompty/model/wire/_AnthropicToolDefinition.py index 531b9ec3..c726e5b6 100644 --- a/runtime/python/prompty/prompty/model/wire/_AnthropicToolDefinition.py +++ b/runtime/python/prompty/prompty/model/wire/_AnthropicToolDefinition.py @@ -1,3 +1,4 @@ +# ########################################## # WARNING: This is an auto-generated file. # DO NOT EDIT THIS FILE DIRECTLY diff --git a/runtime/python/prompty/prompty/model/wire/_AnthropicToolResultBlock.py b/runtime/python/prompty/prompty/model/wire/_AnthropicToolResultBlock.py index bfed762a..9144beb1 100644 --- a/runtime/python/prompty/prompty/model/wire/_AnthropicToolResultBlock.py +++ b/runtime/python/prompty/prompty/model/wire/_AnthropicToolResultBlock.py @@ -1,3 +1,4 @@ +# ########################################## # WARNING: This is an auto-generated file. # DO NOT EDIT THIS FILE DIRECTLY diff --git a/runtime/python/prompty/prompty/model/wire/_AnthropicToolUseBlock.py b/runtime/python/prompty/prompty/model/wire/_AnthropicToolUseBlock.py index e6447659..f4bec46e 100644 --- a/runtime/python/prompty/prompty/model/wire/_AnthropicToolUseBlock.py +++ b/runtime/python/prompty/prompty/model/wire/_AnthropicToolUseBlock.py @@ -1,3 +1,4 @@ +# ########################################## # WARNING: This is an auto-generated file. # DO NOT EDIT THIS FILE DIRECTLY diff --git a/runtime/python/prompty/prompty/model/wire/_AnthropicUsage.py b/runtime/python/prompty/prompty/model/wire/_AnthropicUsage.py index 68c45ff0..e85927b6 100644 --- a/runtime/python/prompty/prompty/model/wire/_AnthropicUsage.py +++ b/runtime/python/prompty/prompty/model/wire/_AnthropicUsage.py @@ -1,3 +1,4 @@ +# ########################################## # WARNING: This is an auto-generated file. # DO NOT EDIT THIS FILE DIRECTLY diff --git a/runtime/python/prompty/prompty/model/wire/_AnthropicWireMessage.py b/runtime/python/prompty/prompty/model/wire/_AnthropicWireMessage.py index 45911ef5..cd52a857 100644 --- a/runtime/python/prompty/prompty/model/wire/_AnthropicWireMessage.py +++ b/runtime/python/prompty/prompty/model/wire/_AnthropicWireMessage.py @@ -1,3 +1,4 @@ +# ########################################## # WARNING: This is an auto-generated file. # DO NOT EDIT THIS FILE DIRECTLY diff --git a/runtime/python/prompty/prompty/model/wire/__init__.py b/runtime/python/prompty/prompty/model/wire/__init__.py index ec174f95..e3abec73 100644 --- a/runtime/python/prompty/prompty/model/wire/__init__.py +++ b/runtime/python/prompty/prompty/model/wire/__init__.py @@ -1,3 +1,4 @@ +# ########################################## # WARNING: This is an auto-generated file. # DO NOT EDIT THIS FILE DIRECTLY diff --git a/runtime/python/prompty/tests/model/agent/test_guardrail_result.py b/runtime/python/prompty/tests/model/agent/test_guardrail_result.py index c0ae828d..b5c9a22a 100644 --- a/runtime/python/prompty/tests/model/agent/test_guardrail_result.py +++ b/runtime/python/prompty/tests/model/agent/test_guardrail_result.py @@ -1,3 +1,4 @@ +# import json import yaml @@ -23,7 +24,7 @@ def test_load_yaml_guardrailresult(): yaml_data = r""" allowed: true reason: Content is safe - + """ data = yaml.load(yaml_data, Loader=yaml.FullLoader) instance = GuardrailResult.load(data) diff --git a/runtime/python/prompty/tests/model/agent/test_prompty.py b/runtime/python/prompty/tests/model/agent/test_prompty.py index 997af6e1..8a455edb 100644 --- a/runtime/python/prompty/tests/model/agent/test_prompty.py +++ b/runtime/python/prompty/tests/model/agent/test_prompty.py @@ -1,3 +1,4 @@ +# import json import yaml @@ -142,26 +143,26 @@ def test_load_yaml_prompty(): format: mustache parser: prompty instructions: "system: - + You are an AI assistant who helps people find information. - + As the assistant, you answer questions briefly, succinctly, - + and in a personable manner using markdown and even add some\ - + personal flair with appropriate emojis. - - + + # Customer - + You are helping {{firstName}} {{lastName}} to find answers to\ - + their questions. Use their name to address them in your responses. - + user: - + {{question}}" - + """ data = yaml.load(yaml_data, Loader=yaml.FullLoader) instance = Prompty.load(data) @@ -568,26 +569,26 @@ def test_load_yaml_prompty_1(): format: mustache parser: prompty instructions: "system: - + You are an AI assistant who helps people find information. - + As the assistant, you answer questions briefly, succinctly, - + and in a personable manner using markdown and even add some\ - + personal flair with appropriate emojis. - - + + # Customer - + You are helping {{firstName}} {{lastName}} to find answers to\ - + their questions. Use their name to address them in your responses. - + user: - + {{question}}" - + """ data = yaml.load(yaml_data, Loader=yaml.FullLoader) instance = Prompty.load(data) @@ -993,26 +994,26 @@ def test_load_yaml_prompty_2(): format: mustache parser: prompty instructions: "system: - + You are an AI assistant who helps people find information. - + As the assistant, you answer questions briefly, succinctly, - + and in a personable manner using markdown and even add some\ - + personal flair with appropriate emojis. - - + + # Customer - + You are helping {{firstName}} {{lastName}} to find answers to\ - + their questions. Use their name to address them in your responses. - + user: - + {{question}}" - + """ data = yaml.load(yaml_data, Loader=yaml.FullLoader) instance = Prompty.load(data) @@ -1423,26 +1424,26 @@ def test_load_yaml_prompty_3(): format: mustache parser: prompty instructions: "system: - + You are an AI assistant who helps people find information. - + As the assistant, you answer questions briefly, succinctly, - + and in a personable manner using markdown and even add some\ - + personal flair with appropriate emojis. - - + + # Customer - + You are helping {{firstName}} {{lastName}} to find answers to\ - + their questions. Use their name to address them in your responses. - + user: - + {{question}}" - + """ data = yaml.load(yaml_data, Loader=yaml.FullLoader) instance = Prompty.load(data) @@ -1853,26 +1854,26 @@ def test_load_yaml_prompty_4(): format: mustache parser: prompty instructions: "system: - + You are an AI assistant who helps people find information. - + As the assistant, you answer questions briefly, succinctly, - + and in a personable manner using markdown and even add some\ - + personal flair with appropriate emojis. - - + + # Customer - + You are helping {{firstName}} {{lastName}} to find answers to\ - + their questions. Use their name to address them in your responses. - + user: - + {{question}}" - + """ data = yaml.load(yaml_data, Loader=yaml.FullLoader) instance = Prompty.load(data) @@ -2291,26 +2292,26 @@ def test_load_yaml_prompty_5(): format: mustache parser: prompty instructions: "system: - + You are an AI assistant who helps people find information. - + As the assistant, you answer questions briefly, succinctly, - + and in a personable manner using markdown and even add some\ - + personal flair with appropriate emojis. - - + + # Customer - + You are helping {{firstName}} {{lastName}} to find answers to\ - + their questions. Use their name to address them in your responses. - + user: - + {{question}}" - + """ data = yaml.load(yaml_data, Loader=yaml.FullLoader) instance = Prompty.load(data) @@ -2728,26 +2729,26 @@ def test_load_yaml_prompty_6(): format: mustache parser: prompty instructions: "system: - + You are an AI assistant who helps people find information. - + As the assistant, you answer questions briefly, succinctly, - + and in a personable manner using markdown and even add some\ - + personal flair with appropriate emojis. - - + + # Customer - + You are helping {{firstName}} {{lastName}} to find answers to\ - + their questions. Use their name to address them in your responses. - + user: - + {{question}}" - + """ data = yaml.load(yaml_data, Loader=yaml.FullLoader) instance = Prompty.load(data) @@ -3170,26 +3171,26 @@ def test_load_yaml_prompty_7(): format: mustache parser: prompty instructions: "system: - + You are an AI assistant who helps people find information. - + As the assistant, you answer questions briefly, succinctly, - + and in a personable manner using markdown and even add some\ - + personal flair with appropriate emojis. - - + + # Customer - + You are helping {{firstName}} {{lastName}} to find answers to\ - + their questions. Use their name to address them in your responses. - + user: - + {{question}}" - + """ data = yaml.load(yaml_data, Loader=yaml.FullLoader) instance = Prompty.load(data) diff --git a/runtime/python/prompty/tests/model/connection/test_anonymous_connection.py b/runtime/python/prompty/tests/model/connection/test_anonymous_connection.py index 9dbc7afe..0c3aedc4 100644 --- a/runtime/python/prompty/tests/model/connection/test_anonymous_connection.py +++ b/runtime/python/prompty/tests/model/connection/test_anonymous_connection.py @@ -1,3 +1,4 @@ +# import json import yaml @@ -23,7 +24,7 @@ def test_load_yaml_anonymousconnection(): yaml_data = r""" kind: anonymous endpoint: "https://{your-custom-endpoint}.openai.azure.com/" - + """ data = yaml.load(yaml_data, Loader=yaml.FullLoader) instance = AnonymousConnection.load(data) diff --git a/runtime/python/prompty/tests/model/connection/test_api_key_connection.py b/runtime/python/prompty/tests/model/connection/test_api_key_connection.py index 79616486..9497397a 100644 --- a/runtime/python/prompty/tests/model/connection/test_api_key_connection.py +++ b/runtime/python/prompty/tests/model/connection/test_api_key_connection.py @@ -1,3 +1,4 @@ +# import json import yaml @@ -26,7 +27,7 @@ def test_load_yaml_apikeyconnection(): kind: key endpoint: "https://{your-custom-endpoint}.openai.azure.com/" apiKey: your-api-key - + """ data = yaml.load(yaml_data, Loader=yaml.FullLoader) instance = ApiKeyConnection.load(data) diff --git a/runtime/python/prompty/tests/model/connection/test_connection.py b/runtime/python/prompty/tests/model/connection/test_connection.py index 872a0998..2277658c 100644 --- a/runtime/python/prompty/tests/model/connection/test_connection.py +++ b/runtime/python/prompty/tests/model/connection/test_connection.py @@ -1,3 +1,4 @@ +# import json import yaml @@ -26,7 +27,7 @@ def test_load_yaml_connection(): kind: reference authenticationMode: system usageDescription: This will allow the agent to respond to an email on your behalf - + """ data = yaml.load(yaml_data, Loader=yaml.FullLoader) instance = Connection.load(data) diff --git a/runtime/python/prompty/tests/model/connection/test_foundry_connection.py b/runtime/python/prompty/tests/model/connection/test_foundry_connection.py index 8cb16477..52677bc5 100644 --- a/runtime/python/prompty/tests/model/connection/test_foundry_connection.py +++ b/runtime/python/prompty/tests/model/connection/test_foundry_connection.py @@ -1,3 +1,4 @@ +# import json import yaml @@ -29,7 +30,7 @@ def test_load_yaml_foundryconnection(): endpoint: "https://myresource.services.ai.azure.com/api/projects/myproject" name: my-openai-connection connectionType: model - + """ data = yaml.load(yaml_data, Loader=yaml.FullLoader) instance = FoundryConnection.load(data) diff --git a/runtime/python/prompty/tests/model/connection/test_o_auth_connection.py b/runtime/python/prompty/tests/model/connection/test_o_auth_connection.py index d00be0da..c38381cd 100644 --- a/runtime/python/prompty/tests/model/connection/test_o_auth_connection.py +++ b/runtime/python/prompty/tests/model/connection/test_o_auth_connection.py @@ -1,3 +1,4 @@ +# import json import yaml @@ -37,7 +38,7 @@ def test_load_yaml_oauthconnection(): tokenUrl: "https://login.microsoftonline.com/{tenant}/oauth2/v2.0/token" scopes: - "https://cognitiveservices.azure.com/.default" - + """ data = yaml.load(yaml_data, Loader=yaml.FullLoader) instance = OAuthConnection.load(data) diff --git a/runtime/python/prompty/tests/model/connection/test_reference_connection.py b/runtime/python/prompty/tests/model/connection/test_reference_connection.py index e1413c3a..12abdb16 100644 --- a/runtime/python/prompty/tests/model/connection/test_reference_connection.py +++ b/runtime/python/prompty/tests/model/connection/test_reference_connection.py @@ -1,3 +1,4 @@ +# import json import yaml @@ -26,7 +27,7 @@ def test_load_yaml_referenceconnection(): kind: reference name: my-reference-connection target: my-target-resource - + """ data = yaml.load(yaml_data, Loader=yaml.FullLoader) instance = ReferenceConnection.load(data) diff --git a/runtime/python/prompty/tests/model/connection/test_remote_connection.py b/runtime/python/prompty/tests/model/connection/test_remote_connection.py index c5799c41..bc1ea153 100644 --- a/runtime/python/prompty/tests/model/connection/test_remote_connection.py +++ b/runtime/python/prompty/tests/model/connection/test_remote_connection.py @@ -1,3 +1,4 @@ +# import json import yaml @@ -26,7 +27,7 @@ def test_load_yaml_remoteconnection(): kind: remote name: my-reference-connection endpoint: "https://{your-custom-endpoint}.openai.azure.com/" - + """ data = yaml.load(yaml_data, Loader=yaml.FullLoader) instance = RemoteConnection.load(data) diff --git a/runtime/python/prompty/tests/model/conversation/test_audio_part.py b/runtime/python/prompty/tests/model/conversation/test_audio_part.py index aa6fb24a..1bf28610 100644 --- a/runtime/python/prompty/tests/model/conversation/test_audio_part.py +++ b/runtime/python/prompty/tests/model/conversation/test_audio_part.py @@ -1,3 +1,4 @@ +# import json import yaml @@ -23,7 +24,7 @@ def test_load_yaml_audiopart(): yaml_data = r""" source: "https://example.com/audio.wav" mediaType: audio/wav - + """ data = yaml.load(yaml_data, Loader=yaml.FullLoader) instance = AudioPart.load(data) diff --git a/runtime/python/prompty/tests/model/conversation/test_content_part.py b/runtime/python/prompty/tests/model/conversation/test_content_part.py index 8b137891..47b56d67 100644 --- a/runtime/python/prompty/tests/model/conversation/test_content_part.py +++ b/runtime/python/prompty/tests/model/conversation/test_content_part.py @@ -1 +1 @@ - +# diff --git a/runtime/python/prompty/tests/model/conversation/test_file_part.py b/runtime/python/prompty/tests/model/conversation/test_file_part.py index f47505b4..c2ef2bea 100644 --- a/runtime/python/prompty/tests/model/conversation/test_file_part.py +++ b/runtime/python/prompty/tests/model/conversation/test_file_part.py @@ -1,3 +1,4 @@ +# import json import yaml @@ -23,7 +24,7 @@ def test_load_yaml_filepart(): yaml_data = r""" source: "https://example.com/document.pdf" mediaType: application/pdf - + """ data = yaml.load(yaml_data, Loader=yaml.FullLoader) instance = FilePart.load(data) diff --git a/runtime/python/prompty/tests/model/conversation/test_image_part.py b/runtime/python/prompty/tests/model/conversation/test_image_part.py index 97e53a92..0c326bf7 100644 --- a/runtime/python/prompty/tests/model/conversation/test_image_part.py +++ b/runtime/python/prompty/tests/model/conversation/test_image_part.py @@ -1,3 +1,4 @@ +# import json import yaml @@ -26,7 +27,7 @@ def test_load_yaml_imagepart(): source: "https://example.com/image.png" detail: auto mediaType: image/png - + """ data = yaml.load(yaml_data, Loader=yaml.FullLoader) instance = ImagePart.load(data) diff --git a/runtime/python/prompty/tests/model/conversation/test_message.py b/runtime/python/prompty/tests/model/conversation/test_message.py index ac52230a..e1fc9276 100644 --- a/runtime/python/prompty/tests/model/conversation/test_message.py +++ b/runtime/python/prompty/tests/model/conversation/test_message.py @@ -1,3 +1,4 @@ +# import json import yaml @@ -34,7 +35,7 @@ def test_load_yaml_message(): value: Hello! metadata: source: user-input - + """ data = yaml.load(yaml_data, Loader=yaml.FullLoader) instance = Message.load(data) diff --git a/runtime/python/prompty/tests/model/conversation/test_text_part.py b/runtime/python/prompty/tests/model/conversation/test_text_part.py index b8d22986..01cf1469 100644 --- a/runtime/python/prompty/tests/model/conversation/test_text_part.py +++ b/runtime/python/prompty/tests/model/conversation/test_text_part.py @@ -1,3 +1,4 @@ +# import json import yaml @@ -20,7 +21,7 @@ def test_load_json_textpart(): def test_load_yaml_textpart(): yaml_data = r""" value: Hello, world! - + """ data = yaml.load(yaml_data, Loader=yaml.FullLoader) instance = TextPart.load(data) diff --git a/runtime/python/prompty/tests/model/conversation/test_thread_marker.py b/runtime/python/prompty/tests/model/conversation/test_thread_marker.py index b9a9b20a..92b566d0 100644 --- a/runtime/python/prompty/tests/model/conversation/test_thread_marker.py +++ b/runtime/python/prompty/tests/model/conversation/test_thread_marker.py @@ -1,3 +1,4 @@ +# import json import yaml @@ -23,7 +24,7 @@ def test_load_yaml_threadmarker(): yaml_data = r""" name: thread kind: thread - + """ data = yaml.load(yaml_data, Loader=yaml.FullLoader) instance = ThreadMarker.load(data) diff --git a/runtime/python/prompty/tests/model/conversation/test_tool_call.py b/runtime/python/prompty/tests/model/conversation/test_tool_call.py index a7248f51..9f0c636b 100644 --- a/runtime/python/prompty/tests/model/conversation/test_tool_call.py +++ b/runtime/python/prompty/tests/model/conversation/test_tool_call.py @@ -1,3 +1,4 @@ +# import json import yaml @@ -26,7 +27,7 @@ def test_load_yaml_toolcall(): id: call_abc123 name: get_weather arguments: "{\"city\": \"Paris\"}" - + """ data = yaml.load(yaml_data, Loader=yaml.FullLoader) instance = ToolCall.load(data) diff --git a/runtime/python/prompty/tests/model/conversation/test_tool_result.py b/runtime/python/prompty/tests/model/conversation/test_tool_result.py index 1717e47d..6e26fbbc 100644 --- a/runtime/python/prompty/tests/model/conversation/test_tool_result.py +++ b/runtime/python/prompty/tests/model/conversation/test_tool_result.py @@ -1,3 +1,4 @@ +# import json import yaml @@ -13,12 +14,18 @@ def test_load_json_toolresult(): "kind": "text", "value": "72°F and sunny" } - ] + ], + "errorKind": "missing_tool", + "errorMessage": "Tool 'get_weather' is not registered", + "durationMs": 42 } """ data = json.loads(json_data, strict=False) instance = ToolResult.load(data) assert instance is not None + assert instance.error_kind == "missing_tool" + assert instance.error_message == "Tool 'get_weather' is not registered" + assert instance.duration_ms == 42 def test_load_yaml_toolresult(): @@ -26,11 +33,17 @@ def test_load_yaml_toolresult(): parts: - kind: text value: 72°F and sunny - + errorKind: missing_tool + errorMessage: Tool 'get_weather' is not registered + durationMs: 42 + """ data = yaml.load(yaml_data, Loader=yaml.FullLoader) instance = ToolResult.load(data) assert instance is not None + assert instance.error_kind == "missing_tool" + assert instance.error_message == "Tool 'get_weather' is not registered" + assert instance.duration_ms == 42 def test_roundtrip_json_toolresult(): @@ -42,7 +55,10 @@ def test_roundtrip_json_toolresult(): "kind": "text", "value": "72°F and sunny" } - ] + ], + "errorKind": "missing_tool", + "errorMessage": "Tool 'get_weather' is not registered", + "durationMs": 42 } """ original_data = json.loads(json_data, strict=False) @@ -50,6 +66,9 @@ def test_roundtrip_json_toolresult(): saved_data = instance.save() reloaded = ToolResult.load(saved_data) assert reloaded is not None + assert reloaded.error_kind == "missing_tool" + assert reloaded.error_message == "Tool 'get_weather' is not registered" + assert reloaded.duration_ms == 42 def test_to_json_toolresult(): @@ -61,7 +80,10 @@ def test_to_json_toolresult(): "kind": "text", "value": "72°F and sunny" } - ] + ], + "errorKind": "missing_tool", + "errorMessage": "Tool 'get_weather' is not registered", + "durationMs": 42 } """ data = json.loads(json_data, strict=False) @@ -81,7 +103,10 @@ def test_to_yaml_toolresult(): "kind": "text", "value": "72°F and sunny" } - ] + ], + "errorKind": "missing_tool", + "errorMessage": "Tool 'get_weather' is not registered", + "durationMs": 42 } """ data = json.loads(json_data, strict=False) diff --git a/runtime/python/prompty/tests/model/core/test_array_property.py b/runtime/python/prompty/tests/model/core/test_array_property.py index 3f63517a..70f29e0b 100644 --- a/runtime/python/prompty/tests/model/core/test_array_property.py +++ b/runtime/python/prompty/tests/model/core/test_array_property.py @@ -1,3 +1,4 @@ +# import json import yaml @@ -22,7 +23,7 @@ def test_load_yaml_arrayproperty(): yaml_data = r""" items: kind: string - + """ data = yaml.load(yaml_data, Loader=yaml.FullLoader) instance = ArrayProperty.load(data) diff --git a/runtime/python/prompty/tests/model/core/test_file_not_found_error.py b/runtime/python/prompty/tests/model/core/test_file_not_found_error.py index 4151e7c4..e0ab2125 100644 --- a/runtime/python/prompty/tests/model/core/test_file_not_found_error.py +++ b/runtime/python/prompty/tests/model/core/test_file_not_found_error.py @@ -1,3 +1,4 @@ +# import json import yaml @@ -23,7 +24,7 @@ def test_load_yaml_filenotfounderror(): yaml_data = r""" message: "Prompty file not found: ./chat.prompty" path: ./chat.prompty - + """ data = yaml.load(yaml_data, Loader=yaml.FullLoader) instance = FileNotFoundError.load(data) diff --git a/runtime/python/prompty/tests/model/core/test_invoker_error.py b/runtime/python/prompty/tests/model/core/test_invoker_error.py index f179e211..7647a976 100644 --- a/runtime/python/prompty/tests/model/core/test_invoker_error.py +++ b/runtime/python/prompty/tests/model/core/test_invoker_error.py @@ -1,3 +1,4 @@ +# import json import yaml @@ -26,7 +27,7 @@ def test_load_yaml_invokererror(): message: "No renderer registered for key: jinja2" component: renderer key: jinja2 - + """ data = yaml.load(yaml_data, Loader=yaml.FullLoader) instance = InvokerError.load(data) diff --git a/runtime/python/prompty/tests/model/core/test_object_property.py b/runtime/python/prompty/tests/model/core/test_object_property.py index 8956d1c0..d1e518d9 100644 --- a/runtime/python/prompty/tests/model/core/test_object_property.py +++ b/runtime/python/prompty/tests/model/core/test_object_property.py @@ -1,3 +1,4 @@ +# import json import yaml @@ -30,7 +31,7 @@ def test_load_yaml_objectproperty(): kind: string property2: kind: number - + """ data = yaml.load(yaml_data, Loader=yaml.FullLoader) instance = ObjectProperty.load(data) diff --git a/runtime/python/prompty/tests/model/core/test_property.py b/runtime/python/prompty/tests/model/core/test_property.py index 52a0e219..c7dc731e 100644 --- a/runtime/python/prompty/tests/model/core/test_property.py +++ b/runtime/python/prompty/tests/model/core/test_property.py @@ -1,3 +1,4 @@ +# import json import yaml @@ -44,7 +45,7 @@ def test_load_yaml_property(): - value1 - value2 - value3 - + """ data = yaml.load(yaml_data, Loader=yaml.FullLoader) instance = Property.load(data) diff --git a/runtime/python/prompty/tests/model/core/test_validation_error.py b/runtime/python/prompty/tests/model/core/test_validation_error.py index 4c7191b8..dd94ab06 100644 --- a/runtime/python/prompty/tests/model/core/test_validation_error.py +++ b/runtime/python/prompty/tests/model/core/test_validation_error.py @@ -1,3 +1,4 @@ +# import json import yaml @@ -26,7 +27,7 @@ def test_load_yaml_validationerror(): message: "Missing required input: firstName" property: firstName constraint: required - + """ data = yaml.load(yaml_data, Loader=yaml.FullLoader) instance = ValidationError.load(data) diff --git a/runtime/python/prompty/tests/model/core/test_validation_result.py b/runtime/python/prompty/tests/model/core/test_validation_result.py index dbd693f9..1f33af40 100644 --- a/runtime/python/prompty/tests/model/core/test_validation_result.py +++ b/runtime/python/prompty/tests/model/core/test_validation_result.py @@ -1,3 +1,4 @@ +# import json import yaml @@ -22,7 +23,7 @@ def test_load_yaml_validationresult(): yaml_data = r""" valid: true errors: [] - + """ data = yaml.load(yaml_data, Loader=yaml.FullLoader) instance = ValidationResult.load(data) diff --git a/runtime/python/prompty/tests/model/events/test_checkpoint.py b/runtime/python/prompty/tests/model/events/test_checkpoint.py new file mode 100644 index 00000000..4c8621b1 --- /dev/null +++ b/runtime/python/prompty/tests/model/events/test_checkpoint.py @@ -0,0 +1,114 @@ +# +import json + +import yaml + +from prompty.model import Checkpoint + + +def test_load_json_checkpoint(): + json_data = r""" + { + "id": "chk_abc123", + "sessionId": "sess_abc123", + "turnId": "turn_001", + "checkpointNumber": 3, + "title": "Added harness contracts", + "createdAt": "2026-06-09T20:00:00Z" + } + """ + data = json.loads(json_data, strict=False) + instance = Checkpoint.load(data) + assert instance is not None + assert instance.id == "chk_abc123" + assert instance.session_id == "sess_abc123" + assert instance.turn_id == "turn_001" + assert instance.checkpoint_number == 3 + assert instance.title == "Added harness contracts" + assert instance.created_at == "2026-06-09T20:00:00Z" + + +def test_load_yaml_checkpoint(): + yaml_data = r""" + id: chk_abc123 + sessionId: sess_abc123 + turnId: turn_001 + checkpointNumber: 3 + title: Added harness contracts + createdAt: "2026-06-09T20:00:00Z" + + """ + data = yaml.load(yaml_data, Loader=yaml.FullLoader) + instance = Checkpoint.load(data) + assert instance is not None + assert instance.id == "chk_abc123" + assert instance.session_id == "sess_abc123" + assert instance.turn_id == "turn_001" + assert instance.checkpoint_number == 3 + assert instance.title == "Added harness contracts" + assert instance.created_at == "2026-06-09T20:00:00Z" + + +def test_roundtrip_json_checkpoint(): + """Test that load -> save -> load produces equivalent data.""" + json_data = r""" + { + "id": "chk_abc123", + "sessionId": "sess_abc123", + "turnId": "turn_001", + "checkpointNumber": 3, + "title": "Added harness contracts", + "createdAt": "2026-06-09T20:00:00Z" + } + """ + original_data = json.loads(json_data, strict=False) + instance = Checkpoint.load(original_data) + saved_data = instance.save() + reloaded = Checkpoint.load(saved_data) + assert reloaded is not None + assert reloaded.id == "chk_abc123" + assert reloaded.session_id == "sess_abc123" + assert reloaded.turn_id == "turn_001" + assert reloaded.checkpoint_number == 3 + assert reloaded.title == "Added harness contracts" + assert reloaded.created_at == "2026-06-09T20:00:00Z" + + +def test_to_json_checkpoint(): + """Test that to_json produces valid JSON.""" + json_data = r""" + { + "id": "chk_abc123", + "sessionId": "sess_abc123", + "turnId": "turn_001", + "checkpointNumber": 3, + "title": "Added harness contracts", + "createdAt": "2026-06-09T20:00:00Z" + } + """ + data = json.loads(json_data, strict=False) + instance = Checkpoint.load(data) + json_output = instance.to_json() + assert json_output is not None + parsed = json.loads(json_output) + assert isinstance(parsed, dict) + + +def test_to_yaml_checkpoint(): + """Test that to_yaml produces valid YAML.""" + json_data = r""" + { + "id": "chk_abc123", + "sessionId": "sess_abc123", + "turnId": "turn_001", + "checkpointNumber": 3, + "title": "Added harness contracts", + "createdAt": "2026-06-09T20:00:00Z" + } + """ + data = json.loads(json_data, strict=False) + instance = Checkpoint.load(data) + yaml_output = instance.to_yaml() + assert yaml_output is not None + parsed = yaml.safe_load(yaml_output) + assert isinstance(parsed, dict) diff --git a/runtime/python/prompty/tests/model/events/test_compaction_complete_payload.py b/runtime/python/prompty/tests/model/events/test_compaction_complete_payload.py index e235eb85..50ef24d7 100644 --- a/runtime/python/prompty/tests/model/events/test_compaction_complete_payload.py +++ b/runtime/python/prompty/tests/model/events/test_compaction_complete_payload.py @@ -1,3 +1,4 @@ +# import json import yaml @@ -9,7 +10,8 @@ def test_load_json_compactioncompletepayload(): json_data = r""" { "removed": 5, - "remaining": 3 + "remaining": 3, + "summaryLength": 1200 } """ data = json.loads(json_data, strict=False) @@ -17,19 +19,22 @@ def test_load_json_compactioncompletepayload(): assert instance is not None assert instance.removed == 5 assert instance.remaining == 3 + assert instance.summary_length == 1200 def test_load_yaml_compactioncompletepayload(): yaml_data = r""" removed: 5 remaining: 3 - + summaryLength: 1200 + """ data = yaml.load(yaml_data, Loader=yaml.FullLoader) instance = CompactionCompletePayload.load(data) assert instance is not None assert instance.removed == 5 assert instance.remaining == 3 + assert instance.summary_length == 1200 def test_roundtrip_json_compactioncompletepayload(): @@ -37,7 +42,8 @@ def test_roundtrip_json_compactioncompletepayload(): json_data = r""" { "removed": 5, - "remaining": 3 + "remaining": 3, + "summaryLength": 1200 } """ original_data = json.loads(json_data, strict=False) @@ -47,6 +53,7 @@ def test_roundtrip_json_compactioncompletepayload(): assert reloaded is not None assert reloaded.removed == 5 assert reloaded.remaining == 3 + assert reloaded.summary_length == 1200 def test_to_json_compactioncompletepayload(): @@ -54,7 +61,8 @@ def test_to_json_compactioncompletepayload(): json_data = r""" { "removed": 5, - "remaining": 3 + "remaining": 3, + "summaryLength": 1200 } """ data = json.loads(json_data, strict=False) @@ -70,7 +78,8 @@ def test_to_yaml_compactioncompletepayload(): json_data = r""" { "removed": 5, - "remaining": 3 + "remaining": 3, + "summaryLength": 1200 } """ data = json.loads(json_data, strict=False) diff --git a/runtime/python/prompty/tests/model/events/test_compaction_failed_payload.py b/runtime/python/prompty/tests/model/events/test_compaction_failed_payload.py index d7ef0d06..43c0e248 100644 --- a/runtime/python/prompty/tests/model/events/test_compaction_failed_payload.py +++ b/runtime/python/prompty/tests/model/events/test_compaction_failed_payload.py @@ -1,3 +1,4 @@ +# import json import yaml @@ -20,7 +21,7 @@ def test_load_json_compactionfailedpayload(): def test_load_yaml_compactionfailedpayload(): yaml_data = r""" message: Summarization prompt exceeded context window - + """ data = yaml.load(yaml_data, Loader=yaml.FullLoader) instance = CompactionFailedPayload.load(data) diff --git a/runtime/python/prompty/tests/model/events/test_compaction_start_payload.py b/runtime/python/prompty/tests/model/events/test_compaction_start_payload.py new file mode 100644 index 00000000..07a9c224 --- /dev/null +++ b/runtime/python/prompty/tests/model/events/test_compaction_start_payload.py @@ -0,0 +1,74 @@ +# +import json + +import yaml + +from prompty.model import CompactionStartPayload + + +def test_load_json_compactionstartpayload(): + json_data = r""" + { + "droppedCount": 5 + } + """ + data = json.loads(json_data, strict=False) + instance = CompactionStartPayload.load(data) + assert instance is not None + assert instance.dropped_count == 5 + + +def test_load_yaml_compactionstartpayload(): + yaml_data = r""" + droppedCount: 5 + + """ + data = yaml.load(yaml_data, Loader=yaml.FullLoader) + instance = CompactionStartPayload.load(data) + assert instance is not None + assert instance.dropped_count == 5 + + +def test_roundtrip_json_compactionstartpayload(): + """Test that load -> save -> load produces equivalent data.""" + json_data = r""" + { + "droppedCount": 5 + } + """ + original_data = json.loads(json_data, strict=False) + instance = CompactionStartPayload.load(original_data) + saved_data = instance.save() + reloaded = CompactionStartPayload.load(saved_data) + assert reloaded is not None + assert reloaded.dropped_count == 5 + + +def test_to_json_compactionstartpayload(): + """Test that to_json produces valid JSON.""" + json_data = r""" + { + "droppedCount": 5 + } + """ + data = json.loads(json_data, strict=False) + instance = CompactionStartPayload.load(data) + json_output = instance.to_json() + assert json_output is not None + parsed = json.loads(json_output) + assert isinstance(parsed, dict) + + +def test_to_yaml_compactionstartpayload(): + """Test that to_yaml produces valid YAML.""" + json_data = r""" + { + "droppedCount": 5 + } + """ + data = json.loads(json_data, strict=False) + instance = CompactionStartPayload.load(data) + yaml_output = instance.to_yaml() + assert yaml_output is not None + parsed = yaml.safe_load(yaml_output) + assert isinstance(parsed, dict) diff --git a/runtime/python/prompty/tests/model/events/test_done_event_payload.py b/runtime/python/prompty/tests/model/events/test_done_event_payload.py index fbc99566..47b56d67 100644 --- a/runtime/python/prompty/tests/model/events/test_done_event_payload.py +++ b/runtime/python/prompty/tests/model/events/test_done_event_payload.py @@ -1,73 +1 @@ -import json - -import yaml - -from prompty.model import DoneEventPayload - - -def test_load_json_doneeventpayload(): - json_data = r""" - { - "response": "The weather in Paris is 72°F and sunny." - } - """ - data = json.loads(json_data, strict=False) - instance = DoneEventPayload.load(data) - assert instance is not None - assert instance.response == "The weather in Paris is 72°F and sunny." - - -def test_load_yaml_doneeventpayload(): - yaml_data = r""" - response: The weather in Paris is 72°F and sunny. - - """ - data = yaml.load(yaml_data, Loader=yaml.FullLoader) - instance = DoneEventPayload.load(data) - assert instance is not None - assert instance.response == "The weather in Paris is 72°F and sunny." - - -def test_roundtrip_json_doneeventpayload(): - """Test that load -> save -> load produces equivalent data.""" - json_data = r""" - { - "response": "The weather in Paris is 72°F and sunny." - } - """ - original_data = json.loads(json_data, strict=False) - instance = DoneEventPayload.load(original_data) - saved_data = instance.save() - reloaded = DoneEventPayload.load(saved_data) - assert reloaded is not None - assert reloaded.response == "The weather in Paris is 72°F and sunny." - - -def test_to_json_doneeventpayload(): - """Test that to_json produces valid JSON.""" - json_data = r""" - { - "response": "The weather in Paris is 72°F and sunny." - } - """ - data = json.loads(json_data, strict=False) - instance = DoneEventPayload.load(data) - json_output = instance.to_json() - assert json_output is not None - parsed = json.loads(json_output) - assert isinstance(parsed, dict) - - -def test_to_yaml_doneeventpayload(): - """Test that to_yaml produces valid YAML.""" - json_data = r""" - { - "response": "The weather in Paris is 72°F and sunny." - } - """ - data = json.loads(json_data, strict=False) - instance = DoneEventPayload.load(data) - yaml_output = instance.to_yaml() - assert yaml_output is not None - parsed = yaml.safe_load(yaml_output) - assert isinstance(parsed, dict) +# diff --git a/runtime/python/prompty/tests/model/events/test_error_chunk.py b/runtime/python/prompty/tests/model/events/test_error_chunk.py index 2116a0b5..b4c1eb28 100644 --- a/runtime/python/prompty/tests/model/events/test_error_chunk.py +++ b/runtime/python/prompty/tests/model/events/test_error_chunk.py @@ -1,3 +1,4 @@ +# import json import yaml @@ -20,7 +21,7 @@ def test_load_json_errorchunk(): def test_load_yaml_errorchunk(): yaml_data = r""" message: Rate limit exceeded - + """ data = yaml.load(yaml_data, Loader=yaml.FullLoader) instance = ErrorChunk.load(data) diff --git a/runtime/python/prompty/tests/model/events/test_error_event_payload.py b/runtime/python/prompty/tests/model/events/test_error_event_payload.py index a68637b2..8056a237 100644 --- a/runtime/python/prompty/tests/model/events/test_error_event_payload.py +++ b/runtime/python/prompty/tests/model/events/test_error_event_payload.py @@ -1,3 +1,4 @@ +# import json import yaml @@ -8,31 +9,41 @@ def test_load_json_erroreventpayload(): json_data = r""" { - "message": "Rate limit exceeded" + "message": "Rate limit exceeded", + "errorKind": "rate_limit", + "phase": "llm" } """ data = json.loads(json_data, strict=False) instance = ErrorEventPayload.load(data) assert instance is not None assert instance.message == "Rate limit exceeded" + assert instance.error_kind == "rate_limit" + assert instance.phase == "llm" def test_load_yaml_erroreventpayload(): yaml_data = r""" message: Rate limit exceeded - + errorKind: rate_limit + phase: llm + """ data = yaml.load(yaml_data, Loader=yaml.FullLoader) instance = ErrorEventPayload.load(data) assert instance is not None assert instance.message == "Rate limit exceeded" + assert instance.error_kind == "rate_limit" + assert instance.phase == "llm" def test_roundtrip_json_erroreventpayload(): """Test that load -> save -> load produces equivalent data.""" json_data = r""" { - "message": "Rate limit exceeded" + "message": "Rate limit exceeded", + "errorKind": "rate_limit", + "phase": "llm" } """ original_data = json.loads(json_data, strict=False) @@ -41,13 +52,17 @@ def test_roundtrip_json_erroreventpayload(): reloaded = ErrorEventPayload.load(saved_data) assert reloaded is not None assert reloaded.message == "Rate limit exceeded" + assert reloaded.error_kind == "rate_limit" + assert reloaded.phase == "llm" def test_to_json_erroreventpayload(): """Test that to_json produces valid JSON.""" json_data = r""" { - "message": "Rate limit exceeded" + "message": "Rate limit exceeded", + "errorKind": "rate_limit", + "phase": "llm" } """ data = json.loads(json_data, strict=False) @@ -62,7 +77,9 @@ def test_to_yaml_erroreventpayload(): """Test that to_yaml produces valid YAML.""" json_data = r""" { - "message": "Rate limit exceeded" + "message": "Rate limit exceeded", + "errorKind": "rate_limit", + "phase": "llm" } """ data = json.loads(json_data, strict=False) diff --git a/runtime/python/prompty/tests/model/events/test_harness_context.py b/runtime/python/prompty/tests/model/events/test_harness_context.py new file mode 100644 index 00000000..1c6b51ba --- /dev/null +++ b/runtime/python/prompty/tests/model/events/test_harness_context.py @@ -0,0 +1,82 @@ +# +import json + +import yaml + +from prompty.model import HarnessContext + + +def test_load_json_harnesscontext(): + json_data = r""" + { + "cwd": "/workspace/project", + "gitRoot": "/workspace/project" + } + """ + data = json.loads(json_data, strict=False) + instance = HarnessContext.load(data) + assert instance is not None + assert instance.cwd == "/workspace/project" + assert instance.git_root == "/workspace/project" + + +def test_load_yaml_harnesscontext(): + yaml_data = r""" + cwd: /workspace/project + gitRoot: /workspace/project + + """ + data = yaml.load(yaml_data, Loader=yaml.FullLoader) + instance = HarnessContext.load(data) + assert instance is not None + assert instance.cwd == "/workspace/project" + assert instance.git_root == "/workspace/project" + + +def test_roundtrip_json_harnesscontext(): + """Test that load -> save -> load produces equivalent data.""" + json_data = r""" + { + "cwd": "/workspace/project", + "gitRoot": "/workspace/project" + } + """ + original_data = json.loads(json_data, strict=False) + instance = HarnessContext.load(original_data) + saved_data = instance.save() + reloaded = HarnessContext.load(saved_data) + assert reloaded is not None + assert reloaded.cwd == "/workspace/project" + assert reloaded.git_root == "/workspace/project" + + +def test_to_json_harnesscontext(): + """Test that to_json produces valid JSON.""" + json_data = r""" + { + "cwd": "/workspace/project", + "gitRoot": "/workspace/project" + } + """ + data = json.loads(json_data, strict=False) + instance = HarnessContext.load(data) + json_output = instance.to_json() + assert json_output is not None + parsed = json.loads(json_output) + assert isinstance(parsed, dict) + + +def test_to_yaml_harnesscontext(): + """Test that to_yaml produces valid YAML.""" + json_data = r""" + { + "cwd": "/workspace/project", + "gitRoot": "/workspace/project" + } + """ + data = json.loads(json_data, strict=False) + instance = HarnessContext.load(data) + yaml_output = instance.to_yaml() + assert yaml_output is not None + parsed = yaml.safe_load(yaml_output) + assert isinstance(parsed, dict) diff --git a/runtime/python/prompty/tests/model/events/test_hook_end_payload.py b/runtime/python/prompty/tests/model/events/test_hook_end_payload.py new file mode 100644 index 00000000..8c94ae9c --- /dev/null +++ b/runtime/python/prompty/tests/model/events/test_hook_end_payload.py @@ -0,0 +1,106 @@ +# +import json + +import yaml + +from prompty.model import HookEndPayload + + +def test_load_json_hookendpayload(): + json_data = r""" + { + "hookInvocationId": "hook_abc123", + "hookType": "preToolUse", + "success": true, + "durationMs": 12, + "error": "hook failed" + } + """ + data = json.loads(json_data, strict=False) + instance = HookEndPayload.load(data) + assert instance is not None + assert instance.hook_invocation_id == "hook_abc123" + assert instance.hook_type == "preToolUse" + assert instance.success + assert instance.duration_ms == 12 + assert instance.error == "hook failed" + + +def test_load_yaml_hookendpayload(): + yaml_data = r""" + hookInvocationId: hook_abc123 + hookType: preToolUse + success: true + durationMs: 12 + error: hook failed + + """ + data = yaml.load(yaml_data, Loader=yaml.FullLoader) + instance = HookEndPayload.load(data) + assert instance is not None + assert instance.hook_invocation_id == "hook_abc123" + assert instance.hook_type == "preToolUse" + assert instance.success + assert instance.duration_ms == 12 + assert instance.error == "hook failed" + + +def test_roundtrip_json_hookendpayload(): + """Test that load -> save -> load produces equivalent data.""" + json_data = r""" + { + "hookInvocationId": "hook_abc123", + "hookType": "preToolUse", + "success": true, + "durationMs": 12, + "error": "hook failed" + } + """ + original_data = json.loads(json_data, strict=False) + instance = HookEndPayload.load(original_data) + saved_data = instance.save() + reloaded = HookEndPayload.load(saved_data) + assert reloaded is not None + assert reloaded.hook_invocation_id == "hook_abc123" + assert reloaded.hook_type == "preToolUse" + assert reloaded.success + assert reloaded.duration_ms == 12 + assert reloaded.error == "hook failed" + + +def test_to_json_hookendpayload(): + """Test that to_json produces valid JSON.""" + json_data = r""" + { + "hookInvocationId": "hook_abc123", + "hookType": "preToolUse", + "success": true, + "durationMs": 12, + "error": "hook failed" + } + """ + data = json.loads(json_data, strict=False) + instance = HookEndPayload.load(data) + json_output = instance.to_json() + assert json_output is not None + parsed = json.loads(json_output) + assert isinstance(parsed, dict) + + +def test_to_yaml_hookendpayload(): + """Test that to_yaml produces valid YAML.""" + json_data = r""" + { + "hookInvocationId": "hook_abc123", + "hookType": "preToolUse", + "success": true, + "durationMs": 12, + "error": "hook failed" + } + """ + data = json.loads(json_data, strict=False) + instance = HookEndPayload.load(data) + yaml_output = instance.to_yaml() + assert yaml_output is not None + parsed = yaml.safe_load(yaml_output) + assert isinstance(parsed, dict) diff --git a/runtime/python/prompty/tests/model/events/test_hook_start_payload.py b/runtime/python/prompty/tests/model/events/test_hook_start_payload.py new file mode 100644 index 00000000..f67c22fd --- /dev/null +++ b/runtime/python/prompty/tests/model/events/test_hook_start_payload.py @@ -0,0 +1,82 @@ +# +import json + +import yaml + +from prompty.model import HookStartPayload + + +def test_load_json_hookstartpayload(): + json_data = r""" + { + "hookInvocationId": "hook_abc123", + "hookType": "preToolUse" + } + """ + data = json.loads(json_data, strict=False) + instance = HookStartPayload.load(data) + assert instance is not None + assert instance.hook_invocation_id == "hook_abc123" + assert instance.hook_type == "preToolUse" + + +def test_load_yaml_hookstartpayload(): + yaml_data = r""" + hookInvocationId: hook_abc123 + hookType: preToolUse + + """ + data = yaml.load(yaml_data, Loader=yaml.FullLoader) + instance = HookStartPayload.load(data) + assert instance is not None + assert instance.hook_invocation_id == "hook_abc123" + assert instance.hook_type == "preToolUse" + + +def test_roundtrip_json_hookstartpayload(): + """Test that load -> save -> load produces equivalent data.""" + json_data = r""" + { + "hookInvocationId": "hook_abc123", + "hookType": "preToolUse" + } + """ + original_data = json.loads(json_data, strict=False) + instance = HookStartPayload.load(original_data) + saved_data = instance.save() + reloaded = HookStartPayload.load(saved_data) + assert reloaded is not None + assert reloaded.hook_invocation_id == "hook_abc123" + assert reloaded.hook_type == "preToolUse" + + +def test_to_json_hookstartpayload(): + """Test that to_json produces valid JSON.""" + json_data = r""" + { + "hookInvocationId": "hook_abc123", + "hookType": "preToolUse" + } + """ + data = json.loads(json_data, strict=False) + instance = HookStartPayload.load(data) + json_output = instance.to_json() + assert json_output is not None + parsed = json.loads(json_output) + assert isinstance(parsed, dict) + + +def test_to_yaml_hookstartpayload(): + """Test that to_yaml produces valid YAML.""" + json_data = r""" + { + "hookInvocationId": "hook_abc123", + "hookType": "preToolUse" + } + """ + data = json.loads(json_data, strict=False) + instance = HookStartPayload.load(data) + yaml_output = instance.to_yaml() + assert yaml_output is not None + parsed = yaml.safe_load(yaml_output) + assert isinstance(parsed, dict) diff --git a/runtime/python/prompty/tests/model/events/test_host_tool_request.py b/runtime/python/prompty/tests/model/events/test_host_tool_request.py new file mode 100644 index 00000000..670be8af --- /dev/null +++ b/runtime/python/prompty/tests/model/events/test_host_tool_request.py @@ -0,0 +1,98 @@ +# +import json + +import yaml + +from prompty.model import HostToolRequest + + +def test_load_json_hosttoolrequest(): + json_data = r""" + { + "requestId": "exec_abc123", + "toolCallId": "call_abc123", + "toolName": "powershell", + "workingDirectory": "/workspace/project" + } + """ + data = json.loads(json_data, strict=False) + instance = HostToolRequest.load(data) + assert instance is not None + assert instance.request_id == "exec_abc123" + assert instance.tool_call_id == "call_abc123" + assert instance.tool_name == "powershell" + assert instance.working_directory == "/workspace/project" + + +def test_load_yaml_hosttoolrequest(): + yaml_data = r""" + requestId: exec_abc123 + toolCallId: call_abc123 + toolName: powershell + workingDirectory: /workspace/project + + """ + data = yaml.load(yaml_data, Loader=yaml.FullLoader) + instance = HostToolRequest.load(data) + assert instance is not None + assert instance.request_id == "exec_abc123" + assert instance.tool_call_id == "call_abc123" + assert instance.tool_name == "powershell" + assert instance.working_directory == "/workspace/project" + + +def test_roundtrip_json_hosttoolrequest(): + """Test that load -> save -> load produces equivalent data.""" + json_data = r""" + { + "requestId": "exec_abc123", + "toolCallId": "call_abc123", + "toolName": "powershell", + "workingDirectory": "/workspace/project" + } + """ + original_data = json.loads(json_data, strict=False) + instance = HostToolRequest.load(original_data) + saved_data = instance.save() + reloaded = HostToolRequest.load(saved_data) + assert reloaded is not None + assert reloaded.request_id == "exec_abc123" + assert reloaded.tool_call_id == "call_abc123" + assert reloaded.tool_name == "powershell" + assert reloaded.working_directory == "/workspace/project" + + +def test_to_json_hosttoolrequest(): + """Test that to_json produces valid JSON.""" + json_data = r""" + { + "requestId": "exec_abc123", + "toolCallId": "call_abc123", + "toolName": "powershell", + "workingDirectory": "/workspace/project" + } + """ + data = json.loads(json_data, strict=False) + instance = HostToolRequest.load(data) + json_output = instance.to_json() + assert json_output is not None + parsed = json.loads(json_output) + assert isinstance(parsed, dict) + + +def test_to_yaml_hosttoolrequest(): + """Test that to_yaml produces valid YAML.""" + json_data = r""" + { + "requestId": "exec_abc123", + "toolCallId": "call_abc123", + "toolName": "powershell", + "workingDirectory": "/workspace/project" + } + """ + data = json.loads(json_data, strict=False) + instance = HostToolRequest.load(data) + yaml_output = instance.to_yaml() + assert yaml_output is not None + parsed = yaml.safe_load(yaml_output) + assert isinstance(parsed, dict) diff --git a/runtime/python/prompty/tests/model/events/test_host_tool_result.py b/runtime/python/prompty/tests/model/events/test_host_tool_result.py new file mode 100644 index 00000000..77a04cd4 --- /dev/null +++ b/runtime/python/prompty/tests/model/events/test_host_tool_result.py @@ -0,0 +1,122 @@ +# +import json + +import yaml + +from prompty.model import HostToolResult + + +def test_load_json_hosttoolresult(): + json_data = r""" + { + "requestId": "exec_abc123", + "toolCallId": "call_abc123", + "toolName": "powershell", + "success": true, + "exitCode": 0, + "durationMs": 250, + "errorKind": "timeout" + } + """ + data = json.loads(json_data, strict=False) + instance = HostToolResult.load(data) + assert instance is not None + assert instance.request_id == "exec_abc123" + assert instance.tool_call_id == "call_abc123" + assert instance.tool_name == "powershell" + assert instance.success + assert instance.exit_code == 0 + assert instance.duration_ms == 250 + assert instance.error_kind == "timeout" + + +def test_load_yaml_hosttoolresult(): + yaml_data = r""" + requestId: exec_abc123 + toolCallId: call_abc123 + toolName: powershell + success: true + exitCode: 0 + durationMs: 250 + errorKind: timeout + + """ + data = yaml.load(yaml_data, Loader=yaml.FullLoader) + instance = HostToolResult.load(data) + assert instance is not None + assert instance.request_id == "exec_abc123" + assert instance.tool_call_id == "call_abc123" + assert instance.tool_name == "powershell" + assert instance.success + assert instance.exit_code == 0 + assert instance.duration_ms == 250 + assert instance.error_kind == "timeout" + + +def test_roundtrip_json_hosttoolresult(): + """Test that load -> save -> load produces equivalent data.""" + json_data = r""" + { + "requestId": "exec_abc123", + "toolCallId": "call_abc123", + "toolName": "powershell", + "success": true, + "exitCode": 0, + "durationMs": 250, + "errorKind": "timeout" + } + """ + original_data = json.loads(json_data, strict=False) + instance = HostToolResult.load(original_data) + saved_data = instance.save() + reloaded = HostToolResult.load(saved_data) + assert reloaded is not None + assert reloaded.request_id == "exec_abc123" + assert reloaded.tool_call_id == "call_abc123" + assert reloaded.tool_name == "powershell" + assert reloaded.success + assert reloaded.exit_code == 0 + assert reloaded.duration_ms == 250 + assert reloaded.error_kind == "timeout" + + +def test_to_json_hosttoolresult(): + """Test that to_json produces valid JSON.""" + json_data = r""" + { + "requestId": "exec_abc123", + "toolCallId": "call_abc123", + "toolName": "powershell", + "success": true, + "exitCode": 0, + "durationMs": 250, + "errorKind": "timeout" + } + """ + data = json.loads(json_data, strict=False) + instance = HostToolResult.load(data) + json_output = instance.to_json() + assert json_output is not None + parsed = json.loads(json_output) + assert isinstance(parsed, dict) + + +def test_to_yaml_hosttoolresult(): + """Test that to_yaml produces valid YAML.""" + json_data = r""" + { + "requestId": "exec_abc123", + "toolCallId": "call_abc123", + "toolName": "powershell", + "success": true, + "exitCode": 0, + "durationMs": 250, + "errorKind": "timeout" + } + """ + data = json.loads(json_data, strict=False) + instance = HostToolResult.load(data) + yaml_output = instance.to_yaml() + assert yaml_output is not None + parsed = yaml.safe_load(yaml_output) + assert isinstance(parsed, dict) diff --git a/runtime/python/prompty/tests/model/events/test_llm_complete_payload.py b/runtime/python/prompty/tests/model/events/test_llm_complete_payload.py new file mode 100644 index 00000000..be3065bf --- /dev/null +++ b/runtime/python/prompty/tests/model/events/test_llm_complete_payload.py @@ -0,0 +1,90 @@ +# +import json + +import yaml + +from prompty.model import LlmCompletePayload + + +def test_load_json_llmcompletepayload(): + json_data = r""" + { + "requestId": "req_abc123", + "serviceRequestId": "srv_abc123", + "durationMs": 820 + } + """ + data = json.loads(json_data, strict=False) + instance = LlmCompletePayload.load(data) + assert instance is not None + assert instance.request_id == "req_abc123" + assert instance.service_request_id == "srv_abc123" + assert instance.duration_ms == 820 + + +def test_load_yaml_llmcompletepayload(): + yaml_data = r""" + requestId: req_abc123 + serviceRequestId: srv_abc123 + durationMs: 820 + + """ + data = yaml.load(yaml_data, Loader=yaml.FullLoader) + instance = LlmCompletePayload.load(data) + assert instance is not None + assert instance.request_id == "req_abc123" + assert instance.service_request_id == "srv_abc123" + assert instance.duration_ms == 820 + + +def test_roundtrip_json_llmcompletepayload(): + """Test that load -> save -> load produces equivalent data.""" + json_data = r""" + { + "requestId": "req_abc123", + "serviceRequestId": "srv_abc123", + "durationMs": 820 + } + """ + original_data = json.loads(json_data, strict=False) + instance = LlmCompletePayload.load(original_data) + saved_data = instance.save() + reloaded = LlmCompletePayload.load(saved_data) + assert reloaded is not None + assert reloaded.request_id == "req_abc123" + assert reloaded.service_request_id == "srv_abc123" + assert reloaded.duration_ms == 820 + + +def test_to_json_llmcompletepayload(): + """Test that to_json produces valid JSON.""" + json_data = r""" + { + "requestId": "req_abc123", + "serviceRequestId": "srv_abc123", + "durationMs": 820 + } + """ + data = json.loads(json_data, strict=False) + instance = LlmCompletePayload.load(data) + json_output = instance.to_json() + assert json_output is not None + parsed = json.loads(json_output) + assert isinstance(parsed, dict) + + +def test_to_yaml_llmcompletepayload(): + """Test that to_yaml produces valid YAML.""" + json_data = r""" + { + "requestId": "req_abc123", + "serviceRequestId": "srv_abc123", + "durationMs": 820 + } + """ + data = json.loads(json_data, strict=False) + instance = LlmCompletePayload.load(data) + yaml_output = instance.to_yaml() + assert yaml_output is not None + parsed = yaml.safe_load(yaml_output) + assert isinstance(parsed, dict) diff --git a/runtime/python/prompty/tests/model/events/test_llm_start_payload.py b/runtime/python/prompty/tests/model/events/test_llm_start_payload.py new file mode 100644 index 00000000..7c233949 --- /dev/null +++ b/runtime/python/prompty/tests/model/events/test_llm_start_payload.py @@ -0,0 +1,98 @@ +# +import json + +import yaml + +from prompty.model import LlmStartPayload + + +def test_load_json_llmstartpayload(): + json_data = r""" + { + "provider": "openai", + "modelId": "gpt-4o-mini", + "messageCount": 4, + "attempt": 0 + } + """ + data = json.loads(json_data, strict=False) + instance = LlmStartPayload.load(data) + assert instance is not None + assert instance.provider == "openai" + assert instance.model_id == "gpt-4o-mini" + assert instance.message_count == 4 + assert instance.attempt == 0 + + +def test_load_yaml_llmstartpayload(): + yaml_data = r""" + provider: openai + modelId: gpt-4o-mini + messageCount: 4 + attempt: 0 + + """ + data = yaml.load(yaml_data, Loader=yaml.FullLoader) + instance = LlmStartPayload.load(data) + assert instance is not None + assert instance.provider == "openai" + assert instance.model_id == "gpt-4o-mini" + assert instance.message_count == 4 + assert instance.attempt == 0 + + +def test_roundtrip_json_llmstartpayload(): + """Test that load -> save -> load produces equivalent data.""" + json_data = r""" + { + "provider": "openai", + "modelId": "gpt-4o-mini", + "messageCount": 4, + "attempt": 0 + } + """ + original_data = json.loads(json_data, strict=False) + instance = LlmStartPayload.load(original_data) + saved_data = instance.save() + reloaded = LlmStartPayload.load(saved_data) + assert reloaded is not None + assert reloaded.provider == "openai" + assert reloaded.model_id == "gpt-4o-mini" + assert reloaded.message_count == 4 + assert reloaded.attempt == 0 + + +def test_to_json_llmstartpayload(): + """Test that to_json produces valid JSON.""" + json_data = r""" + { + "provider": "openai", + "modelId": "gpt-4o-mini", + "messageCount": 4, + "attempt": 0 + } + """ + data = json.loads(json_data, strict=False) + instance = LlmStartPayload.load(data) + json_output = instance.to_json() + assert json_output is not None + parsed = json.loads(json_output) + assert isinstance(parsed, dict) + + +def test_to_yaml_llmstartpayload(): + """Test that to_yaml produces valid YAML.""" + json_data = r""" + { + "provider": "openai", + "modelId": "gpt-4o-mini", + "messageCount": 4, + "attempt": 0 + } + """ + data = json.loads(json_data, strict=False) + instance = LlmStartPayload.load(data) + yaml_output = instance.to_yaml() + assert yaml_output is not None + parsed = yaml.safe_load(yaml_output) + assert isinstance(parsed, dict) diff --git a/runtime/python/prompty/tests/model/events/test_messages_updated_payload.py b/runtime/python/prompty/tests/model/events/test_messages_updated_payload.py index 8b137891..d304426a 100644 --- a/runtime/python/prompty/tests/model/events/test_messages_updated_payload.py +++ b/runtime/python/prompty/tests/model/events/test_messages_updated_payload.py @@ -1 +1,82 @@ +# +import json +import yaml + +from prompty.model import MessagesUpdatedPayload + + +def test_load_json_messagesupdatedpayload(): + json_data = r""" + { + "reason": "tool_results", + "removed": 2 + } + """ + data = json.loads(json_data, strict=False) + instance = MessagesUpdatedPayload.load(data) + assert instance is not None + assert instance.reason == "tool_results" + assert instance.removed == 2 + + +def test_load_yaml_messagesupdatedpayload(): + yaml_data = r""" + reason: tool_results + removed: 2 + + """ + data = yaml.load(yaml_data, Loader=yaml.FullLoader) + instance = MessagesUpdatedPayload.load(data) + assert instance is not None + assert instance.reason == "tool_results" + assert instance.removed == 2 + + +def test_roundtrip_json_messagesupdatedpayload(): + """Test that load -> save -> load produces equivalent data.""" + json_data = r""" + { + "reason": "tool_results", + "removed": 2 + } + """ + original_data = json.loads(json_data, strict=False) + instance = MessagesUpdatedPayload.load(original_data) + saved_data = instance.save() + reloaded = MessagesUpdatedPayload.load(saved_data) + assert reloaded is not None + assert reloaded.reason == "tool_results" + assert reloaded.removed == 2 + + +def test_to_json_messagesupdatedpayload(): + """Test that to_json produces valid JSON.""" + json_data = r""" + { + "reason": "tool_results", + "removed": 2 + } + """ + data = json.loads(json_data, strict=False) + instance = MessagesUpdatedPayload.load(data) + json_output = instance.to_json() + assert json_output is not None + parsed = json.loads(json_output) + assert isinstance(parsed, dict) + + +def test_to_yaml_messagesupdatedpayload(): + """Test that to_yaml produces valid YAML.""" + json_data = r""" + { + "reason": "tool_results", + "removed": 2 + } + """ + data = json.loads(json_data, strict=False) + instance = MessagesUpdatedPayload.load(data) + yaml_output = instance.to_yaml() + assert yaml_output is not None + parsed = yaml.safe_load(yaml_output) + assert isinstance(parsed, dict) diff --git a/runtime/python/prompty/tests/model/events/test_permission_completed_payload.py b/runtime/python/prompty/tests/model/events/test_permission_completed_payload.py new file mode 100644 index 00000000..d9f07ca7 --- /dev/null +++ b/runtime/python/prompty/tests/model/events/test_permission_completed_payload.py @@ -0,0 +1,106 @@ +# +import json + +import yaml + +from prompty.model import PermissionCompletedPayload + + +def test_load_json_permissioncompletedpayload(): + json_data = r""" + { + "requestId": "perm_abc123", + "toolCallId": "call_abc123", + "permission": "tool.execute", + "approved": true, + "reason": "user_approved" + } + """ + data = json.loads(json_data, strict=False) + instance = PermissionCompletedPayload.load(data) + assert instance is not None + assert instance.request_id == "perm_abc123" + assert instance.tool_call_id == "call_abc123" + assert instance.permission == "tool.execute" + assert instance.approved + assert instance.reason == "user_approved" + + +def test_load_yaml_permissioncompletedpayload(): + yaml_data = r""" + requestId: perm_abc123 + toolCallId: call_abc123 + permission: tool.execute + approved: true + reason: user_approved + + """ + data = yaml.load(yaml_data, Loader=yaml.FullLoader) + instance = PermissionCompletedPayload.load(data) + assert instance is not None + assert instance.request_id == "perm_abc123" + assert instance.tool_call_id == "call_abc123" + assert instance.permission == "tool.execute" + assert instance.approved + assert instance.reason == "user_approved" + + +def test_roundtrip_json_permissioncompletedpayload(): + """Test that load -> save -> load produces equivalent data.""" + json_data = r""" + { + "requestId": "perm_abc123", + "toolCallId": "call_abc123", + "permission": "tool.execute", + "approved": true, + "reason": "user_approved" + } + """ + original_data = json.loads(json_data, strict=False) + instance = PermissionCompletedPayload.load(original_data) + saved_data = instance.save() + reloaded = PermissionCompletedPayload.load(saved_data) + assert reloaded is not None + assert reloaded.request_id == "perm_abc123" + assert reloaded.tool_call_id == "call_abc123" + assert reloaded.permission == "tool.execute" + assert reloaded.approved + assert reloaded.reason == "user_approved" + + +def test_to_json_permissioncompletedpayload(): + """Test that to_json produces valid JSON.""" + json_data = r""" + { + "requestId": "perm_abc123", + "toolCallId": "call_abc123", + "permission": "tool.execute", + "approved": true, + "reason": "user_approved" + } + """ + data = json.loads(json_data, strict=False) + instance = PermissionCompletedPayload.load(data) + json_output = instance.to_json() + assert json_output is not None + parsed = json.loads(json_output) + assert isinstance(parsed, dict) + + +def test_to_yaml_permissioncompletedpayload(): + """Test that to_yaml produces valid YAML.""" + json_data = r""" + { + "requestId": "perm_abc123", + "toolCallId": "call_abc123", + "permission": "tool.execute", + "approved": true, + "reason": "user_approved" + } + """ + data = json.loads(json_data, strict=False) + instance = PermissionCompletedPayload.load(data) + yaml_output = instance.to_yaml() + assert yaml_output is not None + parsed = yaml.safe_load(yaml_output) + assert isinstance(parsed, dict) diff --git a/runtime/python/prompty/tests/model/events/test_permission_decision.py b/runtime/python/prompty/tests/model/events/test_permission_decision.py new file mode 100644 index 00000000..bf50b5fe --- /dev/null +++ b/runtime/python/prompty/tests/model/events/test_permission_decision.py @@ -0,0 +1,106 @@ +# +import json + +import yaml + +from prompty.model import PermissionDecision + + +def test_load_json_permissiondecision(): + json_data = r""" + { + "requestId": "perm_abc123", + "toolCallId": "call_abc123", + "permission": "tool.execute", + "approved": true, + "reason": "user_approved" + } + """ + data = json.loads(json_data, strict=False) + instance = PermissionDecision.load(data) + assert instance is not None + assert instance.request_id == "perm_abc123" + assert instance.tool_call_id == "call_abc123" + assert instance.permission == "tool.execute" + assert instance.approved + assert instance.reason == "user_approved" + + +def test_load_yaml_permissiondecision(): + yaml_data = r""" + requestId: perm_abc123 + toolCallId: call_abc123 + permission: tool.execute + approved: true + reason: user_approved + + """ + data = yaml.load(yaml_data, Loader=yaml.FullLoader) + instance = PermissionDecision.load(data) + assert instance is not None + assert instance.request_id == "perm_abc123" + assert instance.tool_call_id == "call_abc123" + assert instance.permission == "tool.execute" + assert instance.approved + assert instance.reason == "user_approved" + + +def test_roundtrip_json_permissiondecision(): + """Test that load -> save -> load produces equivalent data.""" + json_data = r""" + { + "requestId": "perm_abc123", + "toolCallId": "call_abc123", + "permission": "tool.execute", + "approved": true, + "reason": "user_approved" + } + """ + original_data = json.loads(json_data, strict=False) + instance = PermissionDecision.load(original_data) + saved_data = instance.save() + reloaded = PermissionDecision.load(saved_data) + assert reloaded is not None + assert reloaded.request_id == "perm_abc123" + assert reloaded.tool_call_id == "call_abc123" + assert reloaded.permission == "tool.execute" + assert reloaded.approved + assert reloaded.reason == "user_approved" + + +def test_to_json_permissiondecision(): + """Test that to_json produces valid JSON.""" + json_data = r""" + { + "requestId": "perm_abc123", + "toolCallId": "call_abc123", + "permission": "tool.execute", + "approved": true, + "reason": "user_approved" + } + """ + data = json.loads(json_data, strict=False) + instance = PermissionDecision.load(data) + json_output = instance.to_json() + assert json_output is not None + parsed = json.loads(json_output) + assert isinstance(parsed, dict) + + +def test_to_yaml_permissiondecision(): + """Test that to_yaml produces valid YAML.""" + json_data = r""" + { + "requestId": "perm_abc123", + "toolCallId": "call_abc123", + "permission": "tool.execute", + "approved": true, + "reason": "user_approved" + } + """ + data = json.loads(json_data, strict=False) + instance = PermissionDecision.load(data) + yaml_output = instance.to_yaml() + assert yaml_output is not None + parsed = yaml.safe_load(yaml_output) + assert isinstance(parsed, dict) diff --git a/runtime/python/prompty/tests/model/events/test_permission_request.py b/runtime/python/prompty/tests/model/events/test_permission_request.py new file mode 100644 index 00000000..11344158 --- /dev/null +++ b/runtime/python/prompty/tests/model/events/test_permission_request.py @@ -0,0 +1,106 @@ +# +import json + +import yaml + +from prompty.model import PermissionRequest + + +def test_load_json_permissionrequest(): + json_data = r""" + { + "requestId": "perm_abc123", + "toolCallId": "call_abc123", + "permission": "tool.execute", + "target": "shell", + "promptRequest": "Allow shell to run tests?" + } + """ + data = json.loads(json_data, strict=False) + instance = PermissionRequest.load(data) + assert instance is not None + assert instance.request_id == "perm_abc123" + assert instance.tool_call_id == "call_abc123" + assert instance.permission == "tool.execute" + assert instance.target == "shell" + assert instance.prompt_request == "Allow shell to run tests?" + + +def test_load_yaml_permissionrequest(): + yaml_data = r""" + requestId: perm_abc123 + toolCallId: call_abc123 + permission: tool.execute + target: shell + promptRequest: Allow shell to run tests? + + """ + data = yaml.load(yaml_data, Loader=yaml.FullLoader) + instance = PermissionRequest.load(data) + assert instance is not None + assert instance.request_id == "perm_abc123" + assert instance.tool_call_id == "call_abc123" + assert instance.permission == "tool.execute" + assert instance.target == "shell" + assert instance.prompt_request == "Allow shell to run tests?" + + +def test_roundtrip_json_permissionrequest(): + """Test that load -> save -> load produces equivalent data.""" + json_data = r""" + { + "requestId": "perm_abc123", + "toolCallId": "call_abc123", + "permission": "tool.execute", + "target": "shell", + "promptRequest": "Allow shell to run tests?" + } + """ + original_data = json.loads(json_data, strict=False) + instance = PermissionRequest.load(original_data) + saved_data = instance.save() + reloaded = PermissionRequest.load(saved_data) + assert reloaded is not None + assert reloaded.request_id == "perm_abc123" + assert reloaded.tool_call_id == "call_abc123" + assert reloaded.permission == "tool.execute" + assert reloaded.target == "shell" + assert reloaded.prompt_request == "Allow shell to run tests?" + + +def test_to_json_permissionrequest(): + """Test that to_json produces valid JSON.""" + json_data = r""" + { + "requestId": "perm_abc123", + "toolCallId": "call_abc123", + "permission": "tool.execute", + "target": "shell", + "promptRequest": "Allow shell to run tests?" + } + """ + data = json.loads(json_data, strict=False) + instance = PermissionRequest.load(data) + json_output = instance.to_json() + assert json_output is not None + parsed = json.loads(json_output) + assert isinstance(parsed, dict) + + +def test_to_yaml_permissionrequest(): + """Test that to_yaml produces valid YAML.""" + json_data = r""" + { + "requestId": "perm_abc123", + "toolCallId": "call_abc123", + "permission": "tool.execute", + "target": "shell", + "promptRequest": "Allow shell to run tests?" + } + """ + data = json.loads(json_data, strict=False) + instance = PermissionRequest.load(data) + yaml_output = instance.to_yaml() + assert yaml_output is not None + parsed = yaml.safe_load(yaml_output) + assert isinstance(parsed, dict) diff --git a/runtime/python/prompty/tests/model/events/test_permission_requested_payload.py b/runtime/python/prompty/tests/model/events/test_permission_requested_payload.py new file mode 100644 index 00000000..20cbbe12 --- /dev/null +++ b/runtime/python/prompty/tests/model/events/test_permission_requested_payload.py @@ -0,0 +1,106 @@ +# +import json + +import yaml + +from prompty.model import PermissionRequestedPayload + + +def test_load_json_permissionrequestedpayload(): + json_data = r""" + { + "requestId": "perm_abc123", + "toolCallId": "call_abc123", + "permission": "tool.execute", + "target": "shell", + "promptRequest": "Allow shell to run tests?" + } + """ + data = json.loads(json_data, strict=False) + instance = PermissionRequestedPayload.load(data) + assert instance is not None + assert instance.request_id == "perm_abc123" + assert instance.tool_call_id == "call_abc123" + assert instance.permission == "tool.execute" + assert instance.target == "shell" + assert instance.prompt_request == "Allow shell to run tests?" + + +def test_load_yaml_permissionrequestedpayload(): + yaml_data = r""" + requestId: perm_abc123 + toolCallId: call_abc123 + permission: tool.execute + target: shell + promptRequest: Allow shell to run tests? + + """ + data = yaml.load(yaml_data, Loader=yaml.FullLoader) + instance = PermissionRequestedPayload.load(data) + assert instance is not None + assert instance.request_id == "perm_abc123" + assert instance.tool_call_id == "call_abc123" + assert instance.permission == "tool.execute" + assert instance.target == "shell" + assert instance.prompt_request == "Allow shell to run tests?" + + +def test_roundtrip_json_permissionrequestedpayload(): + """Test that load -> save -> load produces equivalent data.""" + json_data = r""" + { + "requestId": "perm_abc123", + "toolCallId": "call_abc123", + "permission": "tool.execute", + "target": "shell", + "promptRequest": "Allow shell to run tests?" + } + """ + original_data = json.loads(json_data, strict=False) + instance = PermissionRequestedPayload.load(original_data) + saved_data = instance.save() + reloaded = PermissionRequestedPayload.load(saved_data) + assert reloaded is not None + assert reloaded.request_id == "perm_abc123" + assert reloaded.tool_call_id == "call_abc123" + assert reloaded.permission == "tool.execute" + assert reloaded.target == "shell" + assert reloaded.prompt_request == "Allow shell to run tests?" + + +def test_to_json_permissionrequestedpayload(): + """Test that to_json produces valid JSON.""" + json_data = r""" + { + "requestId": "perm_abc123", + "toolCallId": "call_abc123", + "permission": "tool.execute", + "target": "shell", + "promptRequest": "Allow shell to run tests?" + } + """ + data = json.loads(json_data, strict=False) + instance = PermissionRequestedPayload.load(data) + json_output = instance.to_json() + assert json_output is not None + parsed = json.loads(json_output) + assert isinstance(parsed, dict) + + +def test_to_yaml_permissionrequestedpayload(): + """Test that to_yaml produces valid YAML.""" + json_data = r""" + { + "requestId": "perm_abc123", + "toolCallId": "call_abc123", + "permission": "tool.execute", + "target": "shell", + "promptRequest": "Allow shell to run tests?" + } + """ + data = json.loads(json_data, strict=False) + instance = PermissionRequestedPayload.load(data) + yaml_output = instance.to_yaml() + assert yaml_output is not None + parsed = yaml.safe_load(yaml_output) + assert isinstance(parsed, dict) diff --git a/runtime/python/prompty/tests/model/events/test_redacted_field.py b/runtime/python/prompty/tests/model/events/test_redacted_field.py new file mode 100644 index 00000000..100ea0f1 --- /dev/null +++ b/runtime/python/prompty/tests/model/events/test_redacted_field.py @@ -0,0 +1,90 @@ +# +import json + +import yaml + +from prompty.model import RedactedField + + +def test_load_json_redactedfield(): + json_data = r""" + { + "path": "$.arguments.apiKey", + "mode": "redacted", + "reason": "secret" + } + """ + data = json.loads(json_data, strict=False) + instance = RedactedField.load(data) + assert instance is not None + assert instance.path == "$.arguments.apiKey" + assert instance.mode == "redacted" + assert instance.reason == "secret" + + +def test_load_yaml_redactedfield(): + yaml_data = r""" + path: $.arguments.apiKey + mode: redacted + reason: secret + + """ + data = yaml.load(yaml_data, Loader=yaml.FullLoader) + instance = RedactedField.load(data) + assert instance is not None + assert instance.path == "$.arguments.apiKey" + assert instance.mode == "redacted" + assert instance.reason == "secret" + + +def test_roundtrip_json_redactedfield(): + """Test that load -> save -> load produces equivalent data.""" + json_data = r""" + { + "path": "$.arguments.apiKey", + "mode": "redacted", + "reason": "secret" + } + """ + original_data = json.loads(json_data, strict=False) + instance = RedactedField.load(original_data) + saved_data = instance.save() + reloaded = RedactedField.load(saved_data) + assert reloaded is not None + assert reloaded.path == "$.arguments.apiKey" + assert reloaded.mode == "redacted" + assert reloaded.reason == "secret" + + +def test_to_json_redactedfield(): + """Test that to_json produces valid JSON.""" + json_data = r""" + { + "path": "$.arguments.apiKey", + "mode": "redacted", + "reason": "secret" + } + """ + data = json.loads(json_data, strict=False) + instance = RedactedField.load(data) + json_output = instance.to_json() + assert json_output is not None + parsed = json.loads(json_output) + assert isinstance(parsed, dict) + + +def test_to_yaml_redactedfield(): + """Test that to_yaml produces valid YAML.""" + json_data = r""" + { + "path": "$.arguments.apiKey", + "mode": "redacted", + "reason": "secret" + } + """ + data = json.loads(json_data, strict=False) + instance = RedactedField.load(data) + yaml_output = instance.to_yaml() + assert yaml_output is not None + parsed = yaml.safe_load(yaml_output) + assert isinstance(parsed, dict) diff --git a/runtime/python/prompty/tests/model/events/test_redaction_metadata.py b/runtime/python/prompty/tests/model/events/test_redaction_metadata.py new file mode 100644 index 00000000..644776f5 --- /dev/null +++ b/runtime/python/prompty/tests/model/events/test_redaction_metadata.py @@ -0,0 +1,82 @@ +# +import json + +import yaml + +from prompty.model import RedactionMetadata + + +def test_load_json_redactionmetadata(): + json_data = r""" + { + "sanitized": true, + "policy": "default-v1" + } + """ + data = json.loads(json_data, strict=False) + instance = RedactionMetadata.load(data) + assert instance is not None + assert instance.sanitized + assert instance.policy == "default-v1" + + +def test_load_yaml_redactionmetadata(): + yaml_data = r""" + sanitized: true + policy: default-v1 + + """ + data = yaml.load(yaml_data, Loader=yaml.FullLoader) + instance = RedactionMetadata.load(data) + assert instance is not None + assert instance.sanitized + assert instance.policy == "default-v1" + + +def test_roundtrip_json_redactionmetadata(): + """Test that load -> save -> load produces equivalent data.""" + json_data = r""" + { + "sanitized": true, + "policy": "default-v1" + } + """ + original_data = json.loads(json_data, strict=False) + instance = RedactionMetadata.load(original_data) + saved_data = instance.save() + reloaded = RedactionMetadata.load(saved_data) + assert reloaded is not None + assert reloaded.sanitized + assert reloaded.policy == "default-v1" + + +def test_to_json_redactionmetadata(): + """Test that to_json produces valid JSON.""" + json_data = r""" + { + "sanitized": true, + "policy": "default-v1" + } + """ + data = json.loads(json_data, strict=False) + instance = RedactionMetadata.load(data) + json_output = instance.to_json() + assert json_output is not None + parsed = json.loads(json_output) + assert isinstance(parsed, dict) + + +def test_to_yaml_redactionmetadata(): + """Test that to_yaml produces valid YAML.""" + json_data = r""" + { + "sanitized": true, + "policy": "default-v1" + } + """ + data = json.loads(json_data, strict=False) + instance = RedactionMetadata.load(data) + yaml_output = instance.to_yaml() + assert yaml_output is not None + parsed = yaml.safe_load(yaml_output) + assert isinstance(parsed, dict) diff --git a/runtime/python/prompty/tests/model/events/test_retry_payload.py b/runtime/python/prompty/tests/model/events/test_retry_payload.py new file mode 100644 index 00000000..02095b1f --- /dev/null +++ b/runtime/python/prompty/tests/model/events/test_retry_payload.py @@ -0,0 +1,106 @@ +# +import json + +import yaml + +from prompty.model import RetryPayload + + +def test_load_json_retrypayload(): + json_data = r""" + { + "operation": "llm", + "attempt": 2, + "maxAttempts": 3, + "delayMs": 1250, + "reason": "rate_limit" + } + """ + data = json.loads(json_data, strict=False) + instance = RetryPayload.load(data) + assert instance is not None + assert instance.operation == "llm" + assert instance.attempt == 2 + assert instance.max_attempts == 3 + assert instance.delay_ms == 1250 + assert instance.reason == "rate_limit" + + +def test_load_yaml_retrypayload(): + yaml_data = r""" + operation: llm + attempt: 2 + maxAttempts: 3 + delayMs: 1250 + reason: rate_limit + + """ + data = yaml.load(yaml_data, Loader=yaml.FullLoader) + instance = RetryPayload.load(data) + assert instance is not None + assert instance.operation == "llm" + assert instance.attempt == 2 + assert instance.max_attempts == 3 + assert instance.delay_ms == 1250 + assert instance.reason == "rate_limit" + + +def test_roundtrip_json_retrypayload(): + """Test that load -> save -> load produces equivalent data.""" + json_data = r""" + { + "operation": "llm", + "attempt": 2, + "maxAttempts": 3, + "delayMs": 1250, + "reason": "rate_limit" + } + """ + original_data = json.loads(json_data, strict=False) + instance = RetryPayload.load(original_data) + saved_data = instance.save() + reloaded = RetryPayload.load(saved_data) + assert reloaded is not None + assert reloaded.operation == "llm" + assert reloaded.attempt == 2 + assert reloaded.max_attempts == 3 + assert reloaded.delay_ms == 1250 + assert reloaded.reason == "rate_limit" + + +def test_to_json_retrypayload(): + """Test that to_json produces valid JSON.""" + json_data = r""" + { + "operation": "llm", + "attempt": 2, + "maxAttempts": 3, + "delayMs": 1250, + "reason": "rate_limit" + } + """ + data = json.loads(json_data, strict=False) + instance = RetryPayload.load(data) + json_output = instance.to_json() + assert json_output is not None + parsed = json.loads(json_output) + assert isinstance(parsed, dict) + + +def test_to_yaml_retrypayload(): + """Test that to_yaml produces valid YAML.""" + json_data = r""" + { + "operation": "llm", + "attempt": 2, + "maxAttempts": 3, + "delayMs": 1250, + "reason": "rate_limit" + } + """ + data = json.loads(json_data, strict=False) + instance = RetryPayload.load(data) + yaml_output = instance.to_yaml() + assert yaml_output is not None + parsed = yaml.safe_load(yaml_output) + assert isinstance(parsed, dict) diff --git a/runtime/python/prompty/tests/model/events/test_session_end_payload.py b/runtime/python/prompty/tests/model/events/test_session_end_payload.py new file mode 100644 index 00000000..ac65359d --- /dev/null +++ b/runtime/python/prompty/tests/model/events/test_session_end_payload.py @@ -0,0 +1,98 @@ +# +import json + +import yaml + +from prompty.model import SessionEndPayload + + +def test_load_json_sessionendpayload(): + json_data = r""" + { + "sessionId": "sess_abc123", + "status": "success", + "reason": "complete", + "durationMs": 12500 + } + """ + data = json.loads(json_data, strict=False) + instance = SessionEndPayload.load(data) + assert instance is not None + assert instance.session_id == "sess_abc123" + assert instance.status == "success" + assert instance.reason == "complete" + assert instance.duration_ms == 12500 + + +def test_load_yaml_sessionendpayload(): + yaml_data = r""" + sessionId: sess_abc123 + status: success + reason: complete + durationMs: 12500 + + """ + data = yaml.load(yaml_data, Loader=yaml.FullLoader) + instance = SessionEndPayload.load(data) + assert instance is not None + assert instance.session_id == "sess_abc123" + assert instance.status == "success" + assert instance.reason == "complete" + assert instance.duration_ms == 12500 + + +def test_roundtrip_json_sessionendpayload(): + """Test that load -> save -> load produces equivalent data.""" + json_data = r""" + { + "sessionId": "sess_abc123", + "status": "success", + "reason": "complete", + "durationMs": 12500 + } + """ + original_data = json.loads(json_data, strict=False) + instance = SessionEndPayload.load(original_data) + saved_data = instance.save() + reloaded = SessionEndPayload.load(saved_data) + assert reloaded is not None + assert reloaded.session_id == "sess_abc123" + assert reloaded.status == "success" + assert reloaded.reason == "complete" + assert reloaded.duration_ms == 12500 + + +def test_to_json_sessionendpayload(): + """Test that to_json produces valid JSON.""" + json_data = r""" + { + "sessionId": "sess_abc123", + "status": "success", + "reason": "complete", + "durationMs": 12500 + } + """ + data = json.loads(json_data, strict=False) + instance = SessionEndPayload.load(data) + json_output = instance.to_json() + assert json_output is not None + parsed = json.loads(json_output) + assert isinstance(parsed, dict) + + +def test_to_yaml_sessionendpayload(): + """Test that to_yaml produces valid YAML.""" + json_data = r""" + { + "sessionId": "sess_abc123", + "status": "success", + "reason": "complete", + "durationMs": 12500 + } + """ + data = json.loads(json_data, strict=False) + instance = SessionEndPayload.load(data) + yaml_output = instance.to_yaml() + assert yaml_output is not None + parsed = yaml.safe_load(yaml_output) + assert isinstance(parsed, dict) diff --git a/runtime/python/prompty/tests/model/events/test_session_event.py b/runtime/python/prompty/tests/model/events/test_session_event.py new file mode 100644 index 00000000..7888076f --- /dev/null +++ b/runtime/python/prompty/tests/model/events/test_session_event.py @@ -0,0 +1,114 @@ +# +import json + +import yaml + +from prompty.model import SessionEvent + + +def test_load_json_sessionevent(): + json_data = r""" + { + "id": "evt_abc123", + "timestamp": "2026-06-09T20:00:00Z", + "sessionId": "sess_abc123", + "turnId": "turn_001", + "parentId": "evt_parent", + "spanId": "span_hook_001" + } + """ + data = json.loads(json_data, strict=False) + instance = SessionEvent.load(data) + assert instance is not None + assert instance.id == "evt_abc123" + assert instance.timestamp == "2026-06-09T20:00:00Z" + assert instance.session_id == "sess_abc123" + assert instance.turn_id == "turn_001" + assert instance.parent_id == "evt_parent" + assert instance.span_id == "span_hook_001" + + +def test_load_yaml_sessionevent(): + yaml_data = r""" + id: evt_abc123 + timestamp: "2026-06-09T20:00:00Z" + sessionId: sess_abc123 + turnId: turn_001 + parentId: evt_parent + spanId: span_hook_001 + + """ + data = yaml.load(yaml_data, Loader=yaml.FullLoader) + instance = SessionEvent.load(data) + assert instance is not None + assert instance.id == "evt_abc123" + assert instance.timestamp == "2026-06-09T20:00:00Z" + assert instance.session_id == "sess_abc123" + assert instance.turn_id == "turn_001" + assert instance.parent_id == "evt_parent" + assert instance.span_id == "span_hook_001" + + +def test_roundtrip_json_sessionevent(): + """Test that load -> save -> load produces equivalent data.""" + json_data = r""" + { + "id": "evt_abc123", + "timestamp": "2026-06-09T20:00:00Z", + "sessionId": "sess_abc123", + "turnId": "turn_001", + "parentId": "evt_parent", + "spanId": "span_hook_001" + } + """ + original_data = json.loads(json_data, strict=False) + instance = SessionEvent.load(original_data) + saved_data = instance.save() + reloaded = SessionEvent.load(saved_data) + assert reloaded is not None + assert reloaded.id == "evt_abc123" + assert reloaded.timestamp == "2026-06-09T20:00:00Z" + assert reloaded.session_id == "sess_abc123" + assert reloaded.turn_id == "turn_001" + assert reloaded.parent_id == "evt_parent" + assert reloaded.span_id == "span_hook_001" + + +def test_to_json_sessionevent(): + """Test that to_json produces valid JSON.""" + json_data = r""" + { + "id": "evt_abc123", + "timestamp": "2026-06-09T20:00:00Z", + "sessionId": "sess_abc123", + "turnId": "turn_001", + "parentId": "evt_parent", + "spanId": "span_hook_001" + } + """ + data = json.loads(json_data, strict=False) + instance = SessionEvent.load(data) + json_output = instance.to_json() + assert json_output is not None + parsed = json.loads(json_output) + assert isinstance(parsed, dict) + + +def test_to_yaml_sessionevent(): + """Test that to_yaml produces valid YAML.""" + json_data = r""" + { + "id": "evt_abc123", + "timestamp": "2026-06-09T20:00:00Z", + "sessionId": "sess_abc123", + "turnId": "turn_001", + "parentId": "evt_parent", + "spanId": "span_hook_001" + } + """ + data = json.loads(json_data, strict=False) + instance = SessionEvent.load(data) + yaml_output = instance.to_yaml() + assert yaml_output is not None + parsed = yaml.safe_load(yaml_output) + assert isinstance(parsed, dict) diff --git a/runtime/python/prompty/tests/model/events/test_session_file_ref.py b/runtime/python/prompty/tests/model/events/test_session_file_ref.py new file mode 100644 index 00000000..de77d1e5 --- /dev/null +++ b/runtime/python/prompty/tests/model/events/test_session_file_ref.py @@ -0,0 +1,106 @@ +# +import json + +import yaml + +from prompty.model import SessionFileRef + + +def test_load_json_sessionfileref(): + json_data = r""" + { + "sessionId": "sess_abc123", + "path": "src/index.ts", + "toolName": "view", + "turnIndex": 2, + "firstSeenAt": "2026-06-09T20:00:00Z" + } + """ + data = json.loads(json_data, strict=False) + instance = SessionFileRef.load(data) + assert instance is not None + assert instance.session_id == "sess_abc123" + assert instance.path == "src/index.ts" + assert instance.tool_name == "view" + assert instance.turn_index == 2 + assert instance.first_seen_at == "2026-06-09T20:00:00Z" + + +def test_load_yaml_sessionfileref(): + yaml_data = r""" + sessionId: sess_abc123 + path: src/index.ts + toolName: view + turnIndex: 2 + firstSeenAt: "2026-06-09T20:00:00Z" + + """ + data = yaml.load(yaml_data, Loader=yaml.FullLoader) + instance = SessionFileRef.load(data) + assert instance is not None + assert instance.session_id == "sess_abc123" + assert instance.path == "src/index.ts" + assert instance.tool_name == "view" + assert instance.turn_index == 2 + assert instance.first_seen_at == "2026-06-09T20:00:00Z" + + +def test_roundtrip_json_sessionfileref(): + """Test that load -> save -> load produces equivalent data.""" + json_data = r""" + { + "sessionId": "sess_abc123", + "path": "src/index.ts", + "toolName": "view", + "turnIndex": 2, + "firstSeenAt": "2026-06-09T20:00:00Z" + } + """ + original_data = json.loads(json_data, strict=False) + instance = SessionFileRef.load(original_data) + saved_data = instance.save() + reloaded = SessionFileRef.load(saved_data) + assert reloaded is not None + assert reloaded.session_id == "sess_abc123" + assert reloaded.path == "src/index.ts" + assert reloaded.tool_name == "view" + assert reloaded.turn_index == 2 + assert reloaded.first_seen_at == "2026-06-09T20:00:00Z" + + +def test_to_json_sessionfileref(): + """Test that to_json produces valid JSON.""" + json_data = r""" + { + "sessionId": "sess_abc123", + "path": "src/index.ts", + "toolName": "view", + "turnIndex": 2, + "firstSeenAt": "2026-06-09T20:00:00Z" + } + """ + data = json.loads(json_data, strict=False) + instance = SessionFileRef.load(data) + json_output = instance.to_json() + assert json_output is not None + parsed = json.loads(json_output) + assert isinstance(parsed, dict) + + +def test_to_yaml_sessionfileref(): + """Test that to_yaml produces valid YAML.""" + json_data = r""" + { + "sessionId": "sess_abc123", + "path": "src/index.ts", + "toolName": "view", + "turnIndex": 2, + "firstSeenAt": "2026-06-09T20:00:00Z" + } + """ + data = json.loads(json_data, strict=False) + instance = SessionFileRef.load(data) + yaml_output = instance.to_yaml() + assert yaml_output is not None + parsed = yaml.safe_load(yaml_output) + assert isinstance(parsed, dict) diff --git a/runtime/python/prompty/tests/model/events/test_session_ref.py b/runtime/python/prompty/tests/model/events/test_session_ref.py new file mode 100644 index 00000000..fb88257a --- /dev/null +++ b/runtime/python/prompty/tests/model/events/test_session_ref.py @@ -0,0 +1,106 @@ +# +import json + +import yaml + +from prompty.model import SessionRef + + +def test_load_json_sessionref(): + json_data = r""" + { + "sessionId": "sess_abc123", + "refType": "issue", + "refValue": "owner/repo#123", + "turnIndex": 2, + "createdAt": "2026-06-09T20:00:00Z" + } + """ + data = json.loads(json_data, strict=False) + instance = SessionRef.load(data) + assert instance is not None + assert instance.session_id == "sess_abc123" + assert instance.ref_type == "issue" + assert instance.ref_value == "owner/repo#123" + assert instance.turn_index == 2 + assert instance.created_at == "2026-06-09T20:00:00Z" + + +def test_load_yaml_sessionref(): + yaml_data = r""" + sessionId: sess_abc123 + refType: issue + refValue: "owner/repo#123" + turnIndex: 2 + createdAt: "2026-06-09T20:00:00Z" + + """ + data = yaml.load(yaml_data, Loader=yaml.FullLoader) + instance = SessionRef.load(data) + assert instance is not None + assert instance.session_id == "sess_abc123" + assert instance.ref_type == "issue" + assert instance.ref_value == "owner/repo#123" + assert instance.turn_index == 2 + assert instance.created_at == "2026-06-09T20:00:00Z" + + +def test_roundtrip_json_sessionref(): + """Test that load -> save -> load produces equivalent data.""" + json_data = r""" + { + "sessionId": "sess_abc123", + "refType": "issue", + "refValue": "owner/repo#123", + "turnIndex": 2, + "createdAt": "2026-06-09T20:00:00Z" + } + """ + original_data = json.loads(json_data, strict=False) + instance = SessionRef.load(original_data) + saved_data = instance.save() + reloaded = SessionRef.load(saved_data) + assert reloaded is not None + assert reloaded.session_id == "sess_abc123" + assert reloaded.ref_type == "issue" + assert reloaded.ref_value == "owner/repo#123" + assert reloaded.turn_index == 2 + assert reloaded.created_at == "2026-06-09T20:00:00Z" + + +def test_to_json_sessionref(): + """Test that to_json produces valid JSON.""" + json_data = r""" + { + "sessionId": "sess_abc123", + "refType": "issue", + "refValue": "owner/repo#123", + "turnIndex": 2, + "createdAt": "2026-06-09T20:00:00Z" + } + """ + data = json.loads(json_data, strict=False) + instance = SessionRef.load(data) + json_output = instance.to_json() + assert json_output is not None + parsed = json.loads(json_output) + assert isinstance(parsed, dict) + + +def test_to_yaml_sessionref(): + """Test that to_yaml produces valid YAML.""" + json_data = r""" + { + "sessionId": "sess_abc123", + "refType": "issue", + "refValue": "owner/repo#123", + "turnIndex": 2, + "createdAt": "2026-06-09T20:00:00Z" + } + """ + data = json.loads(json_data, strict=False) + instance = SessionRef.load(data) + yaml_output = instance.to_yaml() + assert yaml_output is not None + parsed = yaml.safe_load(yaml_output) + assert isinstance(parsed, dict) diff --git a/runtime/python/prompty/tests/model/events/test_session_start_payload.py b/runtime/python/prompty/tests/model/events/test_session_start_payload.py new file mode 100644 index 00000000..31b60cb1 --- /dev/null +++ b/runtime/python/prompty/tests/model/events/test_session_start_payload.py @@ -0,0 +1,130 @@ +# +import json + +import yaml + +from prompty.model import SessionStartPayload + + +def test_load_json_sessionstartpayload(): + json_data = r""" + { + "sessionId": "sess_abc123", + "schemaVersion": "1", + "producer": "prompty-agent", + "runtime": "typescript", + "promptyVersion": "2.0.0", + "startTime": "2026-06-09T20:00:00Z", + "selectedModel": "gpt-4o-mini", + "reasoningEffort": "medium" + } + """ + data = json.loads(json_data, strict=False) + instance = SessionStartPayload.load(data) + assert instance is not None + assert instance.session_id == "sess_abc123" + assert instance.schema_version == "1" + assert instance.producer == "prompty-agent" + assert instance.runtime == "typescript" + assert instance.prompty_version == "2.0.0" + assert instance.start_time == "2026-06-09T20:00:00Z" + assert instance.selected_model == "gpt-4o-mini" + assert instance.reasoning_effort == "medium" + + +def test_load_yaml_sessionstartpayload(): + yaml_data = r""" + sessionId: sess_abc123 + schemaVersion: "1" + producer: prompty-agent + runtime: typescript + promptyVersion: 2.0.0 + startTime: "2026-06-09T20:00:00Z" + selectedModel: gpt-4o-mini + reasoningEffort: medium + + """ + data = yaml.load(yaml_data, Loader=yaml.FullLoader) + instance = SessionStartPayload.load(data) + assert instance is not None + assert instance.session_id == "sess_abc123" + assert instance.schema_version == "1" + assert instance.producer == "prompty-agent" + assert instance.runtime == "typescript" + assert instance.prompty_version == "2.0.0" + assert instance.start_time == "2026-06-09T20:00:00Z" + assert instance.selected_model == "gpt-4o-mini" + assert instance.reasoning_effort == "medium" + + +def test_roundtrip_json_sessionstartpayload(): + """Test that load -> save -> load produces equivalent data.""" + json_data = r""" + { + "sessionId": "sess_abc123", + "schemaVersion": "1", + "producer": "prompty-agent", + "runtime": "typescript", + "promptyVersion": "2.0.0", + "startTime": "2026-06-09T20:00:00Z", + "selectedModel": "gpt-4o-mini", + "reasoningEffort": "medium" + } + """ + original_data = json.loads(json_data, strict=False) + instance = SessionStartPayload.load(original_data) + saved_data = instance.save() + reloaded = SessionStartPayload.load(saved_data) + assert reloaded is not None + assert reloaded.session_id == "sess_abc123" + assert reloaded.schema_version == "1" + assert reloaded.producer == "prompty-agent" + assert reloaded.runtime == "typescript" + assert reloaded.prompty_version == "2.0.0" + assert reloaded.start_time == "2026-06-09T20:00:00Z" + assert reloaded.selected_model == "gpt-4o-mini" + assert reloaded.reasoning_effort == "medium" + + +def test_to_json_sessionstartpayload(): + """Test that to_json produces valid JSON.""" + json_data = r""" + { + "sessionId": "sess_abc123", + "schemaVersion": "1", + "producer": "prompty-agent", + "runtime": "typescript", + "promptyVersion": "2.0.0", + "startTime": "2026-06-09T20:00:00Z", + "selectedModel": "gpt-4o-mini", + "reasoningEffort": "medium" + } + """ + data = json.loads(json_data, strict=False) + instance = SessionStartPayload.load(data) + json_output = instance.to_json() + assert json_output is not None + parsed = json.loads(json_output) + assert isinstance(parsed, dict) + + +def test_to_yaml_sessionstartpayload(): + """Test that to_yaml produces valid YAML.""" + json_data = r""" + { + "sessionId": "sess_abc123", + "schemaVersion": "1", + "producer": "prompty-agent", + "runtime": "typescript", + "promptyVersion": "2.0.0", + "startTime": "2026-06-09T20:00:00Z", + "selectedModel": "gpt-4o-mini", + "reasoningEffort": "medium" + } + """ + data = json.loads(json_data, strict=False) + instance = SessionStartPayload.load(data) + yaml_output = instance.to_yaml() + assert yaml_output is not None + parsed = yaml.safe_load(yaml_output) + assert isinstance(parsed, dict) diff --git a/runtime/python/prompty/tests/model/events/test_session_summary.py b/runtime/python/prompty/tests/model/events/test_session_summary.py new file mode 100644 index 00000000..b629ee74 --- /dev/null +++ b/runtime/python/prompty/tests/model/events/test_session_summary.py @@ -0,0 +1,106 @@ +# +import json + +import yaml + +from prompty.model import SessionSummary + + +def test_load_json_sessionsummary(): + json_data = r""" + { + "sessionId": "sess_abc123", + "status": "success", + "turns": 5, + "checkpoints": 2, + "durationMs": 12500 + } + """ + data = json.loads(json_data, strict=False) + instance = SessionSummary.load(data) + assert instance is not None + assert instance.session_id == "sess_abc123" + assert instance.status == "success" + assert instance.turns == 5 + assert instance.checkpoints == 2 + assert instance.duration_ms == 12500 + + +def test_load_yaml_sessionsummary(): + yaml_data = r""" + sessionId: sess_abc123 + status: success + turns: 5 + checkpoints: 2 + durationMs: 12500 + + """ + data = yaml.load(yaml_data, Loader=yaml.FullLoader) + instance = SessionSummary.load(data) + assert instance is not None + assert instance.session_id == "sess_abc123" + assert instance.status == "success" + assert instance.turns == 5 + assert instance.checkpoints == 2 + assert instance.duration_ms == 12500 + + +def test_roundtrip_json_sessionsummary(): + """Test that load -> save -> load produces equivalent data.""" + json_data = r""" + { + "sessionId": "sess_abc123", + "status": "success", + "turns": 5, + "checkpoints": 2, + "durationMs": 12500 + } + """ + original_data = json.loads(json_data, strict=False) + instance = SessionSummary.load(original_data) + saved_data = instance.save() + reloaded = SessionSummary.load(saved_data) + assert reloaded is not None + assert reloaded.session_id == "sess_abc123" + assert reloaded.status == "success" + assert reloaded.turns == 5 + assert reloaded.checkpoints == 2 + assert reloaded.duration_ms == 12500 + + +def test_to_json_sessionsummary(): + """Test that to_json produces valid JSON.""" + json_data = r""" + { + "sessionId": "sess_abc123", + "status": "success", + "turns": 5, + "checkpoints": 2, + "durationMs": 12500 + } + """ + data = json.loads(json_data, strict=False) + instance = SessionSummary.load(data) + json_output = instance.to_json() + assert json_output is not None + parsed = json.loads(json_output) + assert isinstance(parsed, dict) + + +def test_to_yaml_sessionsummary(): + """Test that to_yaml produces valid YAML.""" + json_data = r""" + { + "sessionId": "sess_abc123", + "status": "success", + "turns": 5, + "checkpoints": 2, + "durationMs": 12500 + } + """ + data = json.loads(json_data, strict=False) + instance = SessionSummary.load(data) + yaml_output = instance.to_yaml() + assert yaml_output is not None + parsed = yaml.safe_load(yaml_output) + assert isinstance(parsed, dict) diff --git a/runtime/python/prompty/tests/model/events/test_session_trace.py b/runtime/python/prompty/tests/model/events/test_session_trace.py new file mode 100644 index 00000000..97747afa --- /dev/null +++ b/runtime/python/prompty/tests/model/events/test_session_trace.py @@ -0,0 +1,98 @@ +# +import json + +import yaml + +from prompty.model import SessionTrace + + +def test_load_json_sessiontrace(): + json_data = r""" + { + "version": "1", + "runtime": "typescript", + "promptyVersion": "2.0.0", + "sessionId": "sess_abc123" + } + """ + data = json.loads(json_data, strict=False) + instance = SessionTrace.load(data) + assert instance is not None + assert instance.version == "1" + assert instance.runtime == "typescript" + assert instance.prompty_version == "2.0.0" + assert instance.session_id == "sess_abc123" + + +def test_load_yaml_sessiontrace(): + yaml_data = r""" + version: "1" + runtime: typescript + promptyVersion: 2.0.0 + sessionId: sess_abc123 + + """ + data = yaml.load(yaml_data, Loader=yaml.FullLoader) + instance = SessionTrace.load(data) + assert instance is not None + assert instance.version == "1" + assert instance.runtime == "typescript" + assert instance.prompty_version == "2.0.0" + assert instance.session_id == "sess_abc123" + + +def test_roundtrip_json_sessiontrace(): + """Test that load -> save -> load produces equivalent data.""" + json_data = r""" + { + "version": "1", + "runtime": "typescript", + "promptyVersion": "2.0.0", + "sessionId": "sess_abc123" + } + """ + original_data = json.loads(json_data, strict=False) + instance = SessionTrace.load(original_data) + saved_data = instance.save() + reloaded = SessionTrace.load(saved_data) + assert reloaded is not None + assert reloaded.version == "1" + assert reloaded.runtime == "typescript" + assert reloaded.prompty_version == "2.0.0" + assert reloaded.session_id == "sess_abc123" + + +def test_to_json_sessiontrace(): + """Test that to_json produces valid JSON.""" + json_data = r""" + { + "version": "1", + "runtime": "typescript", + "promptyVersion": "2.0.0", + "sessionId": "sess_abc123" + } + """ + data = json.loads(json_data, strict=False) + instance = SessionTrace.load(data) + json_output = instance.to_json() + assert json_output is not None + parsed = json.loads(json_output) + assert isinstance(parsed, dict) + + +def test_to_yaml_sessiontrace(): + """Test that to_yaml produces valid YAML.""" + json_data = r""" + { + "version": "1", + "runtime": "typescript", + "promptyVersion": "2.0.0", + "sessionId": "sess_abc123" + } + """ + data = json.loads(json_data, strict=False) + instance = SessionTrace.load(data) + yaml_output = instance.to_yaml() + assert yaml_output is not None + parsed = yaml.safe_load(yaml_output) + assert isinstance(parsed, dict) diff --git a/runtime/python/prompty/tests/model/events/test_session_warning_payload.py b/runtime/python/prompty/tests/model/events/test_session_warning_payload.py new file mode 100644 index 00000000..d8be4cab --- /dev/null +++ b/runtime/python/prompty/tests/model/events/test_session_warning_payload.py @@ -0,0 +1,82 @@ +# +import json + +import yaml + +from prompty.model import SessionWarningPayload + + +def test_load_json_sessionwarningpayload(): + json_data = r""" + { + "warningType": "remote", + "message": "Remote session disabled" + } + """ + data = json.loads(json_data, strict=False) + instance = SessionWarningPayload.load(data) + assert instance is not None + assert instance.warning_type == "remote" + assert instance.message == "Remote session disabled" + + +def test_load_yaml_sessionwarningpayload(): + yaml_data = r""" + warningType: remote + message: Remote session disabled + + """ + data = yaml.load(yaml_data, Loader=yaml.FullLoader) + instance = SessionWarningPayload.load(data) + assert instance is not None + assert instance.warning_type == "remote" + assert instance.message == "Remote session disabled" + + +def test_roundtrip_json_sessionwarningpayload(): + """Test that load -> save -> load produces equivalent data.""" + json_data = r""" + { + "warningType": "remote", + "message": "Remote session disabled" + } + """ + original_data = json.loads(json_data, strict=False) + instance = SessionWarningPayload.load(original_data) + saved_data = instance.save() + reloaded = SessionWarningPayload.load(saved_data) + assert reloaded is not None + assert reloaded.warning_type == "remote" + assert reloaded.message == "Remote session disabled" + + +def test_to_json_sessionwarningpayload(): + """Test that to_json produces valid JSON.""" + json_data = r""" + { + "warningType": "remote", + "message": "Remote session disabled" + } + """ + data = json.loads(json_data, strict=False) + instance = SessionWarningPayload.load(data) + json_output = instance.to_json() + assert json_output is not None + parsed = json.loads(json_output) + assert isinstance(parsed, dict) + + +def test_to_yaml_sessionwarningpayload(): + """Test that to_yaml produces valid YAML.""" + json_data = r""" + { + "warningType": "remote", + "message": "Remote session disabled" + } + """ + data = json.loads(json_data, strict=False) + instance = SessionWarningPayload.load(data) + yaml_output = instance.to_yaml() + assert yaml_output is not None + parsed = yaml.safe_load(yaml_output) + assert isinstance(parsed, dict) diff --git a/runtime/python/prompty/tests/model/events/test_status_event_payload.py b/runtime/python/prompty/tests/model/events/test_status_event_payload.py index 4fd24528..8dcfb2cd 100644 --- a/runtime/python/prompty/tests/model/events/test_status_event_payload.py +++ b/runtime/python/prompty/tests/model/events/test_status_event_payload.py @@ -1,3 +1,4 @@ +# import json import yaml @@ -20,7 +21,7 @@ def test_load_json_statuseventpayload(): def test_load_yaml_statuseventpayload(): yaml_data = r""" message: Starting iteration 3 - + """ data = yaml.load(yaml_data, Loader=yaml.FullLoader) instance = StatusEventPayload.load(data) diff --git a/runtime/python/prompty/tests/model/events/test_stream_chunk.py b/runtime/python/prompty/tests/model/events/test_stream_chunk.py index 8b137891..47b56d67 100644 --- a/runtime/python/prompty/tests/model/events/test_stream_chunk.py +++ b/runtime/python/prompty/tests/model/events/test_stream_chunk.py @@ -1 +1 @@ - +# diff --git a/runtime/python/prompty/tests/model/events/test_text_chunk.py b/runtime/python/prompty/tests/model/events/test_text_chunk.py index 54e5bc68..46e425e4 100644 --- a/runtime/python/prompty/tests/model/events/test_text_chunk.py +++ b/runtime/python/prompty/tests/model/events/test_text_chunk.py @@ -1,3 +1,4 @@ +# import json import yaml @@ -20,7 +21,7 @@ def test_load_json_textchunk(): def test_load_yaml_textchunk(): yaml_data = r""" value: Hello - + """ data = yaml.load(yaml_data, Loader=yaml.FullLoader) instance = TextChunk.load(data) diff --git a/runtime/python/prompty/tests/model/events/test_thinking_chunk.py b/runtime/python/prompty/tests/model/events/test_thinking_chunk.py index de449d7d..cccdb224 100644 --- a/runtime/python/prompty/tests/model/events/test_thinking_chunk.py +++ b/runtime/python/prompty/tests/model/events/test_thinking_chunk.py @@ -1,3 +1,4 @@ +# import json import yaml @@ -20,7 +21,7 @@ def test_load_json_thinkingchunk(): def test_load_yaml_thinkingchunk(): yaml_data = r""" value: Let me consider... - + """ data = yaml.load(yaml_data, Loader=yaml.FullLoader) instance = ThinkingChunk.load(data) diff --git a/runtime/python/prompty/tests/model/events/test_thinking_event_payload.py b/runtime/python/prompty/tests/model/events/test_thinking_event_payload.py index 06b5e6c9..56bc343b 100644 --- a/runtime/python/prompty/tests/model/events/test_thinking_event_payload.py +++ b/runtime/python/prompty/tests/model/events/test_thinking_event_payload.py @@ -1,3 +1,4 @@ +# import json import yaml @@ -20,7 +21,7 @@ def test_load_json_thinkingeventpayload(): def test_load_yaml_thinkingeventpayload(): yaml_data = r""" token: Let me consider... - + """ data = yaml.load(yaml_data, Loader=yaml.FullLoader) instance = ThinkingEventPayload.load(data) diff --git a/runtime/python/prompty/tests/model/events/test_token_event_payload.py b/runtime/python/prompty/tests/model/events/test_token_event_payload.py index e0e566ef..4e92f290 100644 --- a/runtime/python/prompty/tests/model/events/test_token_event_payload.py +++ b/runtime/python/prompty/tests/model/events/test_token_event_payload.py @@ -1,3 +1,4 @@ +# import json import yaml @@ -20,7 +21,7 @@ def test_load_json_tokeneventpayload(): def test_load_yaml_tokeneventpayload(): yaml_data = r""" token: Hello - + """ data = yaml.load(yaml_data, Loader=yaml.FullLoader) instance = TokenEventPayload.load(data) diff --git a/runtime/python/prompty/tests/model/events/test_tool_call_complete_payload.py b/runtime/python/prompty/tests/model/events/test_tool_call_complete_payload.py new file mode 100644 index 00000000..c916ce49 --- /dev/null +++ b/runtime/python/prompty/tests/model/events/test_tool_call_complete_payload.py @@ -0,0 +1,106 @@ +# +import json + +import yaml + +from prompty.model import ToolCallCompletePayload + + +def test_load_json_toolcallcompletepayload(): + json_data = r""" + { + "id": "call_abc123", + "name": "get_weather", + "success": true, + "durationMs": 42, + "errorKind": "timeout" + } + """ + data = json.loads(json_data, strict=False) + instance = ToolCallCompletePayload.load(data) + assert instance is not None + assert instance.id == "call_abc123" + assert instance.name == "get_weather" + assert instance.success + assert instance.duration_ms == 42 + assert instance.error_kind == "timeout" + + +def test_load_yaml_toolcallcompletepayload(): + yaml_data = r""" + id: call_abc123 + name: get_weather + success: true + durationMs: 42 + errorKind: timeout + + """ + data = yaml.load(yaml_data, Loader=yaml.FullLoader) + instance = ToolCallCompletePayload.load(data) + assert instance is not None + assert instance.id == "call_abc123" + assert instance.name == "get_weather" + assert instance.success + assert instance.duration_ms == 42 + assert instance.error_kind == "timeout" + + +def test_roundtrip_json_toolcallcompletepayload(): + """Test that load -> save -> load produces equivalent data.""" + json_data = r""" + { + "id": "call_abc123", + "name": "get_weather", + "success": true, + "durationMs": 42, + "errorKind": "timeout" + } + """ + original_data = json.loads(json_data, strict=False) + instance = ToolCallCompletePayload.load(original_data) + saved_data = instance.save() + reloaded = ToolCallCompletePayload.load(saved_data) + assert reloaded is not None + assert reloaded.id == "call_abc123" + assert reloaded.name == "get_weather" + assert reloaded.success + assert reloaded.duration_ms == 42 + assert reloaded.error_kind == "timeout" + + +def test_to_json_toolcallcompletepayload(): + """Test that to_json produces valid JSON.""" + json_data = r""" + { + "id": "call_abc123", + "name": "get_weather", + "success": true, + "durationMs": 42, + "errorKind": "timeout" + } + """ + data = json.loads(json_data, strict=False) + instance = ToolCallCompletePayload.load(data) + json_output = instance.to_json() + assert json_output is not None + parsed = json.loads(json_output) + assert isinstance(parsed, dict) + + +def test_to_yaml_toolcallcompletepayload(): + """Test that to_yaml produces valid YAML.""" + json_data = r""" + { + "id": "call_abc123", + "name": "get_weather", + "success": true, + "durationMs": 42, + "errorKind": "timeout" + } + """ + data = json.loads(json_data, strict=False) + instance = ToolCallCompletePayload.load(data) + yaml_output = instance.to_yaml() + assert yaml_output is not None + parsed = yaml.safe_load(yaml_output) + assert isinstance(parsed, dict) diff --git a/runtime/python/prompty/tests/model/events/test_tool_call_start_payload.py b/runtime/python/prompty/tests/model/events/test_tool_call_start_payload.py index 41d57fe5..9683e763 100644 --- a/runtime/python/prompty/tests/model/events/test_tool_call_start_payload.py +++ b/runtime/python/prompty/tests/model/events/test_tool_call_start_payload.py @@ -1,3 +1,4 @@ +# import json import yaml @@ -8,6 +9,7 @@ def test_load_json_toolcallstartpayload(): json_data = r""" { + "id": "call_abc123", "name": "get_weather", "arguments": "{\"city\": \"Paris\"}" } @@ -15,19 +17,22 @@ def test_load_json_toolcallstartpayload(): data = json.loads(json_data, strict=False) instance = ToolCallStartPayload.load(data) assert instance is not None + assert instance.id == "call_abc123" assert instance.name == "get_weather" assert instance.arguments == '{"city": "Paris"}' def test_load_yaml_toolcallstartpayload(): yaml_data = r""" + id: call_abc123 name: get_weather arguments: "{\"city\": \"Paris\"}" - + """ data = yaml.load(yaml_data, Loader=yaml.FullLoader) instance = ToolCallStartPayload.load(data) assert instance is not None + assert instance.id == "call_abc123" assert instance.name == "get_weather" assert instance.arguments == '{"city": "Paris"}' @@ -36,6 +41,7 @@ def test_roundtrip_json_toolcallstartpayload(): """Test that load -> save -> load produces equivalent data.""" json_data = r""" { + "id": "call_abc123", "name": "get_weather", "arguments": "{\"city\": \"Paris\"}" } @@ -45,6 +51,7 @@ def test_roundtrip_json_toolcallstartpayload(): saved_data = instance.save() reloaded = ToolCallStartPayload.load(saved_data) assert reloaded is not None + assert reloaded.id == "call_abc123" assert reloaded.name == "get_weather" assert reloaded.arguments == '{"city": "Paris"}' @@ -53,6 +60,7 @@ def test_to_json_toolcallstartpayload(): """Test that to_json produces valid JSON.""" json_data = r""" { + "id": "call_abc123", "name": "get_weather", "arguments": "{\"city\": \"Paris\"}" } @@ -69,6 +77,7 @@ def test_to_yaml_toolcallstartpayload(): """Test that to_yaml produces valid YAML.""" json_data = r""" { + "id": "call_abc123", "name": "get_weather", "arguments": "{\"city\": \"Paris\"}" } diff --git a/runtime/python/prompty/tests/model/events/test_tool_chunk.py b/runtime/python/prompty/tests/model/events/test_tool_chunk.py index 670e143c..be59a227 100644 --- a/runtime/python/prompty/tests/model/events/test_tool_chunk.py +++ b/runtime/python/prompty/tests/model/events/test_tool_chunk.py @@ -1,3 +1,4 @@ +# import json import yaml @@ -26,7 +27,7 @@ def test_load_yaml_toolchunk(): id: call_abc123 name: get_weather arguments: "{\"city\": \"Paris\"}" - + """ data = yaml.load(yaml_data, Loader=yaml.FullLoader) instance = ToolChunk.load(data) diff --git a/runtime/python/prompty/tests/model/events/test_tool_execution_complete_payload.py b/runtime/python/prompty/tests/model/events/test_tool_execution_complete_payload.py new file mode 100644 index 00000000..be4c543a --- /dev/null +++ b/runtime/python/prompty/tests/model/events/test_tool_execution_complete_payload.py @@ -0,0 +1,122 @@ +# +import json + +import yaml + +from prompty.model import ToolExecutionCompletePayload + + +def test_load_json_toolexecutioncompletepayload(): + json_data = r""" + { + "requestId": "exec_abc123", + "toolCallId": "call_abc123", + "toolName": "powershell", + "success": true, + "exitCode": 0, + "durationMs": 250, + "errorKind": "timeout" + } + """ + data = json.loads(json_data, strict=False) + instance = ToolExecutionCompletePayload.load(data) + assert instance is not None + assert instance.request_id == "exec_abc123" + assert instance.tool_call_id == "call_abc123" + assert instance.tool_name == "powershell" + assert instance.success + assert instance.exit_code == 0 + assert instance.duration_ms == 250 + assert instance.error_kind == "timeout" + + +def test_load_yaml_toolexecutioncompletepayload(): + yaml_data = r""" + requestId: exec_abc123 + toolCallId: call_abc123 + toolName: powershell + success: true + exitCode: 0 + durationMs: 250 + errorKind: timeout + + """ + data = yaml.load(yaml_data, Loader=yaml.FullLoader) + instance = ToolExecutionCompletePayload.load(data) + assert instance is not None + assert instance.request_id == "exec_abc123" + assert instance.tool_call_id == "call_abc123" + assert instance.tool_name == "powershell" + assert instance.success + assert instance.exit_code == 0 + assert instance.duration_ms == 250 + assert instance.error_kind == "timeout" + + +def test_roundtrip_json_toolexecutioncompletepayload(): + """Test that load -> save -> load produces equivalent data.""" + json_data = r""" + { + "requestId": "exec_abc123", + "toolCallId": "call_abc123", + "toolName": "powershell", + "success": true, + "exitCode": 0, + "durationMs": 250, + "errorKind": "timeout" + } + """ + original_data = json.loads(json_data, strict=False) + instance = ToolExecutionCompletePayload.load(original_data) + saved_data = instance.save() + reloaded = ToolExecutionCompletePayload.load(saved_data) + assert reloaded is not None + assert reloaded.request_id == "exec_abc123" + assert reloaded.tool_call_id == "call_abc123" + assert reloaded.tool_name == "powershell" + assert reloaded.success + assert reloaded.exit_code == 0 + assert reloaded.duration_ms == 250 + assert reloaded.error_kind == "timeout" + + +def test_to_json_toolexecutioncompletepayload(): + """Test that to_json produces valid JSON.""" + json_data = r""" + { + "requestId": "exec_abc123", + "toolCallId": "call_abc123", + "toolName": "powershell", + "success": true, + "exitCode": 0, + "durationMs": 250, + "errorKind": "timeout" + } + """ + data = json.loads(json_data, strict=False) + instance = ToolExecutionCompletePayload.load(data) + json_output = instance.to_json() + assert json_output is not None + parsed = json.loads(json_output) + assert isinstance(parsed, dict) + + +def test_to_yaml_toolexecutioncompletepayload(): + """Test that to_yaml produces valid YAML.""" + json_data = r""" + { + "requestId": "exec_abc123", + "toolCallId": "call_abc123", + "toolName": "powershell", + "success": true, + "exitCode": 0, + "durationMs": 250, + "errorKind": "timeout" + } + """ + data = json.loads(json_data, strict=False) + instance = ToolExecutionCompletePayload.load(data) + yaml_output = instance.to_yaml() + assert yaml_output is not None + parsed = yaml.safe_load(yaml_output) + assert isinstance(parsed, dict) diff --git a/runtime/python/prompty/tests/model/events/test_tool_execution_start_payload.py b/runtime/python/prompty/tests/model/events/test_tool_execution_start_payload.py new file mode 100644 index 00000000..1be78c0c --- /dev/null +++ b/runtime/python/prompty/tests/model/events/test_tool_execution_start_payload.py @@ -0,0 +1,98 @@ +# +import json + +import yaml + +from prompty.model import ToolExecutionStartPayload + + +def test_load_json_toolexecutionstartpayload(): + json_data = r""" + { + "requestId": "exec_abc123", + "toolCallId": "call_abc123", + "toolName": "powershell", + "workingDirectory": "/workspace/project" + } + """ + data = json.loads(json_data, strict=False) + instance = ToolExecutionStartPayload.load(data) + assert instance is not None + assert instance.request_id == "exec_abc123" + assert instance.tool_call_id == "call_abc123" + assert instance.tool_name == "powershell" + assert instance.working_directory == "/workspace/project" + + +def test_load_yaml_toolexecutionstartpayload(): + yaml_data = r""" + requestId: exec_abc123 + toolCallId: call_abc123 + toolName: powershell + workingDirectory: /workspace/project + + """ + data = yaml.load(yaml_data, Loader=yaml.FullLoader) + instance = ToolExecutionStartPayload.load(data) + assert instance is not None + assert instance.request_id == "exec_abc123" + assert instance.tool_call_id == "call_abc123" + assert instance.tool_name == "powershell" + assert instance.working_directory == "/workspace/project" + + +def test_roundtrip_json_toolexecutionstartpayload(): + """Test that load -> save -> load produces equivalent data.""" + json_data = r""" + { + "requestId": "exec_abc123", + "toolCallId": "call_abc123", + "toolName": "powershell", + "workingDirectory": "/workspace/project" + } + """ + original_data = json.loads(json_data, strict=False) + instance = ToolExecutionStartPayload.load(original_data) + saved_data = instance.save() + reloaded = ToolExecutionStartPayload.load(saved_data) + assert reloaded is not None + assert reloaded.request_id == "exec_abc123" + assert reloaded.tool_call_id == "call_abc123" + assert reloaded.tool_name == "powershell" + assert reloaded.working_directory == "/workspace/project" + + +def test_to_json_toolexecutionstartpayload(): + """Test that to_json produces valid JSON.""" + json_data = r""" + { + "requestId": "exec_abc123", + "toolCallId": "call_abc123", + "toolName": "powershell", + "workingDirectory": "/workspace/project" + } + """ + data = json.loads(json_data, strict=False) + instance = ToolExecutionStartPayload.load(data) + json_output = instance.to_json() + assert json_output is not None + parsed = json.loads(json_output) + assert isinstance(parsed, dict) + + +def test_to_yaml_toolexecutionstartpayload(): + """Test that to_yaml produces valid YAML.""" + json_data = r""" + { + "requestId": "exec_abc123", + "toolCallId": "call_abc123", + "toolName": "powershell", + "workingDirectory": "/workspace/project" + } + """ + data = json.loads(json_data, strict=False) + instance = ToolExecutionStartPayload.load(data) + yaml_output = instance.to_yaml() + assert yaml_output is not None + parsed = yaml.safe_load(yaml_output) + assert isinstance(parsed, dict) diff --git a/runtime/python/prompty/tests/model/events/test_tool_result_payload.py b/runtime/python/prompty/tests/model/events/test_tool_result_payload.py index 31eac0d1..394ef0e6 100644 --- a/runtime/python/prompty/tests/model/events/test_tool_result_payload.py +++ b/runtime/python/prompty/tests/model/events/test_tool_result_payload.py @@ -1,3 +1,4 @@ +# import json import yaml @@ -32,7 +33,7 @@ def test_load_yaml_toolresultpayload(): parts: - kind: text value: 72°F and sunny - + """ data = yaml.load(yaml_data, Loader=yaml.FullLoader) instance = ToolResultPayload.load(data) diff --git a/runtime/python/prompty/tests/model/events/test_trajectory_event.py b/runtime/python/prompty/tests/model/events/test_trajectory_event.py new file mode 100644 index 00000000..8545928f --- /dev/null +++ b/runtime/python/prompty/tests/model/events/test_trajectory_event.py @@ -0,0 +1,122 @@ +# +import json + +import yaml + +from prompty.model import TrajectoryEvent + + +def test_load_json_trajectoryevent(): + json_data = r""" + { + "id": "traj_abc123", + "sessionId": "sess_abc123", + "turnId": "turn_001", + "toolCallId": "call_abc123", + "turnIndex": 4, + "eventType": "command", + "createdAt": "2026-06-09T20:00:00Z" + } + """ + data = json.loads(json_data, strict=False) + instance = TrajectoryEvent.load(data) + assert instance is not None + assert instance.id == "traj_abc123" + assert instance.session_id == "sess_abc123" + assert instance.turn_id == "turn_001" + assert instance.tool_call_id == "call_abc123" + assert instance.turn_index == 4 + assert instance.event_type == "command" + assert instance.created_at == "2026-06-09T20:00:00Z" + + +def test_load_yaml_trajectoryevent(): + yaml_data = r""" + id: traj_abc123 + sessionId: sess_abc123 + turnId: turn_001 + toolCallId: call_abc123 + turnIndex: 4 + eventType: command + createdAt: "2026-06-09T20:00:00Z" + + """ + data = yaml.load(yaml_data, Loader=yaml.FullLoader) + instance = TrajectoryEvent.load(data) + assert instance is not None + assert instance.id == "traj_abc123" + assert instance.session_id == "sess_abc123" + assert instance.turn_id == "turn_001" + assert instance.tool_call_id == "call_abc123" + assert instance.turn_index == 4 + assert instance.event_type == "command" + assert instance.created_at == "2026-06-09T20:00:00Z" + + +def test_roundtrip_json_trajectoryevent(): + """Test that load -> save -> load produces equivalent data.""" + json_data = r""" + { + "id": "traj_abc123", + "sessionId": "sess_abc123", + "turnId": "turn_001", + "toolCallId": "call_abc123", + "turnIndex": 4, + "eventType": "command", + "createdAt": "2026-06-09T20:00:00Z" + } + """ + original_data = json.loads(json_data, strict=False) + instance = TrajectoryEvent.load(original_data) + saved_data = instance.save() + reloaded = TrajectoryEvent.load(saved_data) + assert reloaded is not None + assert reloaded.id == "traj_abc123" + assert reloaded.session_id == "sess_abc123" + assert reloaded.turn_id == "turn_001" + assert reloaded.tool_call_id == "call_abc123" + assert reloaded.turn_index == 4 + assert reloaded.event_type == "command" + assert reloaded.created_at == "2026-06-09T20:00:00Z" + + +def test_to_json_trajectoryevent(): + """Test that to_json produces valid JSON.""" + json_data = r""" + { + "id": "traj_abc123", + "sessionId": "sess_abc123", + "turnId": "turn_001", + "toolCallId": "call_abc123", + "turnIndex": 4, + "eventType": "command", + "createdAt": "2026-06-09T20:00:00Z" + } + """ + data = json.loads(json_data, strict=False) + instance = TrajectoryEvent.load(data) + json_output = instance.to_json() + assert json_output is not None + parsed = json.loads(json_output) + assert isinstance(parsed, dict) + + +def test_to_yaml_trajectoryevent(): + """Test that to_yaml produces valid YAML.""" + json_data = r""" + { + "id": "traj_abc123", + "sessionId": "sess_abc123", + "turnId": "turn_001", + "toolCallId": "call_abc123", + "turnIndex": 4, + "eventType": "command", + "createdAt": "2026-06-09T20:00:00Z" + } + """ + data = json.loads(json_data, strict=False) + instance = TrajectoryEvent.load(data) + yaml_output = instance.to_yaml() + assert yaml_output is not None + parsed = yaml.safe_load(yaml_output) + assert isinstance(parsed, dict) diff --git a/runtime/python/prompty/tests/model/events/test_turn_end_payload.py b/runtime/python/prompty/tests/model/events/test_turn_end_payload.py new file mode 100644 index 00000000..01539ad4 --- /dev/null +++ b/runtime/python/prompty/tests/model/events/test_turn_end_payload.py @@ -0,0 +1,82 @@ +# +import json + +import yaml + +from prompty.model import TurnEndPayload + + +def test_load_json_turnendpayload(): + json_data = r""" + { + "iterations": 2, + "durationMs": 1500 + } + """ + data = json.loads(json_data, strict=False) + instance = TurnEndPayload.load(data) + assert instance is not None + assert instance.iterations == 2 + assert instance.duration_ms == 1500 + + +def test_load_yaml_turnendpayload(): + yaml_data = r""" + iterations: 2 + durationMs: 1500 + + """ + data = yaml.load(yaml_data, Loader=yaml.FullLoader) + instance = TurnEndPayload.load(data) + assert instance is not None + assert instance.iterations == 2 + assert instance.duration_ms == 1500 + + +def test_roundtrip_json_turnendpayload(): + """Test that load -> save -> load produces equivalent data.""" + json_data = r""" + { + "iterations": 2, + "durationMs": 1500 + } + """ + original_data = json.loads(json_data, strict=False) + instance = TurnEndPayload.load(original_data) + saved_data = instance.save() + reloaded = TurnEndPayload.load(saved_data) + assert reloaded is not None + assert reloaded.iterations == 2 + assert reloaded.duration_ms == 1500 + + +def test_to_json_turnendpayload(): + """Test that to_json produces valid JSON.""" + json_data = r""" + { + "iterations": 2, + "durationMs": 1500 + } + """ + data = json.loads(json_data, strict=False) + instance = TurnEndPayload.load(data) + json_output = instance.to_json() + assert json_output is not None + parsed = json.loads(json_output) + assert isinstance(parsed, dict) + + +def test_to_yaml_turnendpayload(): + """Test that to_yaml produces valid YAML.""" + json_data = r""" + { + "iterations": 2, + "durationMs": 1500 + } + """ + data = json.loads(json_data, strict=False) + instance = TurnEndPayload.load(data) + yaml_output = instance.to_yaml() + assert yaml_output is not None + parsed = yaml.safe_load(yaml_output) + assert isinstance(parsed, dict) diff --git a/runtime/python/prompty/tests/model/events/test_turn_event.py b/runtime/python/prompty/tests/model/events/test_turn_event.py new file mode 100644 index 00000000..3e6cb774 --- /dev/null +++ b/runtime/python/prompty/tests/model/events/test_turn_event.py @@ -0,0 +1,114 @@ +# +import json + +import yaml + +from prompty.model import TurnEvent + + +def test_load_json_turnevent(): + json_data = r""" + { + "id": "evt_abc123", + "timestamp": "2026-06-09T20:00:00Z", + "turnId": "turn_001", + "iteration": 0, + "parentId": "evt_parent", + "spanId": "span_tool_001" + } + """ + data = json.loads(json_data, strict=False) + instance = TurnEvent.load(data) + assert instance is not None + assert instance.id == "evt_abc123" + assert instance.timestamp == "2026-06-09T20:00:00Z" + assert instance.turn_id == "turn_001" + assert instance.iteration == 0 + assert instance.parent_id == "evt_parent" + assert instance.span_id == "span_tool_001" + + +def test_load_yaml_turnevent(): + yaml_data = r""" + id: evt_abc123 + timestamp: "2026-06-09T20:00:00Z" + turnId: turn_001 + iteration: 0 + parentId: evt_parent + spanId: span_tool_001 + + """ + data = yaml.load(yaml_data, Loader=yaml.FullLoader) + instance = TurnEvent.load(data) + assert instance is not None + assert instance.id == "evt_abc123" + assert instance.timestamp == "2026-06-09T20:00:00Z" + assert instance.turn_id == "turn_001" + assert instance.iteration == 0 + assert instance.parent_id == "evt_parent" + assert instance.span_id == "span_tool_001" + + +def test_roundtrip_json_turnevent(): + """Test that load -> save -> load produces equivalent data.""" + json_data = r""" + { + "id": "evt_abc123", + "timestamp": "2026-06-09T20:00:00Z", + "turnId": "turn_001", + "iteration": 0, + "parentId": "evt_parent", + "spanId": "span_tool_001" + } + """ + original_data = json.loads(json_data, strict=False) + instance = TurnEvent.load(original_data) + saved_data = instance.save() + reloaded = TurnEvent.load(saved_data) + assert reloaded is not None + assert reloaded.id == "evt_abc123" + assert reloaded.timestamp == "2026-06-09T20:00:00Z" + assert reloaded.turn_id == "turn_001" + assert reloaded.iteration == 0 + assert reloaded.parent_id == "evt_parent" + assert reloaded.span_id == "span_tool_001" + + +def test_to_json_turnevent(): + """Test that to_json produces valid JSON.""" + json_data = r""" + { + "id": "evt_abc123", + "timestamp": "2026-06-09T20:00:00Z", + "turnId": "turn_001", + "iteration": 0, + "parentId": "evt_parent", + "spanId": "span_tool_001" + } + """ + data = json.loads(json_data, strict=False) + instance = TurnEvent.load(data) + json_output = instance.to_json() + assert json_output is not None + parsed = json.loads(json_output) + assert isinstance(parsed, dict) + + +def test_to_yaml_turnevent(): + """Test that to_yaml produces valid YAML.""" + json_data = r""" + { + "id": "evt_abc123", + "timestamp": "2026-06-09T20:00:00Z", + "turnId": "turn_001", + "iteration": 0, + "parentId": "evt_parent", + "spanId": "span_tool_001" + } + """ + data = json.loads(json_data, strict=False) + instance = TurnEvent.load(data) + yaml_output = instance.to_yaml() + assert yaml_output is not None + parsed = yaml.safe_load(yaml_output) + assert isinstance(parsed, dict) diff --git a/runtime/python/prompty/tests/model/events/test_turn_start_payload.py b/runtime/python/prompty/tests/model/events/test_turn_start_payload.py new file mode 100644 index 00000000..8f210c8a --- /dev/null +++ b/runtime/python/prompty/tests/model/events/test_turn_start_payload.py @@ -0,0 +1,82 @@ +# +import json + +import yaml + +from prompty.model import TurnStartPayload + + +def test_load_json_turnstartpayload(): + json_data = r""" + { + "agent": "weather-agent", + "maxIterations": 10 + } + """ + data = json.loads(json_data, strict=False) + instance = TurnStartPayload.load(data) + assert instance is not None + assert instance.agent == "weather-agent" + assert instance.max_iterations == 10 + + +def test_load_yaml_turnstartpayload(): + yaml_data = r""" + agent: weather-agent + maxIterations: 10 + + """ + data = yaml.load(yaml_data, Loader=yaml.FullLoader) + instance = TurnStartPayload.load(data) + assert instance is not None + assert instance.agent == "weather-agent" + assert instance.max_iterations == 10 + + +def test_roundtrip_json_turnstartpayload(): + """Test that load -> save -> load produces equivalent data.""" + json_data = r""" + { + "agent": "weather-agent", + "maxIterations": 10 + } + """ + original_data = json.loads(json_data, strict=False) + instance = TurnStartPayload.load(original_data) + saved_data = instance.save() + reloaded = TurnStartPayload.load(saved_data) + assert reloaded is not None + assert reloaded.agent == "weather-agent" + assert reloaded.max_iterations == 10 + + +def test_to_json_turnstartpayload(): + """Test that to_json produces valid JSON.""" + json_data = r""" + { + "agent": "weather-agent", + "maxIterations": 10 + } + """ + data = json.loads(json_data, strict=False) + instance = TurnStartPayload.load(data) + json_output = instance.to_json() + assert json_output is not None + parsed = json.loads(json_output) + assert isinstance(parsed, dict) + + +def test_to_yaml_turnstartpayload(): + """Test that to_yaml produces valid YAML.""" + json_data = r""" + { + "agent": "weather-agent", + "maxIterations": 10 + } + """ + data = json.loads(json_data, strict=False) + instance = TurnStartPayload.load(data) + yaml_output = instance.to_yaml() + assert yaml_output is not None + parsed = yaml.safe_load(yaml_output) + assert isinstance(parsed, dict) diff --git a/runtime/python/prompty/tests/model/events/test_turn_summary.py b/runtime/python/prompty/tests/model/events/test_turn_summary.py new file mode 100644 index 00000000..2cb5af2e --- /dev/null +++ b/runtime/python/prompty/tests/model/events/test_turn_summary.py @@ -0,0 +1,122 @@ +# +import json + +import yaml + +from prompty.model import TurnSummary + + +def test_load_json_turnsummary(): + json_data = r""" + { + "turnId": "turn_001", + "status": "success", + "iterations": 2, + "llmCalls": 3, + "toolCalls": 2, + "retries": 1, + "durationMs": 2500 + } + """ + data = json.loads(json_data, strict=False) + instance = TurnSummary.load(data) + assert instance is not None + assert instance.turn_id == "turn_001" + assert instance.status == "success" + assert instance.iterations == 2 + assert instance.llm_calls == 3 + assert instance.tool_calls == 2 + assert instance.retries == 1 + assert instance.duration_ms == 2500 + + +def test_load_yaml_turnsummary(): + yaml_data = r""" + turnId: turn_001 + status: success + iterations: 2 + llmCalls: 3 + toolCalls: 2 + retries: 1 + durationMs: 2500 + + """ + data = yaml.load(yaml_data, Loader=yaml.FullLoader) + instance = TurnSummary.load(data) + assert instance is not None + assert instance.turn_id == "turn_001" + assert instance.status == "success" + assert instance.iterations == 2 + assert instance.llm_calls == 3 + assert instance.tool_calls == 2 + assert instance.retries == 1 + assert instance.duration_ms == 2500 + + +def test_roundtrip_json_turnsummary(): + """Test that load -> save -> load produces equivalent data.""" + json_data = r""" + { + "turnId": "turn_001", + "status": "success", + "iterations": 2, + "llmCalls": 3, + "toolCalls": 2, + "retries": 1, + "durationMs": 2500 + } + """ + original_data = json.loads(json_data, strict=False) + instance = TurnSummary.load(original_data) + saved_data = instance.save() + reloaded = TurnSummary.load(saved_data) + assert reloaded is not None + assert reloaded.turn_id == "turn_001" + assert reloaded.status == "success" + assert reloaded.iterations == 2 + assert reloaded.llm_calls == 3 + assert reloaded.tool_calls == 2 + assert reloaded.retries == 1 + assert reloaded.duration_ms == 2500 + + +def test_to_json_turnsummary(): + """Test that to_json produces valid JSON.""" + json_data = r""" + { + "turnId": "turn_001", + "status": "success", + "iterations": 2, + "llmCalls": 3, + "toolCalls": 2, + "retries": 1, + "durationMs": 2500 + } + """ + data = json.loads(json_data, strict=False) + instance = TurnSummary.load(data) + json_output = instance.to_json() + assert json_output is not None + parsed = json.loads(json_output) + assert isinstance(parsed, dict) + + +def test_to_yaml_turnsummary(): + """Test that to_yaml produces valid YAML.""" + json_data = r""" + { + "turnId": "turn_001", + "status": "success", + "iterations": 2, + "llmCalls": 3, + "toolCalls": 2, + "retries": 1, + "durationMs": 2500 + } + """ + data = json.loads(json_data, strict=False) + instance = TurnSummary.load(data) + yaml_output = instance.to_yaml() + assert yaml_output is not None + parsed = yaml.safe_load(yaml_output) + assert isinstance(parsed, dict) diff --git a/runtime/python/prompty/tests/model/events/test_turn_trace.py b/runtime/python/prompty/tests/model/events/test_turn_trace.py new file mode 100644 index 00000000..6328d732 --- /dev/null +++ b/runtime/python/prompty/tests/model/events/test_turn_trace.py @@ -0,0 +1,90 @@ +# +import json + +import yaml + +from prompty.model import TurnTrace + + +def test_load_json_turntrace(): + json_data = r""" + { + "version": "1", + "runtime": "typescript", + "promptyVersion": "2.0.0" + } + """ + data = json.loads(json_data, strict=False) + instance = TurnTrace.load(data) + assert instance is not None + assert instance.version == "1" + assert instance.runtime == "typescript" + assert instance.prompty_version == "2.0.0" + + +def test_load_yaml_turntrace(): + yaml_data = r""" + version: "1" + runtime: typescript + promptyVersion: 2.0.0 + + """ + data = yaml.load(yaml_data, Loader=yaml.FullLoader) + instance = TurnTrace.load(data) + assert instance is not None + assert instance.version == "1" + assert instance.runtime == "typescript" + assert instance.prompty_version == "2.0.0" + + +def test_roundtrip_json_turntrace(): + """Test that load -> save -> load produces equivalent data.""" + json_data = r""" + { + "version": "1", + "runtime": "typescript", + "promptyVersion": "2.0.0" + } + """ + original_data = json.loads(json_data, strict=False) + instance = TurnTrace.load(original_data) + saved_data = instance.save() + reloaded = TurnTrace.load(saved_data) + assert reloaded is not None + assert reloaded.version == "1" + assert reloaded.runtime == "typescript" + assert reloaded.prompty_version == "2.0.0" + + +def test_to_json_turntrace(): + """Test that to_json produces valid JSON.""" + json_data = r""" + { + "version": "1", + "runtime": "typescript", + "promptyVersion": "2.0.0" + } + """ + data = json.loads(json_data, strict=False) + instance = TurnTrace.load(data) + json_output = instance.to_json() + assert json_output is not None + parsed = json.loads(json_output) + assert isinstance(parsed, dict) + + +def test_to_yaml_turntrace(): + """Test that to_yaml produces valid YAML.""" + json_data = r""" + { + "version": "1", + "runtime": "typescript", + "promptyVersion": "2.0.0" + } + """ + data = json.loads(json_data, strict=False) + instance = TurnTrace.load(data) + yaml_output = instance.to_yaml() + assert yaml_output is not None + parsed = yaml.safe_load(yaml_output) + assert isinstance(parsed, dict) diff --git a/runtime/python/prompty/tests/model/model/test_model.py b/runtime/python/prompty/tests/model/model/test_model.py index d704c191..f1da05fa 100644 --- a/runtime/python/prompty/tests/model/model/test_model.py +++ b/runtime/python/prompty/tests/model/model/test_model.py @@ -1,3 +1,4 @@ +# import json import yaml @@ -44,7 +45,7 @@ def test_load_yaml_model(): type: chat temperature: 0.7 maxOutputTokens: 1000 - + """ data = yaml.load(yaml_data, Loader=yaml.FullLoader) instance = Model.load(data) diff --git a/runtime/python/prompty/tests/model/model/test_model_info.py b/runtime/python/prompty/tests/model/model/test_model_info.py index da7b8da7..cb68dc3f 100644 --- a/runtime/python/prompty/tests/model/model/test_model_info.py +++ b/runtime/python/prompty/tests/model/model/test_model_info.py @@ -1,3 +1,4 @@ +# import json import yaml @@ -46,7 +47,7 @@ def test_load_yaml_modelinfo(): - text additionalProperties: supportsStreaming: true - + """ data = yaml.load(yaml_data, Loader=yaml.FullLoader) instance = ModelInfo.load(data) diff --git a/runtime/python/prompty/tests/model/model/test_model_options.py b/runtime/python/prompty/tests/model/model/test_model_options.py index 37ee578e..a11fa313 100644 --- a/runtime/python/prompty/tests/model/model/test_model_options.py +++ b/runtime/python/prompty/tests/model/model/test_model_options.py @@ -1,3 +1,4 @@ +# import json import yaml @@ -55,7 +56,7 @@ def test_load_yaml_modeloptions(): additionalProperties: customProperty: value anotherProperty: anotherValue - + """ data = yaml.load(yaml_data, Loader=yaml.FullLoader) instance = ModelOptions.load(data) diff --git a/runtime/python/prompty/tests/model/model/test_token_usage.py b/runtime/python/prompty/tests/model/model/test_token_usage.py index 458c67ae..512b4b1a 100644 --- a/runtime/python/prompty/tests/model/model/test_token_usage.py +++ b/runtime/python/prompty/tests/model/model/test_token_usage.py @@ -1,3 +1,4 @@ +# import json import yaml @@ -26,7 +27,7 @@ def test_load_yaml_tokenusage(): promptTokens: 150 completionTokens: 42 totalTokens: 192 - + """ data = yaml.load(yaml_data, Loader=yaml.FullLoader) instance = TokenUsage.load(data) diff --git a/runtime/python/prompty/tests/model/pipeline/test_compaction_config.py b/runtime/python/prompty/tests/model/pipeline/test_compaction_config.py index ce637de9..311547b9 100644 --- a/runtime/python/prompty/tests/model/pipeline/test_compaction_config.py +++ b/runtime/python/prompty/tests/model/pipeline/test_compaction_config.py @@ -1,3 +1,4 @@ +# import json import yaml @@ -28,7 +29,7 @@ def test_load_yaml_compactionconfig(): budget: 50000 options: preserveSystemMessages: true - + """ data = yaml.load(yaml_data, Loader=yaml.FullLoader) instance = CompactionConfig.load(data) diff --git a/runtime/python/prompty/tests/model/pipeline/test_replay_journal_record.py b/runtime/python/prompty/tests/model/pipeline/test_replay_journal_record.py new file mode 100644 index 00000000..47b56d67 --- /dev/null +++ b/runtime/python/prompty/tests/model/pipeline/test_replay_journal_record.py @@ -0,0 +1 @@ +# diff --git a/runtime/python/prompty/tests/model/pipeline/test_replay_mismatch.py b/runtime/python/prompty/tests/model/pipeline/test_replay_mismatch.py new file mode 100644 index 00000000..47b56d67 --- /dev/null +++ b/runtime/python/prompty/tests/model/pipeline/test_replay_mismatch.py @@ -0,0 +1 @@ +# diff --git a/runtime/python/prompty/tests/model/pipeline/test_replay_verification_request.py b/runtime/python/prompty/tests/model/pipeline/test_replay_verification_request.py new file mode 100644 index 00000000..47b56d67 --- /dev/null +++ b/runtime/python/prompty/tests/model/pipeline/test_replay_verification_request.py @@ -0,0 +1 @@ +# diff --git a/runtime/python/prompty/tests/model/pipeline/test_replay_verification_result.py b/runtime/python/prompty/tests/model/pipeline/test_replay_verification_result.py new file mode 100644 index 00000000..47b56d67 --- /dev/null +++ b/runtime/python/prompty/tests/model/pipeline/test_replay_verification_result.py @@ -0,0 +1 @@ +# diff --git a/runtime/python/prompty/tests/model/pipeline/test_run_turn_request.py b/runtime/python/prompty/tests/model/pipeline/test_run_turn_request.py new file mode 100644 index 00000000..ca9ed63b --- /dev/null +++ b/runtime/python/prompty/tests/model/pipeline/test_run_turn_request.py @@ -0,0 +1,82 @@ +# +import json + +import yaml + +from prompty.model import RunTurnRequest + + +def test_load_json_runturnrequest(): + json_data = r""" + { + "sessionId": "sess_abc123", + "turnId": "turn_abc123" + } + """ + data = json.loads(json_data, strict=False) + instance = RunTurnRequest.load(data) + assert instance is not None + assert instance.session_id == "sess_abc123" + assert instance.turn_id == "turn_abc123" + + +def test_load_yaml_runturnrequest(): + yaml_data = r""" + sessionId: sess_abc123 + turnId: turn_abc123 + + """ + data = yaml.load(yaml_data, Loader=yaml.FullLoader) + instance = RunTurnRequest.load(data) + assert instance is not None + assert instance.session_id == "sess_abc123" + assert instance.turn_id == "turn_abc123" + + +def test_roundtrip_json_runturnrequest(): + """Test that load -> save -> load produces equivalent data.""" + json_data = r""" + { + "sessionId": "sess_abc123", + "turnId": "turn_abc123" + } + """ + original_data = json.loads(json_data, strict=False) + instance = RunTurnRequest.load(original_data) + saved_data = instance.save() + reloaded = RunTurnRequest.load(saved_data) + assert reloaded is not None + assert reloaded.session_id == "sess_abc123" + assert reloaded.turn_id == "turn_abc123" + + +def test_to_json_runturnrequest(): + """Test that to_json produces valid JSON.""" + json_data = r""" + { + "sessionId": "sess_abc123", + "turnId": "turn_abc123" + } + """ + data = json.loads(json_data, strict=False) + instance = RunTurnRequest.load(data) + json_output = instance.to_json() + assert json_output is not None + parsed = json.loads(json_output) + assert isinstance(parsed, dict) + + +def test_to_yaml_runturnrequest(): + """Test that to_yaml produces valid YAML.""" + json_data = r""" + { + "sessionId": "sess_abc123", + "turnId": "turn_abc123" + } + """ + data = json.loads(json_data, strict=False) + instance = RunTurnRequest.load(data) + yaml_output = instance.to_yaml() + assert yaml_output is not None + parsed = yaml.safe_load(yaml_output) + assert isinstance(parsed, dict) diff --git a/runtime/python/prompty/tests/model/pipeline/test_run_turn_result.py b/runtime/python/prompty/tests/model/pipeline/test_run_turn_result.py new file mode 100644 index 00000000..6f6e67e4 --- /dev/null +++ b/runtime/python/prompty/tests/model/pipeline/test_run_turn_result.py @@ -0,0 +1,90 @@ +# +import json + +import yaml + +from prompty.model import RunTurnResult + + +def test_load_json_runturnresult(): + json_data = r""" + { + "sessionId": "sess_abc123", + "turnId": "turn_abc123", + "iterations": 1 + } + """ + data = json.loads(json_data, strict=False) + instance = RunTurnResult.load(data) + assert instance is not None + assert instance.session_id == "sess_abc123" + assert instance.turn_id == "turn_abc123" + assert instance.iterations == 1 + + +def test_load_yaml_runturnresult(): + yaml_data = r""" + sessionId: sess_abc123 + turnId: turn_abc123 + iterations: 1 + + """ + data = yaml.load(yaml_data, Loader=yaml.FullLoader) + instance = RunTurnResult.load(data) + assert instance is not None + assert instance.session_id == "sess_abc123" + assert instance.turn_id == "turn_abc123" + assert instance.iterations == 1 + + +def test_roundtrip_json_runturnresult(): + """Test that load -> save -> load produces equivalent data.""" + json_data = r""" + { + "sessionId": "sess_abc123", + "turnId": "turn_abc123", + "iterations": 1 + } + """ + original_data = json.loads(json_data, strict=False) + instance = RunTurnResult.load(original_data) + saved_data = instance.save() + reloaded = RunTurnResult.load(saved_data) + assert reloaded is not None + assert reloaded.session_id == "sess_abc123" + assert reloaded.turn_id == "turn_abc123" + assert reloaded.iterations == 1 + + +def test_to_json_runturnresult(): + """Test that to_json produces valid JSON.""" + json_data = r""" + { + "sessionId": "sess_abc123", + "turnId": "turn_abc123", + "iterations": 1 + } + """ + data = json.loads(json_data, strict=False) + instance = RunTurnResult.load(data) + json_output = instance.to_json() + assert json_output is not None + parsed = json.loads(json_output) + assert isinstance(parsed, dict) + + +def test_to_yaml_runturnresult(): + """Test that to_yaml produces valid YAML.""" + json_data = r""" + { + "sessionId": "sess_abc123", + "turnId": "turn_abc123", + "iterations": 1 + } + """ + data = json.loads(json_data, strict=False) + instance = RunTurnResult.load(data) + yaml_output = instance.to_yaml() + assert yaml_output is not None + parsed = yaml.safe_load(yaml_output) + assert isinstance(parsed, dict) diff --git a/runtime/python/prompty/tests/model/pipeline/test_turn_model_request.py b/runtime/python/prompty/tests/model/pipeline/test_turn_model_request.py new file mode 100644 index 00000000..59a61293 --- /dev/null +++ b/runtime/python/prompty/tests/model/pipeline/test_turn_model_request.py @@ -0,0 +1,90 @@ +# +import json + +import yaml + +from prompty.model import TurnModelRequest + + +def test_load_json_turnmodelrequest(): + json_data = r""" + { + "sessionId": "sess_abc123", + "turnId": "turn_abc123", + "iteration": 0 + } + """ + data = json.loads(json_data, strict=False) + instance = TurnModelRequest.load(data) + assert instance is not None + assert instance.session_id == "sess_abc123" + assert instance.turn_id == "turn_abc123" + assert instance.iteration == 0 + + +def test_load_yaml_turnmodelrequest(): + yaml_data = r""" + sessionId: sess_abc123 + turnId: turn_abc123 + iteration: 0 + + """ + data = yaml.load(yaml_data, Loader=yaml.FullLoader) + instance = TurnModelRequest.load(data) + assert instance is not None + assert instance.session_id == "sess_abc123" + assert instance.turn_id == "turn_abc123" + assert instance.iteration == 0 + + +def test_roundtrip_json_turnmodelrequest(): + """Test that load -> save -> load produces equivalent data.""" + json_data = r""" + { + "sessionId": "sess_abc123", + "turnId": "turn_abc123", + "iteration": 0 + } + """ + original_data = json.loads(json_data, strict=False) + instance = TurnModelRequest.load(original_data) + saved_data = instance.save() + reloaded = TurnModelRequest.load(saved_data) + assert reloaded is not None + assert reloaded.session_id == "sess_abc123" + assert reloaded.turn_id == "turn_abc123" + assert reloaded.iteration == 0 + + +def test_to_json_turnmodelrequest(): + """Test that to_json produces valid JSON.""" + json_data = r""" + { + "sessionId": "sess_abc123", + "turnId": "turn_abc123", + "iteration": 0 + } + """ + data = json.loads(json_data, strict=False) + instance = TurnModelRequest.load(data) + json_output = instance.to_json() + assert json_output is not None + parsed = json.loads(json_output) + assert isinstance(parsed, dict) + + +def test_to_yaml_turnmodelrequest(): + """Test that to_yaml produces valid YAML.""" + json_data = r""" + { + "sessionId": "sess_abc123", + "turnId": "turn_abc123", + "iteration": 0 + } + """ + data = json.loads(json_data, strict=False) + instance = TurnModelRequest.load(data) + yaml_output = instance.to_yaml() + assert yaml_output is not None + parsed = yaml.safe_load(yaml_output) + assert isinstance(parsed, dict) diff --git a/runtime/python/prompty/tests/model/pipeline/test_turn_model_response.py b/runtime/python/prompty/tests/model/pipeline/test_turn_model_response.py new file mode 100644 index 00000000..47b56d67 --- /dev/null +++ b/runtime/python/prompty/tests/model/pipeline/test_turn_model_response.py @@ -0,0 +1 @@ +# diff --git a/runtime/python/prompty/tests/model/pipeline/test_turn_options.py b/runtime/python/prompty/tests/model/pipeline/test_turn_options.py index 8045097d..8c41cb07 100644 --- a/runtime/python/prompty/tests/model/pipeline/test_turn_options.py +++ b/runtime/python/prompty/tests/model/pipeline/test_turn_options.py @@ -1,3 +1,4 @@ +# import json import yaml @@ -40,7 +41,7 @@ def test_load_yaml_turnoptions(): turn: 1 compaction: strategy: summarize - + """ data = yaml.load(yaml_data, Loader=yaml.FullLoader) instance = TurnOptions.load(data) diff --git a/runtime/python/prompty/tests/model/streaming/test_stream_options.py b/runtime/python/prompty/tests/model/streaming/test_stream_options.py index fafe09e7..2e3a0925 100644 --- a/runtime/python/prompty/tests/model/streaming/test_stream_options.py +++ b/runtime/python/prompty/tests/model/streaming/test_stream_options.py @@ -1,3 +1,4 @@ +# import json import yaml @@ -20,7 +21,7 @@ def test_load_json_streamoptions(): def test_load_yaml_streamoptions(): yaml_data = r""" includeUsage: true - + """ data = yaml.load(yaml_data, Loader=yaml.FullLoader) instance = StreamOptions.load(data) diff --git a/runtime/python/prompty/tests/model/template/test_format_config.py b/runtime/python/prompty/tests/model/template/test_format_config.py index f02d7108..6cccae4f 100644 --- a/runtime/python/prompty/tests/model/template/test_format_config.py +++ b/runtime/python/prompty/tests/model/template/test_format_config.py @@ -1,3 +1,4 @@ +# import json import yaml @@ -28,7 +29,7 @@ def test_load_yaml_formatconfig(): strict: true options: key: value - + """ data = yaml.load(yaml_data, Loader=yaml.FullLoader) instance = FormatConfig.load(data) diff --git a/runtime/python/prompty/tests/model/template/test_parser_config.py b/runtime/python/prompty/tests/model/template/test_parser_config.py index 96b67bc9..57b1b4a6 100644 --- a/runtime/python/prompty/tests/model/template/test_parser_config.py +++ b/runtime/python/prompty/tests/model/template/test_parser_config.py @@ -1,3 +1,4 @@ +# import json import yaml @@ -25,7 +26,7 @@ def test_load_yaml_parserconfig(): kind: prompty options: key: value - + """ data = yaml.load(yaml_data, Loader=yaml.FullLoader) instance = ParserConfig.load(data) diff --git a/runtime/python/prompty/tests/model/template/test_template.py b/runtime/python/prompty/tests/model/template/test_template.py index b3f70bbf..e2fb2f21 100644 --- a/runtime/python/prompty/tests/model/template/test_template.py +++ b/runtime/python/prompty/tests/model/template/test_template.py @@ -1,3 +1,4 @@ +# import json import yaml @@ -27,7 +28,7 @@ def test_load_yaml_template(): kind: mustache parser: kind: mustache - + """ data = yaml.load(yaml_data, Loader=yaml.FullLoader) instance = Template.load(data) diff --git a/runtime/python/prompty/tests/model/test_context.py b/runtime/python/prompty/tests/model/test_context.py index 9cac75b7..b9b1cee1 100644 --- a/runtime/python/prompty/tests/model/test_context.py +++ b/runtime/python/prompty/tests/model/test_context.py @@ -1,4 +1,5 @@ -# Prompty LoadContext +# +# Typra LoadContext from prompty.model._context import LoadContext, SaveContext diff --git a/runtime/python/prompty/tests/model/tools/test_binding.py b/runtime/python/prompty/tests/model/tools/test_binding.py index 5726bb56..7ea45d5b 100644 --- a/runtime/python/prompty/tests/model/tools/test_binding.py +++ b/runtime/python/prompty/tests/model/tools/test_binding.py @@ -1,3 +1,4 @@ +# import json import yaml @@ -23,7 +24,7 @@ def test_load_yaml_binding(): yaml_data = r""" name: my-tool input: input-variable - + """ data = yaml.load(yaml_data, Loader=yaml.FullLoader) instance = Binding.load(data) diff --git a/runtime/python/prompty/tests/model/tools/test_custom_tool.py b/runtime/python/prompty/tests/model/tools/test_custom_tool.py index 84d56a3d..293a742a 100644 --- a/runtime/python/prompty/tests/model/tools/test_custom_tool.py +++ b/runtime/python/prompty/tests/model/tools/test_custom_tool.py @@ -1,3 +1,4 @@ +# import json import yaml @@ -29,7 +30,7 @@ def test_load_yaml_customtool(): options: timeout: 30 retries: 3 - + """ data = yaml.load(yaml_data, Loader=yaml.FullLoader) instance = CustomTool.load(data) diff --git a/runtime/python/prompty/tests/model/tools/test_function_tool.py b/runtime/python/prompty/tests/model/tools/test_function_tool.py index fbecb18f..d0c83506 100644 --- a/runtime/python/prompty/tests/model/tools/test_function_tool.py +++ b/runtime/python/prompty/tests/model/tools/test_function_tool.py @@ -1,3 +1,4 @@ +# import json import yaml @@ -47,7 +48,7 @@ def test_load_yaml_functiontool(): kind: string default: What is the meaning of life? strict: true - + """ data = yaml.load(yaml_data, Loader=yaml.FullLoader) instance = FunctionTool.load(data) @@ -192,7 +193,7 @@ def test_load_yaml_functiontool_1(): kind: string default: What is the meaning of life? strict: true - + """ data = yaml.load(yaml_data, Loader=yaml.FullLoader) instance = FunctionTool.load(data) diff --git a/runtime/python/prompty/tests/model/tools/test_mcp_approval_mode.py b/runtime/python/prompty/tests/model/tools/test_mcp_approval_mode.py index 7e24ff9a..f94be0ec 100644 --- a/runtime/python/prompty/tests/model/tools/test_mcp_approval_mode.py +++ b/runtime/python/prompty/tests/model/tools/test_mcp_approval_mode.py @@ -1,3 +1,4 @@ +# import json import yaml @@ -30,7 +31,7 @@ def test_load_yaml_mcpapprovalmode(): - operation1 neverRequireApprovalTools: - operation2 - + """ data = yaml.load(yaml_data, Loader=yaml.FullLoader) instance = McpApprovalMode.load(data) diff --git a/runtime/python/prompty/tests/model/tools/test_mcp_tool.py b/runtime/python/prompty/tests/model/tools/test_mcp_tool.py index 29583f1b..48c7c35c 100644 --- a/runtime/python/prompty/tests/model/tools/test_mcp_tool.py +++ b/runtime/python/prompty/tests/model/tools/test_mcp_tool.py @@ -1,3 +1,4 @@ +# import json import yaml @@ -43,7 +44,7 @@ def test_load_yaml_mcptool(): allowedTools: - operation1 - operation2 - + """ data = yaml.load(yaml_data, Loader=yaml.FullLoader) instance = McpTool.load(data) diff --git a/runtime/python/prompty/tests/model/tools/test_open_api_tool.py b/runtime/python/prompty/tests/model/tools/test_open_api_tool.py index 28b9c85a..9bd22a71 100644 --- a/runtime/python/prompty/tests/model/tools/test_open_api_tool.py +++ b/runtime/python/prompty/tests/model/tools/test_open_api_tool.py @@ -1,3 +1,4 @@ +# import json import yaml @@ -28,7 +29,7 @@ def test_load_yaml_openapitool(): connection: kind: reference specification: ./openapi.json - + """ data = yaml.load(yaml_data, Loader=yaml.FullLoader) instance = OpenApiTool.load(data) diff --git a/runtime/python/prompty/tests/model/tools/test_prompty_tool.py b/runtime/python/prompty/tests/model/tools/test_prompty_tool.py index 08af5ea0..8187f263 100644 --- a/runtime/python/prompty/tests/model/tools/test_prompty_tool.py +++ b/runtime/python/prompty/tests/model/tools/test_prompty_tool.py @@ -1,3 +1,4 @@ +# import json import yaml @@ -26,7 +27,7 @@ def test_load_yaml_promptytool(): kind: prompty path: ./summarize.prompty mode: single - + """ data = yaml.load(yaml_data, Loader=yaml.FullLoader) instance = PromptyTool.load(data) diff --git a/runtime/python/prompty/tests/model/tools/test_tool.py b/runtime/python/prompty/tests/model/tools/test_tool.py index d2545228..c85f2cff 100644 --- a/runtime/python/prompty/tests/model/tools/test_tool.py +++ b/runtime/python/prompty/tests/model/tools/test_tool.py @@ -1,3 +1,4 @@ +# import json import yaml @@ -31,7 +32,7 @@ def test_load_yaml_tool(): description: A description of the tool bindings: input: value - + """ data = yaml.load(yaml_data, Loader=yaml.FullLoader) instance = Tool.load(data) diff --git a/runtime/python/prompty/tests/model/tools/test_tool_context.py b/runtime/python/prompty/tests/model/tools/test_tool_context.py index 4a7c8552..20818f4b 100644 --- a/runtime/python/prompty/tests/model/tools/test_tool_context.py +++ b/runtime/python/prompty/tests/model/tools/test_tool_context.py @@ -1,3 +1,4 @@ +# import json import yaml @@ -22,7 +23,7 @@ def test_load_yaml_toolcontext(): yaml_data = r""" metadata: userId: user-123 - + """ data = yaml.load(yaml_data, Loader=yaml.FullLoader) instance = ToolContext.load(data) diff --git a/runtime/python/prompty/tests/model/tools/test_tool_dispatch_result.py b/runtime/python/prompty/tests/model/tools/test_tool_dispatch_result.py index 0d8f7ca6..1163f203 100644 --- a/runtime/python/prompty/tests/model/tools/test_tool_dispatch_result.py +++ b/runtime/python/prompty/tests/model/tools/test_tool_dispatch_result.py @@ -1,3 +1,4 @@ +# import json import yaml @@ -35,7 +36,7 @@ def test_load_yaml_tooldispatchresult(): parts: - kind: text value: 72°F and sunny - + """ data = yaml.load(yaml_data, Loader=yaml.FullLoader) instance = ToolDispatchResult.load(data) diff --git a/runtime/python/prompty/tests/model/tracing/test_trace_file.py b/runtime/python/prompty/tests/model/tracing/test_trace_file.py index 44da1474..694a524b 100644 --- a/runtime/python/prompty/tests/model/tracing/test_trace_file.py +++ b/runtime/python/prompty/tests/model/tracing/test_trace_file.py @@ -1,3 +1,4 @@ +# import json import yaml @@ -23,7 +24,7 @@ def test_load_yaml_tracefile(): yaml_data = r""" runtime: python version: 2.0.0 - + """ data = yaml.load(yaml_data, Loader=yaml.FullLoader) instance = TraceFile.load(data) diff --git a/runtime/python/prompty/tests/model/tracing/test_trace_span.py b/runtime/python/prompty/tests/model/tracing/test_trace_span.py index c0bf9fcb..0a683580 100644 --- a/runtime/python/prompty/tests/model/tracing/test_trace_span.py +++ b/runtime/python/prompty/tests/model/tracing/test_trace_span.py @@ -1,3 +1,4 @@ +# import json import yaml @@ -26,7 +27,7 @@ def test_load_yaml_tracespan(): name: prompty.core.pipeline.run signature: prompty.core.pipeline.run error: Connection refused - + """ data = yaml.load(yaml_data, Loader=yaml.FullLoader) instance = TraceSpan.load(data) diff --git a/runtime/python/prompty/tests/model/tracing/test_trace_time.py b/runtime/python/prompty/tests/model/tracing/test_trace_time.py index 4b49e7ba..220a2d56 100644 --- a/runtime/python/prompty/tests/model/tracing/test_trace_time.py +++ b/runtime/python/prompty/tests/model/tracing/test_trace_time.py @@ -1,3 +1,4 @@ +# import json import yaml @@ -26,7 +27,7 @@ def test_load_yaml_tracetime(): start: "2026-04-04T12:00:00Z" end: "2026-04-04T12:00:01Z" duration: 1000 - + """ data = yaml.load(yaml_data, Loader=yaml.FullLoader) instance = TraceTime.load(data) diff --git a/runtime/python/prompty/tests/model/wire/test_anthropic_image_block.py b/runtime/python/prompty/tests/model/wire/test_anthropic_image_block.py index 8b137891..47b56d67 100644 --- a/runtime/python/prompty/tests/model/wire/test_anthropic_image_block.py +++ b/runtime/python/prompty/tests/model/wire/test_anthropic_image_block.py @@ -1 +1 @@ - +# diff --git a/runtime/python/prompty/tests/model/wire/test_anthropic_image_source.py b/runtime/python/prompty/tests/model/wire/test_anthropic_image_source.py index 41b4b1b8..6aba195c 100644 --- a/runtime/python/prompty/tests/model/wire/test_anthropic_image_source.py +++ b/runtime/python/prompty/tests/model/wire/test_anthropic_image_source.py @@ -1,3 +1,4 @@ +# import json import yaml @@ -23,7 +24,7 @@ def test_load_yaml_anthropicimagesource(): yaml_data = r""" media_type: image/png data: iVBORw0KGgo... - + """ data = yaml.load(yaml_data, Loader=yaml.FullLoader) instance = AnthropicImageSource.load(data) diff --git a/runtime/python/prompty/tests/model/wire/test_anthropic_messages_request.py b/runtime/python/prompty/tests/model/wire/test_anthropic_messages_request.py index a785109a..b97daf3e 100644 --- a/runtime/python/prompty/tests/model/wire/test_anthropic_messages_request.py +++ b/runtime/python/prompty/tests/model/wire/test_anthropic_messages_request.py @@ -1,3 +1,4 @@ +# import json import yaml @@ -40,7 +41,7 @@ def test_load_yaml_anthropicmessagesrequest(): top_k: 40 stop_sequences: - "\n\nHuman:" - + """ data = yaml.load(yaml_data, Loader=yaml.FullLoader) instance = AnthropicMessagesRequest.load(data) diff --git a/runtime/python/prompty/tests/model/wire/test_anthropic_messages_response.py b/runtime/python/prompty/tests/model/wire/test_anthropic_messages_response.py index 40f3d812..4ace73d0 100644 --- a/runtime/python/prompty/tests/model/wire/test_anthropic_messages_response.py +++ b/runtime/python/prompty/tests/model/wire/test_anthropic_messages_response.py @@ -1,3 +1,4 @@ +# import json import yaml @@ -26,7 +27,7 @@ def test_load_yaml_anthropicmessagesresponse(): id: msg_01XFDUDYJgAACzvnptvVoYEL model: claude-sonnet-4-20250514 stop_reason: end_turn - + """ data = yaml.load(yaml_data, Loader=yaml.FullLoader) instance = AnthropicMessagesResponse.load(data) diff --git a/runtime/python/prompty/tests/model/wire/test_anthropic_text_block.py b/runtime/python/prompty/tests/model/wire/test_anthropic_text_block.py index 0cfa01a9..f8a1c63e 100644 --- a/runtime/python/prompty/tests/model/wire/test_anthropic_text_block.py +++ b/runtime/python/prompty/tests/model/wire/test_anthropic_text_block.py @@ -1,3 +1,4 @@ +# import json import yaml @@ -20,7 +21,7 @@ def test_load_json_anthropictextblock(): def test_load_yaml_anthropictextblock(): yaml_data = r""" text: Hello, how can I help? - + """ data = yaml.load(yaml_data, Loader=yaml.FullLoader) instance = AnthropicTextBlock.load(data) diff --git a/runtime/python/prompty/tests/model/wire/test_anthropic_tool_definition.py b/runtime/python/prompty/tests/model/wire/test_anthropic_tool_definition.py index 9567cfb8..909c83df 100644 --- a/runtime/python/prompty/tests/model/wire/test_anthropic_tool_definition.py +++ b/runtime/python/prompty/tests/model/wire/test_anthropic_tool_definition.py @@ -1,3 +1,4 @@ +# import json import yaml @@ -23,7 +24,7 @@ def test_load_yaml_anthropictooldefinition(): yaml_data = r""" name: get_weather description: Get the current weather for a city - + """ data = yaml.load(yaml_data, Loader=yaml.FullLoader) instance = AnthropicToolDefinition.load(data) diff --git a/runtime/python/prompty/tests/model/wire/test_anthropic_tool_result_block.py b/runtime/python/prompty/tests/model/wire/test_anthropic_tool_result_block.py index a2d244d2..f25ed786 100644 --- a/runtime/python/prompty/tests/model/wire/test_anthropic_tool_result_block.py +++ b/runtime/python/prompty/tests/model/wire/test_anthropic_tool_result_block.py @@ -1,3 +1,4 @@ +# import json import yaml @@ -23,7 +24,7 @@ def test_load_yaml_anthropictoolresultblock(): yaml_data = r""" tool_use_id: toolu_01A09q90qw90lq917835lq9 content: 72°F and sunny in Paris - + """ data = yaml.load(yaml_data, Loader=yaml.FullLoader) instance = AnthropicToolResultBlock.load(data) diff --git a/runtime/python/prompty/tests/model/wire/test_anthropic_tool_use_block.py b/runtime/python/prompty/tests/model/wire/test_anthropic_tool_use_block.py index 933bbac6..f2d76e27 100644 --- a/runtime/python/prompty/tests/model/wire/test_anthropic_tool_use_block.py +++ b/runtime/python/prompty/tests/model/wire/test_anthropic_tool_use_block.py @@ -1,3 +1,4 @@ +# import json import yaml @@ -28,7 +29,7 @@ def test_load_yaml_anthropictooluseblock(): name: get_weather input: city: Paris - + """ data = yaml.load(yaml_data, Loader=yaml.FullLoader) instance = AnthropicToolUseBlock.load(data) diff --git a/runtime/python/prompty/tests/model/wire/test_anthropic_usage.py b/runtime/python/prompty/tests/model/wire/test_anthropic_usage.py index b117e7e8..6180fd3a 100644 --- a/runtime/python/prompty/tests/model/wire/test_anthropic_usage.py +++ b/runtime/python/prompty/tests/model/wire/test_anthropic_usage.py @@ -1,3 +1,4 @@ +# import json import yaml @@ -23,7 +24,7 @@ def test_load_yaml_anthropicusage(): yaml_data = r""" input_tokens: 150 output_tokens: 42 - + """ data = yaml.load(yaml_data, Loader=yaml.FullLoader) instance = AnthropicUsage.load(data) diff --git a/runtime/python/prompty/tests/model/wire/test_anthropic_wire_message.py b/runtime/python/prompty/tests/model/wire/test_anthropic_wire_message.py index 463b607d..8a55fac8 100644 --- a/runtime/python/prompty/tests/model/wire/test_anthropic_wire_message.py +++ b/runtime/python/prompty/tests/model/wire/test_anthropic_wire_message.py @@ -1,3 +1,4 @@ +# import json import yaml @@ -20,7 +21,7 @@ def test_load_json_anthropicwiremessage(): def test_load_yaml_anthropicwiremessage(): yaml_data = r""" role: user - + """ data = yaml.load(yaml_data, Loader=yaml.FullLoader) instance = AnthropicWireMessage.load(data) diff --git a/runtime/python/prompty/tests/test_harness_adapters.py b/runtime/python/prompty/tests/test_harness_adapters.py new file mode 100644 index 00000000..e44a6b82 --- /dev/null +++ b/runtime/python/prompty/tests/test_harness_adapters.py @@ -0,0 +1,137 @@ +from __future__ import annotations + +import json + +import pytest + +from prompty import ( + AllowAllPermissionResolver, + CollectingEventSink, + DenyAllPermissionResolver, + FunctionHostToolExecutor, + InMemoryCheckpointStore, + JsonlEventJournalWriter, +) +from prompty.model import Checkpoint, HostToolRequest, PermissionRequest, SessionEvent, SessionSummary, TurnEvent + + +def _turn_event() -> TurnEvent: + return TurnEvent(id="turn-event", type="turn_start", timestamp="2026-06-10T00:00:00Z", payload={"phase": "start"}) + + +def _session_event() -> SessionEvent: + return SessionEvent( + id="session-event", + type="session_start", + timestamp="2026-06-10T00:00:00Z", + session_id="session-1", + payload={"phase": "start"}, + ) + + +def test_collecting_event_sink_captures_events() -> None: + sink = CollectingEventSink() + + assert sink.emit_turn(_turn_event()) is True + assert sink.emit_session(_session_event()) is True + + assert [event.id for event in sink.turn_events] == ["turn-event"] + assert [event.id for event in sink.session_events] == ["session-event"] + + +def test_jsonl_event_journal_writer_writes_records(tmp_path) -> None: + trace_path = tmp_path / "trace.jsonl" + writer = JsonlEventJournalWriter(trace_path) + + writer.append_turn(_turn_event()) + writer.append_session(_session_event()) + writer.close(SessionSummary(session_id="session-1", turns=1)) + + lines = [json.loads(line) for line in trace_path.read_text(encoding="utf-8").splitlines()] + assert [line["kind"] for line in lines] == ["turn", "session", "summary"] + assert lines[0]["event"]["id"] == "turn-event" + assert lines[1]["event"]["id"] == "session-event" + assert lines[2]["summary"]["sessionId"] == "session-1" + + +def test_jsonl_event_journal_writer_returns_false_after_close(tmp_path) -> None: + writer = JsonlEventJournalWriter(tmp_path / "trace.jsonl") + + closed = writer.close(None) + assert closed is True + assert writer.append_turn(_turn_event()) is False + + +@pytest.mark.asyncio +async def test_in_memory_checkpoint_store() -> None: + store = InMemoryCheckpointStore() + checkpoint = Checkpoint(id="checkpoint-1", session_id="session-1", title="First") + + assert await store.save(checkpoint) is checkpoint + assert await store.load("session-1", "checkpoint-1") is checkpoint + assert await store.load("session-1", "missing") is None + assert await store.list_checkpoints("session-1") == [checkpoint] + + +@pytest.mark.asyncio +async def test_in_memory_checkpoint_store_requires_keys() -> None: + store = InMemoryCheckpointStore() + + with pytest.raises(ValueError, match="session_id"): + await store.save(Checkpoint(id="checkpoint-1", title="Missing session")) + with pytest.raises(ValueError, match="id"): + await store.save(Checkpoint(session_id="session-1", title="Missing id")) + + +@pytest.mark.asyncio +async def test_permission_resolvers() -> None: + request = PermissionRequest(request_id="permission-1", tool_call_id="tool-call-1", permission="tool.execute") + + allow = await AllowAllPermissionResolver().request(request) + deny = await DenyAllPermissionResolver().request(request) + + assert allow.approved is True + assert allow.reason == "allow_all" + assert allow.request_id == "permission-1" + assert allow.tool_call_id == "tool-call-1" + assert deny.approved is False + assert deny.reason == "deny_all" + + +@pytest.mark.asyncio +async def test_function_host_tool_executor_success() -> None: + executor = FunctionHostToolExecutor({"add": lambda args, request: int(args["a"]) + int(args["b"])}) + + result = await executor.execute(HostToolRequest(request_id="exec-1", tool_name="add", arguments={"a": 2, "b": 3})) + + assert result.success is True + assert result.result == 5 + assert result.request_id == "exec-1" + assert result.tool_name == "add" + + +@pytest.mark.asyncio +async def test_function_host_tool_executor_passes_empty_arguments() -> None: + executor = FunctionHostToolExecutor({"count": lambda args, request: len(args)}) + + result = await executor.execute(HostToolRequest(tool_name="count")) + + assert result.success is True + assert result.result == 0 + + +@pytest.mark.asyncio +async def test_function_host_tool_executor_failures() -> None: + def fail(args, request): + raise RuntimeError("boom") + + executor = FunctionHostToolExecutor({"fail": fail}) + + missing = await executor.execute(HostToolRequest(tool_name="missing")) + thrown = await executor.execute(HostToolRequest(tool_name="fail")) + + assert missing.success is False + assert missing.error_kind == "not_found" + assert thrown.success is False + assert thrown.error_kind == "exception" + assert thrown.result == {"message": "boom"} diff --git a/runtime/python/prompty/tests/test_replay_verifier.py b/runtime/python/prompty/tests/test_replay_verifier.py new file mode 100644 index 00000000..a093aa3d --- /dev/null +++ b/runtime/python/prompty/tests/test_replay_verifier.py @@ -0,0 +1,34 @@ +from __future__ import annotations + +from prompty import ReferenceReplayVerifier +from prompty.model import ReplayJournalRecord, ReplayVerificationRequest + + +def test_replay_verifier_passes_identical_records() -> None: + record = ReplayJournalRecord( + kind="turn", + type="turn_end", + turn_id="turn-1", + iteration=1, + status="success", + ) + + result = ReferenceReplayVerifier().verify(ReplayVerificationRequest(expected=[record], actual=[record])) + + assert result.status == "passed" + assert result.mismatches == [] + assert result.expected_count == 1 + assert result.actual_count == 1 + + +def test_replay_verifier_reports_mismatches_with_generated_types() -> None: + result = ReferenceReplayVerifier().verify( + ReplayVerificationRequest( + expected=[ReplayJournalRecord(kind="summary", session_id="session-1", status="success")], + actual=[ReplayJournalRecord(kind="summary", session_id="session-1", status="error")], + ) + ) + + assert result.status == "failed" + assert result.mismatches[0].index == 0 + assert result.mismatches[0].message == "Replay record mismatch" diff --git a/runtime/python/prompty/tests/test_turn_runner.py b/runtime/python/prompty/tests/test_turn_runner.py new file mode 100644 index 00000000..bfc1be83 --- /dev/null +++ b/runtime/python/prompty/tests/test_turn_runner.py @@ -0,0 +1,337 @@ +from __future__ import annotations + +import json +from pathlib import Path +from typing import Any + +import pytest + +from prompty import ( + AllowAllPermissionResolver, + CollectingEventSink, + DenyAllPermissionResolver, + FunctionHostToolExecutor, + InMemoryCheckpointStore, + JsonlEventJournalWriter, + ReferenceTurnRunner, + RunTurnRequest, + TurnModelRequest, + TurnModelResponse, +) +from prompty.model import HostToolRequest, HostToolResult, TurnOptions + + +def _fixed_ids(): + index = 0 + + def next_id(prefix: str) -> str: + nonlocal index + index += 1 + return f"{prefix}-{index}" + + return next_id + + +def _records(path: Path) -> list[dict[str, Any]]: + return [json.loads(line) for line in path.read_text(encoding="utf-8").splitlines()] + + +def _replay_vectors() -> dict[str, Any]: + path = Path(__file__).parents[4] / "spec" / "vectors" / "harness" / "replay_vectors.json" + vectors = json.loads(path.read_text(encoding="utf-8")) + assert vectors["version"] == 1 + return vectors + + +def _normalize_journal(records: list[dict[str, Any]]) -> list[str]: + normalized = [] + for record in records: + if record["kind"] == "summary": + summary = record["summary"] + normalized.append( + f"summary:{summary['sessionId']}:{summary['status']}:" + f"turns={summary['turns']}:checkpoints={summary['checkpoints']}" + ) + continue + + event = record["event"] + if record["kind"] == "session": + if event["type"] == "session_end": + normalized.append( + f"session:{event['type']}:{event['sessionId']}:{event['turnId']}:{event['payload']['status']}" + ) + else: + normalized.append(f"session:{event['type']}:{event['sessionId']}:{event['turnId']}") + continue + + payload = event.get("payload") or {} + match event["type"]: + case "permission_requested": + normalized.append(f"turn:{event['type']}:{event['iteration']}:{payload['requestId']}") + case "permission_completed": + normalized.append(f"turn:{event['type']}:{event['iteration']}:{str(payload['approved']).lower()}") + case "tool_execution_start": + normalized.append(f"turn:{event['type']}:{event['iteration']}:{payload['toolName']}") + case "tool_execution_complete" | "tool_result": + value = ( + f"turn:{event['type']}:{event['iteration']}:{payload['toolName']}:{str(payload['success']).lower()}" + ) + if payload.get("errorKind"): + value = f"{value}:{payload['errorKind']}" + normalized.append(value) + case "error": + normalized.append(f"turn:{event['type']}:{event['iteration']}:{payload['errorKind']}") + case "turn_end": + normalized.append(f"turn:{event['type']}:{event['iteration']}:{payload['status']}") + case _: + normalized.append(f"turn:{event['type']}:{event['iteration']}") + return normalized + + +def _model_for_scenario(name: str): + def invoke_model(request: TurnModelRequest) -> TurnModelResponse: + if name == "no_tool": + return TurnModelResponse( + output={"text": f"hello {request.inputs['name']}"}, + checkpoint_state={"stable": True}, + ) + if request.iteration == 0: + tool_name = "fail" if name == "tool_failure" else "add" + return TurnModelResponse( + tool_requests=[ + HostToolRequest( + request_id="exec-1", + tool_call_id="call-1", + tool_name=tool_name, + arguments={"a": 2, "b": 3}, + ) + ] + ) + return TurnModelResponse( + output={"toolResult": request.tool_results[0].result, "errorKind": request.tool_results[0].error_kind} + ) + + return invoke_model + + +@pytest.mark.asyncio +async def test_turn_runner_emits_journals_and_checkpoints_in_order(tmp_path: Path) -> None: + journal_path = tmp_path / "trace.jsonl" + sink = CollectingEventSink() + checkpoint_store = InMemoryCheckpointStore() + + runner = ReferenceTurnRunner( + event_sink=sink, + journal=JsonlEventJournalWriter(journal_path), + checkpoint_store=checkpoint_store, + permission_resolver=AllowAllPermissionResolver(), + host_tool_executor=FunctionHostToolExecutor({}), + invoke_model=lambda request: TurnModelResponse( + output={"text": f"hello {request.inputs['name']}"}, + checkpoint_state={"stable": True}, + ), + now=lambda: "2026-06-28T00:00:00Z", + next_id=_fixed_ids(), + ) + + result = await runner.run( + RunTurnRequest( + session_id="session-1", + turn_id="turn-1", + inputs={"name": "Ada"}, + options=TurnOptions(max_iterations=3), + ) + ) + + assert result.status == "success" + assert result.iterations == 1 + assert result.output == {"text": "hello Ada"} + assert [event.type for event in sink.session_events] == ["session_start", "checkpoint_created", "session_end"] + assert [event.type for event in sink.turn_events] == ["turn_start", "llm_start", "llm_complete", "turn_end"] + checkpoint = await checkpoint_store.load("session-1", "turn-1-checkpoint-0") + assert checkpoint is not None + assert checkpoint.state == { + "iteration": 0, + "output": {"text": "hello Ada"}, + "toolRequests": [], + "stable": True, + } + assert [record["kind"] for record in _records(journal_path)] == [ + "session", + "turn", + "turn", + "turn", + "session", + "turn", + "session", + "summary", + ] + + +@pytest.mark.asyncio +async def test_turn_runner_requests_permission_and_executes_host_tools(tmp_path: Path) -> None: + sink = CollectingEventSink() + + def invoke_model(request: TurnModelRequest) -> TurnModelResponse: + if request.iteration == 0: + return TurnModelResponse( + tool_requests=[ + HostToolRequest( + request_id="exec-1", + tool_call_id="call-1", + tool_name="add", + arguments={"a": 2, "b": 3}, + ) + ] + ) + return TurnModelResponse(output={"toolResult": request.tool_results[0].result}) + + runner = ReferenceTurnRunner( + event_sink=sink, + journal=JsonlEventJournalWriter(tmp_path / "trace.jsonl"), + checkpoint_store=InMemoryCheckpointStore(), + permission_resolver=AllowAllPermissionResolver(), + host_tool_executor=FunctionHostToolExecutor({"add": lambda args, request: int(args["a"]) + int(args["b"])}), + invoke_model=invoke_model, + now=lambda: "2026-06-28T00:00:00Z", + next_id=_fixed_ids(), + ) + + result = await runner.run(RunTurnRequest(session_id="session-1", turn_id="turn-1")) + + assert result.output == {"toolResult": 5} + assert result.tool_results[0].success is True + assert result.tool_results[0].result == 5 + assert [event.type for event in sink.turn_events] == [ + "turn_start", + "llm_start", + "llm_complete", + "permission_requested", + "permission_completed", + "tool_execution_start", + "tool_execution_complete", + "tool_result", + "messages_updated", + "llm_start", + "llm_complete", + "turn_end", + ] + + +@pytest.mark.asyncio +async def test_turn_runner_records_denied_permission_without_executing_tool(tmp_path: Path) -> None: + class FailingExecutor: + async def execute(self, request: HostToolRequest) -> HostToolResult: + raise AssertionError("should not execute") + + sink = CollectingEventSink() + + def invoke_model(request: TurnModelRequest) -> TurnModelResponse: + if request.iteration == 0: + return TurnModelResponse(tool_requests=[HostToolRequest(request_id="exec-1", tool_name="shell")]) + return TurnModelResponse(output={"denied": request.tool_results[0].error_kind}) + + runner = ReferenceTurnRunner( + event_sink=sink, + journal=JsonlEventJournalWriter(tmp_path / "trace.jsonl"), + checkpoint_store=InMemoryCheckpointStore(), + permission_resolver=DenyAllPermissionResolver(), + host_tool_executor=FailingExecutor(), + invoke_model=invoke_model, + now=lambda: "2026-06-28T00:00:00Z", + next_id=_fixed_ids(), + ) + + result = await runner.run(RunTurnRequest(session_id="session-1", turn_id="turn-1")) + + assert result.output == {"denied": "permission_denied"} + assert result.tool_results[0].success is False + assert result.tool_results[0].error_kind == "permission_denied" + assert "tool_execution_start" not in [event.type for event in sink.turn_events] + + +@pytest.mark.asyncio +async def test_turn_runner_surfaces_host_tool_failure(tmp_path: Path) -> None: + def fail(args: dict[str, Any], request: HostToolRequest) -> object: + raise RuntimeError("boom") + + def invoke_model(request: TurnModelRequest) -> TurnModelResponse: + if request.iteration == 0: + return TurnModelResponse(tool_requests=[HostToolRequest(request_id="exec-1", tool_name="fail")]) + return TurnModelResponse(output=request.tool_results[0].save()) + + runner = ReferenceTurnRunner( + event_sink=CollectingEventSink(), + journal=JsonlEventJournalWriter(tmp_path / "trace.jsonl"), + checkpoint_store=InMemoryCheckpointStore(), + permission_resolver=AllowAllPermissionResolver(), + host_tool_executor=FunctionHostToolExecutor({"fail": fail}), + invoke_model=invoke_model, + now=lambda: "2026-06-28T00:00:00Z", + next_id=_fixed_ids(), + ) + + result = await runner.run(RunTurnRequest(session_id="session-1", turn_id="turn-1")) + + assert result.output["success"] is False + assert result.output["errorKind"] == "exception" + assert result.output["result"] == {"message": "boom"} + + +@pytest.mark.asyncio +async def test_turn_runner_produces_deterministic_replayable_records(tmp_path: Path) -> None: + async def run_once(path: Path) -> list[dict[str, Any]]: + runner = ReferenceTurnRunner( + event_sink=CollectingEventSink(), + journal=JsonlEventJournalWriter(path), + checkpoint_store=InMemoryCheckpointStore(), + permission_resolver=AllowAllPermissionResolver(), + host_tool_executor=FunctionHostToolExecutor({}), + invoke_model=lambda request: TurnModelResponse(output="done"), + now=lambda: "2026-06-28T00:00:00Z", + next_id=_fixed_ids(), + ) + await runner.run(RunTurnRequest(session_id="session-1", turn_id="turn-1")) + return _records(path) + + assert await run_once(tmp_path / "first.jsonl") == await run_once(tmp_path / "second.jsonl") + + +@pytest.mark.asyncio +async def test_turn_runner_matches_shared_golden_replay_vectors(tmp_path: Path) -> None: + vectors = _replay_vectors() + + for scenario in vectors["scenarios"]: + journal_path = tmp_path / f"{scenario['name']}.jsonl" + + def fail(args: dict[str, Any], request: HostToolRequest) -> object: + raise RuntimeError("boom") + + runner = ReferenceTurnRunner( + event_sink=CollectingEventSink(), + journal=JsonlEventJournalWriter(journal_path), + checkpoint_store=InMemoryCheckpointStore(), + permission_resolver=DenyAllPermissionResolver() + if scenario["name"] == "permission_denied" + else AllowAllPermissionResolver(), + host_tool_executor=FunctionHostToolExecutor( + { + "add": lambda args, request: int(args["a"]) + int(args["b"]), + "fail": fail, + } + ), + invoke_model=_model_for_scenario(scenario["name"]), + now=lambda: vectors["clock"], + next_id=_fixed_ids(), + ) + + await runner.run( + RunTurnRequest( + session_id=vectors["sessionId"], + turn_id=vectors["turnId"], + inputs=scenario.get("inputs"), + options=TurnOptions(max_iterations=scenario.get("maxIterations")), + ) + ) + + assert _normalize_journal(_records(journal_path)) == scenario["expected"], scenario["name"] diff --git a/runtime/rust/prompty/src/harness.rs b/runtime/rust/prompty/src/harness.rs new file mode 100644 index 00000000..42eb4334 --- /dev/null +++ b/runtime/rust/prompty/src/harness.rs @@ -0,0 +1,725 @@ +//! Reference harness adapters for event, trace, permission, checkpoint, and tool protocols. + +use std::collections::HashMap; +use std::error::Error; +use std::fs::{OpenOptions, create_dir_all}; +use std::io::Write; +use std::path::PathBuf; +use std::sync::{Arc, Mutex}; +use std::time::Instant; + +use serde_json::{Value, json}; + +use crate::model::context::SaveContext; +use crate::model::events::{ + checkpoint::Checkpoint, + host_tool_request::HostToolRequest, + host_tool_result::HostToolResult, + permission_decision::PermissionDecision, + permission_request::PermissionRequest, + session_event::{SessionEvent, SessionEventType}, + session_summary::{SessionSummary, SessionSummaryStatus}, + turn_event::{TurnEvent, TurnEventType}, +}; +use crate::model::pipeline::{ + checkpoint_store::CheckpointStore, + event_journal_writer::EventJournalWriter, + event_sink::EventSink, + host_tool_executor::HostToolExecutor, + permission_resolver::PermissionResolver, + replay_journal_record::ReplayJournalRecord, + replay_mismatch::ReplayMismatch, + replay_verification_request::ReplayVerificationRequest, + replay_verification_result::{ReplayVerificationResult, ReplayVerificationStatus}, + run_turn_request::RunTurnRequest, + run_turn_result::{RunTurnResult, RunTurnStatus}, + turn_model_request::TurnModelRequest, + turn_model_response::TurnModelResponse, +}; + +pub type AdapterError = Box; +type ToolHandler = dyn Fn(&Value, &HostToolRequest) -> Result + Send + Sync; + +fn checkpoint_key(session_id: &str, checkpoint_id: &str) -> (String, String) { + (session_id.to_string(), checkpoint_id.to_string()) +} + +fn require_checkpoint_key(checkpoint: &Checkpoint) -> Result<(String, String), AdapterError> { + let session_id = checkpoint.session_id.clone().ok_or_else(|| { + std::io::Error::new( + std::io::ErrorKind::InvalidInput, + "Checkpoint session_id is required", + ) + })?; + let checkpoint_id = checkpoint.id.clone().ok_or_else(|| { + std::io::Error::new( + std::io::ErrorKind::InvalidInput, + "Checkpoint id is required", + ) + })?; + Ok(checkpoint_key(&session_id, &checkpoint_id)) +} + +/// Captures emitted turn and session events in memory. +#[derive(Debug, Clone, Default)] +pub struct CollectingEventSink { + turn_events: Arc>>, + session_events: Arc>>, +} + +impl CollectingEventSink { + pub fn new() -> Self { + Self::default() + } + + pub fn turn_events(&self) -> Vec { + self.turn_events + .lock() + .expect("turn events lock poisoned") + .clone() + } + + pub fn session_events(&self) -> Vec { + self.session_events + .lock() + .expect("session events lock poisoned") + .clone() + } +} + +impl EventSink for CollectingEventSink { + fn emit_turn(&self, turn_event: &TurnEvent) -> bool { + self.turn_events + .lock() + .expect("turn events lock poisoned") + .push(turn_event.clone()); + true + } + + fn emit_session(&self, session_event: &SessionEvent) -> bool { + self.session_events + .lock() + .expect("session events lock poisoned") + .push(session_event.clone()); + true + } +} + +/// Appends replayable event journal records as newline-delimited JSON. +#[derive(Debug)] +pub struct JsonlEventJournalWriter { + path: PathBuf, + closed: Mutex, +} + +impl JsonlEventJournalWriter { + pub fn new(path: impl Into) -> Self { + let path = path.into(); + if let Some(parent) = path.parent() { + let _ = create_dir_all(parent); + } + Self { + path, + closed: Mutex::new(false), + } + } + + fn write(&self, record: Value) -> bool { + let closed = self.closed.lock().expect("trace writer lock poisoned"); + if *closed { + return false; + } + Self::append_record(&self.path, record) + } + + fn append_record(path: &PathBuf, record: Value) -> bool { + let mut file = match OpenOptions::new().create(true).append(true).open(path) { + Ok(file) => file, + Err(_) => return false, + }; + writeln!(file, "{}", record).is_ok() + } +} + +impl EventJournalWriter for JsonlEventJournalWriter { + fn append_turn(&self, turn_event: &TurnEvent) -> bool { + self.write(json!({ "kind": "turn", "event": turn_event.to_value(&SaveContext::new()) })) + } + + fn append_session(&self, session_event: &SessionEvent) -> bool { + self.write( + json!({ "kind": "session", "event": session_event.to_value(&SaveContext::new()) }), + ) + } + + fn close(&self, summary: &Option) -> bool { + let mut closed = self.closed.lock().expect("trace writer lock poisoned"); + if *closed { + return false; + } + let wrote_summary = match summary { + Some(summary) => Self::append_record( + &self.path, + json!({ "kind": "summary", "summary": summary.to_value(&SaveContext::new()) }), + ), + None => true, + }; + if !wrote_summary { + return false; + } + *closed = true; + wrote_summary + } +} + +/// Verifies normalized replay journal records. +#[derive(Debug, Clone, Default)] +pub struct ReferenceReplayVerifier; + +impl ReferenceReplayVerifier { + pub fn verify(&self, request: ReplayVerificationRequest) -> ReplayVerificationResult { + let expected = request.expected; + let actual = request.actual; + let max = expected.len().max(actual.len()); + let mut mismatches = Vec::new(); + + for index in 0..max { + let expected_record = expected.get(index).cloned(); + let actual_record = actual.get(index).cloned(); + if comparable_replay_record(expected_record.as_ref()) + != comparable_replay_record(actual_record.as_ref()) + { + let message = if expected_record.is_none() { + "Unexpected extra replay record" + } else if actual_record.is_none() { + "Missing replay record" + } else { + "Replay record mismatch" + }; + mismatches.push(ReplayMismatch { + index: index as i32, + expected: expected_record, + actual: actual_record, + message: message.to_string(), + }); + } + } + + ReplayVerificationResult { + status: if mismatches.is_empty() { + ReplayVerificationStatus::Passed + } else { + ReplayVerificationStatus::Failed + }, + expected_count: expected.len() as i32, + actual_count: actual.len() as i32, + mismatches, + } + } +} + +fn comparable_replay_record(record: Option<&ReplayJournalRecord>) -> Option { + record.map(|record| serde_json::to_string(&record.to_value(&SaveContext::new())).unwrap()) +} + +/// Stores checkpoints in memory by session and checkpoint identifier. +#[derive(Debug, Clone, Default)] +pub struct InMemoryCheckpointStore { + checkpoints: Arc>>, +} + +impl InMemoryCheckpointStore { + pub fn new() -> Self { + Self::default() + } +} + +#[async_trait::async_trait] +impl CheckpointStore for InMemoryCheckpointStore { + async fn save(&self, checkpoint: &Checkpoint) -> Result { + self.checkpoints + .lock() + .expect("checkpoint store lock poisoned") + .insert(require_checkpoint_key(checkpoint)?, checkpoint.clone()); + Ok(checkpoint.clone()) + } + + async fn load( + &self, + session_id: &String, + checkpoint_id: &String, + ) -> Result, AdapterError> { + Ok(self + .checkpoints + .lock() + .expect("checkpoint store lock poisoned") + .get(&checkpoint_key(session_id, checkpoint_id)) + .cloned()) + } + + async fn list_checkpoints(&self, session_id: &String) -> Result, AdapterError> { + let mut checkpoints: Vec = self + .checkpoints + .lock() + .expect("checkpoint store lock poisoned") + .values() + .filter(|checkpoint| checkpoint.session_id.as_deref() == Some(session_id.as_str())) + .cloned() + .collect(); + checkpoints.sort_by(|left, right| left.id.cmp(&right.id)); + Ok(checkpoints) + } +} + +/// Resolves every permission request as approved. +#[derive(Debug, Clone, Default)] +pub struct AllowAllPermissionResolver; + +#[async_trait::async_trait] +impl PermissionResolver for AllowAllPermissionResolver { + async fn request( + &self, + request: &PermissionRequest, + ) -> Result { + Ok(PermissionDecision { + request_id: request.request_id.clone(), + tool_call_id: request.tool_call_id.clone(), + permission: request.permission.clone(), + approved: true, + reason: Some("allow_all".to_string()), + result: Value::Null, + }) + } +} + +/// Resolves every permission request as denied. +#[derive(Debug, Clone, Default)] +pub struct DenyAllPermissionResolver; + +#[async_trait::async_trait] +impl PermissionResolver for DenyAllPermissionResolver { + async fn request( + &self, + request: &PermissionRequest, + ) -> Result { + Ok(PermissionDecision { + request_id: request.request_id.clone(), + tool_call_id: request.tool_call_id.clone(), + permission: request.permission.clone(), + approved: false, + reason: Some("deny_all".to_string()), + result: Value::Null, + }) + } +} + +/// Dispatches host tool requests to registered local functions. +#[derive(Clone, Default)] +pub struct FunctionHostToolExecutor { + handlers: Arc>>, +} + +impl FunctionHostToolExecutor { + pub fn new(handlers: HashMap>) -> Self { + Self { + handlers: Arc::new(handlers), + } + } +} + +#[async_trait::async_trait] +impl HostToolExecutor for FunctionHostToolExecutor { + async fn execute(&self, request: &HostToolRequest) -> Result { + let started = Instant::now(); + let Some(handler) = self.handlers.get(&request.tool_name) else { + return Ok(HostToolResult { + request_id: request.request_id.clone(), + tool_call_id: request.tool_call_id.clone(), + tool_name: request.tool_name.clone(), + success: false, + result: Some( + json!({ "message": format!("No host tool registered for '{}'", request.tool_name) }), + ), + exit_code: None, + duration_ms: Some(started.elapsed().as_secs_f64() * 1000.0), + error_kind: Some("not_found".to_string()), + telemetry: Value::Null, + }); + }; + + let empty_arguments; + let arguments = if request.arguments.is_null() { + empty_arguments = Value::Object(serde_json::Map::new()); + &empty_arguments + } else { + &request.arguments + }; + + match handler(arguments, request) { + Ok(result) => Ok(HostToolResult { + request_id: request.request_id.clone(), + tool_call_id: request.tool_call_id.clone(), + tool_name: request.tool_name.clone(), + success: true, + result: Some(result), + exit_code: None, + duration_ms: Some(started.elapsed().as_secs_f64() * 1000.0), + error_kind: None, + telemetry: Value::Null, + }), + Err(error) => Ok(HostToolResult { + request_id: request.request_id.clone(), + tool_call_id: request.tool_call_id.clone(), + tool_name: request.tool_name.clone(), + success: false, + result: Some(json!({ "message": error.to_string() })), + exit_code: None, + duration_ms: Some(started.elapsed().as_secs_f64() * 1000.0), + error_kind: Some("exception".to_string()), + telemetry: Value::Null, + }), + } + } +} + +type ModelCallback = + dyn Fn(TurnModelRequest) -> Result + Send + Sync; +type Clock = dyn Fn() -> String + Send + Sync; +type IdFactory = dyn Fn(&str) -> String + Send + Sync; + +pub struct ReferenceTurnRunner +where + S: EventSink, + J: EventJournalWriter, + C: CheckpointStore, + P: PermissionResolver, + H: HostToolExecutor, +{ + event_sink: S, + journal: J, + checkpoint_store: C, + permission_resolver: P, + host_tool_executor: H, + invoke_model: Arc, + now: Arc, + next_id: Arc, +} + +impl ReferenceTurnRunner +where + S: EventSink, + J: EventJournalWriter, + C: CheckpointStore, + P: PermissionResolver, + H: HostToolExecutor, +{ + #[allow(clippy::too_many_arguments)] + pub fn new( + event_sink: S, + journal: J, + checkpoint_store: C, + permission_resolver: P, + host_tool_executor: H, + invoke_model: Arc, + now: Arc, + next_id: Arc, + ) -> Self { + Self { + event_sink, + journal, + checkpoint_store, + permission_resolver, + host_tool_executor, + invoke_model, + now, + next_id, + } + } + + pub async fn run(&self, request: RunTurnRequest) -> Result { + let options = request.options.clone().unwrap_or_default(); + let max_iterations = options.max_iterations.unwrap_or(10).max(0) as usize; + let inputs = if request.inputs.is_null() { + json!({}) + } else { + request.inputs.clone() + }; + let mut checkpoints = Vec::new(); + let mut all_tool_results = Vec::new(); + let mut pending_tool_results = Vec::new(); + let mut output = None; + let mut status = RunTurnStatus::Success; + let mut iterations = 0i32; + + self.record_session( + SessionEventType::Session_start, + &request.session_id, + &request.turn_id, + json!({ "sessionId": request.session_id, "schemaVersion": "1" }), + ); + self.record_turn( + TurnEventType::Turn_start, + &request.turn_id, + 0, + json!({ "inputs": inputs, "maxIterations": max_iterations }), + ); + + for iteration in 0..max_iterations { + iterations = iteration as i32 + 1; + self.record_turn( + TurnEventType::Llm_start, + &request.turn_id, + iteration, + json!({ "attempt": 0 }), + ); + let model_response = (self.invoke_model)(TurnModelRequest { + session_id: request.session_id.clone(), + turn_id: request.turn_id.clone(), + iteration: iteration as i32, + inputs: inputs.clone(), + options: Some(options.clone()), + tool_results: pending_tool_results.clone(), + })?; + self.record_turn( + TurnEventType::Llm_complete, + &request.turn_id, + iteration, + json!({}), + ); + let checkpoint = self + .save_checkpoint( + &request.session_id, + &request.turn_id, + iteration, + &model_response, + ) + .await?; + checkpoints.push(checkpoint); + + if model_response.tool_requests.is_empty() { + output = model_response.output; + break; + } + + pending_tool_results = Vec::new(); + for tool_request in model_response.tool_requests { + let tool_result = self + .resolve_and_execute_tool(&request.turn_id, iteration, &tool_request) + .await?; + pending_tool_results.push(tool_result.clone()); + all_tool_results.push(tool_result); + } + + self.record_turn( + TurnEventType::Messages_updated, + &request.turn_id, + iteration, + json!({ + "toolResults": pending_tool_results + .iter() + .map(|result| result.to_value(&SaveContext::new())) + .collect::>() + }), + ); + } + + if output.is_none() && !pending_tool_results.is_empty() { + status = RunTurnStatus::Error; + output = Some(json!({ "message": "Maximum turn iterations reached" })); + self.record_turn( + TurnEventType::Error, + &request.turn_id, + iterations as usize, + json!({ + "errorKind": "max_iterations", + "message": "Maximum turn iterations reached" + }), + ); + } + + self.record_turn( + TurnEventType::Turn_end, + &request.turn_id, + iterations as usize, + json!({ "iterations": iterations, "status": status.as_str(), "response": output }), + ); + self.record_session( + SessionEventType::Session_end, + &request.session_id, + &request.turn_id, + json!({ "sessionId": request.session_id, "status": status.as_str(), "reason": "turn_complete" }), + ); + let summary_status = if status == RunTurnStatus::Success { + SessionSummaryStatus::Success + } else { + SessionSummaryStatus::Error + }; + self.journal.close(&Some(SessionSummary { + session_id: request.session_id.clone(), + status: Some(summary_status), + turns: Some(1), + checkpoints: Some(checkpoints.len() as i32), + ..Default::default() + })); + + Ok(RunTurnResult { + session_id: request.session_id, + turn_id: request.turn_id, + status, + output, + iterations, + tool_results: all_tool_results, + checkpoints, + }) + } + + async fn save_checkpoint( + &self, + session_id: &str, + turn_id: &str, + iteration: usize, + response: &TurnModelResponse, + ) -> Result { + let mut state = json!({ + "iteration": iteration, + "output": response.output, + "toolRequests": response + .tool_requests + .iter() + .map(|request| request.to_value(&SaveContext::new())) + .collect::>() + }); + if let (Some(target), Some(extra)) = + (state.as_object_mut(), response.checkpoint_state.as_object()) + { + for (key, value) in extra { + target.insert(key.clone(), value.clone()); + } + } + let checkpoint = Checkpoint { + id: Some(format!("{turn_id}-checkpoint-{iteration}")), + session_id: Some(session_id.to_string()), + turn_id: Some(turn_id.to_string()), + checkpoint_number: Some(iteration as i32 + 1), + title: format!("Turn {turn_id} iteration {iteration}"), + state, + created_at: Some((self.now)()), + ..Default::default() + }; + let saved = self.checkpoint_store.save(&checkpoint).await?; + self.record_session( + SessionEventType::Checkpoint_created, + session_id, + turn_id, + json!({ + "checkpointId": saved.id, + "checkpointNumber": saved.checkpoint_number + }), + ); + Ok(saved) + } + + async fn resolve_and_execute_tool( + &self, + turn_id: &str, + iteration: usize, + tool_request: &HostToolRequest, + ) -> Result { + let permission = PermissionRequest { + request_id: Some( + tool_request + .request_id + .as_ref() + .map(|request_id| format!("{request_id}-permission")) + .unwrap_or_else(|| (self.next_id)("permission")), + ), + tool_call_id: tool_request.tool_call_id.clone(), + permission: "tool.execute".to_string(), + target: Some(tool_request.tool_name.clone()), + details: tool_request.to_value(&SaveContext::new()), + ..Default::default() + }; + self.record_turn( + TurnEventType::Permission_requested, + turn_id, + iteration, + permission.to_value(&SaveContext::new()), + ); + let decision = self.permission_resolver.request(&permission).await?; + self.record_turn( + TurnEventType::Permission_completed, + turn_id, + iteration, + decision.to_value(&SaveContext::new()), + ); + + if !decision.approved { + return Ok(HostToolResult { + request_id: tool_request.request_id.clone(), + tool_call_id: tool_request.tool_call_id.clone(), + tool_name: tool_request.tool_name.clone(), + success: false, + result: Some( + json!({ "message": decision.reason.unwrap_or_else(|| "Permission denied".to_string()) }), + ), + error_kind: Some("permission_denied".to_string()), + ..Default::default() + }); + } + + self.record_turn( + TurnEventType::Tool_execution_start, + turn_id, + iteration, + tool_request.to_value(&SaveContext::new()), + ); + let result = self.host_tool_executor.execute(tool_request).await?; + self.record_turn( + TurnEventType::Tool_execution_complete, + turn_id, + iteration, + result.to_value(&SaveContext::new()), + ); + self.record_turn( + TurnEventType::Tool_result, + turn_id, + iteration, + result.to_value(&SaveContext::new()), + ); + Ok(result) + } + + fn record_turn(&self, r#type: TurnEventType, turn_id: &str, iteration: usize, payload: Value) { + let event = TurnEvent { + id: (self.next_id)("turn-event"), + r#type, + timestamp: (self.now)(), + turn_id: Some(turn_id.to_string()), + iteration: Some(iteration as i32), + payload, + ..Default::default() + }; + self.event_sink.emit_turn(&event); + self.journal.append_turn(&event); + } + + fn record_session( + &self, + r#type: SessionEventType, + session_id: &str, + turn_id: &str, + payload: Value, + ) { + let event = SessionEvent { + id: (self.next_id)("session-event"), + r#type, + timestamp: (self.now)(), + session_id: Some(session_id.to_string()), + turn_id: Some(turn_id.to_string()), + payload, + ..Default::default() + }; + self.event_sink.emit_session(&event); + self.journal.append_session(&event); + } +} diff --git a/runtime/rust/prompty/src/lib.rs b/runtime/rust/prompty/src/lib.rs index bafb6711..f73bfafe 100644 --- a/runtime/rust/prompty/src/lib.rs +++ b/runtime/rust/prompty/src/lib.rs @@ -38,9 +38,11 @@ pub mod connections; pub mod context; pub mod guardrails; +pub mod harness; pub mod interfaces; pub mod loader; pub mod model; +pub use model::pipeline::{RunTurnRequest, RunTurnResult, TurnModelRequest, TurnModelResponse}; mod model_ext; pub mod parsers; pub mod pipeline; @@ -62,6 +64,11 @@ pub use guardrails::{ GuardrailError, GuardrailPhase, GuardrailResult, Guardrails, InputGuardrail, OutputGuardrail, ToolGuardrail, }; +pub use harness::{ + AllowAllPermissionResolver, CollectingEventSink, DenyAllPermissionResolver, + FunctionHostToolExecutor, InMemoryCheckpointStore, JsonlEventJournalWriter, + ReferenceReplayVerifier, ReferenceTurnRunner, +}; pub use interfaces::{ExecuteError, Executor, InvokerError, Parser, Processor, Renderer}; pub use loader::{ LoadError, LoadOptions, load, load_async, load_async_with_options, load_from_string, diff --git a/runtime/rust/prompty/src/model/agent/guardrail_result.rs b/runtime/rust/prompty/src/model/agent/guardrail_result.rs index 881dfcff..08ab63cd 100644 --- a/runtime/rust/prompty/src/model/agent/guardrail_result.rs +++ b/runtime/rust/prompty/src/model/agent/guardrail_result.rs @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. #![allow( unused_imports, diff --git a/runtime/rust/prompty/src/model/agent/mod.rs b/runtime/rust/prompty/src/model/agent/mod.rs index fd2959ea..c8214401 100644 --- a/runtime/rust/prompty/src/model/agent/mod.rs +++ b/runtime/rust/prompty/src/model/agent/mod.rs @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. #![allow( unused_imports, diff --git a/runtime/rust/prompty/src/model/agent/prompty.rs b/runtime/rust/prompty/src/model/agent/prompty.rs index e1bdff83..4dbca6d1 100644 --- a/runtime/rust/prompty/src/model/agent/prompty.rs +++ b/runtime/rust/prompty/src/model/agent/prompty.rs @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. #![allow( unused_imports, @@ -18,7 +19,7 @@ use super::super::template::template::Template; use super::super::tools::tool::Tool; -/// A Prompty is a markdown file format for LLM prompts. The frontmatter defines structured metadata including model configuration, input/output schemas, tools, and template settings. The markdown body becomes the instructions. This is the single root type for the Prompty schema — there is no abstract base class or kind discriminator. A .prompty file always produces a Prompty instance. +/// A Prompty is a markdown file format for LLM prompts. The frontmatter defines structured metadata including model configuration, input/output schemas, tools, and template settings. The markdown body becomes the instructions. This is the single root type for the Prompty schema — there is no abstract base class or kind discriminator. A .prompty file always produces a Prompty instance. Runtime loaders may resolve frontmatter references such as `${env:VAR}` and `${file:relative/path}`. File references must be treated as a host-controlled capability: by default they are scoped to the containing .prompty file's directory tree after canonicalization, and any additional allowed roots must be supplied by the host application's load options rather than frontmatter. #[derive(Debug, Clone, Default)] pub struct Prompty { /// Human-readable name of the prompt diff --git a/runtime/rust/prompty/src/model/connection/connection.rs b/runtime/rust/prompty/src/model/connection/connection.rs index b6aaed31..e01781a9 100644 --- a/runtime/rust/prompty/src/model/connection/connection.rs +++ b/runtime/rust/prompty/src/model/connection/connection.rs @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. #![allow( unused_imports, diff --git a/runtime/rust/prompty/src/model/connection/mod.rs b/runtime/rust/prompty/src/model/connection/mod.rs index e340a4a6..1d114672 100644 --- a/runtime/rust/prompty/src/model/connection/mod.rs +++ b/runtime/rust/prompty/src/model/connection/mod.rs @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. #![allow( unused_imports, diff --git a/runtime/rust/prompty/src/model/context.rs b/runtime/rust/prompty/src/model/context.rs index 43af8e61..cacfe41f 100644 --- a/runtime/rust/prompty/src/model/context.rs +++ b/runtime/rust/prompty/src/model/context.rs @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. // Prompty Context #![allow( diff --git a/runtime/rust/prompty/src/model/conversation/content_part.rs b/runtime/rust/prompty/src/model/conversation/content_part.rs index 732c3176..010a56fb 100644 --- a/runtime/rust/prompty/src/model/conversation/content_part.rs +++ b/runtime/rust/prompty/src/model/conversation/content_part.rs @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. #![allow( unused_imports, diff --git a/runtime/rust/prompty/src/model/conversation/message.rs b/runtime/rust/prompty/src/model/conversation/message.rs index a41f7c94..1b285aed 100644 --- a/runtime/rust/prompty/src/model/conversation/message.rs +++ b/runtime/rust/prompty/src/model/conversation/message.rs @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. #![allow( unused_imports, diff --git a/runtime/rust/prompty/src/model/conversation/mod.rs b/runtime/rust/prompty/src/model/conversation/mod.rs index 62881c2c..649d0a30 100644 --- a/runtime/rust/prompty/src/model/conversation/mod.rs +++ b/runtime/rust/prompty/src/model/conversation/mod.rs @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. #![allow( unused_imports, diff --git a/runtime/rust/prompty/src/model/conversation/thread_marker.rs b/runtime/rust/prompty/src/model/conversation/thread_marker.rs index 0cfc6c8a..da1b4cbe 100644 --- a/runtime/rust/prompty/src/model/conversation/thread_marker.rs +++ b/runtime/rust/prompty/src/model/conversation/thread_marker.rs @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. #![allow( unused_imports, diff --git a/runtime/rust/prompty/src/model/conversation/tool_call.rs b/runtime/rust/prompty/src/model/conversation/tool_call.rs index aadecaee..9c14a366 100644 --- a/runtime/rust/prompty/src/model/conversation/tool_call.rs +++ b/runtime/rust/prompty/src/model/conversation/tool_call.rs @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. #![allow( unused_imports, diff --git a/runtime/rust/prompty/src/model/conversation/tool_result.rs b/runtime/rust/prompty/src/model/conversation/tool_result.rs index a4f511ed..5fbee4ce 100644 --- a/runtime/rust/prompty/src/model/conversation/tool_result.rs +++ b/runtime/rust/prompty/src/model/conversation/tool_result.rs @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. #![allow( unused_imports, @@ -12,11 +13,65 @@ use super::super::context::{LoadContext, SaveContext}; use super::content_part::{ContentPart, ContentPartKind}; +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum ToolResultStatus { + Success, + Error, + Cancelled, + Timeout, +} + +impl Default for ToolResultStatus { + fn default() -> Self { + Self::Success + } +} + +impl std::fmt::Display for ToolResultStatus { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Success => write!(f, "success"), + Self::Error => write!(f, "error"), + Self::Cancelled => write!(f, "cancelled"), + Self::Timeout => write!(f, "timeout"), + } + } +} + +impl ToolResultStatus { + pub fn from_str_opt(s: &str) -> Option { + match s { + "success" => Some(Self::Success), + "error" => Some(Self::Error), + "cancelled" => Some(Self::Cancelled), + "timeout" => Some(Self::Timeout), + _ => None, + } + } + + pub fn as_str(&self) -> &str { + match self { + Self::Success => "success", + Self::Error => "error", + Self::Cancelled => "cancelled", + Self::Timeout => "timeout", + } + } +} + /// The result of a tool execution. Contains a list of content parts, enabling rich tool results (text, images, files, audio) rather than just strings. Implementations MUST support conversion from a plain string to a ToolResult containing a single TextPart for backward compatibility. #[derive(Debug, Clone, Default)] pub struct ToolResult { /// The content parts of the tool result pub parts: Vec, + /// Semantic execution status for the tool result + pub status: Option, + /// Stable machine-readable error category when status is not success + pub error_kind: Option, + /// Human-readable error message when status is not success + pub error_message: Option, + /// Tool execution duration in milliseconds + pub duration_ms: Option, } impl ToolResult { @@ -47,6 +102,19 @@ impl ToolResult { .get("parts") .map(|v| Self::load_parts(v, ctx)) .unwrap_or_default(), + status: value + .get("status") + .and_then(|v| v.as_str()) + .and_then(|s| ToolResultStatus::from_str_opt(s)), + error_kind: value + .get("errorKind") + .and_then(|v| v.as_str()) + .map(|s| s.to_string()), + error_message: value + .get("errorMessage") + .and_then(|v| v.as_str()) + .map(|s| s.to_string()), + duration_ms: value.get("durationMs").and_then(|v| v.as_f64()), } } @@ -59,6 +127,32 @@ impl ToolResult { if !self.parts.is_empty() { result.insert("parts".to_string(), Self::save_parts(&self.parts, ctx)); } + if let Some(ref val) = self.status { + result.insert( + "status".to_string(), + serde_json::Value::String(val.to_string()), + ); + } + if let Some(ref val) = self.error_kind { + result.insert( + "errorKind".to_string(), + serde_json::Value::String(val.clone()), + ); + } + if let Some(ref val) = self.error_message { + result.insert( + "errorMessage".to_string(), + serde_json::Value::String(val.clone()), + ); + } + if let Some(val) = self.duration_ms { + result.insert( + "durationMs".to_string(), + serde_json::Number::from_f64(val as f64) + .map(serde_json::Value::Number) + .unwrap_or(serde_json::Value::Null), + ); + } ctx.process_dict(serde_json::Value::Object(result)) } diff --git a/runtime/rust/prompty/src/model/core/file_not_found_error.rs b/runtime/rust/prompty/src/model/core/file_not_found_error.rs index 7be33010..369ce03e 100644 --- a/runtime/rust/prompty/src/model/core/file_not_found_error.rs +++ b/runtime/rust/prompty/src/model/core/file_not_found_error.rs @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. #![allow( unused_imports, diff --git a/runtime/rust/prompty/src/model/core/invoker_error.rs b/runtime/rust/prompty/src/model/core/invoker_error.rs index abeee49f..36cdefce 100644 --- a/runtime/rust/prompty/src/model/core/invoker_error.rs +++ b/runtime/rust/prompty/src/model/core/invoker_error.rs @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. #![allow( unused_imports, diff --git a/runtime/rust/prompty/src/model/core/mod.rs b/runtime/rust/prompty/src/model/core/mod.rs index b4509728..8213f1b5 100644 --- a/runtime/rust/prompty/src/model/core/mod.rs +++ b/runtime/rust/prompty/src/model/core/mod.rs @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. #![allow( unused_imports, diff --git a/runtime/rust/prompty/src/model/core/property.rs b/runtime/rust/prompty/src/model/core/property.rs index 16149f5b..9e68ac62 100644 --- a/runtime/rust/prompty/src/model/core/property.rs +++ b/runtime/rust/prompty/src/model/core/property.rs @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. #![allow( unused_imports, diff --git a/runtime/rust/prompty/src/model/core/validation_error.rs b/runtime/rust/prompty/src/model/core/validation_error.rs index f56c9115..25b7d06e 100644 --- a/runtime/rust/prompty/src/model/core/validation_error.rs +++ b/runtime/rust/prompty/src/model/core/validation_error.rs @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. #![allow( unused_imports, diff --git a/runtime/rust/prompty/src/model/core/validation_result.rs b/runtime/rust/prompty/src/model/core/validation_result.rs index 823d12c4..3603317b 100644 --- a/runtime/rust/prompty/src/model/core/validation_result.rs +++ b/runtime/rust/prompty/src/model/core/validation_result.rs @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. #![allow( unused_imports, diff --git a/runtime/rust/prompty/src/model/events/checkpoint.rs b/runtime/rust/prompty/src/model/events/checkpoint.rs new file mode 100644 index 00000000..bc3bb170 --- /dev/null +++ b/runtime/rust/prompty/src/model/events/checkpoint.rs @@ -0,0 +1,198 @@ +// +// Code generated by Typra emitter; DO NOT EDIT. + +#![allow( + unused_imports, + dead_code, + non_camel_case_types, + unused_variables, + clippy::all +)] + +use super::super::context::{LoadContext, SaveContext}; + +use super::redaction_metadata::RedactionMetadata; + +/// A persisted handoff point for a harness session. +#[derive(Debug, Clone, Default)] +pub struct Checkpoint { + /// Stable checkpoint identifier + pub id: Option, + /// Stable session identifier + pub session_id: Option, + /// Associated turn identifier, when the checkpoint was created inside a turn + pub turn_id: Option, + /// Monotonic checkpoint number within the session + pub checkpoint_number: Option, + /// Short checkpoint title + pub title: String, + /// Short human-readable overview + pub overview: Option, + /// Portable checkpoint state needed to resume or hand off the session + pub state: serde_json::Value, + /// Optional host-authored summary or handoff note + pub summary: Option, + /// Host-defined checkpoint metadata + pub metadata: serde_json::Value, + /// ISO 8601 UTC timestamp when the checkpoint was created + pub created_at: Option, + /// Redaction state for sensitive checkpoint fields + pub redaction: Option, +} + +impl Checkpoint { + /// Create a new Checkpoint with default values. + pub fn new() -> Self { + Self::default() + } + + /// Load Checkpoint from a JSON string. + pub fn from_json(json: &str, ctx: &LoadContext) -> Result { + let value: serde_json::Value = serde_json::from_str(json)?; + Ok(Self::load_from_value(&value, ctx)) + } + + /// Load Checkpoint from a YAML string. + pub fn from_yaml(yaml: &str, ctx: &LoadContext) -> Result { + let value: serde_json::Value = serde_yaml::from_str(yaml)?; + Ok(Self::load_from_value(&value, ctx)) + } + + /// Load Checkpoint from a `serde_json::Value`. + /// + /// Calls `ctx.process_input` before field extraction. + pub fn load_from_value(value: &serde_json::Value, ctx: &LoadContext) -> Self { + let value = ctx.process_input(value.clone()); + Self { + id: value + .get("id") + .and_then(|v| v.as_str()) + .map(|s| s.to_string()), + session_id: value + .get("sessionId") + .and_then(|v| v.as_str()) + .map(|s| s.to_string()), + turn_id: value + .get("turnId") + .and_then(|v| v.as_str()) + .map(|s| s.to_string()), + checkpoint_number: value + .get("checkpointNumber") + .and_then(|v| v.as_i64()) + .map(|v| v as i32), + title: value + .get("title") + .and_then(|v| v.as_str()) + .unwrap_or_default() + .to_string(), + overview: value + .get("overview") + .and_then(|v| v.as_str()) + .map(|s| s.to_string()), + state: value + .get("state") + .cloned() + .unwrap_or(serde_json::Value::Null), + summary: value + .get("summary") + .and_then(|v| v.as_str()) + .map(|s| s.to_string()), + metadata: value + .get("metadata") + .cloned() + .unwrap_or(serde_json::Value::Null), + created_at: value + .get("createdAt") + .and_then(|v| v.as_str()) + .map(|s| s.to_string()), + redaction: value + .get("redaction") + .filter(|v| v.is_object() || v.is_array() || v.is_string()) + .map(|v| RedactionMetadata::load_from_value(v, ctx)), + } + } + + /// Serialize Checkpoint to a `serde_json::Value`. + /// + /// Calls `ctx.process_dict` after serialization. + pub fn to_value(&self, ctx: &SaveContext) -> serde_json::Value { + let mut result = serde_json::Map::new(); + // Write base fields + if let Some(ref val) = self.id { + result.insert("id".to_string(), serde_json::Value::String(val.clone())); + } + if let Some(ref val) = self.session_id { + result.insert( + "sessionId".to_string(), + serde_json::Value::String(val.clone()), + ); + } + if let Some(ref val) = self.turn_id { + result.insert("turnId".to_string(), serde_json::Value::String(val.clone())); + } + if let Some(val) = self.checkpoint_number { + result.insert( + "checkpointNumber".to_string(), + serde_json::Value::Number(serde_json::Number::from(val)), + ); + } + if !self.title.is_empty() { + result.insert( + "title".to_string(), + serde_json::Value::String(self.title.clone()), + ); + } + if let Some(ref val) = self.overview { + result.insert( + "overview".to_string(), + serde_json::Value::String(val.clone()), + ); + } + if !self.state.is_null() { + result.insert("state".to_string(), self.state.clone()); + } + if let Some(ref val) = self.summary { + result.insert( + "summary".to_string(), + serde_json::Value::String(val.clone()), + ); + } + if !self.metadata.is_null() { + result.insert("metadata".to_string(), self.metadata.clone()); + } + if let Some(ref val) = self.created_at { + result.insert( + "createdAt".to_string(), + serde_json::Value::String(val.clone()), + ); + } + if let Some(ref val) = self.redaction { + let nested = val.to_value(ctx); + if !nested.is_null() { + result.insert("redaction".to_string(), nested); + } + } + ctx.process_dict(serde_json::Value::Object(result)) + } + + /// Serialize Checkpoint to a JSON string. + pub fn to_json(&self, ctx: &SaveContext) -> Result { + serde_json::to_string_pretty(&self.to_value(ctx)) + } + + /// Serialize Checkpoint to a YAML string. + pub fn to_yaml(&self, ctx: &SaveContext) -> Result { + serde_yaml::to_string(&self.to_value(ctx)) + } + /// Returns typed reference to the map if the field is an object. + /// Returns `None` if the field is null or not an object. + pub fn as_state_dict(&self) -> Option<&serde_json::Map> { + self.state.as_object() + } + + /// Returns typed reference to the map if the field is an object. + /// Returns `None` if the field is null or not an object. + pub fn as_metadata_dict(&self) -> Option<&serde_json::Map> { + self.metadata.as_object() + } +} diff --git a/runtime/rust/prompty/src/model/events/compaction_complete_payload.rs b/runtime/rust/prompty/src/model/events/compaction_complete_payload.rs index 4c710e86..54e72fa4 100644 --- a/runtime/rust/prompty/src/model/events/compaction_complete_payload.rs +++ b/runtime/rust/prompty/src/model/events/compaction_complete_payload.rs @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. #![allow( unused_imports, @@ -17,6 +18,8 @@ pub struct CompactionCompletePayload { pub removed: i32, /// Number of messages remaining after compaction pub remaining: i32, + /// Length of the generated summary, when a summarization strategy is used + pub summary_length: Option, } impl CompactionCompletePayload { @@ -45,6 +48,10 @@ impl CompactionCompletePayload { Self { removed: value.get("removed").and_then(|v| v.as_i64()).unwrap_or(0) as i32, remaining: value.get("remaining").and_then(|v| v.as_i64()).unwrap_or(0) as i32, + summary_length: value + .get("summaryLength") + .and_then(|v| v.as_i64()) + .map(|v| v as i32), } } @@ -66,6 +73,12 @@ impl CompactionCompletePayload { serde_json::Value::Number(serde_json::Number::from(self.remaining)), ); } + if let Some(val) = self.summary_length { + result.insert( + "summaryLength".to_string(), + serde_json::Value::Number(serde_json::Number::from(val)), + ); + } ctx.process_dict(serde_json::Value::Object(result)) } diff --git a/runtime/rust/prompty/src/model/events/compaction_failed_payload.rs b/runtime/rust/prompty/src/model/events/compaction_failed_payload.rs index d8c2ccc1..73a4e147 100644 --- a/runtime/rust/prompty/src/model/events/compaction_failed_payload.rs +++ b/runtime/rust/prompty/src/model/events/compaction_failed_payload.rs @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. #![allow( unused_imports, diff --git a/runtime/rust/prompty/src/model/events/compaction_start_payload.rs b/runtime/rust/prompty/src/model/events/compaction_start_payload.rs new file mode 100644 index 00000000..18ee7137 --- /dev/null +++ b/runtime/rust/prompty/src/model/events/compaction_start_payload.rs @@ -0,0 +1,76 @@ +// +// Code generated by Typra emitter; DO NOT EDIT. + +#![allow( + unused_imports, + dead_code, + non_camel_case_types, + unused_variables, + clippy::all +)] + +use super::super::context::{LoadContext, SaveContext}; + +/// Payload for "compaction_start" events — context compaction is beginning. +#[derive(Debug, Clone, Default)] +pub struct CompactionStartPayload { + /// Number of messages selected for compaction + pub dropped_count: i32, +} + +impl CompactionStartPayload { + /// Create a new CompactionStartPayload with default values. + pub fn new() -> Self { + Self::default() + } + + /// Load CompactionStartPayload from a JSON string. + pub fn from_json(json: &str, ctx: &LoadContext) -> Result { + let value: serde_json::Value = serde_json::from_str(json)?; + Ok(Self::load_from_value(&value, ctx)) + } + + /// Load CompactionStartPayload from a YAML string. + pub fn from_yaml(yaml: &str, ctx: &LoadContext) -> Result { + let value: serde_json::Value = serde_yaml::from_str(yaml)?; + Ok(Self::load_from_value(&value, ctx)) + } + + /// Load CompactionStartPayload from a `serde_json::Value`. + /// + /// Calls `ctx.process_input` before field extraction. + pub fn load_from_value(value: &serde_json::Value, ctx: &LoadContext) -> Self { + let value = ctx.process_input(value.clone()); + Self { + dropped_count: value + .get("droppedCount") + .and_then(|v| v.as_i64()) + .unwrap_or(0) as i32, + } + } + + /// Serialize CompactionStartPayload to a `serde_json::Value`. + /// + /// Calls `ctx.process_dict` after serialization. + pub fn to_value(&self, ctx: &SaveContext) -> serde_json::Value { + let mut result = serde_json::Map::new(); + // Write base fields + if self.dropped_count != 0 { + result.insert( + "droppedCount".to_string(), + serde_json::Value::Number(serde_json::Number::from(self.dropped_count)), + ); + } + ctx.process_dict(serde_json::Value::Object(result)) + } + + /// Serialize CompactionStartPayload to a JSON string. + pub fn to_json(&self, ctx: &SaveContext) -> Result { + serde_json::to_string_pretty(&self.to_value(ctx)) + } + + /// Serialize CompactionStartPayload to a YAML string. + pub fn to_yaml(&self, ctx: &SaveContext) -> Result { + serde_yaml::to_string(&self.to_value(ctx)) + } +} diff --git a/runtime/rust/prompty/src/model/events/done_event_payload.rs b/runtime/rust/prompty/src/model/events/done_event_payload.rs index c3ad2cfe..566510dd 100644 --- a/runtime/rust/prompty/src/model/events/done_event_payload.rs +++ b/runtime/rust/prompty/src/model/events/done_event_payload.rs @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. #![allow( unused_imports, @@ -15,8 +16,8 @@ use super::super::conversation::message::Message; /// Payload for "done" events — the agent loop completed successfully. #[derive(Debug, Clone, Default)] pub struct DoneEventPayload { - /// The final text response from the LLM - pub response: String, + /// The final response from the LLM after processing + pub response: serde_json::Value, /// The final conversation state including all messages pub messages: Vec, } @@ -47,9 +48,8 @@ impl DoneEventPayload { Self { response: value .get("response") - .and_then(|v| v.as_str()) - .unwrap_or_default() - .to_string(), + .cloned() + .unwrap_or(serde_json::Value::Null), messages: value .get("messages") .map(|v| Self::load_messages(v, ctx)) @@ -63,11 +63,8 @@ impl DoneEventPayload { pub fn to_value(&self, ctx: &SaveContext) -> serde_json::Value { let mut result = serde_json::Map::new(); // Write base fields - if !self.response.is_empty() { - result.insert( - "response".to_string(), - serde_json::Value::String(self.response.clone()), - ); + if !self.response.is_null() { + result.insert("response".to_string(), self.response.clone()); } if !self.messages.is_empty() { result.insert( diff --git a/runtime/rust/prompty/src/model/events/error_event_payload.rs b/runtime/rust/prompty/src/model/events/error_event_payload.rs index d3f1a9e2..2cc751d9 100644 --- a/runtime/rust/prompty/src/model/events/error_event_payload.rs +++ b/runtime/rust/prompty/src/model/events/error_event_payload.rs @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. #![allow( unused_imports, @@ -15,6 +16,10 @@ use super::super::context::{LoadContext, SaveContext}; pub struct ErrorEventPayload { /// Human-readable error description pub message: String, + /// Stable machine-readable error category + pub error_kind: Option, + /// Operation or phase where the error occurred + pub phase: Option, } impl ErrorEventPayload { @@ -46,6 +51,14 @@ impl ErrorEventPayload { .and_then(|v| v.as_str()) .unwrap_or_default() .to_string(), + error_kind: value + .get("errorKind") + .and_then(|v| v.as_str()) + .map(|s| s.to_string()), + phase: value + .get("phase") + .and_then(|v| v.as_str()) + .map(|s| s.to_string()), } } @@ -61,6 +74,15 @@ impl ErrorEventPayload { serde_json::Value::String(self.message.clone()), ); } + if let Some(ref val) = self.error_kind { + result.insert( + "errorKind".to_string(), + serde_json::Value::String(val.clone()), + ); + } + if let Some(ref val) = self.phase { + result.insert("phase".to_string(), serde_json::Value::String(val.clone())); + } ctx.process_dict(serde_json::Value::Object(result)) } diff --git a/runtime/rust/prompty/src/model/events/harness_context.rs b/runtime/rust/prompty/src/model/events/harness_context.rs new file mode 100644 index 00000000..603fb466 --- /dev/null +++ b/runtime/rust/prompty/src/model/events/harness_context.rs @@ -0,0 +1,99 @@ +// +// Code generated by Typra emitter; DO NOT EDIT. + +#![allow( + unused_imports, + dead_code, + non_camel_case_types, + unused_variables, + clippy::all +)] + +use super::super::context::{LoadContext, SaveContext}; + +/// Execution context associated with a harness session. Host-specific environments can store detailed profiles in metadata without making the core contract depend on one source-control provider or workspace shape. +#[derive(Debug, Clone, Default)] +pub struct HarnessContext { + /// Current working directory for the harness + pub cwd: Option, + /// Git repository root, when known + pub git_root: Option, + /// Host-defined context metadata, such as source-control or sandbox details + pub metadata: serde_json::Value, +} + +impl HarnessContext { + /// Create a new HarnessContext with default values. + pub fn new() -> Self { + Self::default() + } + + /// Load HarnessContext from a JSON string. + pub fn from_json(json: &str, ctx: &LoadContext) -> Result { + let value: serde_json::Value = serde_json::from_str(json)?; + Ok(Self::load_from_value(&value, ctx)) + } + + /// Load HarnessContext from a YAML string. + pub fn from_yaml(yaml: &str, ctx: &LoadContext) -> Result { + let value: serde_json::Value = serde_yaml::from_str(yaml)?; + Ok(Self::load_from_value(&value, ctx)) + } + + /// Load HarnessContext from a `serde_json::Value`. + /// + /// Calls `ctx.process_input` before field extraction. + pub fn load_from_value(value: &serde_json::Value, ctx: &LoadContext) -> Self { + let value = ctx.process_input(value.clone()); + Self { + cwd: value + .get("cwd") + .and_then(|v| v.as_str()) + .map(|s| s.to_string()), + git_root: value + .get("gitRoot") + .and_then(|v| v.as_str()) + .map(|s| s.to_string()), + metadata: value + .get("metadata") + .cloned() + .unwrap_or(serde_json::Value::Null), + } + } + + /// Serialize HarnessContext to a `serde_json::Value`. + /// + /// Calls `ctx.process_dict` after serialization. + pub fn to_value(&self, ctx: &SaveContext) -> serde_json::Value { + let mut result = serde_json::Map::new(); + // Write base fields + if let Some(ref val) = self.cwd { + result.insert("cwd".to_string(), serde_json::Value::String(val.clone())); + } + if let Some(ref val) = self.git_root { + result.insert( + "gitRoot".to_string(), + serde_json::Value::String(val.clone()), + ); + } + if !self.metadata.is_null() { + result.insert("metadata".to_string(), self.metadata.clone()); + } + ctx.process_dict(serde_json::Value::Object(result)) + } + + /// Serialize HarnessContext to a JSON string. + pub fn to_json(&self, ctx: &SaveContext) -> Result { + serde_json::to_string_pretty(&self.to_value(ctx)) + } + + /// Serialize HarnessContext to a YAML string. + pub fn to_yaml(&self, ctx: &SaveContext) -> Result { + serde_yaml::to_string(&self.to_value(ctx)) + } + /// Returns typed reference to the map if the field is an object. + /// Returns `None` if the field is null or not an object. + pub fn as_metadata_dict(&self) -> Option<&serde_json::Map> { + self.metadata.as_object() + } +} diff --git a/runtime/rust/prompty/src/model/events/hook_end_payload.rs b/runtime/rust/prompty/src/model/events/hook_end_payload.rs new file mode 100644 index 00000000..ecf0e651 --- /dev/null +++ b/runtime/rust/prompty/src/model/events/hook_end_payload.rs @@ -0,0 +1,195 @@ +// +// Code generated by Typra emitter; DO NOT EDIT. + +#![allow( + unused_imports, + dead_code, + non_camel_case_types, + unused_variables, + clippy::all +)] + +use super::super::context::{LoadContext, SaveContext}; + +use super::redaction_metadata::RedactionMetadata; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum HookEndScope { + Turn, + Session, +} + +impl Default for HookEndScope { + fn default() -> Self { + Self::Turn + } +} + +impl std::fmt::Display for HookEndScope { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Turn => write!(f, "turn"), + Self::Session => write!(f, "session"), + } + } +} + +impl HookEndScope { + pub fn from_str_opt(s: &str) -> Option { + match s { + "turn" => Some(Self::Turn), + "session" => Some(Self::Session), + _ => None, + } + } + + pub fn as_str(&self) -> &str { + match self { + Self::Turn => "turn", + Self::Session => "session", + } + } +} + +/// Payload for "hook_end" events — a host lifecycle hook finished. +#[derive(Debug, Clone, Default)] +pub struct HookEndPayload { + /// Stable hook invocation identifier + pub hook_invocation_id: String, + /// Host-defined hook type + pub hook_type: String, + /// Whether the hook is scoped to a turn or the outer session + pub scope: Option, + /// Whether the hook completed successfully + pub success: bool, + /// Hook output after host-side sanitization + pub output: serde_json::Value, + /// Hook execution duration in milliseconds + pub duration_ms: Option, + /// Human-readable error when success is false + pub error: Option, + /// Redaction state for sensitive hook output fields + pub redaction: Option, +} + +impl HookEndPayload { + /// Create a new HookEndPayload with default values. + pub fn new() -> Self { + Self::default() + } + + /// Load HookEndPayload from a JSON string. + pub fn from_json(json: &str, ctx: &LoadContext) -> Result { + let value: serde_json::Value = serde_json::from_str(json)?; + Ok(Self::load_from_value(&value, ctx)) + } + + /// Load HookEndPayload from a YAML string. + pub fn from_yaml(yaml: &str, ctx: &LoadContext) -> Result { + let value: serde_json::Value = serde_yaml::from_str(yaml)?; + Ok(Self::load_from_value(&value, ctx)) + } + + /// Load HookEndPayload from a `serde_json::Value`. + /// + /// Calls `ctx.process_input` before field extraction. + pub fn load_from_value(value: &serde_json::Value, ctx: &LoadContext) -> Self { + let value = ctx.process_input(value.clone()); + Self { + hook_invocation_id: value + .get("hookInvocationId") + .and_then(|v| v.as_str()) + .unwrap_or_default() + .to_string(), + hook_type: value + .get("hookType") + .and_then(|v| v.as_str()) + .unwrap_or_default() + .to_string(), + scope: value + .get("scope") + .and_then(|v| v.as_str()) + .and_then(|s| HookEndScope::from_str_opt(s)), + success: value + .get("success") + .and_then(|v| v.as_bool()) + .unwrap_or(false), + output: value + .get("output") + .cloned() + .unwrap_or(serde_json::Value::Null), + duration_ms: value.get("durationMs").and_then(|v| v.as_f64()), + error: value + .get("error") + .and_then(|v| v.as_str()) + .map(|s| s.to_string()), + redaction: value + .get("redaction") + .filter(|v| v.is_object() || v.is_array() || v.is_string()) + .map(|v| RedactionMetadata::load_from_value(v, ctx)), + } + } + + /// Serialize HookEndPayload to a `serde_json::Value`. + /// + /// Calls `ctx.process_dict` after serialization. + pub fn to_value(&self, ctx: &SaveContext) -> serde_json::Value { + let mut result = serde_json::Map::new(); + // Write base fields + if !self.hook_invocation_id.is_empty() { + result.insert( + "hookInvocationId".to_string(), + serde_json::Value::String(self.hook_invocation_id.clone()), + ); + } + if !self.hook_type.is_empty() { + result.insert( + "hookType".to_string(), + serde_json::Value::String(self.hook_type.clone()), + ); + } + if let Some(ref val) = self.scope { + result.insert( + "scope".to_string(), + serde_json::Value::String(val.to_string()), + ); + } + result.insert("success".to_string(), serde_json::Value::Bool(self.success)); + if !self.output.is_null() { + result.insert("output".to_string(), self.output.clone()); + } + if let Some(val) = self.duration_ms { + result.insert( + "durationMs".to_string(), + serde_json::Number::from_f64(val as f64) + .map(serde_json::Value::Number) + .unwrap_or(serde_json::Value::Null), + ); + } + if let Some(ref val) = self.error { + result.insert("error".to_string(), serde_json::Value::String(val.clone())); + } + if let Some(ref val) = self.redaction { + let nested = val.to_value(ctx); + if !nested.is_null() { + result.insert("redaction".to_string(), nested); + } + } + ctx.process_dict(serde_json::Value::Object(result)) + } + + /// Serialize HookEndPayload to a JSON string. + pub fn to_json(&self, ctx: &SaveContext) -> Result { + serde_json::to_string_pretty(&self.to_value(ctx)) + } + + /// Serialize HookEndPayload to a YAML string. + pub fn to_yaml(&self, ctx: &SaveContext) -> Result { + serde_yaml::to_string(&self.to_value(ctx)) + } + /// Returns typed reference to the map if the field is an object. + /// Returns `None` if the field is null or not an object. + pub fn as_output_dict(&self) -> Option<&serde_json::Map> { + self.output.as_object() + } +} diff --git a/runtime/rust/prompty/src/model/events/hook_start_payload.rs b/runtime/rust/prompty/src/model/events/hook_start_payload.rs new file mode 100644 index 00000000..219d4294 --- /dev/null +++ b/runtime/rust/prompty/src/model/events/hook_start_payload.rs @@ -0,0 +1,168 @@ +// +// Code generated by Typra emitter; DO NOT EDIT. + +#![allow( + unused_imports, + dead_code, + non_camel_case_types, + unused_variables, + clippy::all +)] + +use super::super::context::{LoadContext, SaveContext}; + +use super::redaction_metadata::RedactionMetadata; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum HookStartScope { + Turn, + Session, +} + +impl Default for HookStartScope { + fn default() -> Self { + Self::Turn + } +} + +impl std::fmt::Display for HookStartScope { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Turn => write!(f, "turn"), + Self::Session => write!(f, "session"), + } + } +} + +impl HookStartScope { + pub fn from_str_opt(s: &str) -> Option { + match s { + "turn" => Some(Self::Turn), + "session" => Some(Self::Session), + _ => None, + } + } + + pub fn as_str(&self) -> &str { + match self { + Self::Turn => "turn", + Self::Session => "session", + } + } +} + +/// Payload for "hook_start" events — a host lifecycle hook is beginning. +#[derive(Debug, Clone, Default)] +pub struct HookStartPayload { + /// Stable hook invocation identifier + pub hook_invocation_id: String, + /// Host-defined hook type + pub hook_type: String, + /// Whether the hook is scoped to a turn or the outer session + pub scope: Option, + /// Hook input after host-side sanitization + pub input: serde_json::Value, + /// Redaction state for sensitive hook input fields + pub redaction: Option, +} + +impl HookStartPayload { + /// Create a new HookStartPayload with default values. + pub fn new() -> Self { + Self::default() + } + + /// Load HookStartPayload from a JSON string. + pub fn from_json(json: &str, ctx: &LoadContext) -> Result { + let value: serde_json::Value = serde_json::from_str(json)?; + Ok(Self::load_from_value(&value, ctx)) + } + + /// Load HookStartPayload from a YAML string. + pub fn from_yaml(yaml: &str, ctx: &LoadContext) -> Result { + let value: serde_json::Value = serde_yaml::from_str(yaml)?; + Ok(Self::load_from_value(&value, ctx)) + } + + /// Load HookStartPayload from a `serde_json::Value`. + /// + /// Calls `ctx.process_input` before field extraction. + pub fn load_from_value(value: &serde_json::Value, ctx: &LoadContext) -> Self { + let value = ctx.process_input(value.clone()); + Self { + hook_invocation_id: value + .get("hookInvocationId") + .and_then(|v| v.as_str()) + .unwrap_or_default() + .to_string(), + hook_type: value + .get("hookType") + .and_then(|v| v.as_str()) + .unwrap_or_default() + .to_string(), + scope: value + .get("scope") + .and_then(|v| v.as_str()) + .and_then(|s| HookStartScope::from_str_opt(s)), + input: value + .get("input") + .cloned() + .unwrap_or(serde_json::Value::Null), + redaction: value + .get("redaction") + .filter(|v| v.is_object() || v.is_array() || v.is_string()) + .map(|v| RedactionMetadata::load_from_value(v, ctx)), + } + } + + /// Serialize HookStartPayload to a `serde_json::Value`. + /// + /// Calls `ctx.process_dict` after serialization. + pub fn to_value(&self, ctx: &SaveContext) -> serde_json::Value { + let mut result = serde_json::Map::new(); + // Write base fields + if !self.hook_invocation_id.is_empty() { + result.insert( + "hookInvocationId".to_string(), + serde_json::Value::String(self.hook_invocation_id.clone()), + ); + } + if !self.hook_type.is_empty() { + result.insert( + "hookType".to_string(), + serde_json::Value::String(self.hook_type.clone()), + ); + } + if let Some(ref val) = self.scope { + result.insert( + "scope".to_string(), + serde_json::Value::String(val.to_string()), + ); + } + if !self.input.is_null() { + result.insert("input".to_string(), self.input.clone()); + } + if let Some(ref val) = self.redaction { + let nested = val.to_value(ctx); + if !nested.is_null() { + result.insert("redaction".to_string(), nested); + } + } + ctx.process_dict(serde_json::Value::Object(result)) + } + + /// Serialize HookStartPayload to a JSON string. + pub fn to_json(&self, ctx: &SaveContext) -> Result { + serde_json::to_string_pretty(&self.to_value(ctx)) + } + + /// Serialize HookStartPayload to a YAML string. + pub fn to_yaml(&self, ctx: &SaveContext) -> Result { + serde_yaml::to_string(&self.to_value(ctx)) + } + /// Returns typed reference to the map if the field is an object. + /// Returns `None` if the field is null or not an object. + pub fn as_input_dict(&self) -> Option<&serde_json::Map> { + self.input.as_object() + } +} diff --git a/runtime/rust/prompty/src/model/events/host_tool_request.rs b/runtime/rust/prompty/src/model/events/host_tool_request.rs new file mode 100644 index 00000000..60053f09 --- /dev/null +++ b/runtime/rust/prompty/src/model/events/host_tool_request.rs @@ -0,0 +1,127 @@ +// +// Code generated by Typra emitter; DO NOT EDIT. + +#![allow( + unused_imports, + dead_code, + non_camel_case_types, + unused_variables, + clippy::all +)] + +use super::super::context::{LoadContext, SaveContext}; + +/// Request passed to a host tool executor after policy and permission checks. +#[derive(Debug, Clone, Default)] +pub struct HostToolRequest { + /// Stable host execution request identifier + pub request_id: Option, + /// Associated model tool call identifier, when available + pub tool_call_id: Option, + /// Name of the host tool being executed + pub tool_name: String, + /// Tool arguments after host-side sanitization + pub arguments: serde_json::Value, + /// Working directory or execution scope for the tool + pub working_directory: Option, +} + +impl HostToolRequest { + /// Create a new HostToolRequest with default values. + pub fn new() -> Self { + Self::default() + } + + /// Load HostToolRequest from a JSON string. + pub fn from_json(json: &str, ctx: &LoadContext) -> Result { + let value: serde_json::Value = serde_json::from_str(json)?; + Ok(Self::load_from_value(&value, ctx)) + } + + /// Load HostToolRequest from a YAML string. + pub fn from_yaml(yaml: &str, ctx: &LoadContext) -> Result { + let value: serde_json::Value = serde_yaml::from_str(yaml)?; + Ok(Self::load_from_value(&value, ctx)) + } + + /// Load HostToolRequest from a `serde_json::Value`. + /// + /// Calls `ctx.process_input` before field extraction. + pub fn load_from_value(value: &serde_json::Value, ctx: &LoadContext) -> Self { + let value = ctx.process_input(value.clone()); + Self { + request_id: value + .get("requestId") + .and_then(|v| v.as_str()) + .map(|s| s.to_string()), + tool_call_id: value + .get("toolCallId") + .and_then(|v| v.as_str()) + .map(|s| s.to_string()), + tool_name: value + .get("toolName") + .and_then(|v| v.as_str()) + .unwrap_or_default() + .to_string(), + arguments: value + .get("arguments") + .cloned() + .unwrap_or(serde_json::Value::Null), + working_directory: value + .get("workingDirectory") + .and_then(|v| v.as_str()) + .map(|s| s.to_string()), + } + } + + /// Serialize HostToolRequest to a `serde_json::Value`. + /// + /// Calls `ctx.process_dict` after serialization. + pub fn to_value(&self, ctx: &SaveContext) -> serde_json::Value { + let mut result = serde_json::Map::new(); + // Write base fields + if let Some(ref val) = self.request_id { + result.insert( + "requestId".to_string(), + serde_json::Value::String(val.clone()), + ); + } + if let Some(ref val) = self.tool_call_id { + result.insert( + "toolCallId".to_string(), + serde_json::Value::String(val.clone()), + ); + } + if !self.tool_name.is_empty() { + result.insert( + "toolName".to_string(), + serde_json::Value::String(self.tool_name.clone()), + ); + } + if !self.arguments.is_null() { + result.insert("arguments".to_string(), self.arguments.clone()); + } + if let Some(ref val) = self.working_directory { + result.insert( + "workingDirectory".to_string(), + serde_json::Value::String(val.clone()), + ); + } + ctx.process_dict(serde_json::Value::Object(result)) + } + + /// Serialize HostToolRequest to a JSON string. + pub fn to_json(&self, ctx: &SaveContext) -> Result { + serde_json::to_string_pretty(&self.to_value(ctx)) + } + + /// Serialize HostToolRequest to a YAML string. + pub fn to_yaml(&self, ctx: &SaveContext) -> Result { + serde_yaml::to_string(&self.to_value(ctx)) + } + /// Returns typed reference to the map if the field is an object. + /// Returns `None` if the field is null or not an object. + pub fn as_arguments_dict(&self) -> Option<&serde_json::Map> { + self.arguments.as_object() + } +} diff --git a/runtime/rust/prompty/src/model/events/host_tool_result.rs b/runtime/rust/prompty/src/model/events/host_tool_result.rs new file mode 100644 index 00000000..928ad094 --- /dev/null +++ b/runtime/rust/prompty/src/model/events/host_tool_result.rs @@ -0,0 +1,163 @@ +// +// Code generated by Typra emitter; DO NOT EDIT. + +#![allow( + unused_imports, + dead_code, + non_camel_case_types, + unused_variables, + clippy::all +)] + +use super::super::context::{LoadContext, SaveContext}; + +/// Result returned by a host tool executor. +#[derive(Debug, Clone, Default)] +pub struct HostToolResult { + /// Stable host execution request identifier + pub request_id: Option, + /// Associated model tool call identifier, when available + pub tool_call_id: Option, + /// Name of the host tool that executed + pub tool_name: String, + /// Whether the host execution completed successfully + pub success: bool, + /// Host-normalized execution result + pub result: Option, + /// Process or host exit code, when applicable + pub exit_code: Option, + /// Tool execution duration in milliseconds + pub duration_ms: Option, + /// Machine-readable error category when success is false + pub error_kind: Option, + /// Host-specific telemetry for the execution + pub telemetry: serde_json::Value, +} + +impl HostToolResult { + /// Create a new HostToolResult with default values. + pub fn new() -> Self { + Self::default() + } + + /// Load HostToolResult from a JSON string. + pub fn from_json(json: &str, ctx: &LoadContext) -> Result { + let value: serde_json::Value = serde_json::from_str(json)?; + Ok(Self::load_from_value(&value, ctx)) + } + + /// Load HostToolResult from a YAML string. + pub fn from_yaml(yaml: &str, ctx: &LoadContext) -> Result { + let value: serde_json::Value = serde_yaml::from_str(yaml)?; + Ok(Self::load_from_value(&value, ctx)) + } + + /// Load HostToolResult from a `serde_json::Value`. + /// + /// Calls `ctx.process_input` before field extraction. + pub fn load_from_value(value: &serde_json::Value, ctx: &LoadContext) -> Self { + let value = ctx.process_input(value.clone()); + Self { + request_id: value + .get("requestId") + .and_then(|v| v.as_str()) + .map(|s| s.to_string()), + tool_call_id: value + .get("toolCallId") + .and_then(|v| v.as_str()) + .map(|s| s.to_string()), + tool_name: value + .get("toolName") + .and_then(|v| v.as_str()) + .unwrap_or_default() + .to_string(), + success: value + .get("success") + .and_then(|v| v.as_bool()) + .unwrap_or(false), + result: value.get("result").cloned(), + exit_code: value + .get("exitCode") + .and_then(|v| v.as_i64()) + .map(|v| v as i32), + duration_ms: value.get("durationMs").and_then(|v| v.as_f64()), + error_kind: value + .get("errorKind") + .and_then(|v| v.as_str()) + .map(|s| s.to_string()), + telemetry: value + .get("telemetry") + .cloned() + .unwrap_or(serde_json::Value::Null), + } + } + + /// Serialize HostToolResult to a `serde_json::Value`. + /// + /// Calls `ctx.process_dict` after serialization. + pub fn to_value(&self, ctx: &SaveContext) -> serde_json::Value { + let mut result = serde_json::Map::new(); + // Write base fields + if let Some(ref val) = self.request_id { + result.insert( + "requestId".to_string(), + serde_json::Value::String(val.clone()), + ); + } + if let Some(ref val) = self.tool_call_id { + result.insert( + "toolCallId".to_string(), + serde_json::Value::String(val.clone()), + ); + } + if !self.tool_name.is_empty() { + result.insert( + "toolName".to_string(), + serde_json::Value::String(self.tool_name.clone()), + ); + } + result.insert("success".to_string(), serde_json::Value::Bool(self.success)); + if let Some(ref val) = self.result { + result.insert("result".to_string(), val.clone()); + } + if let Some(val) = self.exit_code { + result.insert( + "exitCode".to_string(), + serde_json::Value::Number(serde_json::Number::from(val)), + ); + } + if let Some(val) = self.duration_ms { + result.insert( + "durationMs".to_string(), + serde_json::Number::from_f64(val as f64) + .map(serde_json::Value::Number) + .unwrap_or(serde_json::Value::Null), + ); + } + if let Some(ref val) = self.error_kind { + result.insert( + "errorKind".to_string(), + serde_json::Value::String(val.clone()), + ); + } + if !self.telemetry.is_null() { + result.insert("telemetry".to_string(), self.telemetry.clone()); + } + ctx.process_dict(serde_json::Value::Object(result)) + } + + /// Serialize HostToolResult to a JSON string. + pub fn to_json(&self, ctx: &SaveContext) -> Result { + serde_json::to_string_pretty(&self.to_value(ctx)) + } + + /// Serialize HostToolResult to a YAML string. + pub fn to_yaml(&self, ctx: &SaveContext) -> Result { + serde_yaml::to_string(&self.to_value(ctx)) + } + /// Returns typed reference to the map if the field is an object. + /// Returns `None` if the field is null or not an object. + pub fn as_telemetry_dict(&self) -> Option<&serde_json::Map> { + self.telemetry.as_object() + } +} diff --git a/runtime/rust/prompty/src/model/events/llm_complete_payload.rs b/runtime/rust/prompty/src/model/events/llm_complete_payload.rs new file mode 100644 index 00000000..b55c8fac --- /dev/null +++ b/runtime/rust/prompty/src/model/events/llm_complete_payload.rs @@ -0,0 +1,113 @@ +// +// Code generated by Typra emitter; DO NOT EDIT. + +#![allow( + unused_imports, + dead_code, + non_camel_case_types, + unused_variables, + clippy::all +)] + +use super::super::context::{LoadContext, SaveContext}; + +use super::super::model::token_usage::TokenUsage; + +/// Payload for "llm_complete" events — an LLM request completed. +#[derive(Debug, Clone, Default)] +pub struct LlmCompletePayload { + /// Provider request identifier, when supplied by the SDK/API + pub request_id: Option, + /// Service request identifier, when supplied by the SDK/API + pub service_request_id: Option, + /// Token usage reported by the provider + pub usage: Option, + /// LLM call duration in milliseconds + pub duration_ms: Option, +} + +impl LlmCompletePayload { + /// Create a new LlmCompletePayload with default values. + pub fn new() -> Self { + Self::default() + } + + /// Load LlmCompletePayload from a JSON string. + pub fn from_json(json: &str, ctx: &LoadContext) -> Result { + let value: serde_json::Value = serde_json::from_str(json)?; + Ok(Self::load_from_value(&value, ctx)) + } + + /// Load LlmCompletePayload from a YAML string. + pub fn from_yaml(yaml: &str, ctx: &LoadContext) -> Result { + let value: serde_json::Value = serde_yaml::from_str(yaml)?; + Ok(Self::load_from_value(&value, ctx)) + } + + /// Load LlmCompletePayload from a `serde_json::Value`. + /// + /// Calls `ctx.process_input` before field extraction. + pub fn load_from_value(value: &serde_json::Value, ctx: &LoadContext) -> Self { + let value = ctx.process_input(value.clone()); + Self { + request_id: value + .get("requestId") + .and_then(|v| v.as_str()) + .map(|s| s.to_string()), + service_request_id: value + .get("serviceRequestId") + .and_then(|v| v.as_str()) + .map(|s| s.to_string()), + usage: value + .get("usage") + .filter(|v| v.is_object() || v.is_array() || v.is_string()) + .map(|v| TokenUsage::load_from_value(v, ctx)), + duration_ms: value.get("durationMs").and_then(|v| v.as_f64()), + } + } + + /// Serialize LlmCompletePayload to a `serde_json::Value`. + /// + /// Calls `ctx.process_dict` after serialization. + pub fn to_value(&self, ctx: &SaveContext) -> serde_json::Value { + let mut result = serde_json::Map::new(); + // Write base fields + if let Some(ref val) = self.request_id { + result.insert( + "requestId".to_string(), + serde_json::Value::String(val.clone()), + ); + } + if let Some(ref val) = self.service_request_id { + result.insert( + "serviceRequestId".to_string(), + serde_json::Value::String(val.clone()), + ); + } + if let Some(ref val) = self.usage { + let nested = val.to_value(ctx); + if !nested.is_null() { + result.insert("usage".to_string(), nested); + } + } + if let Some(val) = self.duration_ms { + result.insert( + "durationMs".to_string(), + serde_json::Number::from_f64(val as f64) + .map(serde_json::Value::Number) + .unwrap_or(serde_json::Value::Null), + ); + } + ctx.process_dict(serde_json::Value::Object(result)) + } + + /// Serialize LlmCompletePayload to a JSON string. + pub fn to_json(&self, ctx: &SaveContext) -> Result { + serde_json::to_string_pretty(&self.to_value(ctx)) + } + + /// Serialize LlmCompletePayload to a YAML string. + pub fn to_yaml(&self, ctx: &SaveContext) -> Result { + serde_yaml::to_string(&self.to_value(ctx)) + } +} diff --git a/runtime/rust/prompty/src/model/events/llm_start_payload.rs b/runtime/rust/prompty/src/model/events/llm_start_payload.rs new file mode 100644 index 00000000..095bbebc --- /dev/null +++ b/runtime/rust/prompty/src/model/events/llm_start_payload.rs @@ -0,0 +1,112 @@ +// +// Code generated by Typra emitter; DO NOT EDIT. + +#![allow( + unused_imports, + dead_code, + non_camel_case_types, + unused_variables, + clippy::all +)] + +use super::super::context::{LoadContext, SaveContext}; + +/// Payload for "llm_start" events — an LLM request is about to be sent. +#[derive(Debug, Clone, Default)] +pub struct LlmStartPayload { + /// Provider identifier used for the request + pub provider: Option, + /// Model or deployment identifier used for the request + pub model_id: Option, + /// Number of messages sent to the provider + pub message_count: Option, + /// Retry attempt number, zero for the initial attempt + pub attempt: Option, +} + +impl LlmStartPayload { + /// Create a new LlmStartPayload with default values. + pub fn new() -> Self { + Self::default() + } + + /// Load LlmStartPayload from a JSON string. + pub fn from_json(json: &str, ctx: &LoadContext) -> Result { + let value: serde_json::Value = serde_json::from_str(json)?; + Ok(Self::load_from_value(&value, ctx)) + } + + /// Load LlmStartPayload from a YAML string. + pub fn from_yaml(yaml: &str, ctx: &LoadContext) -> Result { + let value: serde_json::Value = serde_yaml::from_str(yaml)?; + Ok(Self::load_from_value(&value, ctx)) + } + + /// Load LlmStartPayload from a `serde_json::Value`. + /// + /// Calls `ctx.process_input` before field extraction. + pub fn load_from_value(value: &serde_json::Value, ctx: &LoadContext) -> Self { + let value = ctx.process_input(value.clone()); + Self { + provider: value + .get("provider") + .and_then(|v| v.as_str()) + .map(|s| s.to_string()), + model_id: value + .get("modelId") + .and_then(|v| v.as_str()) + .map(|s| s.to_string()), + message_count: value + .get("messageCount") + .and_then(|v| v.as_i64()) + .map(|v| v as i32), + attempt: value + .get("attempt") + .and_then(|v| v.as_i64()) + .map(|v| v as i32), + } + } + + /// Serialize LlmStartPayload to a `serde_json::Value`. + /// + /// Calls `ctx.process_dict` after serialization. + pub fn to_value(&self, ctx: &SaveContext) -> serde_json::Value { + let mut result = serde_json::Map::new(); + // Write base fields + if let Some(ref val) = self.provider { + result.insert( + "provider".to_string(), + serde_json::Value::String(val.clone()), + ); + } + if let Some(ref val) = self.model_id { + result.insert( + "modelId".to_string(), + serde_json::Value::String(val.clone()), + ); + } + if let Some(val) = self.message_count { + result.insert( + "messageCount".to_string(), + serde_json::Value::Number(serde_json::Number::from(val)), + ); + } + if let Some(val) = self.attempt { + result.insert( + "attempt".to_string(), + serde_json::Value::Number(serde_json::Number::from(val)), + ); + } + ctx.process_dict(serde_json::Value::Object(result)) + } + + /// Serialize LlmStartPayload to a JSON string. + pub fn to_json(&self, ctx: &SaveContext) -> Result { + serde_json::to_string_pretty(&self.to_value(ctx)) + } + + /// Serialize LlmStartPayload to a YAML string. + pub fn to_yaml(&self, ctx: &SaveContext) -> Result { + serde_yaml::to_string(&self.to_value(ctx)) + } +} diff --git a/runtime/rust/prompty/src/model/events/messages_updated_payload.rs b/runtime/rust/prompty/src/model/events/messages_updated_payload.rs index a15da8c9..90adea51 100644 --- a/runtime/rust/prompty/src/model/events/messages_updated_payload.rs +++ b/runtime/rust/prompty/src/model/events/messages_updated_payload.rs @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. #![allow( unused_imports, @@ -17,6 +18,12 @@ use super::super::conversation::message::Message; pub struct MessagesUpdatedPayload { /// The current full message list after the update pub messages: Vec, + /// Why the message list changed + pub reason: Option, + /// Messages appended by this update, when available + pub appended: Vec, + /// Number of messages removed by this update, when available + pub removed: Option, } impl MessagesUpdatedPayload { @@ -47,6 +54,18 @@ impl MessagesUpdatedPayload { .get("messages") .map(|v| Self::load_messages(v, ctx)) .unwrap_or_default(), + reason: value + .get("reason") + .and_then(|v| v.as_str()) + .map(|s| s.to_string()), + appended: value + .get("appended") + .map(|v| Self::load_appended(v, ctx)) + .unwrap_or_default(), + removed: value + .get("removed") + .and_then(|v| v.as_i64()) + .map(|v| v as i32), } } @@ -62,6 +81,21 @@ impl MessagesUpdatedPayload { Self::save_messages(&self.messages, ctx), ); } + if let Some(ref val) = self.reason { + result.insert("reason".to_string(), serde_json::Value::String(val.clone())); + } + if !self.appended.is_empty() { + result.insert( + "appended".to_string(), + Self::save_appended(&self.appended, ctx), + ); + } + if let Some(val) = self.removed { + result.insert( + "removed".to_string(), + serde_json::Value::Number(serde_json::Number::from(val)), + ); + } ctx.process_dict(serde_json::Value::Object(result)) } @@ -97,4 +131,27 @@ impl MessagesUpdatedPayload { .collect::>(), ) } + + /// Load a collection of Message from a JSON value. + /// Handles both array format `[{...}]`. + fn load_appended(data: &serde_json::Value, ctx: &LoadContext) -> Vec { + match data { + serde_json::Value::Array(arr) => arr + .iter() + .map(|v| Message::load_from_value(v, ctx)) + .collect(), + + _ => Vec::new(), + } + } + + /// Save a collection of Message to a JSON value. + fn save_appended(items: &[Message], ctx: &SaveContext) -> serde_json::Value { + serde_json::Value::Array( + items + .iter() + .map(|item| item.to_value(ctx)) + .collect::>(), + ) + } } diff --git a/runtime/rust/prompty/src/model/events/mod.rs b/runtime/rust/prompty/src/model/events/mod.rs index 6019c3cd..45c16b62 100644 --- a/runtime/rust/prompty/src/model/events/mod.rs +++ b/runtime/rust/prompty/src/model/events/mod.rs @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. #![allow( unused_imports, @@ -8,6 +9,51 @@ clippy::all )] +pub mod host_tool_result; +pub use host_tool_result::*; + +pub mod host_tool_request; +pub use host_tool_request::*; + +pub mod redacted_field; +pub use redacted_field::*; + +pub mod redaction_metadata; +pub use redaction_metadata::*; + +pub mod checkpoint; +pub use checkpoint::*; + +pub mod turn_event; +pub use turn_event::*; + +pub mod turn_start_payload; +pub use turn_start_payload::*; + +pub mod turn_end_payload; +pub use turn_end_payload::*; + +pub mod llm_start_payload; +pub use llm_start_payload::*; + +pub mod llm_complete_payload; +pub use llm_complete_payload::*; + +pub mod retry_payload; +pub use retry_payload::*; + +pub mod permission_requested_payload; +pub use permission_requested_payload::*; + +pub mod permission_completed_payload; +pub use permission_completed_payload::*; + +pub mod permission_request; +pub use permission_request::*; + +pub mod permission_decision; +pub use permission_decision::*; + pub mod token_event_payload; pub use token_event_payload::*; @@ -17,6 +63,21 @@ pub use thinking_event_payload::*; pub mod tool_call_start_payload; pub use tool_call_start_payload::*; +pub mod tool_call_complete_payload; +pub use tool_call_complete_payload::*; + +pub mod tool_execution_start_payload; +pub use tool_execution_start_payload::*; + +pub mod tool_execution_complete_payload; +pub use tool_execution_complete_payload::*; + +pub mod hook_start_payload; +pub use hook_start_payload::*; + +pub mod hook_end_payload; +pub use hook_end_payload::*; + pub mod tool_result_payload; pub use tool_result_payload::*; @@ -32,11 +93,50 @@ pub use done_event_payload::*; pub mod error_event_payload; pub use error_event_payload::*; +pub mod compaction_start_payload; +pub use compaction_start_payload::*; + pub mod compaction_complete_payload; pub use compaction_complete_payload::*; pub mod compaction_failed_payload; pub use compaction_failed_payload::*; +pub mod turn_summary; +pub use turn_summary::*; + +pub mod turn_trace; +pub use turn_trace::*; + +pub mod harness_context; +pub use harness_context::*; + +pub mod session_start_payload; +pub use session_start_payload::*; + +pub mod session_end_payload; +pub use session_end_payload::*; + +pub mod session_warning_payload; +pub use session_warning_payload::*; + +pub mod session_event; +pub use session_event::*; + +pub mod trajectory_event; +pub use trajectory_event::*; + +pub mod session_file_ref; +pub use session_file_ref::*; + +pub mod session_ref; +pub use session_ref::*; + +pub mod session_summary; +pub use session_summary::*; + +pub mod session_trace; +pub use session_trace::*; + pub mod stream_chunk; pub use stream_chunk::*; diff --git a/runtime/rust/prompty/src/model/events/permission_completed_payload.rs b/runtime/rust/prompty/src/model/events/permission_completed_payload.rs new file mode 100644 index 00000000..108c94fb --- /dev/null +++ b/runtime/rust/prompty/src/model/events/permission_completed_payload.rs @@ -0,0 +1,148 @@ +// +// Code generated by Typra emitter; DO NOT EDIT. + +#![allow( + unused_imports, + dead_code, + non_camel_case_types, + unused_variables, + clippy::all +)] + +use super::super::context::{LoadContext, SaveContext}; + +use super::redaction_metadata::RedactionMetadata; + +/// Payload for permission completion events — an approval decision was made. +#[derive(Debug, Clone, Default)] +pub struct PermissionCompletedPayload { + /// Stable permission request identifier + pub request_id: Option, + /// Associated tool call identifier, when the permission gated a tool call + pub tool_call_id: Option, + /// Permission/action name that was decided + pub permission: String, + /// Whether the requested permission was approved + pub approved: bool, + /// Decision reason, if available + pub reason: Option, + /// Host-specific decision result, such as a durable approval token or denial details + pub result: serde_json::Value, + /// Redaction state for sensitive decision fields + pub redaction: Option, +} + +impl PermissionCompletedPayload { + /// Create a new PermissionCompletedPayload with default values. + pub fn new() -> Self { + Self::default() + } + + /// Load PermissionCompletedPayload from a JSON string. + pub fn from_json(json: &str, ctx: &LoadContext) -> Result { + let value: serde_json::Value = serde_json::from_str(json)?; + Ok(Self::load_from_value(&value, ctx)) + } + + /// Load PermissionCompletedPayload from a YAML string. + pub fn from_yaml(yaml: &str, ctx: &LoadContext) -> Result { + let value: serde_json::Value = serde_yaml::from_str(yaml)?; + Ok(Self::load_from_value(&value, ctx)) + } + + /// Load PermissionCompletedPayload from a `serde_json::Value`. + /// + /// Calls `ctx.process_input` before field extraction. + pub fn load_from_value(value: &serde_json::Value, ctx: &LoadContext) -> Self { + let value = ctx.process_input(value.clone()); + Self { + request_id: value + .get("requestId") + .and_then(|v| v.as_str()) + .map(|s| s.to_string()), + tool_call_id: value + .get("toolCallId") + .and_then(|v| v.as_str()) + .map(|s| s.to_string()), + permission: value + .get("permission") + .and_then(|v| v.as_str()) + .unwrap_or_default() + .to_string(), + approved: value + .get("approved") + .and_then(|v| v.as_bool()) + .unwrap_or(false), + reason: value + .get("reason") + .and_then(|v| v.as_str()) + .map(|s| s.to_string()), + result: value + .get("result") + .cloned() + .unwrap_or(serde_json::Value::Null), + redaction: value + .get("redaction") + .filter(|v| v.is_object() || v.is_array() || v.is_string()) + .map(|v| RedactionMetadata::load_from_value(v, ctx)), + } + } + + /// Serialize PermissionCompletedPayload to a `serde_json::Value`. + /// + /// Calls `ctx.process_dict` after serialization. + pub fn to_value(&self, ctx: &SaveContext) -> serde_json::Value { + let mut result = serde_json::Map::new(); + // Write base fields + if let Some(ref val) = self.request_id { + result.insert( + "requestId".to_string(), + serde_json::Value::String(val.clone()), + ); + } + if let Some(ref val) = self.tool_call_id { + result.insert( + "toolCallId".to_string(), + serde_json::Value::String(val.clone()), + ); + } + if !self.permission.is_empty() { + result.insert( + "permission".to_string(), + serde_json::Value::String(self.permission.clone()), + ); + } + result.insert( + "approved".to_string(), + serde_json::Value::Bool(self.approved), + ); + if let Some(ref val) = self.reason { + result.insert("reason".to_string(), serde_json::Value::String(val.clone())); + } + if !self.result.is_null() { + result.insert("result".to_string(), self.result.clone()); + } + if let Some(ref val) = self.redaction { + let nested = val.to_value(ctx); + if !nested.is_null() { + result.insert("redaction".to_string(), nested); + } + } + ctx.process_dict(serde_json::Value::Object(result)) + } + + /// Serialize PermissionCompletedPayload to a JSON string. + pub fn to_json(&self, ctx: &SaveContext) -> Result { + serde_json::to_string_pretty(&self.to_value(ctx)) + } + + /// Serialize PermissionCompletedPayload to a YAML string. + pub fn to_yaml(&self, ctx: &SaveContext) -> Result { + serde_yaml::to_string(&self.to_value(ctx)) + } + /// Returns typed reference to the map if the field is an object. + /// Returns `None` if the field is null or not an object. + pub fn as_result_dict(&self) -> Option<&serde_json::Map> { + self.result.as_object() + } +} diff --git a/runtime/rust/prompty/src/model/events/permission_decision.rs b/runtime/rust/prompty/src/model/events/permission_decision.rs new file mode 100644 index 00000000..7df0473a --- /dev/null +++ b/runtime/rust/prompty/src/model/events/permission_decision.rs @@ -0,0 +1,134 @@ +// +// Code generated by Typra emitter; DO NOT EDIT. + +#![allow( + unused_imports, + dead_code, + non_camel_case_types, + unused_variables, + clippy::all +)] + +use super::super::context::{LoadContext, SaveContext}; + +/// Decision returned by a permission resolver. +#[derive(Debug, Clone, Default)] +pub struct PermissionDecision { + /// Stable permission request identifier + pub request_id: Option, + /// Associated tool call identifier, when the permission gated a tool call + pub tool_call_id: Option, + /// Permission/action name that was decided + pub permission: String, + /// Whether the requested permission was approved + pub approved: bool, + /// Decision reason, if available + pub reason: Option, + /// Host-specific decision result, such as a durable approval token or denial details + pub result: serde_json::Value, +} + +impl PermissionDecision { + /// Create a new PermissionDecision with default values. + pub fn new() -> Self { + Self::default() + } + + /// Load PermissionDecision from a JSON string. + pub fn from_json(json: &str, ctx: &LoadContext) -> Result { + let value: serde_json::Value = serde_json::from_str(json)?; + Ok(Self::load_from_value(&value, ctx)) + } + + /// Load PermissionDecision from a YAML string. + pub fn from_yaml(yaml: &str, ctx: &LoadContext) -> Result { + let value: serde_json::Value = serde_yaml::from_str(yaml)?; + Ok(Self::load_from_value(&value, ctx)) + } + + /// Load PermissionDecision from a `serde_json::Value`. + /// + /// Calls `ctx.process_input` before field extraction. + pub fn load_from_value(value: &serde_json::Value, ctx: &LoadContext) -> Self { + let value = ctx.process_input(value.clone()); + Self { + request_id: value + .get("requestId") + .and_then(|v| v.as_str()) + .map(|s| s.to_string()), + tool_call_id: value + .get("toolCallId") + .and_then(|v| v.as_str()) + .map(|s| s.to_string()), + permission: value + .get("permission") + .and_then(|v| v.as_str()) + .unwrap_or_default() + .to_string(), + approved: value + .get("approved") + .and_then(|v| v.as_bool()) + .unwrap_or(false), + reason: value + .get("reason") + .and_then(|v| v.as_str()) + .map(|s| s.to_string()), + result: value + .get("result") + .cloned() + .unwrap_or(serde_json::Value::Null), + } + } + + /// Serialize PermissionDecision to a `serde_json::Value`. + /// + /// Calls `ctx.process_dict` after serialization. + pub fn to_value(&self, ctx: &SaveContext) -> serde_json::Value { + let mut result = serde_json::Map::new(); + // Write base fields + if let Some(ref val) = self.request_id { + result.insert( + "requestId".to_string(), + serde_json::Value::String(val.clone()), + ); + } + if let Some(ref val) = self.tool_call_id { + result.insert( + "toolCallId".to_string(), + serde_json::Value::String(val.clone()), + ); + } + if !self.permission.is_empty() { + result.insert( + "permission".to_string(), + serde_json::Value::String(self.permission.clone()), + ); + } + result.insert( + "approved".to_string(), + serde_json::Value::Bool(self.approved), + ); + if let Some(ref val) = self.reason { + result.insert("reason".to_string(), serde_json::Value::String(val.clone())); + } + if !self.result.is_null() { + result.insert("result".to_string(), self.result.clone()); + } + ctx.process_dict(serde_json::Value::Object(result)) + } + + /// Serialize PermissionDecision to a JSON string. + pub fn to_json(&self, ctx: &SaveContext) -> Result { + serde_json::to_string_pretty(&self.to_value(ctx)) + } + + /// Serialize PermissionDecision to a YAML string. + pub fn to_yaml(&self, ctx: &SaveContext) -> Result { + serde_yaml::to_string(&self.to_value(ctx)) + } + /// Returns typed reference to the map if the field is an object. + /// Returns `None` if the field is null or not an object. + pub fn as_result_dict(&self) -> Option<&serde_json::Map> { + self.result.as_object() + } +} diff --git a/runtime/rust/prompty/src/model/events/permission_request.rs b/runtime/rust/prompty/src/model/events/permission_request.rs new file mode 100644 index 00000000..1c808f61 --- /dev/null +++ b/runtime/rust/prompty/src/model/events/permission_request.rs @@ -0,0 +1,151 @@ +// +// Code generated by Typra emitter; DO NOT EDIT. + +#![allow( + unused_imports, + dead_code, + non_camel_case_types, + unused_variables, + clippy::all +)] + +use super::super::context::{LoadContext, SaveContext}; + +/// Request passed to a permission resolver. This is the live protocol shape; the event payloads above can include trace-only metadata such as redaction state. +#[derive(Debug, Clone, Default)] +pub struct PermissionRequest { + /// Stable permission request identifier + pub request_id: Option, + /// Associated tool call identifier, when the permission gates a tool call + pub tool_call_id: Option, + /// Permission/action name being requested + pub permission: String, + /// Resource or tool the permission applies to + pub target: Option, + /// Additional host-specific permission details + pub details: serde_json::Value, + /// Human-readable prompt or rationale that can be shown to an approval UI + pub prompt_request: Option, + /// Policy metadata used to evaluate or explain the permission request + pub policy: serde_json::Value, +} + +impl PermissionRequest { + /// Create a new PermissionRequest with default values. + pub fn new() -> Self { + Self::default() + } + + /// Load PermissionRequest from a JSON string. + pub fn from_json(json: &str, ctx: &LoadContext) -> Result { + let value: serde_json::Value = serde_json::from_str(json)?; + Ok(Self::load_from_value(&value, ctx)) + } + + /// Load PermissionRequest from a YAML string. + pub fn from_yaml(yaml: &str, ctx: &LoadContext) -> Result { + let value: serde_json::Value = serde_yaml::from_str(yaml)?; + Ok(Self::load_from_value(&value, ctx)) + } + + /// Load PermissionRequest from a `serde_json::Value`. + /// + /// Calls `ctx.process_input` before field extraction. + pub fn load_from_value(value: &serde_json::Value, ctx: &LoadContext) -> Self { + let value = ctx.process_input(value.clone()); + Self { + request_id: value + .get("requestId") + .and_then(|v| v.as_str()) + .map(|s| s.to_string()), + tool_call_id: value + .get("toolCallId") + .and_then(|v| v.as_str()) + .map(|s| s.to_string()), + permission: value + .get("permission") + .and_then(|v| v.as_str()) + .unwrap_or_default() + .to_string(), + target: value + .get("target") + .and_then(|v| v.as_str()) + .map(|s| s.to_string()), + details: value + .get("details") + .cloned() + .unwrap_or(serde_json::Value::Null), + prompt_request: value + .get("promptRequest") + .and_then(|v| v.as_str()) + .map(|s| s.to_string()), + policy: value + .get("policy") + .cloned() + .unwrap_or(serde_json::Value::Null), + } + } + + /// Serialize PermissionRequest to a `serde_json::Value`. + /// + /// Calls `ctx.process_dict` after serialization. + pub fn to_value(&self, ctx: &SaveContext) -> serde_json::Value { + let mut result = serde_json::Map::new(); + // Write base fields + if let Some(ref val) = self.request_id { + result.insert( + "requestId".to_string(), + serde_json::Value::String(val.clone()), + ); + } + if let Some(ref val) = self.tool_call_id { + result.insert( + "toolCallId".to_string(), + serde_json::Value::String(val.clone()), + ); + } + if !self.permission.is_empty() { + result.insert( + "permission".to_string(), + serde_json::Value::String(self.permission.clone()), + ); + } + if let Some(ref val) = self.target { + result.insert("target".to_string(), serde_json::Value::String(val.clone())); + } + if !self.details.is_null() { + result.insert("details".to_string(), self.details.clone()); + } + if let Some(ref val) = self.prompt_request { + result.insert( + "promptRequest".to_string(), + serde_json::Value::String(val.clone()), + ); + } + if !self.policy.is_null() { + result.insert("policy".to_string(), self.policy.clone()); + } + ctx.process_dict(serde_json::Value::Object(result)) + } + + /// Serialize PermissionRequest to a JSON string. + pub fn to_json(&self, ctx: &SaveContext) -> Result { + serde_json::to_string_pretty(&self.to_value(ctx)) + } + + /// Serialize PermissionRequest to a YAML string. + pub fn to_yaml(&self, ctx: &SaveContext) -> Result { + serde_yaml::to_string(&self.to_value(ctx)) + } + /// Returns typed reference to the map if the field is an object. + /// Returns `None` if the field is null or not an object. + pub fn as_details_dict(&self) -> Option<&serde_json::Map> { + self.details.as_object() + } + + /// Returns typed reference to the map if the field is an object. + /// Returns `None` if the field is null or not an object. + pub fn as_policy_dict(&self) -> Option<&serde_json::Map> { + self.policy.as_object() + } +} diff --git a/runtime/rust/prompty/src/model/events/permission_requested_payload.rs b/runtime/rust/prompty/src/model/events/permission_requested_payload.rs new file mode 100644 index 00000000..e28cd971 --- /dev/null +++ b/runtime/rust/prompty/src/model/events/permission_requested_payload.rs @@ -0,0 +1,165 @@ +// +// Code generated by Typra emitter; DO NOT EDIT. + +#![allow( + unused_imports, + dead_code, + non_camel_case_types, + unused_variables, + clippy::all +)] + +use super::super::context::{LoadContext, SaveContext}; + +use super::redaction_metadata::RedactionMetadata; + +/// Payload for permission request events — a host is asked to approve an action. +#[derive(Debug, Clone, Default)] +pub struct PermissionRequestedPayload { + /// Stable permission request identifier + pub request_id: Option, + /// Associated tool call identifier, when the permission gates a tool call + pub tool_call_id: Option, + /// Permission/action name being requested + pub permission: String, + /// Resource or tool the permission applies to + pub target: Option, + /// Additional host-specific permission details + pub details: serde_json::Value, + /// Human-readable prompt or rationale that can be shown to an approval UI + pub prompt_request: Option, + /// Policy metadata used to evaluate or explain the permission request + pub policy: serde_json::Value, + /// Redaction state for sensitive request fields + pub redaction: Option, +} + +impl PermissionRequestedPayload { + /// Create a new PermissionRequestedPayload with default values. + pub fn new() -> Self { + Self::default() + } + + /// Load PermissionRequestedPayload from a JSON string. + pub fn from_json(json: &str, ctx: &LoadContext) -> Result { + let value: serde_json::Value = serde_json::from_str(json)?; + Ok(Self::load_from_value(&value, ctx)) + } + + /// Load PermissionRequestedPayload from a YAML string. + pub fn from_yaml(yaml: &str, ctx: &LoadContext) -> Result { + let value: serde_json::Value = serde_yaml::from_str(yaml)?; + Ok(Self::load_from_value(&value, ctx)) + } + + /// Load PermissionRequestedPayload from a `serde_json::Value`. + /// + /// Calls `ctx.process_input` before field extraction. + pub fn load_from_value(value: &serde_json::Value, ctx: &LoadContext) -> Self { + let value = ctx.process_input(value.clone()); + Self { + request_id: value + .get("requestId") + .and_then(|v| v.as_str()) + .map(|s| s.to_string()), + tool_call_id: value + .get("toolCallId") + .and_then(|v| v.as_str()) + .map(|s| s.to_string()), + permission: value + .get("permission") + .and_then(|v| v.as_str()) + .unwrap_or_default() + .to_string(), + target: value + .get("target") + .and_then(|v| v.as_str()) + .map(|s| s.to_string()), + details: value + .get("details") + .cloned() + .unwrap_or(serde_json::Value::Null), + prompt_request: value + .get("promptRequest") + .and_then(|v| v.as_str()) + .map(|s| s.to_string()), + policy: value + .get("policy") + .cloned() + .unwrap_or(serde_json::Value::Null), + redaction: value + .get("redaction") + .filter(|v| v.is_object() || v.is_array() || v.is_string()) + .map(|v| RedactionMetadata::load_from_value(v, ctx)), + } + } + + /// Serialize PermissionRequestedPayload to a `serde_json::Value`. + /// + /// Calls `ctx.process_dict` after serialization. + pub fn to_value(&self, ctx: &SaveContext) -> serde_json::Value { + let mut result = serde_json::Map::new(); + // Write base fields + if let Some(ref val) = self.request_id { + result.insert( + "requestId".to_string(), + serde_json::Value::String(val.clone()), + ); + } + if let Some(ref val) = self.tool_call_id { + result.insert( + "toolCallId".to_string(), + serde_json::Value::String(val.clone()), + ); + } + if !self.permission.is_empty() { + result.insert( + "permission".to_string(), + serde_json::Value::String(self.permission.clone()), + ); + } + if let Some(ref val) = self.target { + result.insert("target".to_string(), serde_json::Value::String(val.clone())); + } + if !self.details.is_null() { + result.insert("details".to_string(), self.details.clone()); + } + if let Some(ref val) = self.prompt_request { + result.insert( + "promptRequest".to_string(), + serde_json::Value::String(val.clone()), + ); + } + if !self.policy.is_null() { + result.insert("policy".to_string(), self.policy.clone()); + } + if let Some(ref val) = self.redaction { + let nested = val.to_value(ctx); + if !nested.is_null() { + result.insert("redaction".to_string(), nested); + } + } + ctx.process_dict(serde_json::Value::Object(result)) + } + + /// Serialize PermissionRequestedPayload to a JSON string. + pub fn to_json(&self, ctx: &SaveContext) -> Result { + serde_json::to_string_pretty(&self.to_value(ctx)) + } + + /// Serialize PermissionRequestedPayload to a YAML string. + pub fn to_yaml(&self, ctx: &SaveContext) -> Result { + serde_yaml::to_string(&self.to_value(ctx)) + } + /// Returns typed reference to the map if the field is an object. + /// Returns `None` if the field is null or not an object. + pub fn as_details_dict(&self) -> Option<&serde_json::Map> { + self.details.as_object() + } + + /// Returns typed reference to the map if the field is an object. + /// Returns `None` if the field is null or not an object. + pub fn as_policy_dict(&self) -> Option<&serde_json::Map> { + self.policy.as_object() + } +} diff --git a/runtime/rust/prompty/src/model/events/redacted_field.rs b/runtime/rust/prompty/src/model/events/redacted_field.rs new file mode 100644 index 00000000..ba90dd51 --- /dev/null +++ b/runtime/rust/prompty/src/model/events/redacted_field.rs @@ -0,0 +1,147 @@ +// +// Code generated by Typra emitter; DO NOT EDIT. + +#![allow( + unused_imports, + dead_code, + non_camel_case_types, + unused_variables, + clippy::all +)] + +use super::super::context::{LoadContext, SaveContext}; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum RedactionMode { + None, + Redacted, + Hashed, + Summary, + Reference, +} + +impl Default for RedactionMode { + fn default() -> Self { + Self::None + } +} + +impl std::fmt::Display for RedactionMode { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::None => write!(f, "none"), + Self::Redacted => write!(f, "redacted"), + Self::Hashed => write!(f, "hashed"), + Self::Summary => write!(f, "summary"), + Self::Reference => write!(f, "reference"), + } + } +} + +impl RedactionMode { + pub fn from_str_opt(s: &str) -> Option { + match s { + "none" => Some(Self::None), + "redacted" => Some(Self::Redacted), + "hashed" => Some(Self::Hashed), + "summary" => Some(Self::Summary), + "reference" => Some(Self::Reference), + _ => None, + } + } + + pub fn as_str(&self) -> &str { + match self { + Self::None => "none", + Self::Redacted => "redacted", + Self::Hashed => "hashed", + Self::Summary => "summary", + Self::Reference => "reference", + } + } +} + +/// Redaction handling for one JSON-shaped field path. +#[derive(Debug, Clone, Default)] +pub struct RedactedField { + /// JSONPath-like field path, relative to the containing payload + pub path: String, + /// How the field was represented + pub mode: RedactionMode, + /// Human-readable reason or policy that caused this handling + pub reason: Option, +} + +impl RedactedField { + /// Create a new RedactedField with default values. + pub fn new() -> Self { + Self::default() + } + + /// Load RedactedField from a JSON string. + pub fn from_json(json: &str, ctx: &LoadContext) -> Result { + let value: serde_json::Value = serde_json::from_str(json)?; + Ok(Self::load_from_value(&value, ctx)) + } + + /// Load RedactedField from a YAML string. + pub fn from_yaml(yaml: &str, ctx: &LoadContext) -> Result { + let value: serde_json::Value = serde_yaml::from_str(yaml)?; + Ok(Self::load_from_value(&value, ctx)) + } + + /// Load RedactedField from a `serde_json::Value`. + /// + /// Calls `ctx.process_input` before field extraction. + pub fn load_from_value(value: &serde_json::Value, ctx: &LoadContext) -> Self { + let value = ctx.process_input(value.clone()); + Self { + path: value + .get("path") + .and_then(|v| v.as_str()) + .unwrap_or_default() + .to_string(), + mode: value + .get("mode") + .and_then(|v| v.as_str()) + .and_then(|s| RedactionMode::from_str_opt(s)) + .unwrap_or(RedactionMode::None), + reason: value + .get("reason") + .and_then(|v| v.as_str()) + .map(|s| s.to_string()), + } + } + + /// Serialize RedactedField to a `serde_json::Value`. + /// + /// Calls `ctx.process_dict` after serialization. + pub fn to_value(&self, ctx: &SaveContext) -> serde_json::Value { + let mut result = serde_json::Map::new(); + // Write base fields + if !self.path.is_empty() { + result.insert( + "path".to_string(), + serde_json::Value::String(self.path.clone()), + ); + } + result.insert( + "mode".to_string(), + serde_json::Value::String(self.mode.to_string()), + ); + if let Some(ref val) = self.reason { + result.insert("reason".to_string(), serde_json::Value::String(val.clone())); + } + ctx.process_dict(serde_json::Value::Object(result)) + } + + /// Serialize RedactedField to a JSON string. + pub fn to_json(&self, ctx: &SaveContext) -> Result { + serde_json::to_string_pretty(&self.to_value(ctx)) + } + + /// Serialize RedactedField to a YAML string. + pub fn to_yaml(&self, ctx: &SaveContext) -> Result { + serde_yaml::to_string(&self.to_value(ctx)) + } +} diff --git a/runtime/rust/prompty/src/model/events/redaction_metadata.rs b/runtime/rust/prompty/src/model/events/redaction_metadata.rs new file mode 100644 index 00000000..f2da1343 --- /dev/null +++ b/runtime/rust/prompty/src/model/events/redaction_metadata.rs @@ -0,0 +1,113 @@ +// +// Code generated by Typra emitter; DO NOT EDIT. + +#![allow( + unused_imports, + dead_code, + non_camel_case_types, + unused_variables, + clippy::all +)] + +use super::super::context::{LoadContext, SaveContext}; + +use super::redacted_field::RedactedField; + +/// Metadata describing whether and how a payload was sanitized. +#[derive(Debug, Clone, Default)] +pub struct RedactionMetadata { + /// Whether the payload has been sanitized for persistence or external display + pub sanitized: Option, + /// Field-level redaction details + pub fields: Vec, + /// Host policy or sanitizer version that produced this metadata + pub policy: Option, +} + +impl RedactionMetadata { + /// Create a new RedactionMetadata with default values. + pub fn new() -> Self { + Self::default() + } + + /// Load RedactionMetadata from a JSON string. + pub fn from_json(json: &str, ctx: &LoadContext) -> Result { + let value: serde_json::Value = serde_json::from_str(json)?; + Ok(Self::load_from_value(&value, ctx)) + } + + /// Load RedactionMetadata from a YAML string. + pub fn from_yaml(yaml: &str, ctx: &LoadContext) -> Result { + let value: serde_json::Value = serde_yaml::from_str(yaml)?; + Ok(Self::load_from_value(&value, ctx)) + } + + /// Load RedactionMetadata from a `serde_json::Value`. + /// + /// Calls `ctx.process_input` before field extraction. + pub fn load_from_value(value: &serde_json::Value, ctx: &LoadContext) -> Self { + let value = ctx.process_input(value.clone()); + Self { + sanitized: value.get("sanitized").and_then(|v| v.as_bool()), + fields: value + .get("fields") + .map(|v| Self::load_fields(v, ctx)) + .unwrap_or_default(), + policy: value + .get("policy") + .and_then(|v| v.as_str()) + .map(|s| s.to_string()), + } + } + + /// Serialize RedactionMetadata to a `serde_json::Value`. + /// + /// Calls `ctx.process_dict` after serialization. + pub fn to_value(&self, ctx: &SaveContext) -> serde_json::Value { + let mut result = serde_json::Map::new(); + // Write base fields + if let Some(val) = self.sanitized { + result.insert("sanitized".to_string(), serde_json::Value::Bool(val)); + } + if !self.fields.is_empty() { + result.insert("fields".to_string(), Self::save_fields(&self.fields, ctx)); + } + if let Some(ref val) = self.policy { + result.insert("policy".to_string(), serde_json::Value::String(val.clone())); + } + ctx.process_dict(serde_json::Value::Object(result)) + } + + /// Serialize RedactionMetadata to a JSON string. + pub fn to_json(&self, ctx: &SaveContext) -> Result { + serde_json::to_string_pretty(&self.to_value(ctx)) + } + + /// Serialize RedactionMetadata to a YAML string. + pub fn to_yaml(&self, ctx: &SaveContext) -> Result { + serde_yaml::to_string(&self.to_value(ctx)) + } + + /// Load a collection of RedactedField from a JSON value. + /// Handles both array format `[{...}]`. + fn load_fields(data: &serde_json::Value, ctx: &LoadContext) -> Vec { + match data { + serde_json::Value::Array(arr) => arr + .iter() + .map(|v| RedactedField::load_from_value(v, ctx)) + .collect(), + + _ => Vec::new(), + } + } + + /// Save a collection of RedactedField to a JSON value. + fn save_fields(items: &[RedactedField], ctx: &SaveContext) -> serde_json::Value { + serde_json::Value::Array( + items + .iter() + .map(|item| item.to_value(ctx)) + .collect::>(), + ) + } +} diff --git a/runtime/rust/prompty/src/model/events/retry_payload.rs b/runtime/rust/prompty/src/model/events/retry_payload.rs new file mode 100644 index 00000000..03fe2df4 --- /dev/null +++ b/runtime/rust/prompty/src/model/events/retry_payload.rs @@ -0,0 +1,118 @@ +// +// Code generated by Typra emitter; DO NOT EDIT. + +#![allow( + unused_imports, + dead_code, + non_camel_case_types, + unused_variables, + clippy::all +)] + +use super::super::context::{LoadContext, SaveContext}; + +/// Payload for "retry" events — a transient operation will be retried. +#[derive(Debug, Clone, Default)] +pub struct RetryPayload { + /// Operation being retried + pub operation: String, + /// Attempt number about to run + pub attempt: i32, + /// Maximum configured attempts + pub max_attempts: Option, + /// Backoff delay before the next attempt in milliseconds + pub delay_ms: Option, + /// Reason for the retry + pub reason: Option, +} + +impl RetryPayload { + /// Create a new RetryPayload with default values. + pub fn new() -> Self { + Self::default() + } + + /// Load RetryPayload from a JSON string. + pub fn from_json(json: &str, ctx: &LoadContext) -> Result { + let value: serde_json::Value = serde_json::from_str(json)?; + Ok(Self::load_from_value(&value, ctx)) + } + + /// Load RetryPayload from a YAML string. + pub fn from_yaml(yaml: &str, ctx: &LoadContext) -> Result { + let value: serde_json::Value = serde_yaml::from_str(yaml)?; + Ok(Self::load_from_value(&value, ctx)) + } + + /// Load RetryPayload from a `serde_json::Value`. + /// + /// Calls `ctx.process_input` before field extraction. + pub fn load_from_value(value: &serde_json::Value, ctx: &LoadContext) -> Self { + let value = ctx.process_input(value.clone()); + Self { + operation: value + .get("operation") + .and_then(|v| v.as_str()) + .unwrap_or_default() + .to_string(), + attempt: value.get("attempt").and_then(|v| v.as_i64()).unwrap_or(0) as i32, + max_attempts: value + .get("maxAttempts") + .and_then(|v| v.as_i64()) + .map(|v| v as i32), + delay_ms: value.get("delayMs").and_then(|v| v.as_f64()), + reason: value + .get("reason") + .and_then(|v| v.as_str()) + .map(|s| s.to_string()), + } + } + + /// Serialize RetryPayload to a `serde_json::Value`. + /// + /// Calls `ctx.process_dict` after serialization. + pub fn to_value(&self, ctx: &SaveContext) -> serde_json::Value { + let mut result = serde_json::Map::new(); + // Write base fields + if !self.operation.is_empty() { + result.insert( + "operation".to_string(), + serde_json::Value::String(self.operation.clone()), + ); + } + if self.attempt != 0 { + result.insert( + "attempt".to_string(), + serde_json::Value::Number(serde_json::Number::from(self.attempt)), + ); + } + if let Some(val) = self.max_attempts { + result.insert( + "maxAttempts".to_string(), + serde_json::Value::Number(serde_json::Number::from(val)), + ); + } + if let Some(val) = self.delay_ms { + result.insert( + "delayMs".to_string(), + serde_json::Number::from_f64(val as f64) + .map(serde_json::Value::Number) + .unwrap_or(serde_json::Value::Null), + ); + } + if let Some(ref val) = self.reason { + result.insert("reason".to_string(), serde_json::Value::String(val.clone())); + } + ctx.process_dict(serde_json::Value::Object(result)) + } + + /// Serialize RetryPayload to a JSON string. + pub fn to_json(&self, ctx: &SaveContext) -> Result { + serde_json::to_string_pretty(&self.to_value(ctx)) + } + + /// Serialize RetryPayload to a YAML string. + pub fn to_yaml(&self, ctx: &SaveContext) -> Result { + serde_yaml::to_string(&self.to_value(ctx)) + } +} diff --git a/runtime/rust/prompty/src/model/events/session_end_payload.rs b/runtime/rust/prompty/src/model/events/session_end_payload.rs new file mode 100644 index 00000000..f6dec72f --- /dev/null +++ b/runtime/rust/prompty/src/model/events/session_end_payload.rs @@ -0,0 +1,154 @@ +// +// Code generated by Typra emitter; DO NOT EDIT. + +#![allow( + unused_imports, + dead_code, + non_camel_case_types, + unused_variables, + clippy::all +)] + +use super::super::context::{LoadContext, SaveContext}; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum SessionEndStatus { + Success, + Error, + Cancelled, + Interrupted, +} + +impl Default for SessionEndStatus { + fn default() -> Self { + Self::Success + } +} + +impl std::fmt::Display for SessionEndStatus { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Success => write!(f, "success"), + Self::Error => write!(f, "error"), + Self::Cancelled => write!(f, "cancelled"), + Self::Interrupted => write!(f, "interrupted"), + } + } +} + +impl SessionEndStatus { + pub fn from_str_opt(s: &str) -> Option { + match s { + "success" => Some(Self::Success), + "error" => Some(Self::Error), + "cancelled" => Some(Self::Cancelled), + "interrupted" => Some(Self::Interrupted), + _ => None, + } + } + + pub fn as_str(&self) -> &str { + match self { + Self::Success => "success", + Self::Error => "error", + Self::Cancelled => "cancelled", + Self::Interrupted => "interrupted", + } + } +} + +/// Payload for "session_end" events. +#[derive(Debug, Clone, Default)] +pub struct SessionEndPayload { + /// Stable session identifier + pub session_id: Option, + /// Final session status + pub status: Option, + /// Host-specific reason the session ended + pub reason: Option, + /// Total elapsed session duration in milliseconds + pub duration_ms: Option, +} + +impl SessionEndPayload { + /// Create a new SessionEndPayload with default values. + pub fn new() -> Self { + Self::default() + } + + /// Load SessionEndPayload from a JSON string. + pub fn from_json(json: &str, ctx: &LoadContext) -> Result { + let value: serde_json::Value = serde_json::from_str(json)?; + Ok(Self::load_from_value(&value, ctx)) + } + + /// Load SessionEndPayload from a YAML string. + pub fn from_yaml(yaml: &str, ctx: &LoadContext) -> Result { + let value: serde_json::Value = serde_yaml::from_str(yaml)?; + Ok(Self::load_from_value(&value, ctx)) + } + + /// Load SessionEndPayload from a `serde_json::Value`. + /// + /// Calls `ctx.process_input` before field extraction. + pub fn load_from_value(value: &serde_json::Value, ctx: &LoadContext) -> Self { + let value = ctx.process_input(value.clone()); + Self { + session_id: value + .get("sessionId") + .and_then(|v| v.as_str()) + .map(|s| s.to_string()), + status: value + .get("status") + .and_then(|v| v.as_str()) + .and_then(|s| SessionEndStatus::from_str_opt(s)), + reason: value + .get("reason") + .and_then(|v| v.as_str()) + .map(|s| s.to_string()), + duration_ms: value.get("durationMs").and_then(|v| v.as_f64()), + } + } + + /// Serialize SessionEndPayload to a `serde_json::Value`. + /// + /// Calls `ctx.process_dict` after serialization. + pub fn to_value(&self, ctx: &SaveContext) -> serde_json::Value { + let mut result = serde_json::Map::new(); + // Write base fields + if let Some(ref val) = self.session_id { + result.insert( + "sessionId".to_string(), + serde_json::Value::String(val.clone()), + ); + } + if let Some(ref val) = self.status { + result.insert( + "status".to_string(), + serde_json::Value::String(val.to_string()), + ); + } + if let Some(ref val) = self.reason { + result.insert("reason".to_string(), serde_json::Value::String(val.clone())); + } + if let Some(val) = self.duration_ms { + result.insert( + "durationMs".to_string(), + serde_json::Number::from_f64(val as f64) + .map(serde_json::Value::Number) + .unwrap_or(serde_json::Value::Null), + ); + } + ctx.process_dict(serde_json::Value::Object(result)) + } + + /// Serialize SessionEndPayload to a JSON string. + pub fn to_json(&self, ctx: &SaveContext) -> Result { + serde_json::to_string_pretty(&self.to_value(ctx)) + } + + /// Serialize SessionEndPayload to a YAML string. + pub fn to_yaml(&self, ctx: &SaveContext) -> Result { + serde_yaml::to_string(&self.to_value(ctx)) + } +} diff --git a/runtime/rust/prompty/src/model/events/session_event.rs b/runtime/rust/prompty/src/model/events/session_event.rs new file mode 100644 index 00000000..89f71f6d --- /dev/null +++ b/runtime/rust/prompty/src/model/events/session_event.rs @@ -0,0 +1,226 @@ +// +// Code generated by Typra emitter; DO NOT EDIT. + +#![allow( + unused_imports, + dead_code, + non_camel_case_types, + unused_variables, + clippy::all +)] + +use super::super::context::{LoadContext, SaveContext}; + +use super::redaction_metadata::RedactionMetadata; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum SessionEventType { + Session_start, + Session_end, + Session_warning, + Session_hook_start, + Session_hook_end, + Checkpoint_created, + Trajectory_event, +} + +impl Default for SessionEventType { + fn default() -> Self { + Self::Session_start + } +} + +impl std::fmt::Display for SessionEventType { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Session_start => write!(f, "session_start"), + Self::Session_end => write!(f, "session_end"), + Self::Session_warning => write!(f, "session_warning"), + Self::Session_hook_start => write!(f, "session_hook_start"), + Self::Session_hook_end => write!(f, "session_hook_end"), + Self::Checkpoint_created => write!(f, "checkpoint_created"), + Self::Trajectory_event => write!(f, "trajectory_event"), + } + } +} + +impl SessionEventType { + pub fn from_str_opt(s: &str) -> Option { + match s { + "session_start" => Some(Self::Session_start), + "session_end" => Some(Self::Session_end), + "session_warning" => Some(Self::Session_warning), + "session_hook_start" => Some(Self::Session_hook_start), + "session_hook_end" => Some(Self::Session_hook_end), + "checkpoint_created" => Some(Self::Checkpoint_created), + "trajectory_event" => Some(Self::Trajectory_event), + _ => None, + } + } + + pub fn as_str(&self) -> &str { + match self { + Self::Session_start => "session_start", + Self::Session_end => "session_end", + Self::Session_warning => "session_warning", + Self::Session_hook_start => "session_hook_start", + Self::Session_hook_end => "session_hook_end", + Self::Checkpoint_created => "checkpoint_created", + Self::Trajectory_event => "trajectory_event", + } + } +} + +/// A canonical event envelope emitted by an outer harness session. +#[derive(Debug, Clone, Default)] +pub struct SessionEvent { + /// Unique identifier for this event + pub id: String, + /// Event type discriminator + pub r#type: SessionEventType, + /// ISO 8601 UTC timestamp when the event was emitted + pub timestamp: String, + /// Stable identifier for the outer session + pub session_id: Option, + /// Associated turn identifier, when this session event is linked to a turn + pub turn_id: Option, + /// Parent event or span identifier for reconstructing event hierarchy + pub parent_id: Option, + /// Trace span identifier associated with this event + pub span_id: Option, + /// Event-specific payload. Use the typed payload model matching 'type'. + pub payload: serde_json::Value, + /// Redaction state for sensitive payload fields + pub redaction: Option, +} + +impl SessionEvent { + /// Create a new SessionEvent with default values. + pub fn new() -> Self { + Self::default() + } + + /// Load SessionEvent from a JSON string. + pub fn from_json(json: &str, ctx: &LoadContext) -> Result { + let value: serde_json::Value = serde_json::from_str(json)?; + Ok(Self::load_from_value(&value, ctx)) + } + + /// Load SessionEvent from a YAML string. + pub fn from_yaml(yaml: &str, ctx: &LoadContext) -> Result { + let value: serde_json::Value = serde_yaml::from_str(yaml)?; + Ok(Self::load_from_value(&value, ctx)) + } + + /// Load SessionEvent from a `serde_json::Value`. + /// + /// Calls `ctx.process_input` before field extraction. + pub fn load_from_value(value: &serde_json::Value, ctx: &LoadContext) -> Self { + let value = ctx.process_input(value.clone()); + Self { + id: value + .get("id") + .and_then(|v| v.as_str()) + .unwrap_or_default() + .to_string(), + r#type: value + .get("type") + .and_then(|v| v.as_str()) + .and_then(|s| SessionEventType::from_str_opt(s)) + .unwrap_or(SessionEventType::Session_start), + timestamp: value + .get("timestamp") + .and_then(|v| v.as_str()) + .unwrap_or_default() + .to_string(), + session_id: value + .get("sessionId") + .and_then(|v| v.as_str()) + .map(|s| s.to_string()), + turn_id: value + .get("turnId") + .and_then(|v| v.as_str()) + .map(|s| s.to_string()), + parent_id: value + .get("parentId") + .and_then(|v| v.as_str()) + .map(|s| s.to_string()), + span_id: value + .get("spanId") + .and_then(|v| v.as_str()) + .map(|s| s.to_string()), + payload: value + .get("payload") + .cloned() + .unwrap_or(serde_json::Value::Null), + redaction: value + .get("redaction") + .filter(|v| v.is_object() || v.is_array() || v.is_string()) + .map(|v| RedactionMetadata::load_from_value(v, ctx)), + } + } + + /// Serialize SessionEvent to a `serde_json::Value`. + /// + /// Calls `ctx.process_dict` after serialization. + pub fn to_value(&self, ctx: &SaveContext) -> serde_json::Value { + let mut result = serde_json::Map::new(); + // Write base fields + if !self.id.is_empty() { + result.insert("id".to_string(), serde_json::Value::String(self.id.clone())); + } + result.insert( + "type".to_string(), + serde_json::Value::String(self.r#type.to_string()), + ); + if !self.timestamp.is_empty() { + result.insert( + "timestamp".to_string(), + serde_json::Value::String(self.timestamp.clone()), + ); + } + if let Some(ref val) = self.session_id { + result.insert( + "sessionId".to_string(), + serde_json::Value::String(val.clone()), + ); + } + if let Some(ref val) = self.turn_id { + result.insert("turnId".to_string(), serde_json::Value::String(val.clone())); + } + if let Some(ref val) = self.parent_id { + result.insert( + "parentId".to_string(), + serde_json::Value::String(val.clone()), + ); + } + if let Some(ref val) = self.span_id { + result.insert("spanId".to_string(), serde_json::Value::String(val.clone())); + } + if !self.payload.is_null() { + result.insert("payload".to_string(), self.payload.clone()); + } + if let Some(ref val) = self.redaction { + let nested = val.to_value(ctx); + if !nested.is_null() { + result.insert("redaction".to_string(), nested); + } + } + ctx.process_dict(serde_json::Value::Object(result)) + } + + /// Serialize SessionEvent to a JSON string. + pub fn to_json(&self, ctx: &SaveContext) -> Result { + serde_json::to_string_pretty(&self.to_value(ctx)) + } + + /// Serialize SessionEvent to a YAML string. + pub fn to_yaml(&self, ctx: &SaveContext) -> Result { + serde_yaml::to_string(&self.to_value(ctx)) + } + /// Returns typed reference to the map if the field is an object. + /// Returns `None` if the field is null or not an object. + pub fn as_payload_dict(&self) -> Option<&serde_json::Map> { + self.payload.as_object() + } +} diff --git a/runtime/rust/prompty/src/model/events/session_file_ref.rs b/runtime/rust/prompty/src/model/events/session_file_ref.rs new file mode 100644 index 00000000..42b3bbd8 --- /dev/null +++ b/runtime/rust/prompty/src/model/events/session_file_ref.rs @@ -0,0 +1,125 @@ +// +// Code generated by Typra emitter; DO NOT EDIT. + +#![allow( + unused_imports, + dead_code, + non_camel_case_types, + unused_variables, + clippy::all +)] + +use super::super::context::{LoadContext, SaveContext}; + +/// A file observed or touched by a harness session. +#[derive(Debug, Clone, Default)] +pub struct SessionFileRef { + /// Stable session identifier + pub session_id: Option, + /// File path, relative to the harness workspace when possible + pub path: String, + /// Tool that first observed the file, when known + pub tool_name: Option, + /// Zero-based turn index where the file was first observed + pub turn_index: Option, + /// ISO 8601 UTC timestamp when the file was first observed + pub first_seen_at: Option, +} + +impl SessionFileRef { + /// Create a new SessionFileRef with default values. + pub fn new() -> Self { + Self::default() + } + + /// Load SessionFileRef from a JSON string. + pub fn from_json(json: &str, ctx: &LoadContext) -> Result { + let value: serde_json::Value = serde_json::from_str(json)?; + Ok(Self::load_from_value(&value, ctx)) + } + + /// Load SessionFileRef from a YAML string. + pub fn from_yaml(yaml: &str, ctx: &LoadContext) -> Result { + let value: serde_json::Value = serde_yaml::from_str(yaml)?; + Ok(Self::load_from_value(&value, ctx)) + } + + /// Load SessionFileRef from a `serde_json::Value`. + /// + /// Calls `ctx.process_input` before field extraction. + pub fn load_from_value(value: &serde_json::Value, ctx: &LoadContext) -> Self { + let value = ctx.process_input(value.clone()); + Self { + session_id: value + .get("sessionId") + .and_then(|v| v.as_str()) + .map(|s| s.to_string()), + path: value + .get("path") + .and_then(|v| v.as_str()) + .unwrap_or_default() + .to_string(), + tool_name: value + .get("toolName") + .and_then(|v| v.as_str()) + .map(|s| s.to_string()), + turn_index: value + .get("turnIndex") + .and_then(|v| v.as_i64()) + .map(|v| v as i32), + first_seen_at: value + .get("firstSeenAt") + .and_then(|v| v.as_str()) + .map(|s| s.to_string()), + } + } + + /// Serialize SessionFileRef to a `serde_json::Value`. + /// + /// Calls `ctx.process_dict` after serialization. + pub fn to_value(&self, ctx: &SaveContext) -> serde_json::Value { + let mut result = serde_json::Map::new(); + // Write base fields + if let Some(ref val) = self.session_id { + result.insert( + "sessionId".to_string(), + serde_json::Value::String(val.clone()), + ); + } + if !self.path.is_empty() { + result.insert( + "path".to_string(), + serde_json::Value::String(self.path.clone()), + ); + } + if let Some(ref val) = self.tool_name { + result.insert( + "toolName".to_string(), + serde_json::Value::String(val.clone()), + ); + } + if let Some(val) = self.turn_index { + result.insert( + "turnIndex".to_string(), + serde_json::Value::Number(serde_json::Number::from(val)), + ); + } + if let Some(ref val) = self.first_seen_at { + result.insert( + "firstSeenAt".to_string(), + serde_json::Value::String(val.clone()), + ); + } + ctx.process_dict(serde_json::Value::Object(result)) + } + + /// Serialize SessionFileRef to a JSON string. + pub fn to_json(&self, ctx: &SaveContext) -> Result { + serde_json::to_string_pretty(&self.to_value(ctx)) + } + + /// Serialize SessionFileRef to a YAML string. + pub fn to_yaml(&self, ctx: &SaveContext) -> Result { + serde_yaml::to_string(&self.to_value(ctx)) + } +} diff --git a/runtime/rust/prompty/src/model/events/session_ref.rs b/runtime/rust/prompty/src/model/events/session_ref.rs new file mode 100644 index 00000000..6760161b --- /dev/null +++ b/runtime/rust/prompty/src/model/events/session_ref.rs @@ -0,0 +1,126 @@ +// +// Code generated by Typra emitter; DO NOT EDIT. + +#![allow( + unused_imports, + dead_code, + non_camel_case_types, + unused_variables, + clippy::all +)] + +use super::super::context::{LoadContext, SaveContext}; + +/// A non-file reference observed by a harness session. +#[derive(Debug, Clone, Default)] +pub struct SessionRef { + /// Stable session identifier + pub session_id: Option, + /// Reference category + pub ref_type: String, + /// Reference value + pub ref_value: String, + /// Zero-based turn index where the reference was first observed + pub turn_index: Option, + /// ISO 8601 UTC timestamp when the reference was recorded + pub created_at: Option, +} + +impl SessionRef { + /// Create a new SessionRef with default values. + pub fn new() -> Self { + Self::default() + } + + /// Load SessionRef from a JSON string. + pub fn from_json(json: &str, ctx: &LoadContext) -> Result { + let value: serde_json::Value = serde_json::from_str(json)?; + Ok(Self::load_from_value(&value, ctx)) + } + + /// Load SessionRef from a YAML string. + pub fn from_yaml(yaml: &str, ctx: &LoadContext) -> Result { + let value: serde_json::Value = serde_yaml::from_str(yaml)?; + Ok(Self::load_from_value(&value, ctx)) + } + + /// Load SessionRef from a `serde_json::Value`. + /// + /// Calls `ctx.process_input` before field extraction. + pub fn load_from_value(value: &serde_json::Value, ctx: &LoadContext) -> Self { + let value = ctx.process_input(value.clone()); + Self { + session_id: value + .get("sessionId") + .and_then(|v| v.as_str()) + .map(|s| s.to_string()), + ref_type: value + .get("refType") + .and_then(|v| v.as_str()) + .unwrap_or_default() + .to_string(), + ref_value: value + .get("refValue") + .and_then(|v| v.as_str()) + .unwrap_or_default() + .to_string(), + turn_index: value + .get("turnIndex") + .and_then(|v| v.as_i64()) + .map(|v| v as i32), + created_at: value + .get("createdAt") + .and_then(|v| v.as_str()) + .map(|s| s.to_string()), + } + } + + /// Serialize SessionRef to a `serde_json::Value`. + /// + /// Calls `ctx.process_dict` after serialization. + pub fn to_value(&self, ctx: &SaveContext) -> serde_json::Value { + let mut result = serde_json::Map::new(); + // Write base fields + if let Some(ref val) = self.session_id { + result.insert( + "sessionId".to_string(), + serde_json::Value::String(val.clone()), + ); + } + if !self.ref_type.is_empty() { + result.insert( + "refType".to_string(), + serde_json::Value::String(self.ref_type.clone()), + ); + } + if !self.ref_value.is_empty() { + result.insert( + "refValue".to_string(), + serde_json::Value::String(self.ref_value.clone()), + ); + } + if let Some(val) = self.turn_index { + result.insert( + "turnIndex".to_string(), + serde_json::Value::Number(serde_json::Number::from(val)), + ); + } + if let Some(ref val) = self.created_at { + result.insert( + "createdAt".to_string(), + serde_json::Value::String(val.clone()), + ); + } + ctx.process_dict(serde_json::Value::Object(result)) + } + + /// Serialize SessionRef to a JSON string. + pub fn to_json(&self, ctx: &SaveContext) -> Result { + serde_json::to_string_pretty(&self.to_value(ctx)) + } + + /// Serialize SessionRef to a YAML string. + pub fn to_yaml(&self, ctx: &SaveContext) -> Result { + serde_yaml::to_string(&self.to_value(ctx)) + } +} diff --git a/runtime/rust/prompty/src/model/events/session_start_payload.rs b/runtime/rust/prompty/src/model/events/session_start_payload.rs new file mode 100644 index 00000000..e6b20c75 --- /dev/null +++ b/runtime/rust/prompty/src/model/events/session_start_payload.rs @@ -0,0 +1,175 @@ +// +// Code generated by Typra emitter; DO NOT EDIT. + +#![allow( + unused_imports, + dead_code, + non_camel_case_types, + unused_variables, + clippy::all +)] + +use super::super::context::{LoadContext, SaveContext}; + +use super::harness_context::HarnessContext; + +/// Payload for "session_start" events. +#[derive(Debug, Clone, Default)] +pub struct SessionStartPayload { + /// Stable session identifier + pub session_id: String, + /// Session event schema version + pub schema_version: Option, + /// Producer that started the session + pub producer: Option, + /// Runtime that produced the session + pub runtime: Option, + /// Prompty library version + pub prompty_version: Option, + /// ISO 8601 UTC timestamp when the session started + pub start_time: Option, + /// Selected model identifier, when known + pub selected_model: Option, + /// Selected reasoning effort or equivalent model setting, when known + pub reasoning_effort: Option, + /// Repository and execution context + pub context: Option, +} + +impl SessionStartPayload { + /// Create a new SessionStartPayload with default values. + pub fn new() -> Self { + Self::default() + } + + /// Load SessionStartPayload from a JSON string. + pub fn from_json(json: &str, ctx: &LoadContext) -> Result { + let value: serde_json::Value = serde_json::from_str(json)?; + Ok(Self::load_from_value(&value, ctx)) + } + + /// Load SessionStartPayload from a YAML string. + pub fn from_yaml(yaml: &str, ctx: &LoadContext) -> Result { + let value: serde_json::Value = serde_yaml::from_str(yaml)?; + Ok(Self::load_from_value(&value, ctx)) + } + + /// Load SessionStartPayload from a `serde_json::Value`. + /// + /// Calls `ctx.process_input` before field extraction. + pub fn load_from_value(value: &serde_json::Value, ctx: &LoadContext) -> Self { + let value = ctx.process_input(value.clone()); + Self { + session_id: value + .get("sessionId") + .and_then(|v| v.as_str()) + .unwrap_or_default() + .to_string(), + schema_version: value + .get("schemaVersion") + .and_then(|v| v.as_str()) + .map(|s| s.to_string()), + producer: value + .get("producer") + .and_then(|v| v.as_str()) + .map(|s| s.to_string()), + runtime: value + .get("runtime") + .and_then(|v| v.as_str()) + .map(|s| s.to_string()), + prompty_version: value + .get("promptyVersion") + .and_then(|v| v.as_str()) + .map(|s| s.to_string()), + start_time: value + .get("startTime") + .and_then(|v| v.as_str()) + .map(|s| s.to_string()), + selected_model: value + .get("selectedModel") + .and_then(|v| v.as_str()) + .map(|s| s.to_string()), + reasoning_effort: value + .get("reasoningEffort") + .and_then(|v| v.as_str()) + .map(|s| s.to_string()), + context: value + .get("context") + .filter(|v| v.is_object() || v.is_array() || v.is_string()) + .map(|v| HarnessContext::load_from_value(v, ctx)), + } + } + + /// Serialize SessionStartPayload to a `serde_json::Value`. + /// + /// Calls `ctx.process_dict` after serialization. + pub fn to_value(&self, ctx: &SaveContext) -> serde_json::Value { + let mut result = serde_json::Map::new(); + // Write base fields + if !self.session_id.is_empty() { + result.insert( + "sessionId".to_string(), + serde_json::Value::String(self.session_id.clone()), + ); + } + if let Some(ref val) = self.schema_version { + result.insert( + "schemaVersion".to_string(), + serde_json::Value::String(val.clone()), + ); + } + if let Some(ref val) = self.producer { + result.insert( + "producer".to_string(), + serde_json::Value::String(val.clone()), + ); + } + if let Some(ref val) = self.runtime { + result.insert( + "runtime".to_string(), + serde_json::Value::String(val.clone()), + ); + } + if let Some(ref val) = self.prompty_version { + result.insert( + "promptyVersion".to_string(), + serde_json::Value::String(val.clone()), + ); + } + if let Some(ref val) = self.start_time { + result.insert( + "startTime".to_string(), + serde_json::Value::String(val.clone()), + ); + } + if let Some(ref val) = self.selected_model { + result.insert( + "selectedModel".to_string(), + serde_json::Value::String(val.clone()), + ); + } + if let Some(ref val) = self.reasoning_effort { + result.insert( + "reasoningEffort".to_string(), + serde_json::Value::String(val.clone()), + ); + } + if let Some(ref val) = self.context { + let nested = val.to_value(ctx); + if !nested.is_null() { + result.insert("context".to_string(), nested); + } + } + ctx.process_dict(serde_json::Value::Object(result)) + } + + /// Serialize SessionStartPayload to a JSON string. + pub fn to_json(&self, ctx: &SaveContext) -> Result { + serde_json::to_string_pretty(&self.to_value(ctx)) + } + + /// Serialize SessionStartPayload to a YAML string. + pub fn to_yaml(&self, ctx: &SaveContext) -> Result { + serde_yaml::to_string(&self.to_value(ctx)) + } +} diff --git a/runtime/rust/prompty/src/model/events/session_summary.rs b/runtime/rust/prompty/src/model/events/session_summary.rs new file mode 100644 index 00000000..23669d39 --- /dev/null +++ b/runtime/rust/prompty/src/model/events/session_summary.rs @@ -0,0 +1,184 @@ +// +// Code generated by Typra emitter; DO NOT EDIT. + +#![allow( + unused_imports, + dead_code, + non_camel_case_types, + unused_variables, + clippy::all +)] + +use super::super::context::{LoadContext, SaveContext}; + +use super::super::model::token_usage::TokenUsage; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum SessionSummaryStatus { + Success, + Error, + Cancelled, + Interrupted, +} + +impl Default for SessionSummaryStatus { + fn default() -> Self { + Self::Success + } +} + +impl std::fmt::Display for SessionSummaryStatus { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Success => write!(f, "success"), + Self::Error => write!(f, "error"), + Self::Cancelled => write!(f, "cancelled"), + Self::Interrupted => write!(f, "interrupted"), + } + } +} + +impl SessionSummaryStatus { + pub fn from_str_opt(s: &str) -> Option { + match s { + "success" => Some(Self::Success), + "error" => Some(Self::Error), + "cancelled" => Some(Self::Cancelled), + "interrupted" => Some(Self::Interrupted), + _ => None, + } + } + + pub fn as_str(&self) -> &str { + match self { + Self::Success => "success", + Self::Error => "error", + Self::Cancelled => "cancelled", + Self::Interrupted => "interrupted", + } + } +} + +/// Summary statistics for a completed session trace. +#[derive(Debug, Clone, Default)] +pub struct SessionSummary { + /// Stable session identifier + pub session_id: String, + /// Final session status + pub status: Option, + /// Number of user/assistant turns in the session + pub turns: Option, + /// Number of checkpoints created + pub checkpoints: Option, + /// Aggregated token usage for the session + pub usage: Option, + /// Total elapsed session duration in milliseconds + pub duration_ms: Option, +} + +impl SessionSummary { + /// Create a new SessionSummary with default values. + pub fn new() -> Self { + Self::default() + } + + /// Load SessionSummary from a JSON string. + pub fn from_json(json: &str, ctx: &LoadContext) -> Result { + let value: serde_json::Value = serde_json::from_str(json)?; + Ok(Self::load_from_value(&value, ctx)) + } + + /// Load SessionSummary from a YAML string. + pub fn from_yaml(yaml: &str, ctx: &LoadContext) -> Result { + let value: serde_json::Value = serde_yaml::from_str(yaml)?; + Ok(Self::load_from_value(&value, ctx)) + } + + /// Load SessionSummary from a `serde_json::Value`. + /// + /// Calls `ctx.process_input` before field extraction. + pub fn load_from_value(value: &serde_json::Value, ctx: &LoadContext) -> Self { + let value = ctx.process_input(value.clone()); + Self { + session_id: value + .get("sessionId") + .and_then(|v| v.as_str()) + .unwrap_or_default() + .to_string(), + status: value + .get("status") + .and_then(|v| v.as_str()) + .and_then(|s| SessionSummaryStatus::from_str_opt(s)), + turns: value + .get("turns") + .and_then(|v| v.as_i64()) + .map(|v| v as i32), + checkpoints: value + .get("checkpoints") + .and_then(|v| v.as_i64()) + .map(|v| v as i32), + usage: value + .get("usage") + .filter(|v| v.is_object() || v.is_array() || v.is_string()) + .map(|v| TokenUsage::load_from_value(v, ctx)), + duration_ms: value.get("durationMs").and_then(|v| v.as_f64()), + } + } + + /// Serialize SessionSummary to a `serde_json::Value`. + /// + /// Calls `ctx.process_dict` after serialization. + pub fn to_value(&self, ctx: &SaveContext) -> serde_json::Value { + let mut result = serde_json::Map::new(); + // Write base fields + if !self.session_id.is_empty() { + result.insert( + "sessionId".to_string(), + serde_json::Value::String(self.session_id.clone()), + ); + } + if let Some(ref val) = self.status { + result.insert( + "status".to_string(), + serde_json::Value::String(val.to_string()), + ); + } + if let Some(val) = self.turns { + result.insert( + "turns".to_string(), + serde_json::Value::Number(serde_json::Number::from(val)), + ); + } + if let Some(val) = self.checkpoints { + result.insert( + "checkpoints".to_string(), + serde_json::Value::Number(serde_json::Number::from(val)), + ); + } + if let Some(ref val) = self.usage { + let nested = val.to_value(ctx); + if !nested.is_null() { + result.insert("usage".to_string(), nested); + } + } + if let Some(val) = self.duration_ms { + result.insert( + "durationMs".to_string(), + serde_json::Number::from_f64(val as f64) + .map(serde_json::Value::Number) + .unwrap_or(serde_json::Value::Null), + ); + } + ctx.process_dict(serde_json::Value::Object(result)) + } + + /// Serialize SessionSummary to a JSON string. + pub fn to_json(&self, ctx: &SaveContext) -> Result { + serde_json::to_string_pretty(&self.to_value(ctx)) + } + + /// Serialize SessionSummary to a YAML string. + pub fn to_yaml(&self, ctx: &SaveContext) -> Result { + serde_yaml::to_string(&self.to_value(ctx)) + } +} diff --git a/runtime/rust/prompty/src/model/events/session_trace.rs b/runtime/rust/prompty/src/model/events/session_trace.rs new file mode 100644 index 00000000..d4c51be1 --- /dev/null +++ b/runtime/rust/prompty/src/model/events/session_trace.rs @@ -0,0 +1,337 @@ +// +// Code generated by Typra emitter; DO NOT EDIT. + +#![allow( + unused_imports, + dead_code, + non_camel_case_types, + unused_variables, + clippy::all +)] + +use super::super::context::{LoadContext, SaveContext}; + +use super::checkpoint::Checkpoint; + +use super::session_event::SessionEvent; + +use super::session_file_ref::SessionFileRef; + +use super::session_ref::SessionRef; + +use super::session_summary::SessionSummary; + +use super::trajectory_event::TrajectoryEvent; + +use super::turn_trace::TurnTrace; + +/// Portable replay container for an outer harness session. +#[derive(Debug, Clone, Default)] +pub struct SessionTrace { + /// Trace schema version + pub version: String, + /// Runtime name that produced the trace + pub runtime: Option, + /// Prompty library version that produced the trace + pub prompty_version: Option, + /// Stable session identifier + pub session_id: Option, + /// Recorded session events in emission order + pub events: Vec, + /// Recorded turn traces associated with the session + pub turns: Vec, + /// Checkpoints created during the session + pub checkpoints: Vec, + /// Compact trajectory records associated with the session + pub trajectory: Vec, + /// Files observed or touched during the session + pub files: Vec, + /// Non-file references observed during the session + pub refs: Vec, + /// Optional summary computed from the event stream + pub summary: Option, +} + +impl SessionTrace { + /// Create a new SessionTrace with default values. + pub fn new() -> Self { + Self::default() + } + + /// Load SessionTrace from a JSON string. + pub fn from_json(json: &str, ctx: &LoadContext) -> Result { + let value: serde_json::Value = serde_json::from_str(json)?; + Ok(Self::load_from_value(&value, ctx)) + } + + /// Load SessionTrace from a YAML string. + pub fn from_yaml(yaml: &str, ctx: &LoadContext) -> Result { + let value: serde_json::Value = serde_yaml::from_str(yaml)?; + Ok(Self::load_from_value(&value, ctx)) + } + + /// Load SessionTrace from a `serde_json::Value`. + /// + /// Calls `ctx.process_input` before field extraction. + pub fn load_from_value(value: &serde_json::Value, ctx: &LoadContext) -> Self { + let value = ctx.process_input(value.clone()); + Self { + version: value + .get("version") + .and_then(|v| v.as_str()) + .unwrap_or_default() + .to_string(), + runtime: value + .get("runtime") + .and_then(|v| v.as_str()) + .map(|s| s.to_string()), + prompty_version: value + .get("promptyVersion") + .and_then(|v| v.as_str()) + .map(|s| s.to_string()), + session_id: value + .get("sessionId") + .and_then(|v| v.as_str()) + .map(|s| s.to_string()), + events: value + .get("events") + .map(|v| Self::load_events(v, ctx)) + .unwrap_or_default(), + turns: value + .get("turns") + .map(|v| Self::load_turns(v, ctx)) + .unwrap_or_default(), + checkpoints: value + .get("checkpoints") + .map(|v| Self::load_checkpoints(v, ctx)) + .unwrap_or_default(), + trajectory: value + .get("trajectory") + .map(|v| Self::load_trajectory(v, ctx)) + .unwrap_or_default(), + files: value + .get("files") + .map(|v| Self::load_files(v, ctx)) + .unwrap_or_default(), + refs: value + .get("refs") + .map(|v| Self::load_refs(v, ctx)) + .unwrap_or_default(), + summary: value + .get("summary") + .filter(|v| v.is_object() || v.is_array() || v.is_string()) + .map(|v| SessionSummary::load_from_value(v, ctx)), + } + } + + /// Serialize SessionTrace to a `serde_json::Value`. + /// + /// Calls `ctx.process_dict` after serialization. + pub fn to_value(&self, ctx: &SaveContext) -> serde_json::Value { + let mut result = serde_json::Map::new(); + // Write base fields + if !self.version.is_empty() { + result.insert( + "version".to_string(), + serde_json::Value::String(self.version.clone()), + ); + } + if let Some(ref val) = self.runtime { + result.insert( + "runtime".to_string(), + serde_json::Value::String(val.clone()), + ); + } + if let Some(ref val) = self.prompty_version { + result.insert( + "promptyVersion".to_string(), + serde_json::Value::String(val.clone()), + ); + } + if let Some(ref val) = self.session_id { + result.insert( + "sessionId".to_string(), + serde_json::Value::String(val.clone()), + ); + } + if !self.events.is_empty() { + result.insert("events".to_string(), Self::save_events(&self.events, ctx)); + } + if !self.turns.is_empty() { + result.insert("turns".to_string(), Self::save_turns(&self.turns, ctx)); + } + if !self.checkpoints.is_empty() { + result.insert( + "checkpoints".to_string(), + Self::save_checkpoints(&self.checkpoints, ctx), + ); + } + if !self.trajectory.is_empty() { + result.insert( + "trajectory".to_string(), + Self::save_trajectory(&self.trajectory, ctx), + ); + } + if !self.files.is_empty() { + result.insert("files".to_string(), Self::save_files(&self.files, ctx)); + } + if !self.refs.is_empty() { + result.insert("refs".to_string(), Self::save_refs(&self.refs, ctx)); + } + if let Some(ref val) = self.summary { + let nested = val.to_value(ctx); + if !nested.is_null() { + result.insert("summary".to_string(), nested); + } + } + ctx.process_dict(serde_json::Value::Object(result)) + } + + /// Serialize SessionTrace to a JSON string. + pub fn to_json(&self, ctx: &SaveContext) -> Result { + serde_json::to_string_pretty(&self.to_value(ctx)) + } + + /// Serialize SessionTrace to a YAML string. + pub fn to_yaml(&self, ctx: &SaveContext) -> Result { + serde_yaml::to_string(&self.to_value(ctx)) + } + + /// Load a collection of SessionEvent from a JSON value. + /// Handles both array format `[{...}]`. + fn load_events(data: &serde_json::Value, ctx: &LoadContext) -> Vec { + match data { + serde_json::Value::Array(arr) => arr + .iter() + .map(|v| SessionEvent::load_from_value(v, ctx)) + .collect(), + + _ => Vec::new(), + } + } + + /// Save a collection of SessionEvent to a JSON value. + fn save_events(items: &[SessionEvent], ctx: &SaveContext) -> serde_json::Value { + serde_json::Value::Array( + items + .iter() + .map(|item| item.to_value(ctx)) + .collect::>(), + ) + } + + /// Load a collection of TurnTrace from a JSON value. + /// Handles both array format `[{...}]`. + fn load_turns(data: &serde_json::Value, ctx: &LoadContext) -> Vec { + match data { + serde_json::Value::Array(arr) => arr + .iter() + .map(|v| TurnTrace::load_from_value(v, ctx)) + .collect(), + + _ => Vec::new(), + } + } + + /// Save a collection of TurnTrace to a JSON value. + fn save_turns(items: &[TurnTrace], ctx: &SaveContext) -> serde_json::Value { + serde_json::Value::Array( + items + .iter() + .map(|item| item.to_value(ctx)) + .collect::>(), + ) + } + + /// Load a collection of Checkpoint from a JSON value. + /// Handles both array format `[{...}]`. + fn load_checkpoints(data: &serde_json::Value, ctx: &LoadContext) -> Vec { + match data { + serde_json::Value::Array(arr) => arr + .iter() + .map(|v| Checkpoint::load_from_value(v, ctx)) + .collect(), + + _ => Vec::new(), + } + } + + /// Save a collection of Checkpoint to a JSON value. + fn save_checkpoints(items: &[Checkpoint], ctx: &SaveContext) -> serde_json::Value { + serde_json::Value::Array( + items + .iter() + .map(|item| item.to_value(ctx)) + .collect::>(), + ) + } + + /// Load a collection of TrajectoryEvent from a JSON value. + /// Handles both array format `[{...}]`. + fn load_trajectory(data: &serde_json::Value, ctx: &LoadContext) -> Vec { + match data { + serde_json::Value::Array(arr) => arr + .iter() + .map(|v| TrajectoryEvent::load_from_value(v, ctx)) + .collect(), + + _ => Vec::new(), + } + } + + /// Save a collection of TrajectoryEvent to a JSON value. + fn save_trajectory(items: &[TrajectoryEvent], ctx: &SaveContext) -> serde_json::Value { + serde_json::Value::Array( + items + .iter() + .map(|item| item.to_value(ctx)) + .collect::>(), + ) + } + + /// Load a collection of SessionFileRef from a JSON value. + /// Handles both array format `[{...}]`. + fn load_files(data: &serde_json::Value, ctx: &LoadContext) -> Vec { + match data { + serde_json::Value::Array(arr) => arr + .iter() + .map(|v| SessionFileRef::load_from_value(v, ctx)) + .collect(), + + _ => Vec::new(), + } + } + + /// Save a collection of SessionFileRef to a JSON value. + fn save_files(items: &[SessionFileRef], ctx: &SaveContext) -> serde_json::Value { + serde_json::Value::Array( + items + .iter() + .map(|item| item.to_value(ctx)) + .collect::>(), + ) + } + + /// Load a collection of SessionRef from a JSON value. + /// Handles both array format `[{...}]`. + fn load_refs(data: &serde_json::Value, ctx: &LoadContext) -> Vec { + match data { + serde_json::Value::Array(arr) => arr + .iter() + .map(|v| SessionRef::load_from_value(v, ctx)) + .collect(), + + _ => Vec::new(), + } + } + + /// Save a collection of SessionRef to a JSON value. + fn save_refs(items: &[SessionRef], ctx: &SaveContext) -> serde_json::Value { + serde_json::Value::Array( + items + .iter() + .map(|item| item.to_value(ctx)) + .collect::>(), + ) + } +} diff --git a/runtime/rust/prompty/src/model/events/session_warning_payload.rs b/runtime/rust/prompty/src/model/events/session_warning_payload.rs new file mode 100644 index 00000000..899465ec --- /dev/null +++ b/runtime/rust/prompty/src/model/events/session_warning_payload.rs @@ -0,0 +1,104 @@ +// +// Code generated by Typra emitter; DO NOT EDIT. + +#![allow( + unused_imports, + dead_code, + non_camel_case_types, + unused_variables, + clippy::all +)] + +use super::super::context::{LoadContext, SaveContext}; + +/// Payload for "session_warning" events. +#[derive(Debug, Clone, Default)] +pub struct SessionWarningPayload { + /// Stable machine-readable warning category + pub warning_type: String, + /// Human-readable warning message + pub message: String, + /// Additional host-specific warning details + pub details: serde_json::Value, +} + +impl SessionWarningPayload { + /// Create a new SessionWarningPayload with default values. + pub fn new() -> Self { + Self::default() + } + + /// Load SessionWarningPayload from a JSON string. + pub fn from_json(json: &str, ctx: &LoadContext) -> Result { + let value: serde_json::Value = serde_json::from_str(json)?; + Ok(Self::load_from_value(&value, ctx)) + } + + /// Load SessionWarningPayload from a YAML string. + pub fn from_yaml(yaml: &str, ctx: &LoadContext) -> Result { + let value: serde_json::Value = serde_yaml::from_str(yaml)?; + Ok(Self::load_from_value(&value, ctx)) + } + + /// Load SessionWarningPayload from a `serde_json::Value`. + /// + /// Calls `ctx.process_input` before field extraction. + pub fn load_from_value(value: &serde_json::Value, ctx: &LoadContext) -> Self { + let value = ctx.process_input(value.clone()); + Self { + warning_type: value + .get("warningType") + .and_then(|v| v.as_str()) + .unwrap_or_default() + .to_string(), + message: value + .get("message") + .and_then(|v| v.as_str()) + .unwrap_or_default() + .to_string(), + details: value + .get("details") + .cloned() + .unwrap_or(serde_json::Value::Null), + } + } + + /// Serialize SessionWarningPayload to a `serde_json::Value`. + /// + /// Calls `ctx.process_dict` after serialization. + pub fn to_value(&self, ctx: &SaveContext) -> serde_json::Value { + let mut result = serde_json::Map::new(); + // Write base fields + if !self.warning_type.is_empty() { + result.insert( + "warningType".to_string(), + serde_json::Value::String(self.warning_type.clone()), + ); + } + if !self.message.is_empty() { + result.insert( + "message".to_string(), + serde_json::Value::String(self.message.clone()), + ); + } + if !self.details.is_null() { + result.insert("details".to_string(), self.details.clone()); + } + ctx.process_dict(serde_json::Value::Object(result)) + } + + /// Serialize SessionWarningPayload to a JSON string. + pub fn to_json(&self, ctx: &SaveContext) -> Result { + serde_json::to_string_pretty(&self.to_value(ctx)) + } + + /// Serialize SessionWarningPayload to a YAML string. + pub fn to_yaml(&self, ctx: &SaveContext) -> Result { + serde_yaml::to_string(&self.to_value(ctx)) + } + /// Returns typed reference to the map if the field is an object. + /// Returns `None` if the field is null or not an object. + pub fn as_details_dict(&self) -> Option<&serde_json::Map> { + self.details.as_object() + } +} diff --git a/runtime/rust/prompty/src/model/events/status_event_payload.rs b/runtime/rust/prompty/src/model/events/status_event_payload.rs index f99d90f6..266b5503 100644 --- a/runtime/rust/prompty/src/model/events/status_event_payload.rs +++ b/runtime/rust/prompty/src/model/events/status_event_payload.rs @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. #![allow( unused_imports, diff --git a/runtime/rust/prompty/src/model/events/stream_chunk.rs b/runtime/rust/prompty/src/model/events/stream_chunk.rs index 0a561319..427fcf54 100644 --- a/runtime/rust/prompty/src/model/events/stream_chunk.rs +++ b/runtime/rust/prompty/src/model/events/stream_chunk.rs @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. #![allow( unused_imports, diff --git a/runtime/rust/prompty/src/model/events/thinking_event_payload.rs b/runtime/rust/prompty/src/model/events/thinking_event_payload.rs index 6971f3f8..9b440896 100644 --- a/runtime/rust/prompty/src/model/events/thinking_event_payload.rs +++ b/runtime/rust/prompty/src/model/events/thinking_event_payload.rs @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. #![allow( unused_imports, diff --git a/runtime/rust/prompty/src/model/events/token_event_payload.rs b/runtime/rust/prompty/src/model/events/token_event_payload.rs index 0d291aa0..9376a1c3 100644 --- a/runtime/rust/prompty/src/model/events/token_event_payload.rs +++ b/runtime/rust/prompty/src/model/events/token_event_payload.rs @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. #![allow( unused_imports, diff --git a/runtime/rust/prompty/src/model/events/tool_call_complete_payload.rs b/runtime/rust/prompty/src/model/events/tool_call_complete_payload.rs new file mode 100644 index 00000000..6322fc76 --- /dev/null +++ b/runtime/rust/prompty/src/model/events/tool_call_complete_payload.rs @@ -0,0 +1,130 @@ +// +// Code generated by Typra emitter; DO NOT EDIT. + +#![allow( + unused_imports, + dead_code, + non_camel_case_types, + unused_variables, + clippy::all +)] + +use super::super::context::{LoadContext, SaveContext}; + +use super::super::conversation::tool_result::ToolResult; + +/// Payload for "tool_call_complete" events — a tool dispatch finished. +#[derive(Debug, Clone, Default)] +pub struct ToolCallCompletePayload { + /// The unique identifier of the tool call + pub id: Option, + /// The name of the tool that completed + pub name: String, + /// Whether the tool dispatch succeeded semantically + pub success: bool, + /// Normalized tool result + pub result: Option, + /// Tool execution duration in milliseconds + pub duration_ms: Option, + /// Machine-readable error category when success is false + pub error_kind: Option, +} + +impl ToolCallCompletePayload { + /// Create a new ToolCallCompletePayload with default values. + pub fn new() -> Self { + Self::default() + } + + /// Load ToolCallCompletePayload from a JSON string. + pub fn from_json(json: &str, ctx: &LoadContext) -> Result { + let value: serde_json::Value = serde_json::from_str(json)?; + Ok(Self::load_from_value(&value, ctx)) + } + + /// Load ToolCallCompletePayload from a YAML string. + pub fn from_yaml(yaml: &str, ctx: &LoadContext) -> Result { + let value: serde_json::Value = serde_yaml::from_str(yaml)?; + Ok(Self::load_from_value(&value, ctx)) + } + + /// Load ToolCallCompletePayload from a `serde_json::Value`. + /// + /// Calls `ctx.process_input` before field extraction. + pub fn load_from_value(value: &serde_json::Value, ctx: &LoadContext) -> Self { + let value = ctx.process_input(value.clone()); + Self { + id: value + .get("id") + .and_then(|v| v.as_str()) + .map(|s| s.to_string()), + name: value + .get("name") + .and_then(|v| v.as_str()) + .unwrap_or_default() + .to_string(), + success: value + .get("success") + .and_then(|v| v.as_bool()) + .unwrap_or(false), + result: value + .get("result") + .filter(|v| v.is_object() || v.is_array() || v.is_string()) + .map(|v| ToolResult::load_from_value(v, ctx)), + duration_ms: value.get("durationMs").and_then(|v| v.as_f64()), + error_kind: value + .get("errorKind") + .and_then(|v| v.as_str()) + .map(|s| s.to_string()), + } + } + + /// Serialize ToolCallCompletePayload to a `serde_json::Value`. + /// + /// Calls `ctx.process_dict` after serialization. + pub fn to_value(&self, ctx: &SaveContext) -> serde_json::Value { + let mut result = serde_json::Map::new(); + // Write base fields + if let Some(ref val) = self.id { + result.insert("id".to_string(), serde_json::Value::String(val.clone())); + } + if !self.name.is_empty() { + result.insert( + "name".to_string(), + serde_json::Value::String(self.name.clone()), + ); + } + result.insert("success".to_string(), serde_json::Value::Bool(self.success)); + if let Some(ref val) = self.result { + let nested = val.to_value(ctx); + if !nested.is_null() { + result.insert("result".to_string(), nested); + } + } + if let Some(val) = self.duration_ms { + result.insert( + "durationMs".to_string(), + serde_json::Number::from_f64(val as f64) + .map(serde_json::Value::Number) + .unwrap_or(serde_json::Value::Null), + ); + } + if let Some(ref val) = self.error_kind { + result.insert( + "errorKind".to_string(), + serde_json::Value::String(val.clone()), + ); + } + ctx.process_dict(serde_json::Value::Object(result)) + } + + /// Serialize ToolCallCompletePayload to a JSON string. + pub fn to_json(&self, ctx: &SaveContext) -> Result { + serde_json::to_string_pretty(&self.to_value(ctx)) + } + + /// Serialize ToolCallCompletePayload to a YAML string. + pub fn to_yaml(&self, ctx: &SaveContext) -> Result { + serde_yaml::to_string(&self.to_value(ctx)) + } +} diff --git a/runtime/rust/prompty/src/model/events/tool_call_start_payload.rs b/runtime/rust/prompty/src/model/events/tool_call_start_payload.rs index ffbbc8e2..4a396113 100644 --- a/runtime/rust/prompty/src/model/events/tool_call_start_payload.rs +++ b/runtime/rust/prompty/src/model/events/tool_call_start_payload.rs @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. #![allow( unused_imports, @@ -13,6 +14,8 @@ use super::super::context::{LoadContext, SaveContext}; /// Payload for "tool_call_start" events — the LLM has requested a tool call. #[derive(Debug, Clone, Default)] pub struct ToolCallStartPayload { + /// The unique identifier of the tool call + pub id: Option, /// The name of the tool being called pub name: String, /// The serialized JSON arguments for the tool call @@ -43,6 +46,10 @@ impl ToolCallStartPayload { pub fn load_from_value(value: &serde_json::Value, ctx: &LoadContext) -> Self { let value = ctx.process_input(value.clone()); Self { + id: value + .get("id") + .and_then(|v| v.as_str()) + .map(|s| s.to_string()), name: value .get("name") .and_then(|v| v.as_str()) @@ -62,6 +69,9 @@ impl ToolCallStartPayload { pub fn to_value(&self, ctx: &SaveContext) -> serde_json::Value { let mut result = serde_json::Map::new(); // Write base fields + if let Some(ref val) = self.id { + result.insert("id".to_string(), serde_json::Value::String(val.clone())); + } if !self.name.is_empty() { result.insert( "name".to_string(), diff --git a/runtime/rust/prompty/src/model/events/tool_execution_complete_payload.rs b/runtime/rust/prompty/src/model/events/tool_execution_complete_payload.rs new file mode 100644 index 00000000..72790c92 --- /dev/null +++ b/runtime/rust/prompty/src/model/events/tool_execution_complete_payload.rs @@ -0,0 +1,177 @@ +// +// Code generated by Typra emitter; DO NOT EDIT. + +#![allow( + unused_imports, + dead_code, + non_camel_case_types, + unused_variables, + clippy::all +)] + +use super::super::context::{LoadContext, SaveContext}; + +use super::redaction_metadata::RedactionMetadata; + +/// Payload for "tool_execution_complete" events — a concrete host tool execution finished. +#[derive(Debug, Clone, Default)] +pub struct ToolExecutionCompletePayload { + /// Stable host execution request identifier + pub request_id: Option, + /// Associated model tool call identifier, when available + pub tool_call_id: Option, + /// Name of the host tool that executed + pub tool_name: String, + /// Whether the host execution completed successfully + pub success: bool, + /// Host-normalized execution result + pub result: Option, + /// Process or host exit code, when applicable + pub exit_code: Option, + /// Tool execution duration in milliseconds + pub duration_ms: Option, + /// Machine-readable error category when success is false + pub error_kind: Option, + /// Host-specific telemetry for the execution + pub telemetry: serde_json::Value, + /// Redaction state for sensitive result fields + pub redaction: Option, +} + +impl ToolExecutionCompletePayload { + /// Create a new ToolExecutionCompletePayload with default values. + pub fn new() -> Self { + Self::default() + } + + /// Load ToolExecutionCompletePayload from a JSON string. + pub fn from_json(json: &str, ctx: &LoadContext) -> Result { + let value: serde_json::Value = serde_json::from_str(json)?; + Ok(Self::load_from_value(&value, ctx)) + } + + /// Load ToolExecutionCompletePayload from a YAML string. + pub fn from_yaml(yaml: &str, ctx: &LoadContext) -> Result { + let value: serde_json::Value = serde_yaml::from_str(yaml)?; + Ok(Self::load_from_value(&value, ctx)) + } + + /// Load ToolExecutionCompletePayload from a `serde_json::Value`. + /// + /// Calls `ctx.process_input` before field extraction. + pub fn load_from_value(value: &serde_json::Value, ctx: &LoadContext) -> Self { + let value = ctx.process_input(value.clone()); + Self { + request_id: value + .get("requestId") + .and_then(|v| v.as_str()) + .map(|s| s.to_string()), + tool_call_id: value + .get("toolCallId") + .and_then(|v| v.as_str()) + .map(|s| s.to_string()), + tool_name: value + .get("toolName") + .and_then(|v| v.as_str()) + .unwrap_or_default() + .to_string(), + success: value + .get("success") + .and_then(|v| v.as_bool()) + .unwrap_or(false), + result: value.get("result").cloned(), + exit_code: value + .get("exitCode") + .and_then(|v| v.as_i64()) + .map(|v| v as i32), + duration_ms: value.get("durationMs").and_then(|v| v.as_f64()), + error_kind: value + .get("errorKind") + .and_then(|v| v.as_str()) + .map(|s| s.to_string()), + telemetry: value + .get("telemetry") + .cloned() + .unwrap_or(serde_json::Value::Null), + redaction: value + .get("redaction") + .filter(|v| v.is_object() || v.is_array() || v.is_string()) + .map(|v| RedactionMetadata::load_from_value(v, ctx)), + } + } + + /// Serialize ToolExecutionCompletePayload to a `serde_json::Value`. + /// + /// Calls `ctx.process_dict` after serialization. + pub fn to_value(&self, ctx: &SaveContext) -> serde_json::Value { + let mut result = serde_json::Map::new(); + // Write base fields + if let Some(ref val) = self.request_id { + result.insert( + "requestId".to_string(), + serde_json::Value::String(val.clone()), + ); + } + if let Some(ref val) = self.tool_call_id { + result.insert( + "toolCallId".to_string(), + serde_json::Value::String(val.clone()), + ); + } + if !self.tool_name.is_empty() { + result.insert( + "toolName".to_string(), + serde_json::Value::String(self.tool_name.clone()), + ); + } + result.insert("success".to_string(), serde_json::Value::Bool(self.success)); + if let Some(ref val) = self.result { + result.insert("result".to_string(), val.clone()); + } + if let Some(val) = self.exit_code { + result.insert( + "exitCode".to_string(), + serde_json::Value::Number(serde_json::Number::from(val)), + ); + } + if let Some(val) = self.duration_ms { + result.insert( + "durationMs".to_string(), + serde_json::Number::from_f64(val as f64) + .map(serde_json::Value::Number) + .unwrap_or(serde_json::Value::Null), + ); + } + if let Some(ref val) = self.error_kind { + result.insert( + "errorKind".to_string(), + serde_json::Value::String(val.clone()), + ); + } + if !self.telemetry.is_null() { + result.insert("telemetry".to_string(), self.telemetry.clone()); + } + if let Some(ref val) = self.redaction { + let nested = val.to_value(ctx); + if !nested.is_null() { + result.insert("redaction".to_string(), nested); + } + } + ctx.process_dict(serde_json::Value::Object(result)) + } + + /// Serialize ToolExecutionCompletePayload to a JSON string. + pub fn to_json(&self, ctx: &SaveContext) -> Result { + serde_json::to_string_pretty(&self.to_value(ctx)) + } + + /// Serialize ToolExecutionCompletePayload to a YAML string. + pub fn to_yaml(&self, ctx: &SaveContext) -> Result { + serde_yaml::to_string(&self.to_value(ctx)) + } + /// Returns typed reference to the map if the field is an object. + /// Returns `None` if the field is null or not an object. + pub fn as_telemetry_dict(&self) -> Option<&serde_json::Map> { + self.telemetry.as_object() + } +} diff --git a/runtime/rust/prompty/src/model/events/tool_execution_start_payload.rs b/runtime/rust/prompty/src/model/events/tool_execution_start_payload.rs new file mode 100644 index 00000000..b37e3194 --- /dev/null +++ b/runtime/rust/prompty/src/model/events/tool_execution_start_payload.rs @@ -0,0 +1,141 @@ +// +// Code generated by Typra emitter; DO NOT EDIT. + +#![allow( + unused_imports, + dead_code, + non_camel_case_types, + unused_variables, + clippy::all +)] + +use super::super::context::{LoadContext, SaveContext}; + +use super::redaction_metadata::RedactionMetadata; + +/// Payload for "tool_execution_start" events — the host is about to execute a concrete tool request. This is distinct from "tool_call_start", which records the model requesting a tool. Tool execution events capture the harness-side action after policy and permission checks. +#[derive(Debug, Clone, Default)] +pub struct ToolExecutionStartPayload { + /// Stable host execution request identifier + pub request_id: Option, + /// Associated model tool call identifier, when available + pub tool_call_id: Option, + /// Name of the host tool being executed + pub tool_name: String, + /// Tool arguments after host-side sanitization + pub arguments: serde_json::Value, + /// Working directory or execution scope for the tool + pub working_directory: Option, + /// Redaction state for sensitive argument fields + pub redaction: Option, +} + +impl ToolExecutionStartPayload { + /// Create a new ToolExecutionStartPayload with default values. + pub fn new() -> Self { + Self::default() + } + + /// Load ToolExecutionStartPayload from a JSON string. + pub fn from_json(json: &str, ctx: &LoadContext) -> Result { + let value: serde_json::Value = serde_json::from_str(json)?; + Ok(Self::load_from_value(&value, ctx)) + } + + /// Load ToolExecutionStartPayload from a YAML string. + pub fn from_yaml(yaml: &str, ctx: &LoadContext) -> Result { + let value: serde_json::Value = serde_yaml::from_str(yaml)?; + Ok(Self::load_from_value(&value, ctx)) + } + + /// Load ToolExecutionStartPayload from a `serde_json::Value`. + /// + /// Calls `ctx.process_input` before field extraction. + pub fn load_from_value(value: &serde_json::Value, ctx: &LoadContext) -> Self { + let value = ctx.process_input(value.clone()); + Self { + request_id: value + .get("requestId") + .and_then(|v| v.as_str()) + .map(|s| s.to_string()), + tool_call_id: value + .get("toolCallId") + .and_then(|v| v.as_str()) + .map(|s| s.to_string()), + tool_name: value + .get("toolName") + .and_then(|v| v.as_str()) + .unwrap_or_default() + .to_string(), + arguments: value + .get("arguments") + .cloned() + .unwrap_or(serde_json::Value::Null), + working_directory: value + .get("workingDirectory") + .and_then(|v| v.as_str()) + .map(|s| s.to_string()), + redaction: value + .get("redaction") + .filter(|v| v.is_object() || v.is_array() || v.is_string()) + .map(|v| RedactionMetadata::load_from_value(v, ctx)), + } + } + + /// Serialize ToolExecutionStartPayload to a `serde_json::Value`. + /// + /// Calls `ctx.process_dict` after serialization. + pub fn to_value(&self, ctx: &SaveContext) -> serde_json::Value { + let mut result = serde_json::Map::new(); + // Write base fields + if let Some(ref val) = self.request_id { + result.insert( + "requestId".to_string(), + serde_json::Value::String(val.clone()), + ); + } + if let Some(ref val) = self.tool_call_id { + result.insert( + "toolCallId".to_string(), + serde_json::Value::String(val.clone()), + ); + } + if !self.tool_name.is_empty() { + result.insert( + "toolName".to_string(), + serde_json::Value::String(self.tool_name.clone()), + ); + } + if !self.arguments.is_null() { + result.insert("arguments".to_string(), self.arguments.clone()); + } + if let Some(ref val) = self.working_directory { + result.insert( + "workingDirectory".to_string(), + serde_json::Value::String(val.clone()), + ); + } + if let Some(ref val) = self.redaction { + let nested = val.to_value(ctx); + if !nested.is_null() { + result.insert("redaction".to_string(), nested); + } + } + ctx.process_dict(serde_json::Value::Object(result)) + } + + /// Serialize ToolExecutionStartPayload to a JSON string. + pub fn to_json(&self, ctx: &SaveContext) -> Result { + serde_json::to_string_pretty(&self.to_value(ctx)) + } + + /// Serialize ToolExecutionStartPayload to a YAML string. + pub fn to_yaml(&self, ctx: &SaveContext) -> Result { + serde_yaml::to_string(&self.to_value(ctx)) + } + /// Returns typed reference to the map if the field is an object. + /// Returns `None` if the field is null or not an object. + pub fn as_arguments_dict(&self) -> Option<&serde_json::Map> { + self.arguments.as_object() + } +} diff --git a/runtime/rust/prompty/src/model/events/tool_result_payload.rs b/runtime/rust/prompty/src/model/events/tool_result_payload.rs index fc29f0b1..ebc4a6dc 100644 --- a/runtime/rust/prompty/src/model/events/tool_result_payload.rs +++ b/runtime/rust/prompty/src/model/events/tool_result_payload.rs @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. #![allow( unused_imports, diff --git a/runtime/rust/prompty/src/model/events/trajectory_event.rs b/runtime/rust/prompty/src/model/events/trajectory_event.rs new file mode 100644 index 00000000..1fc69d4c --- /dev/null +++ b/runtime/rust/prompty/src/model/events/trajectory_event.rs @@ -0,0 +1,171 @@ +// +// Code generated by Typra emitter; DO NOT EDIT. + +#![allow( + unused_imports, + dead_code, + non_camel_case_types, + unused_variables, + clippy::all +)] + +use super::super::context::{LoadContext, SaveContext}; + +use super::redaction_metadata::RedactionMetadata; + +/// A compact, replay-oriented record of one harness-side action or observation. +#[derive(Debug, Clone, Default)] +pub struct TrajectoryEvent { + /// Stable trajectory event identifier + pub id: Option, + /// Stable session identifier + pub session_id: Option, + /// Associated turn identifier, when available + pub turn_id: Option, + /// Associated tool call identifier, when available + pub tool_call_id: Option, + /// Zero-based turn index in the session + pub turn_index: Option, + /// Host-defined trajectory event category + pub event_type: String, + /// Sanitized event data + pub data: serde_json::Value, + /// ISO 8601 UTC timestamp when the trajectory event was recorded + pub created_at: Option, + /// Redaction state for sensitive trajectory fields + pub redaction: Option, +} + +impl TrajectoryEvent { + /// Create a new TrajectoryEvent with default values. + pub fn new() -> Self { + Self::default() + } + + /// Load TrajectoryEvent from a JSON string. + pub fn from_json(json: &str, ctx: &LoadContext) -> Result { + let value: serde_json::Value = serde_json::from_str(json)?; + Ok(Self::load_from_value(&value, ctx)) + } + + /// Load TrajectoryEvent from a YAML string. + pub fn from_yaml(yaml: &str, ctx: &LoadContext) -> Result { + let value: serde_json::Value = serde_yaml::from_str(yaml)?; + Ok(Self::load_from_value(&value, ctx)) + } + + /// Load TrajectoryEvent from a `serde_json::Value`. + /// + /// Calls `ctx.process_input` before field extraction. + pub fn load_from_value(value: &serde_json::Value, ctx: &LoadContext) -> Self { + let value = ctx.process_input(value.clone()); + Self { + id: value + .get("id") + .and_then(|v| v.as_str()) + .map(|s| s.to_string()), + session_id: value + .get("sessionId") + .and_then(|v| v.as_str()) + .map(|s| s.to_string()), + turn_id: value + .get("turnId") + .and_then(|v| v.as_str()) + .map(|s| s.to_string()), + tool_call_id: value + .get("toolCallId") + .and_then(|v| v.as_str()) + .map(|s| s.to_string()), + turn_index: value + .get("turnIndex") + .and_then(|v| v.as_i64()) + .map(|v| v as i32), + event_type: value + .get("eventType") + .and_then(|v| v.as_str()) + .unwrap_or_default() + .to_string(), + data: value + .get("data") + .cloned() + .unwrap_or(serde_json::Value::Null), + created_at: value + .get("createdAt") + .and_then(|v| v.as_str()) + .map(|s| s.to_string()), + redaction: value + .get("redaction") + .filter(|v| v.is_object() || v.is_array() || v.is_string()) + .map(|v| RedactionMetadata::load_from_value(v, ctx)), + } + } + + /// Serialize TrajectoryEvent to a `serde_json::Value`. + /// + /// Calls `ctx.process_dict` after serialization. + pub fn to_value(&self, ctx: &SaveContext) -> serde_json::Value { + let mut result = serde_json::Map::new(); + // Write base fields + if let Some(ref val) = self.id { + result.insert("id".to_string(), serde_json::Value::String(val.clone())); + } + if let Some(ref val) = self.session_id { + result.insert( + "sessionId".to_string(), + serde_json::Value::String(val.clone()), + ); + } + if let Some(ref val) = self.turn_id { + result.insert("turnId".to_string(), serde_json::Value::String(val.clone())); + } + if let Some(ref val) = self.tool_call_id { + result.insert( + "toolCallId".to_string(), + serde_json::Value::String(val.clone()), + ); + } + if let Some(val) = self.turn_index { + result.insert( + "turnIndex".to_string(), + serde_json::Value::Number(serde_json::Number::from(val)), + ); + } + if !self.event_type.is_empty() { + result.insert( + "eventType".to_string(), + serde_json::Value::String(self.event_type.clone()), + ); + } + if !self.data.is_null() { + result.insert("data".to_string(), self.data.clone()); + } + if let Some(ref val) = self.created_at { + result.insert( + "createdAt".to_string(), + serde_json::Value::String(val.clone()), + ); + } + if let Some(ref val) = self.redaction { + let nested = val.to_value(ctx); + if !nested.is_null() { + result.insert("redaction".to_string(), nested); + } + } + ctx.process_dict(serde_json::Value::Object(result)) + } + + /// Serialize TrajectoryEvent to a JSON string. + pub fn to_json(&self, ctx: &SaveContext) -> Result { + serde_json::to_string_pretty(&self.to_value(ctx)) + } + + /// Serialize TrajectoryEvent to a YAML string. + pub fn to_yaml(&self, ctx: &SaveContext) -> Result { + serde_yaml::to_string(&self.to_value(ctx)) + } + /// Returns typed reference to the map if the field is an object. + /// Returns `None` if the field is null or not an object. + pub fn as_data_dict(&self) -> Option<&serde_json::Map> { + self.data.as_object() + } +} diff --git a/runtime/rust/prompty/src/model/events/turn_end_payload.rs b/runtime/rust/prompty/src/model/events/turn_end_payload.rs new file mode 100644 index 00000000..97dceb42 --- /dev/null +++ b/runtime/rust/prompty/src/model/events/turn_end_payload.rs @@ -0,0 +1,147 @@ +// +// Code generated by Typra emitter; DO NOT EDIT. + +#![allow( + unused_imports, + dead_code, + non_camel_case_types, + unused_variables, + clippy::all +)] + +use super::super::context::{LoadContext, SaveContext}; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum TurnStatus { + Success, + Error, + Cancelled, +} + +impl Default for TurnStatus { + fn default() -> Self { + Self::Success + } +} + +impl std::fmt::Display for TurnStatus { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Success => write!(f, "success"), + Self::Error => write!(f, "error"), + Self::Cancelled => write!(f, "cancelled"), + } + } +} + +impl TurnStatus { + pub fn from_str_opt(s: &str) -> Option { + match s { + "success" => Some(Self::Success), + "error" => Some(Self::Error), + "cancelled" => Some(Self::Cancelled), + _ => None, + } + } + + pub fn as_str(&self) -> &str { + match self { + Self::Success => "success", + Self::Error => "error", + Self::Cancelled => "cancelled", + } + } +} + +/// Payload for "turn_end" events — a turn has completed. +#[derive(Debug, Clone, Default)] +pub struct TurnEndPayload { + /// Number of tool-call iterations performed + pub iterations: Option, + /// Final semantic status of the turn + pub status: Option, + /// Final response after processing, if available + pub response: Option, + /// Total elapsed turn duration in milliseconds + pub duration_ms: Option, +} + +impl TurnEndPayload { + /// Create a new TurnEndPayload with default values. + pub fn new() -> Self { + Self::default() + } + + /// Load TurnEndPayload from a JSON string. + pub fn from_json(json: &str, ctx: &LoadContext) -> Result { + let value: serde_json::Value = serde_json::from_str(json)?; + Ok(Self::load_from_value(&value, ctx)) + } + + /// Load TurnEndPayload from a YAML string. + pub fn from_yaml(yaml: &str, ctx: &LoadContext) -> Result { + let value: serde_json::Value = serde_yaml::from_str(yaml)?; + Ok(Self::load_from_value(&value, ctx)) + } + + /// Load TurnEndPayload from a `serde_json::Value`. + /// + /// Calls `ctx.process_input` before field extraction. + pub fn load_from_value(value: &serde_json::Value, ctx: &LoadContext) -> Self { + let value = ctx.process_input(value.clone()); + Self { + iterations: value + .get("iterations") + .and_then(|v| v.as_i64()) + .map(|v| v as i32), + status: value + .get("status") + .and_then(|v| v.as_str()) + .and_then(|s| TurnStatus::from_str_opt(s)), + response: value.get("response").cloned(), + duration_ms: value.get("durationMs").and_then(|v| v.as_f64()), + } + } + + /// Serialize TurnEndPayload to a `serde_json::Value`. + /// + /// Calls `ctx.process_dict` after serialization. + pub fn to_value(&self, ctx: &SaveContext) -> serde_json::Value { + let mut result = serde_json::Map::new(); + // Write base fields + if let Some(val) = self.iterations { + result.insert( + "iterations".to_string(), + serde_json::Value::Number(serde_json::Number::from(val)), + ); + } + if let Some(ref val) = self.status { + result.insert( + "status".to_string(), + serde_json::Value::String(val.to_string()), + ); + } + if let Some(ref val) = self.response { + result.insert("response".to_string(), val.clone()); + } + if let Some(val) = self.duration_ms { + result.insert( + "durationMs".to_string(), + serde_json::Number::from_f64(val as f64) + .map(serde_json::Value::Number) + .unwrap_or(serde_json::Value::Null), + ); + } + ctx.process_dict(serde_json::Value::Object(result)) + } + + /// Serialize TurnEndPayload to a JSON string. + pub fn to_json(&self, ctx: &SaveContext) -> Result { + serde_json::to_string_pretty(&self.to_value(ctx)) + } + + /// Serialize TurnEndPayload to a YAML string. + pub fn to_yaml(&self, ctx: &SaveContext) -> Result { + serde_yaml::to_string(&self.to_value(ctx)) + } +} diff --git a/runtime/rust/prompty/src/model/events/turn_event.rs b/runtime/rust/prompty/src/model/events/turn_event.rs new file mode 100644 index 00000000..b633cd71 --- /dev/null +++ b/runtime/rust/prompty/src/model/events/turn_event.rs @@ -0,0 +1,280 @@ +// +// Code generated by Typra emitter; DO NOT EDIT. + +#![allow( + unused_imports, + dead_code, + non_camel_case_types, + unused_variables, + clippy::all +)] + +use super::super::context::{LoadContext, SaveContext}; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum 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, +} + +impl Default for TurnEventType { + fn default() -> Self { + Self::Turn_start + } +} + +impl std::fmt::Display for TurnEventType { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Turn_start => write!(f, "turn_start"), + Self::Turn_end => write!(f, "turn_end"), + Self::Llm_start => write!(f, "llm_start"), + Self::Llm_complete => write!(f, "llm_complete"), + Self::Retry => write!(f, "retry"), + Self::Permission_requested => write!(f, "permission_requested"), + Self::Permission_completed => write!(f, "permission_completed"), + Self::Token => write!(f, "token"), + Self::Thinking => write!(f, "thinking"), + Self::Tool_call_start => write!(f, "tool_call_start"), + Self::Tool_call_complete => write!(f, "tool_call_complete"), + Self::Tool_execution_start => write!(f, "tool_execution_start"), + Self::Tool_execution_complete => write!(f, "tool_execution_complete"), + Self::Tool_result => write!(f, "tool_result"), + Self::Hook_start => write!(f, "hook_start"), + Self::Hook_end => write!(f, "hook_end"), + Self::Status => write!(f, "status"), + Self::Messages_updated => write!(f, "messages_updated"), + Self::Done => write!(f, "done"), + Self::Error => write!(f, "error"), + Self::Cancelled => write!(f, "cancelled"), + Self::Compaction_start => write!(f, "compaction_start"), + Self::Compaction_complete => write!(f, "compaction_complete"), + Self::Compaction_failed => write!(f, "compaction_failed"), + } + } +} + +impl TurnEventType { + pub fn from_str_opt(s: &str) -> Option { + match s { + "turn_start" => Some(Self::Turn_start), + "turn_end" => Some(Self::Turn_end), + "llm_start" => Some(Self::Llm_start), + "llm_complete" => Some(Self::Llm_complete), + "retry" => Some(Self::Retry), + "permission_requested" => Some(Self::Permission_requested), + "permission_completed" => Some(Self::Permission_completed), + "token" => Some(Self::Token), + "thinking" => Some(Self::Thinking), + "tool_call_start" => Some(Self::Tool_call_start), + "tool_call_complete" => Some(Self::Tool_call_complete), + "tool_execution_start" => Some(Self::Tool_execution_start), + "tool_execution_complete" => Some(Self::Tool_execution_complete), + "tool_result" => Some(Self::Tool_result), + "hook_start" => Some(Self::Hook_start), + "hook_end" => Some(Self::Hook_end), + "status" => Some(Self::Status), + "messages_updated" => Some(Self::Messages_updated), + "done" => Some(Self::Done), + "error" => Some(Self::Error), + "cancelled" => Some(Self::Cancelled), + "compaction_start" => Some(Self::Compaction_start), + "compaction_complete" => Some(Self::Compaction_complete), + "compaction_failed" => Some(Self::Compaction_failed), + _ => None, + } + } + + pub fn as_str(&self) -> &str { + match self { + Self::Turn_start => "turn_start", + Self::Turn_end => "turn_end", + Self::Llm_start => "llm_start", + Self::Llm_complete => "llm_complete", + Self::Retry => "retry", + Self::Permission_requested => "permission_requested", + Self::Permission_completed => "permission_completed", + Self::Token => "token", + Self::Thinking => "thinking", + Self::Tool_call_start => "tool_call_start", + Self::Tool_call_complete => "tool_call_complete", + Self::Tool_execution_start => "tool_execution_start", + Self::Tool_execution_complete => "tool_execution_complete", + Self::Tool_result => "tool_result", + Self::Hook_start => "hook_start", + Self::Hook_end => "hook_end", + Self::Status => "status", + Self::Messages_updated => "messages_updated", + Self::Done => "done", + Self::Error => "error", + Self::Cancelled => "cancelled", + Self::Compaction_start => "compaction_start", + Self::Compaction_complete => "compaction_complete", + Self::Compaction_failed => "compaction_failed", + } + } +} + +/// A canonical event envelope emitted by the turn harness. The payload is kept JSON-shaped so runtimes can load all events even when newer payload types are added; event-specific typed payload models below define the canonical shapes. +#[derive(Debug, Clone, Default)] +pub struct TurnEvent { + /// Unique identifier for this event + pub id: String, + /// Event type discriminator + pub r#type: TurnEventType, + /// ISO 8601 UTC timestamp when the event was emitted + pub timestamp: String, + /// Stable identifier for the outer turn + pub turn_id: Option, + /// Zero-based agent-loop iteration associated with the event + pub iteration: Option, + /// Parent event or span identifier for reconstructing event hierarchy + pub parent_id: Option, + /// Trace span identifier associated with this event + pub span_id: Option, + /// Event-specific payload. Use the typed payload model matching 'type'. + pub payload: serde_json::Value, +} + +impl TurnEvent { + /// Create a new TurnEvent with default values. + pub fn new() -> Self { + Self::default() + } + + /// Load TurnEvent from a JSON string. + pub fn from_json(json: &str, ctx: &LoadContext) -> Result { + let value: serde_json::Value = serde_json::from_str(json)?; + Ok(Self::load_from_value(&value, ctx)) + } + + /// Load TurnEvent from a YAML string. + pub fn from_yaml(yaml: &str, ctx: &LoadContext) -> Result { + let value: serde_json::Value = serde_yaml::from_str(yaml)?; + Ok(Self::load_from_value(&value, ctx)) + } + + /// Load TurnEvent from a `serde_json::Value`. + /// + /// Calls `ctx.process_input` before field extraction. + pub fn load_from_value(value: &serde_json::Value, ctx: &LoadContext) -> Self { + let value = ctx.process_input(value.clone()); + Self { + id: value + .get("id") + .and_then(|v| v.as_str()) + .unwrap_or_default() + .to_string(), + r#type: value + .get("type") + .and_then(|v| v.as_str()) + .and_then(|s| TurnEventType::from_str_opt(s)) + .unwrap_or(TurnEventType::Turn_start), + timestamp: value + .get("timestamp") + .and_then(|v| v.as_str()) + .unwrap_or_default() + .to_string(), + turn_id: value + .get("turnId") + .and_then(|v| v.as_str()) + .map(|s| s.to_string()), + iteration: value + .get("iteration") + .and_then(|v| v.as_i64()) + .map(|v| v as i32), + parent_id: value + .get("parentId") + .and_then(|v| v.as_str()) + .map(|s| s.to_string()), + span_id: value + .get("spanId") + .and_then(|v| v.as_str()) + .map(|s| s.to_string()), + payload: value + .get("payload") + .cloned() + .unwrap_or(serde_json::Value::Null), + } + } + + /// Serialize TurnEvent to a `serde_json::Value`. + /// + /// Calls `ctx.process_dict` after serialization. + pub fn to_value(&self, ctx: &SaveContext) -> serde_json::Value { + let mut result = serde_json::Map::new(); + // Write base fields + if !self.id.is_empty() { + result.insert("id".to_string(), serde_json::Value::String(self.id.clone())); + } + result.insert( + "type".to_string(), + serde_json::Value::String(self.r#type.to_string()), + ); + if !self.timestamp.is_empty() { + result.insert( + "timestamp".to_string(), + serde_json::Value::String(self.timestamp.clone()), + ); + } + if let Some(ref val) = self.turn_id { + result.insert("turnId".to_string(), serde_json::Value::String(val.clone())); + } + if let Some(val) = self.iteration { + result.insert( + "iteration".to_string(), + serde_json::Value::Number(serde_json::Number::from(val)), + ); + } + if let Some(ref val) = self.parent_id { + result.insert( + "parentId".to_string(), + serde_json::Value::String(val.clone()), + ); + } + if let Some(ref val) = self.span_id { + result.insert("spanId".to_string(), serde_json::Value::String(val.clone())); + } + if !self.payload.is_null() { + result.insert("payload".to_string(), self.payload.clone()); + } + ctx.process_dict(serde_json::Value::Object(result)) + } + + /// Serialize TurnEvent to a JSON string. + pub fn to_json(&self, ctx: &SaveContext) -> Result { + serde_json::to_string_pretty(&self.to_value(ctx)) + } + + /// Serialize TurnEvent to a YAML string. + pub fn to_yaml(&self, ctx: &SaveContext) -> Result { + serde_yaml::to_string(&self.to_value(ctx)) + } + /// Returns typed reference to the map if the field is an object. + /// Returns `None` if the field is null or not an object. + pub fn as_payload_dict(&self) -> Option<&serde_json::Map> { + self.payload.as_object() + } +} diff --git a/runtime/rust/prompty/src/model/events/turn_start_payload.rs b/runtime/rust/prompty/src/model/events/turn_start_payload.rs new file mode 100644 index 00000000..1db23a53 --- /dev/null +++ b/runtime/rust/prompty/src/model/events/turn_start_payload.rs @@ -0,0 +1,99 @@ +// +// Code generated by Typra emitter; DO NOT EDIT. + +#![allow( + unused_imports, + dead_code, + non_camel_case_types, + unused_variables, + clippy::all +)] + +use super::super::context::{LoadContext, SaveContext}; + +/// Payload for "turn_start" events — a turn is beginning. +#[derive(Debug, Clone, Default)] +pub struct TurnStartPayload { + /// Name of the loaded prompt/agent, when available + pub agent: Option, + /// Input values supplied to the turn after host-side sanitization + pub inputs: serde_json::Value, + /// Configured maximum tool-call iterations + pub max_iterations: Option, +} + +impl TurnStartPayload { + /// Create a new TurnStartPayload with default values. + pub fn new() -> Self { + Self::default() + } + + /// Load TurnStartPayload from a JSON string. + pub fn from_json(json: &str, ctx: &LoadContext) -> Result { + let value: serde_json::Value = serde_json::from_str(json)?; + Ok(Self::load_from_value(&value, ctx)) + } + + /// Load TurnStartPayload from a YAML string. + pub fn from_yaml(yaml: &str, ctx: &LoadContext) -> Result { + let value: serde_json::Value = serde_yaml::from_str(yaml)?; + Ok(Self::load_from_value(&value, ctx)) + } + + /// Load TurnStartPayload from a `serde_json::Value`. + /// + /// Calls `ctx.process_input` before field extraction. + pub fn load_from_value(value: &serde_json::Value, ctx: &LoadContext) -> Self { + let value = ctx.process_input(value.clone()); + Self { + agent: value + .get("agent") + .and_then(|v| v.as_str()) + .map(|s| s.to_string()), + inputs: value + .get("inputs") + .cloned() + .unwrap_or(serde_json::Value::Null), + max_iterations: value + .get("maxIterations") + .and_then(|v| v.as_i64()) + .map(|v| v as i32), + } + } + + /// Serialize TurnStartPayload to a `serde_json::Value`. + /// + /// Calls `ctx.process_dict` after serialization. + pub fn to_value(&self, ctx: &SaveContext) -> serde_json::Value { + let mut result = serde_json::Map::new(); + // Write base fields + if let Some(ref val) = self.agent { + result.insert("agent".to_string(), serde_json::Value::String(val.clone())); + } + if !self.inputs.is_null() { + result.insert("inputs".to_string(), self.inputs.clone()); + } + if let Some(val) = self.max_iterations { + result.insert( + "maxIterations".to_string(), + serde_json::Value::Number(serde_json::Number::from(val)), + ); + } + ctx.process_dict(serde_json::Value::Object(result)) + } + + /// Serialize TurnStartPayload to a JSON string. + pub fn to_json(&self, ctx: &SaveContext) -> Result { + serde_json::to_string_pretty(&self.to_value(ctx)) + } + + /// Serialize TurnStartPayload to a YAML string. + pub fn to_yaml(&self, ctx: &SaveContext) -> Result { + serde_yaml::to_string(&self.to_value(ctx)) + } + /// Returns typed reference to the map if the field is an object. + /// Returns `None` if the field is null or not an object. + pub fn as_inputs_dict(&self) -> Option<&serde_json::Map> { + self.inputs.as_object() + } +} diff --git a/runtime/rust/prompty/src/model/events/turn_summary.rs b/runtime/rust/prompty/src/model/events/turn_summary.rs new file mode 100644 index 00000000..176f3c83 --- /dev/null +++ b/runtime/rust/prompty/src/model/events/turn_summary.rs @@ -0,0 +1,163 @@ +// +// Code generated by Typra emitter; DO NOT EDIT. + +#![allow( + unused_imports, + dead_code, + non_camel_case_types, + unused_variables, + clippy::all +)] + +use super::super::context::{LoadContext, SaveContext}; + +use super::super::model::token_usage::TokenUsage; + +/// Summary statistics for a completed turn trace. +#[derive(Debug, Clone, Default)] +pub struct TurnSummary { + /// Stable identifier for the outer turn + pub turn_id: String, + /// Final turn status: 'success', 'error', or 'cancelled' + pub status: String, + /// Number of agent-loop iterations + pub iterations: i32, + /// Number of LLM calls made during the turn + pub llm_calls: Option, + /// Number of tool calls dispatched during the turn + pub tool_calls: Option, + /// Number of retry events during the turn + pub retries: Option, + /// Aggregated token usage for the turn + pub usage: Option, + /// Total elapsed turn duration in milliseconds + pub duration_ms: Option, +} + +impl TurnSummary { + /// Create a new TurnSummary with default values. + pub fn new() -> Self { + Self::default() + } + + /// Load TurnSummary from a JSON string. + pub fn from_json(json: &str, ctx: &LoadContext) -> Result { + let value: serde_json::Value = serde_json::from_str(json)?; + Ok(Self::load_from_value(&value, ctx)) + } + + /// Load TurnSummary from a YAML string. + pub fn from_yaml(yaml: &str, ctx: &LoadContext) -> Result { + let value: serde_json::Value = serde_yaml::from_str(yaml)?; + Ok(Self::load_from_value(&value, ctx)) + } + + /// Load TurnSummary from a `serde_json::Value`. + /// + /// Calls `ctx.process_input` before field extraction. + pub fn load_from_value(value: &serde_json::Value, ctx: &LoadContext) -> Self { + let value = ctx.process_input(value.clone()); + Self { + turn_id: value + .get("turnId") + .and_then(|v| v.as_str()) + .unwrap_or_default() + .to_string(), + status: value + .get("status") + .and_then(|v| v.as_str()) + .unwrap_or_default() + .to_string(), + iterations: value + .get("iterations") + .and_then(|v| v.as_i64()) + .unwrap_or(0) as i32, + llm_calls: value + .get("llmCalls") + .and_then(|v| v.as_i64()) + .map(|v| v as i32), + tool_calls: value + .get("toolCalls") + .and_then(|v| v.as_i64()) + .map(|v| v as i32), + retries: value + .get("retries") + .and_then(|v| v.as_i64()) + .map(|v| v as i32), + usage: value + .get("usage") + .filter(|v| v.is_object() || v.is_array() || v.is_string()) + .map(|v| TokenUsage::load_from_value(v, ctx)), + duration_ms: value.get("durationMs").and_then(|v| v.as_f64()), + } + } + + /// Serialize TurnSummary to a `serde_json::Value`. + /// + /// Calls `ctx.process_dict` after serialization. + pub fn to_value(&self, ctx: &SaveContext) -> serde_json::Value { + let mut result = serde_json::Map::new(); + // Write base fields + if !self.turn_id.is_empty() { + result.insert( + "turnId".to_string(), + serde_json::Value::String(self.turn_id.clone()), + ); + } + if !self.status.is_empty() { + result.insert( + "status".to_string(), + serde_json::Value::String(self.status.clone()), + ); + } + if self.iterations != 0 { + result.insert( + "iterations".to_string(), + serde_json::Value::Number(serde_json::Number::from(self.iterations)), + ); + } + if let Some(val) = self.llm_calls { + result.insert( + "llmCalls".to_string(), + serde_json::Value::Number(serde_json::Number::from(val)), + ); + } + if let Some(val) = self.tool_calls { + result.insert( + "toolCalls".to_string(), + serde_json::Value::Number(serde_json::Number::from(val)), + ); + } + if let Some(val) = self.retries { + result.insert( + "retries".to_string(), + serde_json::Value::Number(serde_json::Number::from(val)), + ); + } + if let Some(ref val) = self.usage { + let nested = val.to_value(ctx); + if !nested.is_null() { + result.insert("usage".to_string(), nested); + } + } + if let Some(val) = self.duration_ms { + result.insert( + "durationMs".to_string(), + serde_json::Number::from_f64(val as f64) + .map(serde_json::Value::Number) + .unwrap_or(serde_json::Value::Null), + ); + } + ctx.process_dict(serde_json::Value::Object(result)) + } + + /// Serialize TurnSummary to a JSON string. + pub fn to_json(&self, ctx: &SaveContext) -> Result { + serde_json::to_string_pretty(&self.to_value(ctx)) + } + + /// Serialize TurnSummary to a YAML string. + pub fn to_yaml(&self, ctx: &SaveContext) -> Result { + serde_yaml::to_string(&self.to_value(ctx)) + } +} diff --git a/runtime/rust/prompty/src/model/events/turn_trace.rs b/runtime/rust/prompty/src/model/events/turn_trace.rs new file mode 100644 index 00000000..261d8d49 --- /dev/null +++ b/runtime/rust/prompty/src/model/events/turn_trace.rs @@ -0,0 +1,149 @@ +// +// Code generated by Typra emitter; DO NOT EDIT. + +#![allow( + unused_imports, + dead_code, + non_camel_case_types, + unused_variables, + clippy::all +)] + +use super::super::context::{LoadContext, SaveContext}; + +use super::turn_event::TurnEvent; + +use super::turn_summary::TurnSummary; + +/// Portable JSONL/replay container for a recorded turn harness run. +#[derive(Debug, Clone, Default)] +pub struct TurnTrace { + /// Trace schema version + pub version: String, + /// Runtime name that produced the trace + pub runtime: Option, + /// Prompty library version that produced the trace + pub prompty_version: Option, + /// Recorded turn events in emission order + pub events: Vec, + /// Optional summary computed from the event stream + pub summary: Option, +} + +impl TurnTrace { + /// Create a new TurnTrace with default values. + pub fn new() -> Self { + Self::default() + } + + /// Load TurnTrace from a JSON string. + pub fn from_json(json: &str, ctx: &LoadContext) -> Result { + let value: serde_json::Value = serde_json::from_str(json)?; + Ok(Self::load_from_value(&value, ctx)) + } + + /// Load TurnTrace from a YAML string. + pub fn from_yaml(yaml: &str, ctx: &LoadContext) -> Result { + let value: serde_json::Value = serde_yaml::from_str(yaml)?; + Ok(Self::load_from_value(&value, ctx)) + } + + /// Load TurnTrace from a `serde_json::Value`. + /// + /// Calls `ctx.process_input` before field extraction. + pub fn load_from_value(value: &serde_json::Value, ctx: &LoadContext) -> Self { + let value = ctx.process_input(value.clone()); + Self { + version: value + .get("version") + .and_then(|v| v.as_str()) + .unwrap_or_default() + .to_string(), + runtime: value + .get("runtime") + .and_then(|v| v.as_str()) + .map(|s| s.to_string()), + prompty_version: value + .get("promptyVersion") + .and_then(|v| v.as_str()) + .map(|s| s.to_string()), + events: value + .get("events") + .map(|v| Self::load_events(v, ctx)) + .unwrap_or_default(), + summary: value + .get("summary") + .filter(|v| v.is_object() || v.is_array() || v.is_string()) + .map(|v| TurnSummary::load_from_value(v, ctx)), + } + } + + /// Serialize TurnTrace to a `serde_json::Value`. + /// + /// Calls `ctx.process_dict` after serialization. + pub fn to_value(&self, ctx: &SaveContext) -> serde_json::Value { + let mut result = serde_json::Map::new(); + // Write base fields + if !self.version.is_empty() { + result.insert( + "version".to_string(), + serde_json::Value::String(self.version.clone()), + ); + } + if let Some(ref val) = self.runtime { + result.insert( + "runtime".to_string(), + serde_json::Value::String(val.clone()), + ); + } + if let Some(ref val) = self.prompty_version { + result.insert( + "promptyVersion".to_string(), + serde_json::Value::String(val.clone()), + ); + } + if !self.events.is_empty() { + result.insert("events".to_string(), Self::save_events(&self.events, ctx)); + } + if let Some(ref val) = self.summary { + let nested = val.to_value(ctx); + if !nested.is_null() { + result.insert("summary".to_string(), nested); + } + } + ctx.process_dict(serde_json::Value::Object(result)) + } + + /// Serialize TurnTrace to a JSON string. + pub fn to_json(&self, ctx: &SaveContext) -> Result { + serde_json::to_string_pretty(&self.to_value(ctx)) + } + + /// Serialize TurnTrace to a YAML string. + pub fn to_yaml(&self, ctx: &SaveContext) -> Result { + serde_yaml::to_string(&self.to_value(ctx)) + } + + /// Load a collection of TurnEvent from a JSON value. + /// Handles both array format `[{...}]`. + fn load_events(data: &serde_json::Value, ctx: &LoadContext) -> Vec { + match data { + serde_json::Value::Array(arr) => arr + .iter() + .map(|v| TurnEvent::load_from_value(v, ctx)) + .collect(), + + _ => Vec::new(), + } + } + + /// Save a collection of TurnEvent to a JSON value. + fn save_events(items: &[TurnEvent], ctx: &SaveContext) -> serde_json::Value { + serde_json::Value::Array( + items + .iter() + .map(|item| item.to_value(ctx)) + .collect::>(), + ) + } +} diff --git a/runtime/rust/prompty/src/model/mod.rs b/runtime/rust/prompty/src/model/mod.rs index b49cac0e..1420aa28 100644 --- a/runtime/rust/prompty/src/model/mod.rs +++ b/runtime/rust/prompty/src/model/mod.rs @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. #![allow( unused_imports, diff --git a/runtime/rust/prompty/src/model/model/mod.rs b/runtime/rust/prompty/src/model/model/mod.rs index 6323da0c..c3210380 100644 --- a/runtime/rust/prompty/src/model/model/mod.rs +++ b/runtime/rust/prompty/src/model/model/mod.rs @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. #![allow( unused_imports, diff --git a/runtime/rust/prompty/src/model/model/model.rs b/runtime/rust/prompty/src/model/model/model.rs index ea4374da..e12c63ab 100644 --- a/runtime/rust/prompty/src/model/model/model.rs +++ b/runtime/rust/prompty/src/model/model/model.rs @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. #![allow( unused_imports, diff --git a/runtime/rust/prompty/src/model/model/model_info.rs b/runtime/rust/prompty/src/model/model/model_info.rs index afb597c5..d3a0ab37 100644 --- a/runtime/rust/prompty/src/model/model/model_info.rs +++ b/runtime/rust/prompty/src/model/model/model_info.rs @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. #![allow( unused_imports, diff --git a/runtime/rust/prompty/src/model/model/model_options.rs b/runtime/rust/prompty/src/model/model/model_options.rs index bc1e51fe..03ac772a 100644 --- a/runtime/rust/prompty/src/model/model/model_options.rs +++ b/runtime/rust/prompty/src/model/model/model_options.rs @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. #![allow( unused_imports, diff --git a/runtime/rust/prompty/src/model/model/token_usage.rs b/runtime/rust/prompty/src/model/model/token_usage.rs index 7aa10164..08cdec33 100644 --- a/runtime/rust/prompty/src/model/model/token_usage.rs +++ b/runtime/rust/prompty/src/model/model/token_usage.rs @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. #![allow( unused_imports, diff --git a/runtime/rust/prompty/src/model/pipeline/checkpoint_store.rs b/runtime/rust/prompty/src/model/pipeline/checkpoint_store.rs new file mode 100644 index 00000000..14946be0 --- /dev/null +++ b/runtime/rust/prompty/src/model/pipeline/checkpoint_store.rs @@ -0,0 +1,33 @@ +// +// Code generated by Typra emitter; DO NOT EDIT. + +#![allow( + unused_imports, + dead_code, + non_camel_case_types, + unused_variables, + clippy::all +)] + +use super::super::events::checkpoint::Checkpoint; + +/// Stores and retrieves resumable session checkpoints. +#[async_trait::async_trait] +pub trait CheckpointStore: Send + Sync { + /// Persist a session checkpoint and return the stored checkpoint + async fn save( + &self, + checkpoint: &Checkpoint, + ) -> Result>; + /// Load a checkpoint by session and checkpoint identifier + async fn load( + &self, + session_id: &String, + checkpoint_id: &String, + ) -> Result, Box>; + /// List checkpoints for a session + async fn list_checkpoints( + &self, + session_id: &String, + ) -> Result, Box>; +} diff --git a/runtime/rust/prompty/src/model/pipeline/compaction_config.rs b/runtime/rust/prompty/src/model/pipeline/compaction_config.rs index bc5b26d9..288c1d8f 100644 --- a/runtime/rust/prompty/src/model/pipeline/compaction_config.rs +++ b/runtime/rust/prompty/src/model/pipeline/compaction_config.rs @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. #![allow( unused_imports, diff --git a/runtime/rust/prompty/src/model/pipeline/event_journal_writer.rs b/runtime/rust/prompty/src/model/pipeline/event_journal_writer.rs new file mode 100644 index 00000000..1bb35ecb --- /dev/null +++ b/runtime/rust/prompty/src/model/pipeline/event_journal_writer.rs @@ -0,0 +1,27 @@ +// +// Code generated by Typra emitter; DO NOT EDIT. + +#![allow( + unused_imports, + dead_code, + non_camel_case_types, + unused_variables, + clippy::all +)] + +use super::super::events::session_event::SessionEvent; + +use super::super::events::session_summary::SessionSummary; + +use super::super::events::turn_event::TurnEvent; + +/// Persists typed events to a durable replay journal. +#[async_trait::async_trait] +pub trait EventJournalWriter: Send + Sync { + /// Append a turn event to a durable replay journal + fn append_turn(&self, turn_event: &TurnEvent) -> bool; + /// Append a session event to a durable replay journal + fn append_session(&self, session_event: &SessionEvent) -> bool; + /// Finalize the journal with an optional session summary + fn close(&self, summary: &Option) -> bool; +} diff --git a/runtime/rust/prompty/src/model/pipeline/event_sink.rs b/runtime/rust/prompty/src/model/pipeline/event_sink.rs new file mode 100644 index 00000000..7b3d694e --- /dev/null +++ b/runtime/rust/prompty/src/model/pipeline/event_sink.rs @@ -0,0 +1,23 @@ +// +// Code generated by Typra emitter; DO NOT EDIT. + +#![allow( + unused_imports, + dead_code, + non_camel_case_types, + unused_variables, + clippy::all +)] + +use super::super::events::session_event::SessionEvent; + +use super::super::events::turn_event::TurnEvent; + +/// Receives typed turn and session events from a harness. +#[async_trait::async_trait] +pub trait EventSink: Send + Sync { + /// Emit a typed turn event to a host sink + fn emit_turn(&self, turn_event: &TurnEvent) -> bool; + /// Emit a typed session event to a host sink + fn emit_session(&self, session_event: &SessionEvent) -> bool; +} diff --git a/runtime/rust/prompty/src/model/pipeline/executor.rs b/runtime/rust/prompty/src/model/pipeline/executor.rs index ee18d842..de48cd8b 100644 --- a/runtime/rust/prompty/src/model/pipeline/executor.rs +++ b/runtime/rust/prompty/src/model/pipeline/executor.rs @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. #![allow( unused_imports, diff --git a/runtime/rust/prompty/src/model/pipeline/host_tool_executor.rs b/runtime/rust/prompty/src/model/pipeline/host_tool_executor.rs new file mode 100644 index 00000000..73d49ec4 --- /dev/null +++ b/runtime/rust/prompty/src/model/pipeline/host_tool_executor.rs @@ -0,0 +1,24 @@ +// +// Code generated by Typra emitter; DO NOT EDIT. + +#![allow( + unused_imports, + dead_code, + non_camel_case_types, + unused_variables, + clippy::all +)] + +use super::super::events::host_tool_request::HostToolRequest; + +use super::super::events::host_tool_result::HostToolResult; + +/// Executes host tools after policy and permission checks. +#[async_trait::async_trait] +pub trait HostToolExecutor: Send + Sync { + /// Execute a concrete host tool request and return its completion payload + async fn execute( + &self, + request: &HostToolRequest, + ) -> Result>; +} diff --git a/runtime/rust/prompty/src/model/pipeline/mod.rs b/runtime/rust/prompty/src/model/pipeline/mod.rs index 37040aa2..4ee54aea 100644 --- a/runtime/rust/prompty/src/model/pipeline/mod.rs +++ b/runtime/rust/prompty/src/model/pipeline/mod.rs @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. #![allow( unused_imports, @@ -14,6 +15,30 @@ pub use compaction_config::*; pub mod turn_options; pub use turn_options::*; +pub mod turn_model_request; +pub use turn_model_request::*; + +pub mod turn_model_response; +pub use turn_model_response::*; + +pub mod run_turn_request; +pub use run_turn_request::*; + +pub mod run_turn_result; +pub use run_turn_result::*; + +pub mod replay_journal_record; +pub use replay_journal_record::*; + +pub mod replay_verification_request; +pub use replay_verification_request::*; + +pub mod replay_mismatch; +pub use replay_mismatch::*; + +pub mod replay_verification_result; +pub use replay_verification_result::*; + pub mod renderer; pub use renderer::*; @@ -25,3 +50,18 @@ pub use executor::*; pub mod processor; pub use processor::*; + +pub mod event_sink; +pub use event_sink::*; + +pub mod event_journal_writer; +pub use event_journal_writer::*; + +pub mod permission_resolver; +pub use permission_resolver::*; + +pub mod checkpoint_store; +pub use checkpoint_store::*; + +pub mod host_tool_executor; +pub use host_tool_executor::*; diff --git a/runtime/rust/prompty/src/model/pipeline/parser.rs b/runtime/rust/prompty/src/model/pipeline/parser.rs index bd0a1ae4..9d72e205 100644 --- a/runtime/rust/prompty/src/model/pipeline/parser.rs +++ b/runtime/rust/prompty/src/model/pipeline/parser.rs @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. #![allow( unused_imports, diff --git a/runtime/rust/prompty/src/model/pipeline/permission_resolver.rs b/runtime/rust/prompty/src/model/pipeline/permission_resolver.rs new file mode 100644 index 00000000..a1632afb --- /dev/null +++ b/runtime/rust/prompty/src/model/pipeline/permission_resolver.rs @@ -0,0 +1,24 @@ +// +// Code generated by Typra emitter; DO NOT EDIT. + +#![allow( + unused_imports, + dead_code, + non_camel_case_types, + unused_variables, + clippy::all +)] + +use super::super::events::permission_decision::PermissionDecision; + +use super::super::events::permission_request::PermissionRequest; + +/// Resolves host permission requests for potentially sensitive actions. +#[async_trait::async_trait] +pub trait PermissionResolver: Send + Sync { + /// Resolve a host permission request + async fn request( + &self, + request: &PermissionRequest, + ) -> Result>; +} diff --git a/runtime/rust/prompty/src/model/pipeline/processor.rs b/runtime/rust/prompty/src/model/pipeline/processor.rs index 9c844735..e24cba85 100644 --- a/runtime/rust/prompty/src/model/pipeline/processor.rs +++ b/runtime/rust/prompty/src/model/pipeline/processor.rs @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. #![allow( unused_imports, diff --git a/runtime/rust/prompty/src/model/pipeline/renderer.rs b/runtime/rust/prompty/src/model/pipeline/renderer.rs index b25e511c..4b033097 100644 --- a/runtime/rust/prompty/src/model/pipeline/renderer.rs +++ b/runtime/rust/prompty/src/model/pipeline/renderer.rs @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. #![allow( unused_imports, diff --git a/runtime/rust/prompty/src/model/pipeline/replay_journal_record.rs b/runtime/rust/prompty/src/model/pipeline/replay_journal_record.rs new file mode 100644 index 00000000..e12335fd --- /dev/null +++ b/runtime/rust/prompty/src/model/pipeline/replay_journal_record.rs @@ -0,0 +1,279 @@ +// +// Code generated by Typra emitter; DO NOT EDIT. + +#![allow( + unused_imports, + dead_code, + non_camel_case_types, + unused_variables, + clippy::all +)] + +use super::super::context::{LoadContext, SaveContext}; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum ReplayRecordKind { + Session, + Turn, + Summary, +} + +impl Default for ReplayRecordKind { + fn default() -> Self { + Self::Session + } +} + +impl std::fmt::Display for ReplayRecordKind { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Session => write!(f, "session"), + Self::Turn => write!(f, "turn"), + Self::Summary => write!(f, "summary"), + } + } +} + +impl ReplayRecordKind { + pub fn from_str_opt(s: &str) -> Option { + match s { + "session" => Some(Self::Session), + "turn" => Some(Self::Turn), + "summary" => Some(Self::Summary), + _ => None, + } + } + + pub fn as_str(&self) -> &str { + match self { + Self::Session => "session", + Self::Turn => "turn", + Self::Summary => "summary", + } + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum ReplayRecordStatus { + Success, + Error, + Cancelled, +} + +impl Default for ReplayRecordStatus { + fn default() -> Self { + Self::Success + } +} + +impl std::fmt::Display for ReplayRecordStatus { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Success => write!(f, "success"), + Self::Error => write!(f, "error"), + Self::Cancelled => write!(f, "cancelled"), + } + } +} + +impl ReplayRecordStatus { + pub fn from_str_opt(s: &str) -> Option { + match s { + "success" => Some(Self::Success), + "error" => Some(Self::Error), + "cancelled" => Some(Self::Cancelled), + _ => None, + } + } + + pub fn as_str(&self) -> &str { + match self { + Self::Success => "success", + Self::Error => "error", + Self::Cancelled => "cancelled", + } + } +} + +/// Stable, replay-comparable projection of a journal record. Runtime journal records may carry additional payload fields, durations, telemetry, or provider-specific data. Replay verification compares this normalized shape so deterministic orchestration semantics are mechanically shared across runtimes. +#[derive(Debug, Clone, Default)] +pub struct ReplayJournalRecord { + /// Journal record kind + pub kind: ReplayRecordKind, + /// Turn or session event type, when kind is not summary + pub r#type: Option, + /// Stable harness session identifier + pub session_id: Option, + /// Stable turn identifier within the session + pub turn_id: Option, + /// Zero-based model loop iteration for turn records + pub iteration: Option, + /// Final semantic status for turn/session/summary records + pub status: Option, + /// Permission request identifier for permission request records + pub request_id: Option, + /// Host tool name for tool execution/result records + pub tool_name: Option, + /// Whether a permission or host tool operation succeeded + pub success: Option, + /// Stable error discriminator for failed records + pub error_kind: Option, + /// Number of turns represented by a summary record + pub turns: Option, + /// Number of checkpoints represented by a summary record + pub checkpoints: Option, +} + +impl ReplayJournalRecord { + /// Create a new ReplayJournalRecord with default values. + pub fn new() -> Self { + Self::default() + } + + /// Load ReplayJournalRecord from a JSON string. + pub fn from_json(json: &str, ctx: &LoadContext) -> Result { + let value: serde_json::Value = serde_json::from_str(json)?; + Ok(Self::load_from_value(&value, ctx)) + } + + /// Load ReplayJournalRecord from a YAML string. + pub fn from_yaml(yaml: &str, ctx: &LoadContext) -> Result { + let value: serde_json::Value = serde_yaml::from_str(yaml)?; + Ok(Self::load_from_value(&value, ctx)) + } + + /// Load ReplayJournalRecord from a `serde_json::Value`. + /// + /// Calls `ctx.process_input` before field extraction. + pub fn load_from_value(value: &serde_json::Value, ctx: &LoadContext) -> Self { + let value = ctx.process_input(value.clone()); + Self { + kind: value + .get("kind") + .and_then(|v| v.as_str()) + .and_then(|s| ReplayRecordKind::from_str_opt(s)) + .unwrap_or(ReplayRecordKind::Session), + r#type: value + .get("type") + .and_then(|v| v.as_str()) + .map(|s| s.to_string()), + session_id: value + .get("sessionId") + .and_then(|v| v.as_str()) + .map(|s| s.to_string()), + turn_id: value + .get("turnId") + .and_then(|v| v.as_str()) + .map(|s| s.to_string()), + iteration: value + .get("iteration") + .and_then(|v| v.as_i64()) + .map(|v| v as i32), + status: value + .get("status") + .and_then(|v| v.as_str()) + .and_then(|s| ReplayRecordStatus::from_str_opt(s)), + request_id: value + .get("requestId") + .and_then(|v| v.as_str()) + .map(|s| s.to_string()), + tool_name: value + .get("toolName") + .and_then(|v| v.as_str()) + .map(|s| s.to_string()), + success: value.get("success").and_then(|v| v.as_bool()), + error_kind: value + .get("errorKind") + .and_then(|v| v.as_str()) + .map(|s| s.to_string()), + turns: value + .get("turns") + .and_then(|v| v.as_i64()) + .map(|v| v as i32), + checkpoints: value + .get("checkpoints") + .and_then(|v| v.as_i64()) + .map(|v| v as i32), + } + } + + /// Serialize ReplayJournalRecord to a `serde_json::Value`. + /// + /// Calls `ctx.process_dict` after serialization. + pub fn to_value(&self, ctx: &SaveContext) -> serde_json::Value { + let mut result = serde_json::Map::new(); + // Write base fields + result.insert( + "kind".to_string(), + serde_json::Value::String(self.kind.to_string()), + ); + if let Some(ref val) = self.r#type { + result.insert("type".to_string(), serde_json::Value::String(val.clone())); + } + if let Some(ref val) = self.session_id { + result.insert( + "sessionId".to_string(), + serde_json::Value::String(val.clone()), + ); + } + if let Some(ref val) = self.turn_id { + result.insert("turnId".to_string(), serde_json::Value::String(val.clone())); + } + if let Some(val) = self.iteration { + result.insert( + "iteration".to_string(), + serde_json::Value::Number(serde_json::Number::from(val)), + ); + } + if let Some(ref val) = self.status { + result.insert( + "status".to_string(), + serde_json::Value::String(val.to_string()), + ); + } + if let Some(ref val) = self.request_id { + result.insert( + "requestId".to_string(), + serde_json::Value::String(val.clone()), + ); + } + if let Some(ref val) = self.tool_name { + result.insert( + "toolName".to_string(), + serde_json::Value::String(val.clone()), + ); + } + if let Some(val) = self.success { + result.insert("success".to_string(), serde_json::Value::Bool(val)); + } + if let Some(ref val) = self.error_kind { + result.insert( + "errorKind".to_string(), + serde_json::Value::String(val.clone()), + ); + } + if let Some(val) = self.turns { + result.insert( + "turns".to_string(), + serde_json::Value::Number(serde_json::Number::from(val)), + ); + } + if let Some(val) = self.checkpoints { + result.insert( + "checkpoints".to_string(), + serde_json::Value::Number(serde_json::Number::from(val)), + ); + } + ctx.process_dict(serde_json::Value::Object(result)) + } + + /// Serialize ReplayJournalRecord to a JSON string. + pub fn to_json(&self, ctx: &SaveContext) -> Result { + serde_json::to_string_pretty(&self.to_value(ctx)) + } + + /// Serialize ReplayJournalRecord to a YAML string. + pub fn to_yaml(&self, ctx: &SaveContext) -> Result { + serde_yaml::to_string(&self.to_value(ctx)) + } +} diff --git a/runtime/rust/prompty/src/model/pipeline/replay_mismatch.rs b/runtime/rust/prompty/src/model/pipeline/replay_mismatch.rs new file mode 100644 index 00000000..21639217 --- /dev/null +++ b/runtime/rust/prompty/src/model/pipeline/replay_mismatch.rs @@ -0,0 +1,112 @@ +// +// Code generated by Typra emitter; DO NOT EDIT. + +#![allow( + unused_imports, + dead_code, + non_camel_case_types, + unused_variables, + clippy::all +)] + +use super::super::context::{LoadContext, SaveContext}; + +use super::replay_journal_record::ReplayJournalRecord; + +/// A single mismatch produced by replay verification. +#[derive(Debug, Clone, Default)] +pub struct ReplayMismatch { + /// Zero-based record index where the mismatch was found + pub index: i32, + /// Expected record at this index, when present + pub expected: Option, + /// Actual record at this index, when present + pub actual: Option, + /// Human-readable mismatch explanation + pub message: String, +} + +impl ReplayMismatch { + /// Create a new ReplayMismatch with default values. + pub fn new() -> Self { + Self::default() + } + + /// Load ReplayMismatch from a JSON string. + pub fn from_json(json: &str, ctx: &LoadContext) -> Result { + let value: serde_json::Value = serde_json::from_str(json)?; + Ok(Self::load_from_value(&value, ctx)) + } + + /// Load ReplayMismatch from a YAML string. + pub fn from_yaml(yaml: &str, ctx: &LoadContext) -> Result { + let value: serde_json::Value = serde_yaml::from_str(yaml)?; + Ok(Self::load_from_value(&value, ctx)) + } + + /// Load ReplayMismatch from a `serde_json::Value`. + /// + /// Calls `ctx.process_input` before field extraction. + pub fn load_from_value(value: &serde_json::Value, ctx: &LoadContext) -> Self { + let value = ctx.process_input(value.clone()); + Self { + index: value.get("index").and_then(|v| v.as_i64()).unwrap_or(0) as i32, + expected: value + .get("expected") + .filter(|v| v.is_object() || v.is_array() || v.is_string()) + .map(|v| ReplayJournalRecord::load_from_value(v, ctx)), + actual: value + .get("actual") + .filter(|v| v.is_object() || v.is_array() || v.is_string()) + .map(|v| ReplayJournalRecord::load_from_value(v, ctx)), + message: value + .get("message") + .and_then(|v| v.as_str()) + .unwrap_or_default() + .to_string(), + } + } + + /// Serialize ReplayMismatch to a `serde_json::Value`. + /// + /// Calls `ctx.process_dict` after serialization. + pub fn to_value(&self, ctx: &SaveContext) -> serde_json::Value { + let mut result = serde_json::Map::new(); + // Write base fields + if self.index != 0 { + result.insert( + "index".to_string(), + serde_json::Value::Number(serde_json::Number::from(self.index)), + ); + } + if let Some(ref val) = self.expected { + let nested = val.to_value(ctx); + if !nested.is_null() { + result.insert("expected".to_string(), nested); + } + } + if let Some(ref val) = self.actual { + let nested = val.to_value(ctx); + if !nested.is_null() { + result.insert("actual".to_string(), nested); + } + } + if !self.message.is_empty() { + result.insert( + "message".to_string(), + serde_json::Value::String(self.message.clone()), + ); + } + ctx.process_dict(serde_json::Value::Object(result)) + } + + /// Serialize ReplayMismatch to a JSON string. + pub fn to_json(&self, ctx: &SaveContext) -> Result { + serde_json::to_string_pretty(&self.to_value(ctx)) + } + + /// Serialize ReplayMismatch to a YAML string. + pub fn to_yaml(&self, ctx: &SaveContext) -> Result { + serde_yaml::to_string(&self.to_value(ctx)) + } +} diff --git a/runtime/rust/prompty/src/model/pipeline/replay_verification_request.rs b/runtime/rust/prompty/src/model/pipeline/replay_verification_request.rs new file mode 100644 index 00000000..c3109a1e --- /dev/null +++ b/runtime/rust/prompty/src/model/pipeline/replay_verification_request.rs @@ -0,0 +1,133 @@ +// +// Code generated by Typra emitter; DO NOT EDIT. + +#![allow( + unused_imports, + dead_code, + non_camel_case_types, + unused_variables, + clippy::all +)] + +use super::super::context::{LoadContext, SaveContext}; + +use super::replay_journal_record::ReplayJournalRecord; + +/// Request accepted by a replay verifier implementation. +#[derive(Debug, Clone, Default)] +pub struct ReplayVerificationRequest { + /// Expected normalized replay records + pub expected: Vec, + /// Actual normalized replay records + pub actual: Vec, +} + +impl ReplayVerificationRequest { + /// Create a new ReplayVerificationRequest with default values. + pub fn new() -> Self { + Self::default() + } + + /// Load ReplayVerificationRequest from a JSON string. + pub fn from_json(json: &str, ctx: &LoadContext) -> Result { + let value: serde_json::Value = serde_json::from_str(json)?; + Ok(Self::load_from_value(&value, ctx)) + } + + /// Load ReplayVerificationRequest from a YAML string. + pub fn from_yaml(yaml: &str, ctx: &LoadContext) -> Result { + let value: serde_json::Value = serde_yaml::from_str(yaml)?; + Ok(Self::load_from_value(&value, ctx)) + } + + /// Load ReplayVerificationRequest from a `serde_json::Value`. + /// + /// Calls `ctx.process_input` before field extraction. + pub fn load_from_value(value: &serde_json::Value, ctx: &LoadContext) -> Self { + let value = ctx.process_input(value.clone()); + Self { + expected: value + .get("expected") + .map(|v| Self::load_expected(v, ctx)) + .unwrap_or_default(), + actual: value + .get("actual") + .map(|v| Self::load_actual(v, ctx)) + .unwrap_or_default(), + } + } + + /// Serialize ReplayVerificationRequest to a `serde_json::Value`. + /// + /// Calls `ctx.process_dict` after serialization. + pub fn to_value(&self, ctx: &SaveContext) -> serde_json::Value { + let mut result = serde_json::Map::new(); + // Write base fields + if !self.expected.is_empty() { + result.insert( + "expected".to_string(), + Self::save_expected(&self.expected, ctx), + ); + } + if !self.actual.is_empty() { + result.insert("actual".to_string(), Self::save_actual(&self.actual, ctx)); + } + ctx.process_dict(serde_json::Value::Object(result)) + } + + /// Serialize ReplayVerificationRequest to a JSON string. + pub fn to_json(&self, ctx: &SaveContext) -> Result { + serde_json::to_string_pretty(&self.to_value(ctx)) + } + + /// Serialize ReplayVerificationRequest to a YAML string. + pub fn to_yaml(&self, ctx: &SaveContext) -> Result { + serde_yaml::to_string(&self.to_value(ctx)) + } + + /// Load a collection of ReplayJournalRecord from a JSON value. + /// Handles both array format `[{...}]`. + fn load_expected(data: &serde_json::Value, ctx: &LoadContext) -> Vec { + match data { + serde_json::Value::Array(arr) => arr + .iter() + .map(|v| ReplayJournalRecord::load_from_value(v, ctx)) + .collect(), + + _ => Vec::new(), + } + } + + /// Save a collection of ReplayJournalRecord to a JSON value. + fn save_expected(items: &[ReplayJournalRecord], ctx: &SaveContext) -> serde_json::Value { + serde_json::Value::Array( + items + .iter() + .map(|item| item.to_value(ctx)) + .collect::>(), + ) + } + + /// Load a collection of ReplayJournalRecord from a JSON value. + /// Handles both array format `[{...}]`. + fn load_actual(data: &serde_json::Value, ctx: &LoadContext) -> Vec { + match data { + serde_json::Value::Array(arr) => arr + .iter() + .map(|v| ReplayJournalRecord::load_from_value(v, ctx)) + .collect(), + + _ => Vec::new(), + } + } + + /// Save a collection of ReplayJournalRecord to a JSON value. + fn save_actual(items: &[ReplayJournalRecord], ctx: &SaveContext) -> serde_json::Value { + serde_json::Value::Array( + items + .iter() + .map(|item| item.to_value(ctx)) + .collect::>(), + ) + } +} diff --git a/runtime/rust/prompty/src/model/pipeline/replay_verification_result.rs b/runtime/rust/prompty/src/model/pipeline/replay_verification_result.rs new file mode 100644 index 00000000..c2d4aaf5 --- /dev/null +++ b/runtime/rust/prompty/src/model/pipeline/replay_verification_result.rs @@ -0,0 +1,174 @@ +// +// Code generated by Typra emitter; DO NOT EDIT. + +#![allow( + unused_imports, + dead_code, + non_camel_case_types, + unused_variables, + clippy::all +)] + +use super::super::context::{LoadContext, SaveContext}; + +use super::replay_mismatch::ReplayMismatch; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum ReplayVerificationStatus { + Passed, + Failed, +} + +impl Default for ReplayVerificationStatus { + fn default() -> Self { + Self::Passed + } +} + +impl std::fmt::Display for ReplayVerificationStatus { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Passed => write!(f, "passed"), + Self::Failed => write!(f, "failed"), + } + } +} + +impl ReplayVerificationStatus { + pub fn from_str_opt(s: &str) -> Option { + match s { + "passed" => Some(Self::Passed), + "failed" => Some(Self::Failed), + _ => None, + } + } + + pub fn as_str(&self) -> &str { + match self { + Self::Passed => "passed", + Self::Failed => "failed", + } + } +} + +/// Result returned by a replay verifier implementation. +#[derive(Debug, Clone, Default)] +pub struct ReplayVerificationResult { + /// Replay verification status + pub status: ReplayVerificationStatus, + /// Record mismatches, empty when verification passed + pub mismatches: Vec, + /// Number of expected records + pub expected_count: i32, + /// Number of actual records + pub actual_count: i32, +} + +impl ReplayVerificationResult { + /// Create a new ReplayVerificationResult with default values. + pub fn new() -> Self { + Self::default() + } + + /// Load ReplayVerificationResult from a JSON string. + pub fn from_json(json: &str, ctx: &LoadContext) -> Result { + let value: serde_json::Value = serde_json::from_str(json)?; + Ok(Self::load_from_value(&value, ctx)) + } + + /// Load ReplayVerificationResult from a YAML string. + pub fn from_yaml(yaml: &str, ctx: &LoadContext) -> Result { + let value: serde_json::Value = serde_yaml::from_str(yaml)?; + Ok(Self::load_from_value(&value, ctx)) + } + + /// Load ReplayVerificationResult from a `serde_json::Value`. + /// + /// Calls `ctx.process_input` before field extraction. + pub fn load_from_value(value: &serde_json::Value, ctx: &LoadContext) -> Self { + let value = ctx.process_input(value.clone()); + Self { + status: value + .get("status") + .and_then(|v| v.as_str()) + .and_then(|s| ReplayVerificationStatus::from_str_opt(s)) + .unwrap_or(ReplayVerificationStatus::Passed), + mismatches: value + .get("mismatches") + .map(|v| Self::load_mismatches(v, ctx)) + .unwrap_or_default(), + expected_count: value + .get("expectedCount") + .and_then(|v| v.as_i64()) + .unwrap_or(0) as i32, + actual_count: value + .get("actualCount") + .and_then(|v| v.as_i64()) + .unwrap_or(0) as i32, + } + } + + /// Serialize ReplayVerificationResult to a `serde_json::Value`. + /// + /// Calls `ctx.process_dict` after serialization. + pub fn to_value(&self, ctx: &SaveContext) -> serde_json::Value { + let mut result = serde_json::Map::new(); + // Write base fields + result.insert( + "status".to_string(), + serde_json::Value::String(self.status.to_string()), + ); + if !self.mismatches.is_empty() { + result.insert( + "mismatches".to_string(), + Self::save_mismatches(&self.mismatches, ctx), + ); + } + if self.expected_count != 0 { + result.insert( + "expectedCount".to_string(), + serde_json::Value::Number(serde_json::Number::from(self.expected_count)), + ); + } + if self.actual_count != 0 { + result.insert( + "actualCount".to_string(), + serde_json::Value::Number(serde_json::Number::from(self.actual_count)), + ); + } + ctx.process_dict(serde_json::Value::Object(result)) + } + + /// Serialize ReplayVerificationResult to a JSON string. + pub fn to_json(&self, ctx: &SaveContext) -> Result { + serde_json::to_string_pretty(&self.to_value(ctx)) + } + + /// Serialize ReplayVerificationResult to a YAML string. + pub fn to_yaml(&self, ctx: &SaveContext) -> Result { + serde_yaml::to_string(&self.to_value(ctx)) + } + + /// Load a collection of ReplayMismatch from a JSON value. + /// Handles both array format `[{...}]`. + fn load_mismatches(data: &serde_json::Value, ctx: &LoadContext) -> Vec { + match data { + serde_json::Value::Array(arr) => arr + .iter() + .map(|v| ReplayMismatch::load_from_value(v, ctx)) + .collect(), + + _ => Vec::new(), + } + } + + /// Save a collection of ReplayMismatch to a JSON value. + fn save_mismatches(items: &[ReplayMismatch], ctx: &SaveContext) -> serde_json::Value { + serde_json::Value::Array( + items + .iter() + .map(|item| item.to_value(ctx)) + .collect::>(), + ) + } +} diff --git a/runtime/rust/prompty/src/model/pipeline/run_turn_request.rs b/runtime/rust/prompty/src/model/pipeline/run_turn_request.rs new file mode 100644 index 00000000..78f4e367 --- /dev/null +++ b/runtime/rust/prompty/src/model/pipeline/run_turn_request.rs @@ -0,0 +1,118 @@ +// +// Code generated by Typra emitter; DO NOT EDIT. + +#![allow( + unused_imports, + dead_code, + non_camel_case_types, + unused_variables, + clippy::all +)] + +use super::super::context::{LoadContext, SaveContext}; + +use super::turn_options::TurnOptions; + +/// Request accepted by a reference turn runner implementation. +#[derive(Debug, Clone, Default)] +pub struct RunTurnRequest { + /// Stable harness session identifier + pub session_id: String, + /// Stable turn identifier within the session + pub turn_id: String, + /// Inputs supplied to the deterministic single-turn run + pub inputs: serde_json::Value, + /// Canonical turn execution options + pub options: Option, +} + +impl RunTurnRequest { + /// Create a new RunTurnRequest with default values. + pub fn new() -> Self { + Self::default() + } + + /// Load RunTurnRequest from a JSON string. + pub fn from_json(json: &str, ctx: &LoadContext) -> Result { + let value: serde_json::Value = serde_json::from_str(json)?; + Ok(Self::load_from_value(&value, ctx)) + } + + /// Load RunTurnRequest from a YAML string. + pub fn from_yaml(yaml: &str, ctx: &LoadContext) -> Result { + let value: serde_json::Value = serde_yaml::from_str(yaml)?; + Ok(Self::load_from_value(&value, ctx)) + } + + /// Load RunTurnRequest from a `serde_json::Value`. + /// + /// Calls `ctx.process_input` before field extraction. + pub fn load_from_value(value: &serde_json::Value, ctx: &LoadContext) -> Self { + let value = ctx.process_input(value.clone()); + Self { + session_id: value + .get("sessionId") + .and_then(|v| v.as_str()) + .unwrap_or_default() + .to_string(), + turn_id: value + .get("turnId") + .and_then(|v| v.as_str()) + .unwrap_or_default() + .to_string(), + inputs: value + .get("inputs") + .cloned() + .unwrap_or(serde_json::Value::Null), + options: value + .get("options") + .filter(|v| v.is_object() || v.is_array() || v.is_string()) + .map(|v| TurnOptions::load_from_value(v, ctx)), + } + } + + /// Serialize RunTurnRequest to a `serde_json::Value`. + /// + /// Calls `ctx.process_dict` after serialization. + pub fn to_value(&self, ctx: &SaveContext) -> serde_json::Value { + let mut result = serde_json::Map::new(); + // Write base fields + if !self.session_id.is_empty() { + result.insert( + "sessionId".to_string(), + serde_json::Value::String(self.session_id.clone()), + ); + } + if !self.turn_id.is_empty() { + result.insert( + "turnId".to_string(), + serde_json::Value::String(self.turn_id.clone()), + ); + } + if !self.inputs.is_null() { + result.insert("inputs".to_string(), self.inputs.clone()); + } + if let Some(ref val) = self.options { + let nested = val.to_value(ctx); + if !nested.is_null() { + result.insert("options".to_string(), nested); + } + } + ctx.process_dict(serde_json::Value::Object(result)) + } + + /// Serialize RunTurnRequest to a JSON string. + pub fn to_json(&self, ctx: &SaveContext) -> Result { + serde_json::to_string_pretty(&self.to_value(ctx)) + } + + /// Serialize RunTurnRequest to a YAML string. + pub fn to_yaml(&self, ctx: &SaveContext) -> Result { + serde_yaml::to_string(&self.to_value(ctx)) + } + /// Returns typed reference to the map if the field is an object. + /// Returns `None` if the field is null or not an object. + pub fn as_inputs_dict(&self) -> Option<&serde_json::Map> { + self.inputs.as_object() + } +} diff --git a/runtime/rust/prompty/src/model/pipeline/run_turn_result.rs b/runtime/rust/prompty/src/model/pipeline/run_turn_result.rs new file mode 100644 index 00000000..bd3fd7a2 --- /dev/null +++ b/runtime/rust/prompty/src/model/pipeline/run_turn_result.rs @@ -0,0 +1,235 @@ +// +// Code generated by Typra emitter; DO NOT EDIT. + +#![allow( + unused_imports, + dead_code, + non_camel_case_types, + unused_variables, + clippy::all +)] + +use super::super::context::{LoadContext, SaveContext}; + +use super::super::events::checkpoint::Checkpoint; + +use super::super::events::host_tool_result::HostToolResult; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum RunTurnStatus { + Success, + Error, + Cancelled, +} + +impl Default for RunTurnStatus { + fn default() -> Self { + Self::Success + } +} + +impl std::fmt::Display for RunTurnStatus { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Success => write!(f, "success"), + Self::Error => write!(f, "error"), + Self::Cancelled => write!(f, "cancelled"), + } + } +} + +impl RunTurnStatus { + pub fn from_str_opt(s: &str) -> Option { + match s { + "success" => Some(Self::Success), + "error" => Some(Self::Error), + "cancelled" => Some(Self::Cancelled), + _ => None, + } + } + + pub fn as_str(&self) -> &str { + match self { + Self::Success => "success", + Self::Error => "error", + Self::Cancelled => "cancelled", + } + } +} + +/// Result returned by a reference turn runner implementation. +#[derive(Debug, Clone, Default)] +pub struct RunTurnResult { + /// Stable harness session identifier + pub session_id: String, + /// Stable turn identifier within the session + pub turn_id: String, + /// Final semantic status for the deterministic turn + pub status: RunTurnStatus, + /// Provider-neutral final output returned by the injected model callback + pub output: Option, + /// Number of model loop iterations executed + pub iterations: i32, + /// Host tool results produced during the turn + pub tool_results: Vec, + /// Checkpoints created during the turn + pub checkpoints: Vec, +} + +impl RunTurnResult { + /// Create a new RunTurnResult with default values. + pub fn new() -> Self { + Self::default() + } + + /// Load RunTurnResult from a JSON string. + pub fn from_json(json: &str, ctx: &LoadContext) -> Result { + let value: serde_json::Value = serde_json::from_str(json)?; + Ok(Self::load_from_value(&value, ctx)) + } + + /// Load RunTurnResult from a YAML string. + pub fn from_yaml(yaml: &str, ctx: &LoadContext) -> Result { + let value: serde_json::Value = serde_yaml::from_str(yaml)?; + Ok(Self::load_from_value(&value, ctx)) + } + + /// Load RunTurnResult from a `serde_json::Value`. + /// + /// Calls `ctx.process_input` before field extraction. + pub fn load_from_value(value: &serde_json::Value, ctx: &LoadContext) -> Self { + let value = ctx.process_input(value.clone()); + Self { + session_id: value + .get("sessionId") + .and_then(|v| v.as_str()) + .unwrap_or_default() + .to_string(), + turn_id: value + .get("turnId") + .and_then(|v| v.as_str()) + .unwrap_or_default() + .to_string(), + status: value + .get("status") + .and_then(|v| v.as_str()) + .and_then(|s| RunTurnStatus::from_str_opt(s)) + .unwrap_or(RunTurnStatus::Success), + output: value.get("output").cloned(), + iterations: value + .get("iterations") + .and_then(|v| v.as_i64()) + .unwrap_or(0) as i32, + tool_results: value + .get("toolResults") + .map(|v| Self::load_tool_results(v, ctx)) + .unwrap_or_default(), + checkpoints: value + .get("checkpoints") + .map(|v| Self::load_checkpoints(v, ctx)) + .unwrap_or_default(), + } + } + + /// Serialize RunTurnResult to a `serde_json::Value`. + /// + /// Calls `ctx.process_dict` after serialization. + pub fn to_value(&self, ctx: &SaveContext) -> serde_json::Value { + let mut result = serde_json::Map::new(); + // Write base fields + if !self.session_id.is_empty() { + result.insert( + "sessionId".to_string(), + serde_json::Value::String(self.session_id.clone()), + ); + } + if !self.turn_id.is_empty() { + result.insert( + "turnId".to_string(), + serde_json::Value::String(self.turn_id.clone()), + ); + } + result.insert( + "status".to_string(), + serde_json::Value::String(self.status.to_string()), + ); + if let Some(ref val) = self.output { + result.insert("output".to_string(), val.clone()); + } + if self.iterations != 0 { + result.insert( + "iterations".to_string(), + serde_json::Value::Number(serde_json::Number::from(self.iterations)), + ); + } + if !self.tool_results.is_empty() { + result.insert( + "toolResults".to_string(), + Self::save_tool_results(&self.tool_results, ctx), + ); + } + if !self.checkpoints.is_empty() { + result.insert( + "checkpoints".to_string(), + Self::save_checkpoints(&self.checkpoints, ctx), + ); + } + ctx.process_dict(serde_json::Value::Object(result)) + } + + /// Serialize RunTurnResult to a JSON string. + pub fn to_json(&self, ctx: &SaveContext) -> Result { + serde_json::to_string_pretty(&self.to_value(ctx)) + } + + /// Serialize RunTurnResult to a YAML string. + pub fn to_yaml(&self, ctx: &SaveContext) -> Result { + serde_yaml::to_string(&self.to_value(ctx)) + } + + /// Load a collection of HostToolResult from a JSON value. + /// Handles both array format `[{...}]`. + fn load_tool_results(data: &serde_json::Value, ctx: &LoadContext) -> Vec { + match data { + serde_json::Value::Array(arr) => arr + .iter() + .map(|v| HostToolResult::load_from_value(v, ctx)) + .collect(), + + _ => Vec::new(), + } + } + + /// Save a collection of HostToolResult to a JSON value. + fn save_tool_results(items: &[HostToolResult], ctx: &SaveContext) -> serde_json::Value { + serde_json::Value::Array( + items + .iter() + .map(|item| item.to_value(ctx)) + .collect::>(), + ) + } + + /// Load a collection of Checkpoint from a JSON value. + /// Handles both array format `[{...}]`. + fn load_checkpoints(data: &serde_json::Value, ctx: &LoadContext) -> Vec { + match data { + serde_json::Value::Array(arr) => arr + .iter() + .map(|v| Checkpoint::load_from_value(v, ctx)) + .collect(), + + _ => Vec::new(), + } + } + + /// Save a collection of Checkpoint to a JSON value. + fn save_checkpoints(items: &[Checkpoint], ctx: &SaveContext) -> serde_json::Value { + serde_json::Value::Array( + items + .iter() + .map(|item| item.to_value(ctx)) + .collect::>(), + ) + } +} diff --git a/runtime/rust/prompty/src/model/pipeline/turn_model_request.rs b/runtime/rust/prompty/src/model/pipeline/turn_model_request.rs new file mode 100644 index 00000000..5239ea50 --- /dev/null +++ b/runtime/rust/prompty/src/model/pipeline/turn_model_request.rs @@ -0,0 +1,164 @@ +// +// Code generated by Typra emitter; DO NOT EDIT. + +#![allow( + unused_imports, + dead_code, + non_camel_case_types, + unused_variables, + clippy::all +)] + +use super::super::context::{LoadContext, SaveContext}; + +use super::super::events::host_tool_result::HostToolResult; + +use super::turn_options::TurnOptions; + +/// Request passed by the reference turn runner to the injected model callback. The runner owns deterministic orchestration semantics; model/provider-specific execution stays behind this callback boundary. +#[derive(Debug, Clone, Default)] +pub struct TurnModelRequest { + /// Stable harness session identifier + pub session_id: String, + /// Stable turn identifier within the session + pub turn_id: String, + /// Zero-based model loop iteration + pub iteration: i32, + /// Inputs supplied to the deterministic single-turn run + pub inputs: serde_json::Value, + /// Canonical turn execution options + pub options: Option, + /// Host tool results produced by the previous iteration + pub tool_results: Vec, +} + +impl TurnModelRequest { + /// Create a new TurnModelRequest with default values. + pub fn new() -> Self { + Self::default() + } + + /// Load TurnModelRequest from a JSON string. + pub fn from_json(json: &str, ctx: &LoadContext) -> Result { + let value: serde_json::Value = serde_json::from_str(json)?; + Ok(Self::load_from_value(&value, ctx)) + } + + /// Load TurnModelRequest from a YAML string. + pub fn from_yaml(yaml: &str, ctx: &LoadContext) -> Result { + let value: serde_json::Value = serde_yaml::from_str(yaml)?; + Ok(Self::load_from_value(&value, ctx)) + } + + /// Load TurnModelRequest from a `serde_json::Value`. + /// + /// Calls `ctx.process_input` before field extraction. + pub fn load_from_value(value: &serde_json::Value, ctx: &LoadContext) -> Self { + let value = ctx.process_input(value.clone()); + Self { + session_id: value + .get("sessionId") + .and_then(|v| v.as_str()) + .unwrap_or_default() + .to_string(), + turn_id: value + .get("turnId") + .and_then(|v| v.as_str()) + .unwrap_or_default() + .to_string(), + iteration: value.get("iteration").and_then(|v| v.as_i64()).unwrap_or(0) as i32, + inputs: value + .get("inputs") + .cloned() + .unwrap_or(serde_json::Value::Null), + options: value + .get("options") + .filter(|v| v.is_object() || v.is_array() || v.is_string()) + .map(|v| TurnOptions::load_from_value(v, ctx)), + tool_results: value + .get("toolResults") + .map(|v| Self::load_tool_results(v, ctx)) + .unwrap_or_default(), + } + } + + /// Serialize TurnModelRequest to a `serde_json::Value`. + /// + /// Calls `ctx.process_dict` after serialization. + pub fn to_value(&self, ctx: &SaveContext) -> serde_json::Value { + let mut result = serde_json::Map::new(); + // Write base fields + if !self.session_id.is_empty() { + result.insert( + "sessionId".to_string(), + serde_json::Value::String(self.session_id.clone()), + ); + } + if !self.turn_id.is_empty() { + result.insert( + "turnId".to_string(), + serde_json::Value::String(self.turn_id.clone()), + ); + } + if self.iteration != 0 { + result.insert( + "iteration".to_string(), + serde_json::Value::Number(serde_json::Number::from(self.iteration)), + ); + } + if !self.inputs.is_null() { + result.insert("inputs".to_string(), self.inputs.clone()); + } + if let Some(ref val) = self.options { + let nested = val.to_value(ctx); + if !nested.is_null() { + result.insert("options".to_string(), nested); + } + } + if !self.tool_results.is_empty() { + result.insert( + "toolResults".to_string(), + Self::save_tool_results(&self.tool_results, ctx), + ); + } + ctx.process_dict(serde_json::Value::Object(result)) + } + + /// Serialize TurnModelRequest to a JSON string. + pub fn to_json(&self, ctx: &SaveContext) -> Result { + serde_json::to_string_pretty(&self.to_value(ctx)) + } + + /// Serialize TurnModelRequest to a YAML string. + pub fn to_yaml(&self, ctx: &SaveContext) -> Result { + serde_yaml::to_string(&self.to_value(ctx)) + } + /// Returns typed reference to the map if the field is an object. + /// Returns `None` if the field is null or not an object. + pub fn as_inputs_dict(&self) -> Option<&serde_json::Map> { + self.inputs.as_object() + } + + /// Load a collection of HostToolResult from a JSON value. + /// Handles both array format `[{...}]`. + fn load_tool_results(data: &serde_json::Value, ctx: &LoadContext) -> Vec { + match data { + serde_json::Value::Array(arr) => arr + .iter() + .map(|v| HostToolResult::load_from_value(v, ctx)) + .collect(), + + _ => Vec::new(), + } + } + + /// Save a collection of HostToolResult to a JSON value. + fn save_tool_results(items: &[HostToolResult], ctx: &SaveContext) -> serde_json::Value { + serde_json::Value::Array( + items + .iter() + .map(|item| item.to_value(ctx)) + .collect::>(), + ) + } +} diff --git a/runtime/rust/prompty/src/model/pipeline/turn_model_response.rs b/runtime/rust/prompty/src/model/pipeline/turn_model_response.rs new file mode 100644 index 00000000..d10a8d0a --- /dev/null +++ b/runtime/rust/prompty/src/model/pipeline/turn_model_response.rs @@ -0,0 +1,121 @@ +// +// Code generated by Typra emitter; DO NOT EDIT. + +#![allow( + unused_imports, + dead_code, + non_camel_case_types, + unused_variables, + clippy::all +)] + +use super::super::context::{LoadContext, SaveContext}; + +use super::super::events::host_tool_request::HostToolRequest; + +/// Response returned by the injected model callback to the reference turn runner. +#[derive(Debug, Clone, Default)] +pub struct TurnModelResponse { + /// Provider-neutral final model output for the turn when no more tools are requested + pub output: Option, + /// Host tool execution requests emitted by the model callback + pub tool_requests: Vec, + /// Additional deterministic state to merge into the iteration checkpoint + pub checkpoint_state: serde_json::Value, +} + +impl TurnModelResponse { + /// Create a new TurnModelResponse with default values. + pub fn new() -> Self { + Self::default() + } + + /// Load TurnModelResponse from a JSON string. + pub fn from_json(json: &str, ctx: &LoadContext) -> Result { + let value: serde_json::Value = serde_json::from_str(json)?; + Ok(Self::load_from_value(&value, ctx)) + } + + /// Load TurnModelResponse from a YAML string. + pub fn from_yaml(yaml: &str, ctx: &LoadContext) -> Result { + let value: serde_json::Value = serde_yaml::from_str(yaml)?; + Ok(Self::load_from_value(&value, ctx)) + } + + /// Load TurnModelResponse from a `serde_json::Value`. + /// + /// Calls `ctx.process_input` before field extraction. + pub fn load_from_value(value: &serde_json::Value, ctx: &LoadContext) -> Self { + let value = ctx.process_input(value.clone()); + Self { + output: value.get("output").cloned(), + tool_requests: value + .get("toolRequests") + .map(|v| Self::load_tool_requests(v, ctx)) + .unwrap_or_default(), + checkpoint_state: value + .get("checkpointState") + .cloned() + .unwrap_or(serde_json::Value::Null), + } + } + + /// Serialize TurnModelResponse to a `serde_json::Value`. + /// + /// Calls `ctx.process_dict` after serialization. + pub fn to_value(&self, ctx: &SaveContext) -> serde_json::Value { + let mut result = serde_json::Map::new(); + // Write base fields + if let Some(ref val) = self.output { + result.insert("output".to_string(), val.clone()); + } + if !self.tool_requests.is_empty() { + result.insert( + "toolRequests".to_string(), + Self::save_tool_requests(&self.tool_requests, ctx), + ); + } + if !self.checkpoint_state.is_null() { + result.insert("checkpointState".to_string(), self.checkpoint_state.clone()); + } + ctx.process_dict(serde_json::Value::Object(result)) + } + + /// Serialize TurnModelResponse to a JSON string. + pub fn to_json(&self, ctx: &SaveContext) -> Result { + serde_json::to_string_pretty(&self.to_value(ctx)) + } + + /// Serialize TurnModelResponse to a YAML string. + pub fn to_yaml(&self, ctx: &SaveContext) -> Result { + serde_yaml::to_string(&self.to_value(ctx)) + } + /// Returns typed reference to the map if the field is an object. + /// Returns `None` if the field is null or not an object. + pub fn as_checkpoint_state_dict(&self) -> Option<&serde_json::Map> { + self.checkpoint_state.as_object() + } + + /// Load a collection of HostToolRequest from a JSON value. + /// Handles both array format `[{...}]`. + fn load_tool_requests(data: &serde_json::Value, ctx: &LoadContext) -> Vec { + match data { + serde_json::Value::Array(arr) => arr + .iter() + .map(|v| HostToolRequest::load_from_value(v, ctx)) + .collect(), + + _ => Vec::new(), + } + } + + /// Save a collection of HostToolRequest to a JSON value. + fn save_tool_requests(items: &[HostToolRequest], ctx: &SaveContext) -> serde_json::Value { + serde_json::Value::Array( + items + .iter() + .map(|item| item.to_value(ctx)) + .collect::>(), + ) + } +} diff --git a/runtime/rust/prompty/src/model/pipeline/turn_options.rs b/runtime/rust/prompty/src/model/pipeline/turn_options.rs index 09929a6d..3c8eed09 100644 --- a/runtime/rust/prompty/src/model/pipeline/turn_options.rs +++ b/runtime/rust/prompty/src/model/pipeline/turn_options.rs @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. #![allow( unused_imports, diff --git a/runtime/rust/prompty/src/model/streaming/mod.rs b/runtime/rust/prompty/src/model/streaming/mod.rs index b44116fe..9a49d672 100644 --- a/runtime/rust/prompty/src/model/streaming/mod.rs +++ b/runtime/rust/prompty/src/model/streaming/mod.rs @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. #![allow( unused_imports, diff --git a/runtime/rust/prompty/src/model/streaming/stream_options.rs b/runtime/rust/prompty/src/model/streaming/stream_options.rs index 006443d3..c829adf2 100644 --- a/runtime/rust/prompty/src/model/streaming/stream_options.rs +++ b/runtime/rust/prompty/src/model/streaming/stream_options.rs @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. #![allow( unused_imports, diff --git a/runtime/rust/prompty/src/model/template/format_config.rs b/runtime/rust/prompty/src/model/template/format_config.rs index f22a6fd6..9a18bbc8 100644 --- a/runtime/rust/prompty/src/model/template/format_config.rs +++ b/runtime/rust/prompty/src/model/template/format_config.rs @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. #![allow( unused_imports, diff --git a/runtime/rust/prompty/src/model/template/mod.rs b/runtime/rust/prompty/src/model/template/mod.rs index ee4b7e72..1ad857df 100644 --- a/runtime/rust/prompty/src/model/template/mod.rs +++ b/runtime/rust/prompty/src/model/template/mod.rs @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. #![allow( unused_imports, diff --git a/runtime/rust/prompty/src/model/template/parser_config.rs b/runtime/rust/prompty/src/model/template/parser_config.rs index 1928db3a..0b10ae9d 100644 --- a/runtime/rust/prompty/src/model/template/parser_config.rs +++ b/runtime/rust/prompty/src/model/template/parser_config.rs @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. #![allow( unused_imports, diff --git a/runtime/rust/prompty/src/model/template/template.rs b/runtime/rust/prompty/src/model/template/template.rs index 4804bc44..f7c5daa6 100644 --- a/runtime/rust/prompty/src/model/template/template.rs +++ b/runtime/rust/prompty/src/model/template/template.rs @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. #![allow( unused_imports, diff --git a/runtime/rust/prompty/src/model/tools/binding.rs b/runtime/rust/prompty/src/model/tools/binding.rs index 18e5e062..d2cd6c35 100644 --- a/runtime/rust/prompty/src/model/tools/binding.rs +++ b/runtime/rust/prompty/src/model/tools/binding.rs @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. #![allow( unused_imports, diff --git a/runtime/rust/prompty/src/model/tools/mcp_approval_mode.rs b/runtime/rust/prompty/src/model/tools/mcp_approval_mode.rs index ef20e5e6..ccdc2062 100644 --- a/runtime/rust/prompty/src/model/tools/mcp_approval_mode.rs +++ b/runtime/rust/prompty/src/model/tools/mcp_approval_mode.rs @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. #![allow( unused_imports, diff --git a/runtime/rust/prompty/src/model/tools/mod.rs b/runtime/rust/prompty/src/model/tools/mod.rs index d94aad5e..5e15401c 100644 --- a/runtime/rust/prompty/src/model/tools/mod.rs +++ b/runtime/rust/prompty/src/model/tools/mod.rs @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. #![allow( unused_imports, diff --git a/runtime/rust/prompty/src/model/tools/tool.rs b/runtime/rust/prompty/src/model/tools/tool.rs index 691c8f91..34d6000a 100644 --- a/runtime/rust/prompty/src/model/tools/tool.rs +++ b/runtime/rust/prompty/src/model/tools/tool.rs @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. #![allow( unused_imports, diff --git a/runtime/rust/prompty/src/model/tools/tool_context.rs b/runtime/rust/prompty/src/model/tools/tool_context.rs index 7fcc5cb1..21c7f726 100644 --- a/runtime/rust/prompty/src/model/tools/tool_context.rs +++ b/runtime/rust/prompty/src/model/tools/tool_context.rs @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. #![allow( unused_imports, diff --git a/runtime/rust/prompty/src/model/tools/tool_dispatch_result.rs b/runtime/rust/prompty/src/model/tools/tool_dispatch_result.rs index 802ed6b2..618b02c2 100644 --- a/runtime/rust/prompty/src/model/tools/tool_dispatch_result.rs +++ b/runtime/rust/prompty/src/model/tools/tool_dispatch_result.rs @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. #![allow( unused_imports, diff --git a/runtime/rust/prompty/src/model/tracing/mod.rs b/runtime/rust/prompty/src/model/tracing/mod.rs index 37608078..67313584 100644 --- a/runtime/rust/prompty/src/model/tracing/mod.rs +++ b/runtime/rust/prompty/src/model/tracing/mod.rs @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. #![allow( unused_imports, diff --git a/runtime/rust/prompty/src/model/tracing/trace_file.rs b/runtime/rust/prompty/src/model/tracing/trace_file.rs index 896db469..f4619c7e 100644 --- a/runtime/rust/prompty/src/model/tracing/trace_file.rs +++ b/runtime/rust/prompty/src/model/tracing/trace_file.rs @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. #![allow( unused_imports, diff --git a/runtime/rust/prompty/src/model/tracing/trace_span.rs b/runtime/rust/prompty/src/model/tracing/trace_span.rs index b6411d79..6e5f5ccc 100644 --- a/runtime/rust/prompty/src/model/tracing/trace_span.rs +++ b/runtime/rust/prompty/src/model/tracing/trace_span.rs @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. #![allow( unused_imports, diff --git a/runtime/rust/prompty/src/model/tracing/trace_time.rs b/runtime/rust/prompty/src/model/tracing/trace_time.rs index 587b7e88..b79e8c2d 100644 --- a/runtime/rust/prompty/src/model/tracing/trace_time.rs +++ b/runtime/rust/prompty/src/model/tracing/trace_time.rs @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. #![allow( unused_imports, diff --git a/runtime/rust/prompty/src/model/wire/anthropic_image_block.rs b/runtime/rust/prompty/src/model/wire/anthropic_image_block.rs index 9786ea16..eb56b137 100644 --- a/runtime/rust/prompty/src/model/wire/anthropic_image_block.rs +++ b/runtime/rust/prompty/src/model/wire/anthropic_image_block.rs @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. #![allow( unused_imports, diff --git a/runtime/rust/prompty/src/model/wire/anthropic_image_source.rs b/runtime/rust/prompty/src/model/wire/anthropic_image_source.rs index 7f5b2166..9dc00352 100644 --- a/runtime/rust/prompty/src/model/wire/anthropic_image_source.rs +++ b/runtime/rust/prompty/src/model/wire/anthropic_image_source.rs @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. #![allow( unused_imports, diff --git a/runtime/rust/prompty/src/model/wire/anthropic_messages_request.rs b/runtime/rust/prompty/src/model/wire/anthropic_messages_request.rs index 87d2f98d..3164f0d2 100644 --- a/runtime/rust/prompty/src/model/wire/anthropic_messages_request.rs +++ b/runtime/rust/prompty/src/model/wire/anthropic_messages_request.rs @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. #![allow( unused_imports, diff --git a/runtime/rust/prompty/src/model/wire/anthropic_messages_response.rs b/runtime/rust/prompty/src/model/wire/anthropic_messages_response.rs index abb816ce..9fa5b133 100644 --- a/runtime/rust/prompty/src/model/wire/anthropic_messages_response.rs +++ b/runtime/rust/prompty/src/model/wire/anthropic_messages_response.rs @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. #![allow( unused_imports, diff --git a/runtime/rust/prompty/src/model/wire/anthropic_text_block.rs b/runtime/rust/prompty/src/model/wire/anthropic_text_block.rs index a89e661f..0721944f 100644 --- a/runtime/rust/prompty/src/model/wire/anthropic_text_block.rs +++ b/runtime/rust/prompty/src/model/wire/anthropic_text_block.rs @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. #![allow( unused_imports, diff --git a/runtime/rust/prompty/src/model/wire/anthropic_tool_definition.rs b/runtime/rust/prompty/src/model/wire/anthropic_tool_definition.rs index 2499be2b..9bc271b6 100644 --- a/runtime/rust/prompty/src/model/wire/anthropic_tool_definition.rs +++ b/runtime/rust/prompty/src/model/wire/anthropic_tool_definition.rs @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. #![allow( unused_imports, diff --git a/runtime/rust/prompty/src/model/wire/anthropic_tool_result_block.rs b/runtime/rust/prompty/src/model/wire/anthropic_tool_result_block.rs index 99d0535e..244bb47c 100644 --- a/runtime/rust/prompty/src/model/wire/anthropic_tool_result_block.rs +++ b/runtime/rust/prompty/src/model/wire/anthropic_tool_result_block.rs @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. #![allow( unused_imports, diff --git a/runtime/rust/prompty/src/model/wire/anthropic_tool_use_block.rs b/runtime/rust/prompty/src/model/wire/anthropic_tool_use_block.rs index 504d3808..8e1ecc57 100644 --- a/runtime/rust/prompty/src/model/wire/anthropic_tool_use_block.rs +++ b/runtime/rust/prompty/src/model/wire/anthropic_tool_use_block.rs @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. #![allow( unused_imports, diff --git a/runtime/rust/prompty/src/model/wire/anthropic_usage.rs b/runtime/rust/prompty/src/model/wire/anthropic_usage.rs index 4f8279f5..2b5b050a 100644 --- a/runtime/rust/prompty/src/model/wire/anthropic_usage.rs +++ b/runtime/rust/prompty/src/model/wire/anthropic_usage.rs @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. #![allow( unused_imports, diff --git a/runtime/rust/prompty/src/model/wire/anthropic_wire_message.rs b/runtime/rust/prompty/src/model/wire/anthropic_wire_message.rs index 46d24ca0..fbfb370a 100644 --- a/runtime/rust/prompty/src/model/wire/anthropic_wire_message.rs +++ b/runtime/rust/prompty/src/model/wire/anthropic_wire_message.rs @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. #![allow( unused_imports, diff --git a/runtime/rust/prompty/src/model/wire/mod.rs b/runtime/rust/prompty/src/model/wire/mod.rs index 8daca807..606b3679 100644 --- a/runtime/rust/prompty/src/model/wire/mod.rs +++ b/runtime/rust/prompty/src/model/wire/mod.rs @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. #![allow( unused_imports, diff --git a/runtime/rust/prompty/src/pipeline.rs b/runtime/rust/prompty/src/pipeline.rs index 86f42eb8..7b6c9356 100644 --- a/runtime/rust/prompty/src/pipeline.rs +++ b/runtime/rust/prompty/src/pipeline.rs @@ -558,6 +558,33 @@ pub async fn invoke_from_path( /// Events emitted during the agent loop in `turn()`. #[derive(Debug, Clone)] pub enum AgentEvent { + /// The turn has started. + TurnStart { + agent: Option, + max_iterations: usize, + }, + /// The turn has ended. + TurnEnd { + status: String, + iterations: usize, + response: serde_json::Value, + }, + /// An LLM request is starting. + LlmStart { + provider: String, + model_id: Option, + message_count: usize, + iteration: usize, + }, + /// An LLM request completed. + LlmComplete { iteration: usize }, + /// A transient operation will be retried. + Retry { + operation: String, + attempt: usize, + max_attempts: usize, + reason: String, + }, /// A streaming token from the LLM. Token(String), /// A thinking/reasoning token from the LLM. @@ -566,6 +593,13 @@ pub enum AgentEvent { ToolCallStart { name: String, arguments: String }, /// A tool call has completed with a result. ToolResult { name: String, result: String }, + /// A tool dispatch has completed with normalized success metadata. + ToolCallComplete { + name: String, + success: bool, + result: String, + error_kind: Option, + }, /// Status update from the agent loop. Status(String), /// The message list was updated (e.g., tool results appended). @@ -718,6 +752,14 @@ impl TurnOptions { } } + fn emit_failed_turn_end(&self, status: &str, iterations: usize) { + self.emit(AgentEvent::TurnEnd { + status: status.to_string(), + iterations, + response: serde_json::Value::Null, + }); + } + fn is_cancelled(&self) -> bool { self.cancelled .as_ref() @@ -915,8 +957,27 @@ pub async fn turn( span.emit("description", &json!("Simple turn (no tools)")); let empty = serde_json::json!({}); span.emit("inputs", inputs.unwrap_or(&empty)); + opts.emit(AgentEvent::TurnStart { + agent: Some(agent.name.clone()), + max_iterations: opts.max_iterations, + }); + + if opts.is_cancelled() { + opts.emit(AgentEvent::Cancelled); + opts.emit_failed_turn_end("cancelled", 0); + span.emit("error", &json!("Operation cancelled")); + span.end(); + return Err(InvokerError::Cancelled("Operation cancelled".to_string())); + } - let mut messages = prepare(agent, inputs).await?; + let mut messages = match prepare(agent, inputs).await { + Ok(messages) => messages, + Err(err) => { + opts.emit_failed_turn_end("error", 0); + span.end(); + return Err(err); + } + }; let provider = resolve_provider(agent); // Drain steering @@ -944,6 +1005,7 @@ pub async fn turn( let gr = guardrails.check_input(&messages, agent).await; if !gr.allowed { let reason = gr.reason.unwrap_or_else(|| "Input denied".into()); + opts.emit_failed_turn_end("error", 0); span.emit("error", &json!(format!("Input guardrail: {reason}"))); span.end(); return Err(InvokerError::Execute( @@ -953,13 +1015,35 @@ pub async fn turn( } // Execute (streaming-aware) + if opts.is_cancelled() { + opts.emit(AgentEvent::Cancelled); + opts.emit_failed_turn_end("cancelled", 0); + span.emit("error", &json!("Operation cancelled")); + span.end(); + return Err(InvokerError::Cancelled("Operation cancelled".to_string())); + } let streaming = is_streaming(agent); let processed = if streaming { + opts.emit(AgentEvent::LlmStart { + provider: provider.clone(), + model_id: (!agent.model.id.is_empty()).then(|| agent.model.id.clone()), + message_count: messages.len(), + iteration: 0, + }); match registry::invoke_executor_stream(&provider, agent, &messages).await { Ok(sse_stream) => { + opts.emit(AgentEvent::LlmComplete { iteration: 0 }); let prompty_stream = PromptyStream::from_stream("PromptyStream", sse_stream); - let chunk_stream = - registry::invoke_processor_stream(&provider, Box::pin(prompty_stream))?; + let chunk_stream = match registry::invoke_processor_stream( + &provider, + Box::pin(prompty_stream), + ) { + Ok(stream) => stream, + Err(err) => { + opts.emit_failed_turn_end("error", 0); + return Err(err); + } + }; use futures::StreamExt; let mut text_parts = Vec::new(); @@ -974,6 +1058,7 @@ pub async fn turn( opts.emit(AgentEvent::Thinking(t)); } StreamChunk::Error(e) => { + opts.emit_failed_turn_end("error", 0); span.emit("error", &json!(e)); span.end(); return Err(InvokerError::Execute(e.into())); @@ -986,26 +1071,76 @@ pub async fn turn( Err(_) => { // Fallback to non-streaming let raw_response = - registry::invoke_executor(&provider, agent, &messages).await?; + match registry::invoke_executor(&provider, agent, &messages).await { + Ok(response) => response, + Err(err) => { + opts.emit_failed_turn_end("error", 0); + return Err(err); + } + }; + opts.emit(AgentEvent::LlmComplete { iteration: 0 }); if opts.raw { span.emit("result", &json!("(raw)")); span.end(); + opts.emit(AgentEvent::Done { + response: raw_response.clone(), + messages: messages.clone(), + }); + opts.emit(AgentEvent::TurnEnd { + status: "success".to_string(), + iterations: 0, + response: raw_response.clone(), + }); return Ok(raw_response); } - process(agent, raw_response).await? + match process(agent, raw_response.clone()).await { + Ok(result) => result, + Err(err) => { + opts.emit_failed_turn_end("error", 0); + return Err(err); + } + } } } } else { - let raw_response = registry::invoke_executor(&provider, agent, &messages).await?; + opts.emit(AgentEvent::LlmStart { + provider: provider.clone(), + model_id: (!agent.model.id.is_empty()).then(|| agent.model.id.clone()), + message_count: messages.len(), + iteration: 0, + }); + let raw_response = match registry::invoke_executor(&provider, agent, &messages).await { + Ok(response) => response, + Err(err) => { + opts.emit_failed_turn_end("error", 0); + return Err(err); + } + }; + opts.emit(AgentEvent::LlmComplete { iteration: 0 }); if opts.raw { span.emit("result", &json!("(raw)")); span.end(); + opts.emit(AgentEvent::Done { + response: raw_response.clone(), + messages: messages.clone(), + }); + opts.emit(AgentEvent::TurnEnd { + status: "success".to_string(), + iterations: 0, + response: raw_response.clone(), + }); return Ok(raw_response); } - process(agent, raw_response).await? + match process(agent, raw_response.clone()).await { + Ok(result) => result, + Err(err) => { + opts.emit_failed_turn_end("error", 0); + return Err(err); + } + } }; // Output guardrail @@ -1013,6 +1148,7 @@ pub async fn turn( let gr = guardrails.check_output(&processed, agent).await; if !gr.allowed { let reason = gr.reason.unwrap_or_else(|| "Output denied".into()); + opts.emit_failed_turn_end("error", 0); span.emit("error", &json!(format!("Output guardrail: {reason}"))); span.end(); return Err(InvokerError::Execute( @@ -1022,12 +1158,30 @@ pub async fn turn( if let Some(rewrite) = gr.rewrite { span.emit("result", &rewrite); span.end(); + opts.emit(AgentEvent::Done { + response: rewrite.clone(), + messages: messages.clone(), + }); + opts.emit(AgentEvent::TurnEnd { + status: "success".to_string(), + iterations: 0, + response: rewrite.clone(), + }); return Ok(rewrite); } } span.emit("result", &processed); span.end(); + opts.emit(AgentEvent::Done { + response: processed.clone(), + messages: messages.clone(), + }); + opts.emit(AgentEvent::TurnEnd { + status: "success".to_string(), + iterations: 0, + response: processed.clone(), + }); return Ok(processed); } @@ -1037,23 +1191,36 @@ pub async fn turn( span.emit("description", &json!("Agent turn (tool-calling loop)")); let empty = serde_json::json!({}); span.emit("inputs", inputs.unwrap_or(&empty)); + opts.emit(AgentEvent::TurnStart { + agent: Some(agent.name.clone()), + max_iterations: opts.max_iterations, + }); // Check cancellation at start if opts.is_cancelled() { opts.emit(AgentEvent::Cancelled); + opts.emit_failed_turn_end("cancelled", 0); span.emit("error", &json!("Operation cancelled")); span.end(); return Err(InvokerError::Cancelled("Operation cancelled".to_string())); } // Prepare messages - let mut messages = prepare(agent, inputs).await?; + let mut messages = match prepare(agent, inputs).await { + Ok(messages) => messages, + Err(err) => { + opts.emit_failed_turn_end("error", 0); + span.end(); + return Err(err); + } + }; let provider = resolve_provider(agent); for iteration in 0..opts.max_iterations { // Check cancellation before each LLM call if opts.is_cancelled() { opts.emit(AgentEvent::Cancelled); + opts.emit_failed_turn_end("cancelled", iteration); span.emit("error", &json!("Operation cancelled")); span.end(); return Err(InvokerError::Cancelled("Operation cancelled".to_string())); @@ -1102,6 +1269,7 @@ pub async fn turn( if !gr.allowed { let reason = gr.reason.unwrap_or_else(|| "Input denied".into()); iter_span.end(); + opts.emit_failed_turn_end("error", iteration); span.emit("error", &json!(format!("Input guardrail: {reason}"))); span.end(); return Err(InvokerError::Execute( @@ -1118,17 +1286,28 @@ pub async fn turn( if opts.is_cancelled() { iter_span.end(); opts.emit(AgentEvent::Cancelled); + opts.emit_failed_turn_end("cancelled", iteration); span.emit("error", &json!("Operation cancelled")); span.end(); return Err(InvokerError::Cancelled("Operation cancelled".to_string())); } + opts.emit(AgentEvent::LlmStart { + provider: provider.clone(), + model_id: (!agent.model.id.is_empty()).then(|| agent.model.id.clone()), + message_count: messages.len(), + iteration, + }); match execute_llm_attempt(&provider, agent, &messages, streaming, &opts).await { - Ok(result) => break result, + Ok(result) => { + opts.emit(AgentEvent::LlmComplete { iteration }); + break result; + } Err(e) => { llm_attempts += 1; if llm_attempts >= opts.max_llm_retries as u32 { iter_span.end(); + opts.emit_failed_turn_end("error", iteration); span.emit( "error", &json!(format!( @@ -1152,6 +1331,12 @@ pub async fn turn( llm_attempts + 1, opts.max_llm_retries ))); + opts.emit(AgentEvent::Retry { + operation: "llm".to_string(), + attempt: llm_attempts as usize + 1, + max_attempts: opts.max_llm_retries, + reason: e.clone(), + }); // Exponential backoff with jitter, capped at 60s // Formula: min(2^attempts + jitter, 60) per spec §9.10 let backoff_secs = { @@ -1175,6 +1360,7 @@ pub async fn turn( } } => { opts.emit(AgentEvent::Cancelled); + opts.emit_failed_turn_end("cancelled", iteration); span.emit("error", &json!("Operation cancelled during retry backoff")); span.end(); return Err(InvokerError::Cancelled( @@ -1198,6 +1384,7 @@ pub async fn turn( if !gr.allowed { let reason = gr.reason.unwrap_or_else(|| "Output denied".into()); iter_span.end(); + opts.emit_failed_turn_end("error", iteration + 1); span.emit("error", &json!(format!("Output guardrail: {reason}"))); span.end(); return Err(InvokerError::Execute( @@ -1211,6 +1398,11 @@ pub async fn turn( response: rewrite.clone(), messages: messages.clone(), }); + opts.emit(AgentEvent::TurnEnd { + status: "success".to_string(), + iterations: iteration + 1, + response: rewrite.clone(), + }); span.emit("result", &rewrite); span.emit("iterations", &json!(iteration + 1)); span.end(); @@ -1225,6 +1417,11 @@ pub async fn turn( response: final_result.clone(), messages: messages.clone(), }); + opts.emit(AgentEvent::TurnEnd { + status: "success".to_string(), + iterations: iteration + 1, + response: final_result.clone(), + }); span.emit("result", &final_result); span.emit("iterations", &json!(iteration + 1)); span.end(); @@ -1249,7 +1446,18 @@ pub async fn turn( let tool_results = if opts.parallel_tool_calls { dispatch_tools_parallel(&tool_calls, &opts, agent, inputs).await } else { - dispatch_tools_sequential(&tool_calls, &opts, agent, inputs).await? + match dispatch_tools_sequential(&tool_calls, &opts, agent, inputs).await { + Ok(results) => results, + Err(err) => { + let status = if matches!(err, InvokerError::Cancelled(_)) { + "cancelled" + } else { + "error" + }; + opts.emit_failed_turn_end(status, iteration + 1); + return Err(err); + } + } }; tool_span.emit("result", &json!(tool_results)); @@ -1259,13 +1467,19 @@ pub async fn turn( let text_content = extract_text_from_processed(&processed); // Format tool results into messages using provider-specific formatting - let tool_messages = registry::invoke_format_tool_messages( + let tool_messages = match registry::invoke_format_tool_messages( &provider, &raw_response, &tool_calls, &tool_results, text_content.as_deref(), - )?; + ) { + Ok(messages) => messages, + Err(err) => { + opts.emit_failed_turn_end("error", iteration + 1); + return Err(err); + } + }; messages.extend(tool_messages); opts.emit(AgentEvent::MessagesUpdated { @@ -1290,6 +1504,7 @@ pub async fn turn( opts.max_iterations ); opts.emit(AgentEvent::Error(msg.clone())); + opts.emit_failed_turn_end("error", iteration + 1); span.emit("error", &json!(msg)); span.end(); return Err(InvokerError::Execute(msg.into())); @@ -1302,6 +1517,7 @@ pub async fn turn( opts.max_iterations ); opts.emit(AgentEvent::Error(msg.clone())); + opts.emit_failed_turn_end("error", opts.max_iterations); span.emit("error", &json!(msg)); span.end(); Err(InvokerError::Execute(msg.into())) @@ -1420,6 +1636,12 @@ async fn dispatch_tools_sequential( name: tc.name.clone(), result: format!("Error: Tool guardrail denied: {reason}"), }); + opts.emit(AgentEvent::ToolCallComplete { + name: tc.name.clone(), + success: false, + result: format!("Error: Tool guardrail denied: {reason}"), + error_kind: Some("guardrail_denied".to_string()), + }); tool_results.push(format!("Error: Tool guardrail denied: {reason}")); continue; } @@ -1461,6 +1683,14 @@ async fn dispatch_tools_sequential( name: tc.name.clone(), result: result.clone(), }); + opts.emit(AgentEvent::ToolCallComplete { + name: tc.name.clone(), + success: !result.starts_with("Error:"), + result: result.clone(), + error_kind: result + .starts_with("Error:") + .then(|| "tool_error".to_string()), + }); tool_results.push(result); } Ok(tool_results) @@ -1532,6 +1762,14 @@ async fn dispatch_tools_parallel( name: tc.name.clone(), result: result.clone(), }); + opts.emit(AgentEvent::ToolCallComplete { + name: tc.name.clone(), + success: !result.starts_with("Error:"), + result: result.clone(), + error_kind: result + .starts_with("Error:") + .then(|| "tool_error".to_string()), + }); } results @@ -1972,7 +2210,7 @@ mod tests { // and exercise the full turn() function. use std::sync::Arc; - use std::sync::atomic::{AtomicUsize, Ordering}; + use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; /// Mock executor that returns tool calls on first call, then a final response. struct ToolCallThenDoneExecutor { @@ -2133,6 +2371,52 @@ mod tests { Prompty::load_from_value(&data, &LoadContext::default()) } + fn capture_events() -> (Arc>>, EventCallback) { + let events = Arc::new(std::sync::Mutex::new(Vec::new())); + let events_clone = events.clone(); + let on_event: EventCallback = Box::new(move |event| { + events_clone.lock().unwrap().push(event); + }); + (events, on_event) + } + + fn assert_turn_lifecycle(events: &[AgentEvent], expected_status: &str) { + let start_indices: Vec<_> = events + .iter() + .enumerate() + .filter_map(|(index, event)| { + matches!(event, AgentEvent::TurnStart { .. }).then_some(index) + }) + .collect(); + let end_indices: Vec<_> = events + .iter() + .enumerate() + .filter_map(|(index, event)| { + matches!(event, AgentEvent::TurnEnd { .. }).then_some(index) + }) + .collect(); + + assert_eq!( + start_indices.len(), + 1, + "expected exactly one TurnStart, got {events:?}" + ); + assert_eq!( + end_indices.len(), + 1, + "expected exactly one TurnEnd, got {events:?}" + ); + assert!( + start_indices[0] < end_indices[0], + "TurnStart should precede TurnEnd, got {events:?}" + ); + + match &events[end_indices[0]] { + AgentEvent::TurnEnd { status, .. } => assert_eq!(status, expected_status), + other => panic!("expected TurnEnd, got {other:?}"), + } + } + #[tokio::test] #[serial] async fn test_turn_without_tools_invokes_directly() { @@ -2372,15 +2656,208 @@ mod tests { let _result = turn(&agent, None, Some(opts)).await.unwrap(); let captured = events.lock().unwrap(); - // Should see: ToolCallStart → ToolResult → Done + // Should see lifecycle events plus ToolCallStart → ToolResult → Done. assert!( captured.len() >= 3, "Expected at least 3 events, got {:?}", captured ); - assert!(captured[0].contains("ToolCallStart")); - assert!(captured[1].contains("ToolResult")); - assert!(captured.last().unwrap().contains("Done")); + let tool_start_index = captured + .iter() + .position(|event| event.contains("ToolCallStart")) + .expect("expected ToolCallStart event"); + let tool_result_index = captured + .iter() + .position(|event| event.contains("ToolResult")) + .expect("expected ToolResult event"); + assert!(tool_start_index < tool_result_index); + let done_index = captured + .iter() + .position(|event| event.contains("Done")) + .expect("expected Done event"); + let turn_end_index = captured + .iter() + .position(|event| event.contains("TurnEnd")) + .expect("expected TurnEnd event"); + assert!(done_index < turn_end_index); + } + + #[tokio::test] + #[serial] + async fn test_turn_lifecycle_success_simple_path() { + ensure_defaults(); + let key = "turn_lifecycle_simple_success"; + registry::register_executor( + key, + ToolCallThenDoneExecutor { + call_count: Arc::new(AtomicUsize::new(1)), + }, + ); + registry::register_processor(key, MockProcessor); + + let agent = make_simple_agent(key); + let (events, on_event) = capture_events(); + let opts = TurnOptions { + on_event: Some(on_event), + ..Default::default() + }; + + let result = turn(&agent, None, Some(opts)).await.unwrap(); + assert_eq!(result, "The weather in Seattle is 72°F."); + assert_turn_lifecycle(&events.lock().unwrap(), "success"); + } + + #[tokio::test] + #[serial] + async fn test_turn_lifecycle_success_tool_loop() { + ensure_defaults(); + let key = "turn_lifecycle_tool_success"; + registry::register_executor( + key, + ToolCallThenDoneExecutor { + call_count: Arc::new(AtomicUsize::new(0)), + }, + ); + registry::register_processor(key, MockProcessor); + + let agent = make_simple_agent(key); + let mut tools = HashMap::new(); + tools.insert( + "get_weather".to_string(), + ToolHandler::Sync(Box::new(|_| Ok("72°F and sunny".to_string()))), + ); + let (events, on_event) = capture_events(); + let opts = TurnOptions { + tools, + on_event: Some(on_event), + ..Default::default() + }; + + let result = turn(&agent, None, Some(opts)).await.unwrap(); + assert_eq!(result, "The weather in Seattle is 72°F."); + assert_turn_lifecycle(&events.lock().unwrap(), "success"); + } + + #[tokio::test] + #[serial] + async fn test_turn_lifecycle_input_guardrail_error() { + ensure_defaults(); + let key = "turn_lifecycle_guardrail_error"; + registry::register_executor( + key, + ToolCallThenDoneExecutor { + call_count: Arc::new(AtomicUsize::new(1)), + }, + ); + registry::register_processor(key, MockProcessor); + + let agent = make_simple_agent(key); + let (events, on_event) = capture_events(); + let guardrails = crate::guardrails::Guardrails { + input: Some(Box::new(|_, _| { + Box::pin(async { crate::guardrails::GuardrailResult::deny("blocked") }) + })), + ..Default::default() + }; + let opts = TurnOptions { + on_event: Some(on_event), + guardrails: Some(guardrails), + ..Default::default() + }; + + let result = turn(&agent, None, Some(opts)).await; + assert!(result.is_err()); + assert_turn_lifecycle(&events.lock().unwrap(), "error"); + } + + #[tokio::test] + #[serial] + async fn test_turn_lifecycle_retry_exhausted_error() { + ensure_defaults(); + let key = "turn_lifecycle_retry_error"; + registry::register_executor(key, AlwaysFailExecutor); + registry::register_processor(key, MockProcessor); + + let agent = make_simple_agent(key); + let mut tools = HashMap::new(); + tools.insert( + "dummy".to_string(), + ToolHandler::Sync(Box::new(|_| Ok("ok".to_string()))), + ); + let (events, on_event) = capture_events(); + let opts = TurnOptions { + tools, + on_event: Some(on_event), + max_llm_retries: 1, + ..Default::default() + }; + + let result = turn(&agent, None, Some(opts)).await; + assert!(result.is_err()); + assert_turn_lifecycle(&events.lock().unwrap(), "error"); + } + + #[tokio::test] + #[serial] + async fn test_turn_lifecycle_cancelled_simple_path() { + ensure_defaults(); + let key = "turn_lifecycle_simple_cancelled"; + registry::register_executor( + key, + ToolCallThenDoneExecutor { + call_count: Arc::new(AtomicUsize::new(1)), + }, + ); + registry::register_processor(key, MockProcessor); + + let agent = make_simple_agent(key); + let (events, on_event) = capture_events(); + let opts = TurnOptions { + on_event: Some(on_event), + cancelled: Some(Arc::new(AtomicBool::new(true))), + ..Default::default() + }; + + let result = turn(&agent, None, Some(opts)).await; + assert!(result.is_err()); + assert_turn_lifecycle(&events.lock().unwrap(), "cancelled"); + } + + #[tokio::test] + #[serial] + async fn test_turn_lifecycle_cancelled_during_tool_dispatch() { + ensure_defaults(); + let key = "turn_lifecycle_tool_cancelled"; + registry::register_executor( + key, + ToolCallThenDoneExecutor { + call_count: Arc::new(AtomicUsize::new(0)), + }, + ); + registry::register_processor(key, MockProcessor); + + let agent = make_simple_agent(key); + let cancel = Arc::new(AtomicBool::new(false)); + let cancel_in_tool = cancel.clone(); + let mut tools = HashMap::new(); + tools.insert( + "get_weather".to_string(), + ToolHandler::Sync(Box::new(move |_| { + cancel_in_tool.store(true, Ordering::Relaxed); + Ok("72°F and sunny".to_string()) + })), + ); + let (events, on_event) = capture_events(); + let opts = TurnOptions { + tools, + on_event: Some(on_event), + cancelled: Some(cancel), + ..Default::default() + }; + + let result = turn(&agent, None, Some(opts)).await; + assert!(result.is_err()); + assert_turn_lifecycle(&events.lock().unwrap(), "cancelled"); } #[tokio::test] diff --git a/runtime/rust/prompty/tests/agent_vectors.rs b/runtime/rust/prompty/tests/agent_vectors.rs index 17365801..61c942c8 100644 --- a/runtime/rust/prompty/tests/agent_vectors.rs +++ b/runtime/rust/prompty/tests/agent_vectors.rs @@ -547,9 +547,15 @@ async fn run_vector_with_events( let events_clone = events.clone(); let on_event: EventCallback = Box::new(move |event: AgentEvent| { let event_type = match &event { + AgentEvent::TurnStart { .. } => "turn_start", + AgentEvent::TurnEnd { .. } => "turn_end", + AgentEvent::LlmStart { .. } => "llm_start", + AgentEvent::LlmComplete { .. } => "llm_complete", + AgentEvent::Retry { .. } => "retry", AgentEvent::Token(_) => "token", AgentEvent::Thinking(_) => "thinking", AgentEvent::ToolCallStart { .. } => "tool_call_start", + AgentEvent::ToolCallComplete { .. } => "tool_call_complete", AgentEvent::ToolResult { .. } => "tool_result", AgentEvent::Status(_) => "status", AgentEvent::MessagesUpdated { .. } => "messages_updated", @@ -591,9 +597,17 @@ async fn test_events_basic_tool_loop() { events.contains(&"tool_result".to_string()), "missing tool_result event in {events:?}" ); + let done_index = events + .iter() + .position(|event| event == "done") + .expect("missing done event"); + let turn_end_index = events + .iter() + .position(|event| event == "turn_end") + .expect("missing turn_end event"); assert!( - events.last() == Some(&"done".to_string()), - "last event should be 'done', got {events:?}" + done_index < turn_end_index, + "done should be emitted before turn_end, got {events:?}" ); } @@ -716,9 +730,15 @@ async fn test_cancellation_between_iterations() { let events_clone = events.clone(); let on_event: EventCallback = Box::new(move |event: AgentEvent| { let event_type = match &event { + AgentEvent::TurnStart { .. } => "turn_start", + AgentEvent::TurnEnd { .. } => "turn_end", + AgentEvent::LlmStart { .. } => "llm_start", + AgentEvent::LlmComplete { .. } => "llm_complete", + AgentEvent::Retry { .. } => "retry", AgentEvent::Token(_) => "token", AgentEvent::Thinking(_) => "thinking", AgentEvent::ToolCallStart { .. } => "tool_call_start", + AgentEvent::ToolCallComplete { .. } => "tool_call_complete", AgentEvent::ToolResult { .. } => "tool_result", AgentEvent::Status(_) => "status", AgentEvent::MessagesUpdated { .. } => "messages_updated", @@ -988,7 +1008,7 @@ fn build_extension_opts( // Steering let steering = input.get("steering").map(|s| { - let mut steering = prompty::steering::Steering::new(); + let steering = prompty::steering::Steering::new(); if let Some(msgs) = s.get("messages").and_then(|m| m.as_array()) { for msg in msgs { let text = msg.get("text").and_then(|t| t.as_str()).unwrap_or(""); diff --git a/runtime/rust/prompty/tests/harness_adapters.rs b/runtime/rust/prompty/tests/harness_adapters.rs new file mode 100644 index 00000000..6ffcd402 --- /dev/null +++ b/runtime/rust/prompty/tests/harness_adapters.rs @@ -0,0 +1,282 @@ +use std::collections::HashMap; +use std::fs; +use std::sync::Arc; +use std::time::{SystemTime, UNIX_EPOCH}; + +use prompty::harness::{ + AllowAllPermissionResolver, CollectingEventSink, DenyAllPermissionResolver, + FunctionHostToolExecutor, InMemoryCheckpointStore, JsonlEventJournalWriter, +}; +use prompty::model::events::{ + checkpoint::Checkpoint, + host_tool_request::HostToolRequest, + permission_request::PermissionRequest, + session_event::{SessionEvent, SessionEventType}, + session_summary::SessionSummary, + turn_event::{TurnEvent, TurnEventType}, +}; +use prompty::model::pipeline::{ + checkpoint_store::CheckpointStore, event_journal_writer::EventJournalWriter, + event_sink::EventSink, host_tool_executor::HostToolExecutor, + permission_resolver::PermissionResolver, +}; +use serde_json::{Value, json}; + +fn turn_event() -> TurnEvent { + TurnEvent { + id: "turn-event".to_string(), + r#type: TurnEventType::Turn_start, + timestamp: "2026-06-10T00:00:00Z".to_string(), + payload: json!({ "phase": "start" }), + ..Default::default() + } +} + +fn session_event() -> SessionEvent { + SessionEvent { + id: "session-event".to_string(), + r#type: SessionEventType::Session_start, + timestamp: "2026-06-10T00:00:00Z".to_string(), + session_id: Some("session-1".to_string()), + payload: json!({ "phase": "start" }), + ..Default::default() + } +} + +#[test] +fn collecting_event_sink_captures_events() { + let sink = CollectingEventSink::new(); + + assert!(sink.emit_turn(&turn_event())); + assert!(sink.emit_session(&session_event())); + + assert_eq!(sink.turn_events()[0].id, "turn-event"); + assert_eq!(sink.session_events()[0].id, "session-event"); +} + +#[test] +fn jsonl_event_journal_writer_writes_records() { + let path = std::env::temp_dir().join(format!( + "prompty-trace-{}.jsonl", + SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap() + .as_nanos() + )); + let writer = JsonlEventJournalWriter::new(&path); + + assert!(writer.append_turn(&turn_event())); + assert!(writer.append_session(&session_event())); + assert!(writer.close(&Some(SessionSummary { + session_id: "session-1".to_string(), + turns: Some(1), + ..Default::default() + }))); + + let records: Vec = fs::read_to_string(&path) + .unwrap() + .lines() + .map(|line| serde_json::from_str(line).unwrap()) + .collect(); + let _ = fs::remove_file(path); + + assert_eq!(records[0]["kind"], "turn"); + assert_eq!(records[0]["event"]["id"], "turn-event"); + assert_eq!(records[1]["kind"], "session"); + assert_eq!(records[1]["event"]["id"], "session-event"); + assert_eq!(records[2]["kind"], "summary"); + assert_eq!(records[2]["summary"]["sessionId"], "session-1"); +} + +#[test] +fn jsonl_event_journal_writer_returns_false_after_close() { + let path = std::env::temp_dir().join(format!( + "prompty-trace-closed-{}.jsonl", + SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap() + .as_nanos() + )); + let writer = JsonlEventJournalWriter::new(&path); + + assert!(writer.close(&None)); + assert!(!writer.append_turn(&turn_event())); + let _ = fs::remove_file(path); +} + +#[tokio::test] +async fn in_memory_checkpoint_store_stores_checkpoints() { + let store = InMemoryCheckpointStore::new(); + let checkpoint = Checkpoint { + id: Some("checkpoint-1".to_string()), + session_id: Some("session-1".to_string()), + title: "First".to_string(), + ..Default::default() + }; + + assert_eq!(store.save(&checkpoint).await.unwrap().id, checkpoint.id); + assert_eq!( + store + .load(&"session-1".to_string(), &"checkpoint-1".to_string()) + .await + .unwrap() + .unwrap() + .title, + "First" + ); + assert!( + store + .load(&"session-1".to_string(), &"missing".to_string()) + .await + .unwrap() + .is_none() + ); + assert_eq!( + store + .list_checkpoints(&"session-1".to_string()) + .await + .unwrap() + .len(), + 1 + ); +} + +#[tokio::test] +async fn in_memory_checkpoint_store_requires_keys() { + let store = InMemoryCheckpointStore::new(); + + assert!( + store + .save(&Checkpoint { + id: Some("checkpoint-1".to_string()), + ..Default::default() + }) + .await + .is_err() + ); + assert!( + store + .save(&Checkpoint { + session_id: Some("session-1".to_string()), + ..Default::default() + }) + .await + .is_err() + ); +} + +#[tokio::test] +async fn permission_resolvers_return_decisions() { + let request = PermissionRequest { + request_id: Some("permission-1".to_string()), + tool_call_id: Some("tool-call-1".to_string()), + permission: "tool.execute".to_string(), + ..Default::default() + }; + + let allow = AllowAllPermissionResolver.request(&request).await.unwrap(); + let deny = DenyAllPermissionResolver.request(&request).await.unwrap(); + + assert!(allow.approved); + assert_eq!(allow.reason.as_deref(), Some("allow_all")); + assert_eq!(allow.request_id.as_deref(), Some("permission-1")); + assert_eq!(allow.tool_call_id.as_deref(), Some("tool-call-1")); + assert!(!deny.approved); + assert_eq!(deny.reason.as_deref(), Some("deny_all")); +} + +#[tokio::test] +async fn function_host_tool_executor_executes_registered_handlers() { + let mut handlers = HashMap::new(); + handlers.insert( + "add".to_string(), + Arc::new( + |args: &Value, + _request: &HostToolRequest| + -> Result> { + Ok(json!( + args["a"].as_i64().unwrap() + args["b"].as_i64().unwrap() + )) + }, + ) as Arc<_>, + ); + let executor = FunctionHostToolExecutor::new(handlers); + + let result = executor + .execute(&HostToolRequest { + request_id: Some("exec-1".to_string()), + tool_name: "add".to_string(), + arguments: json!({ "a": 2, "b": 3 }), + ..Default::default() + }) + .await + .unwrap(); + + assert!(result.success); + assert_eq!(result.request_id.as_deref(), Some("exec-1")); + assert_eq!(result.result, Some(json!(5))); +} + +#[tokio::test] +async fn function_host_tool_executor_passes_empty_arguments() { + let mut handlers = HashMap::new(); + handlers.insert( + "count".to_string(), + Arc::new( + |args: &Value, + _request: &HostToolRequest| + -> Result> { + Ok(json!(args.as_object().unwrap().len())) + }, + ) as Arc<_>, + ); + let executor = FunctionHostToolExecutor::new(handlers); + + let result = executor + .execute(&HostToolRequest { + tool_name: "count".to_string(), + ..Default::default() + }) + .await + .unwrap(); + + assert!(result.success); + assert_eq!(result.result, Some(json!(0))); +} + +#[tokio::test] +async fn function_host_tool_executor_returns_failure_results() { + let mut handlers = HashMap::new(); + handlers.insert( + "fail".to_string(), + Arc::new( + |_args: &Value, + _request: &HostToolRequest| + -> Result> { + Err(Box::new(std::io::Error::other("boom"))) + }, + ) as Arc<_>, + ); + let executor = FunctionHostToolExecutor::new(handlers); + + let missing = executor + .execute(&HostToolRequest { + tool_name: "missing".to_string(), + ..Default::default() + }) + .await + .unwrap(); + let thrown = executor + .execute(&HostToolRequest { + tool_name: "fail".to_string(), + ..Default::default() + }) + .await + .unwrap(); + + assert!(!missing.success); + assert_eq!(missing.error_kind.as_deref(), Some("not_found")); + assert!(!thrown.success); + assert_eq!(thrown.error_kind.as_deref(), Some("exception")); + assert_eq!(thrown.result, Some(json!({ "message": "boom" }))); +} diff --git a/runtime/rust/prompty/tests/harness_replay_verifier.rs b/runtime/rust/prompty/tests/harness_replay_verifier.rs new file mode 100644 index 00000000..a274e563 --- /dev/null +++ b/runtime/rust/prompty/tests/harness_replay_verifier.rs @@ -0,0 +1,50 @@ +use prompty::harness::ReferenceReplayVerifier; +use prompty::model::pipeline::replay_journal_record::{ + ReplayJournalRecord, ReplayRecordKind, ReplayRecordStatus, +}; +use prompty::model::pipeline::replay_verification_request::ReplayVerificationRequest; +use prompty::model::pipeline::replay_verification_result::ReplayVerificationStatus; + +#[test] +fn reference_replay_verifier_passes_identical_records() { + let record = ReplayJournalRecord { + kind: ReplayRecordKind::Turn, + r#type: Some("turn_end".to_string()), + turn_id: Some("turn-1".to_string()), + iteration: Some(1), + status: Some(ReplayRecordStatus::Success), + ..Default::default() + }; + + let result = ReferenceReplayVerifier.verify(ReplayVerificationRequest { + expected: vec![record.clone()], + actual: vec![record], + }); + + assert_eq!(result.status, ReplayVerificationStatus::Passed); + assert!(result.mismatches.is_empty()); + assert_eq!(result.expected_count, 1); + assert_eq!(result.actual_count, 1); +} + +#[test] +fn reference_replay_verifier_reports_mismatches() { + let result = ReferenceReplayVerifier.verify(ReplayVerificationRequest { + expected: vec![ReplayJournalRecord { + kind: ReplayRecordKind::Summary, + session_id: Some("session-1".to_string()), + status: Some(ReplayRecordStatus::Success), + ..Default::default() + }], + actual: vec![ReplayJournalRecord { + kind: ReplayRecordKind::Summary, + session_id: Some("session-1".to_string()), + status: Some(ReplayRecordStatus::Error), + ..Default::default() + }], + }); + + assert_eq!(result.status, ReplayVerificationStatus::Failed); + assert_eq!(result.mismatches[0].index, 0); + assert_eq!(result.mismatches[0].message, "Replay record mismatch"); +} diff --git a/runtime/rust/prompty/tests/harness_turn_runner.rs b/runtime/rust/prompty/tests/harness_turn_runner.rs new file mode 100644 index 00000000..1f6a50fe --- /dev/null +++ b/runtime/rust/prompty/tests/harness_turn_runner.rs @@ -0,0 +1,604 @@ +use std::collections::HashMap; +use std::fs; +use std::sync::{Arc, Mutex}; +use std::time::{SystemTime, UNIX_EPOCH}; + +use prompty::harness::{ + AdapterError, AllowAllPermissionResolver, CollectingEventSink, DenyAllPermissionResolver, + FunctionHostToolExecutor, InMemoryCheckpointStore, JsonlEventJournalWriter, + ReferenceTurnRunner, +}; +use prompty::model::events::host_tool_request::HostToolRequest; +use prompty::model::events::permission_decision::PermissionDecision; +use prompty::model::events::permission_request::PermissionRequest; +use prompty::model::pipeline::RunTurnRequest; +use prompty::model::pipeline::TurnModelRequest; +use prompty::model::pipeline::TurnModelResponse; +use prompty::model::pipeline::checkpoint_store::CheckpointStore; +use prompty::model::pipeline::permission_resolver::PermissionResolver; +use prompty::model::pipeline::run_turn_result::RunTurnStatus; +use prompty::model::pipeline::turn_options::TurnOptions; +use serde::Deserialize; +use serde_json::{Value, json}; + +fn trace_path(prefix: &str) -> std::path::PathBuf { + std::env::temp_dir().join(format!( + "{prefix}-{}.jsonl", + SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap() + .as_nanos() + )) +} + +fn fixed_ids() -> Arc String + Send + Sync> { + let index = Arc::new(Mutex::new(0)); + Arc::new(move |prefix: &str| { + let mut index = index.lock().unwrap(); + *index += 1; + format!("{prefix}-{index}") + }) +} + +fn fixed_clock() -> Arc String + Send + Sync> { + Arc::new(|| "2026-06-28T00:00:00Z".to_string()) +} + +fn run_request(session_id: &str, turn_id: &str) -> RunTurnRequest { + RunTurnRequest { + session_id: session_id.to_string(), + turn_id: turn_id.to_string(), + inputs: json!({}), + options: None, + } +} + +fn records(path: &std::path::Path) -> Vec { + fs::read_to_string(path) + .unwrap() + .lines() + .map(|line| serde_json::from_str(line).unwrap()) + .collect() +} + +#[derive(Debug, Deserialize)] +struct ReplayVectors { + version: i32, + clock: String, + #[serde(rename = "sessionId")] + session_id: String, + #[serde(rename = "turnId")] + turn_id: String, + scenarios: Vec, +} + +#[derive(Debug, Deserialize)] +struct ReplayScenario { + name: String, + inputs: Option, + #[serde(rename = "maxIterations")] + max_iterations: Option, + expected: Vec, +} + +#[derive(Clone)] +struct ScenarioPermissionResolver { + approved: bool, +} + +#[async_trait::async_trait] +impl PermissionResolver for ScenarioPermissionResolver { + async fn request( + &self, + request: &PermissionRequest, + ) -> Result { + Ok(PermissionDecision { + request_id: request.request_id.clone(), + tool_call_id: request.tool_call_id.clone(), + permission: request.permission.clone(), + approved: self.approved, + reason: Some( + if self.approved { + "allow_all" + } else { + "deny_all" + } + .to_string(), + ), + result: Value::Null, + }) + } +} + +fn replay_vectors() -> ReplayVectors { + let path = std::path::Path::new(env!("CARGO_MANIFEST_DIR")) + .join("..") + .join("..") + .join("..") + .join("spec") + .join("vectors") + .join("harness") + .join("replay_vectors.json"); + let vectors: ReplayVectors = serde_json::from_str(&fs::read_to_string(path).unwrap()).unwrap(); + assert_eq!(vectors.version, 1); + vectors +} + +fn normalize_journal(records: Vec) -> Vec { + records + .iter() + .map(|record| { + if record["kind"] == "summary" { + let summary = &record["summary"]; + return format!( + "summary:{}:{}:turns={}:checkpoints={}", + summary["sessionId"].as_str().unwrap(), + summary["status"].as_str().unwrap(), + summary["turns"].as_i64().unwrap(), + summary["checkpoints"].as_i64().unwrap() + ); + } + + let event = &record["event"]; + let event_type = event["type"].as_str().unwrap(); + if record["kind"] == "session" { + if event_type == "session_end" { + return format!( + "session:{}:{}:{}:{}", + event_type, + event["sessionId"].as_str().unwrap(), + event["turnId"].as_str().unwrap(), + event["payload"]["status"].as_str().unwrap() + ); + } + return format!( + "session:{}:{}:{}", + event_type, + event["sessionId"].as_str().unwrap(), + event["turnId"].as_str().unwrap() + ); + } + + let payload = &event["payload"]; + let iteration = event["iteration"].as_i64().unwrap(); + match event_type { + "permission_requested" => { + format!( + "turn:{event_type}:{iteration}:{}", + payload["requestId"].as_str().unwrap() + ) + } + "permission_completed" => { + format!( + "turn:{event_type}:{iteration}:{}", + payload["approved"].as_bool().unwrap() + ) + } + "tool_execution_start" => { + format!( + "turn:{event_type}:{iteration}:{}", + payload["toolName"].as_str().unwrap() + ) + } + "tool_execution_complete" | "tool_result" => { + let mut value = format!( + "turn:{event_type}:{iteration}:{}:{}", + payload["toolName"].as_str().unwrap(), + payload["success"].as_bool().unwrap() + ); + if let Some(error_kind) = payload["errorKind"].as_str() { + value.push(':'); + value.push_str(error_kind); + } + value + } + "error" => format!( + "turn:{event_type}:{iteration}:{}", + payload["errorKind"].as_str().unwrap() + ), + "turn_end" => format!( + "turn:{event_type}:{iteration}:{}", + payload["status"].as_str().unwrap() + ), + _ => format!("turn:{event_type}:{iteration}"), + } + }) + .collect() +} + +fn model_for_scenario( + name: &str, +) -> Arc Result + Send + Sync> { + let name = name.to_string(); + Arc::new(move |request: TurnModelRequest| { + if name == "no_tool" { + return Ok(TurnModelResponse { + output: Some( + json!({ "text": format!("hello {}", request.inputs["name"].as_str().unwrap()) }), + ), + checkpoint_state: json!({ "stable": true }), + ..Default::default() + }); + } + if request.iteration == 0 { + return Ok(TurnModelResponse { + tool_requests: vec![HostToolRequest { + request_id: Some("exec-1".to_string()), + tool_call_id: Some("call-1".to_string()), + tool_name: if name == "tool_failure" { + "fail" + } else { + "add" + } + .to_string(), + arguments: json!({ "a": 2, "b": 3 }), + ..Default::default() + }], + ..Default::default() + }); + } + Ok(TurnModelResponse { + output: Some(json!({ + "toolResult": request.tool_results[0].result, + "errorKind": request.tool_results[0].error_kind, + })), + ..Default::default() + }) + }) +} + +#[tokio::test] +async fn reference_turn_runner_emits_journals_and_checkpoints() { + let path = trace_path("prompty-turn"); + let sink = CollectingEventSink::new(); + let checkpoint_store = InMemoryCheckpointStore::new(); + let runner = ReferenceTurnRunner::new( + sink.clone(), + JsonlEventJournalWriter::new(&path), + checkpoint_store.clone(), + AllowAllPermissionResolver, + FunctionHostToolExecutor::default(), + Arc::new(|request: TurnModelRequest| { + Ok(TurnModelResponse { + output: Some( + json!({ "text": format!("hello {}", request.inputs["name"].as_str().unwrap()) }), + ), + checkpoint_state: json!({ "stable": true }), + ..Default::default() + }) + }), + fixed_clock(), + fixed_ids(), + ); + + let result = runner + .run(RunTurnRequest { + session_id: "session-1".to_string(), + turn_id: "turn-1".to_string(), + inputs: json!({ "name": "Ada" }), + options: Some(TurnOptions { + max_iterations: Some(3), + ..Default::default() + }), + }) + .await + .unwrap(); + + assert_eq!(result.status, RunTurnStatus::Success); + assert_eq!(result.iterations, 1); + assert_eq!(result.output, Some(json!({ "text": "hello Ada" }))); + assert_eq!( + sink.turn_events() + .iter() + .map(|event| event.r#type.to_string()) + .collect::>(), + vec!["turn_start", "llm_start", "llm_complete", "turn_end"] + ); + assert_eq!( + sink.session_events() + .iter() + .map(|event| event.r#type.to_string()) + .collect::>(), + vec!["session_start", "checkpoint_created", "session_end"] + ); + let checkpoint = checkpoint_store + .load(&"session-1".to_string(), &"turn-1-checkpoint-0".to_string()) + .await + .unwrap() + .unwrap(); + assert_eq!(checkpoint.state["stable"], true); + assert_eq!( + records(&path) + .iter() + .map(|record| record["kind"].as_str().unwrap()) + .collect::>(), + vec![ + "session", "turn", "turn", "turn", "session", "turn", "session", "summary" + ] + ); + let _ = fs::remove_file(path); +} + +#[tokio::test] +async fn reference_turn_runner_executes_host_tools() { + let path = trace_path("prompty-turn-tool"); + let sink = CollectingEventSink::new(); + let mut handlers = HashMap::new(); + handlers.insert( + "add".to_string(), + Arc::new( + |args: &Value, + _request: &HostToolRequest| + -> Result> { + Ok(json!( + args["a"].as_i64().unwrap() + args["b"].as_i64().unwrap() + )) + }, + ) as Arc<_>, + ); + let runner = ReferenceTurnRunner::new( + sink.clone(), + JsonlEventJournalWriter::new(&path), + InMemoryCheckpointStore::new(), + AllowAllPermissionResolver, + FunctionHostToolExecutor::new(handlers), + Arc::new(|request: TurnModelRequest| { + if request.iteration == 0 { + return Ok(TurnModelResponse { + tool_requests: vec![HostToolRequest { + request_id: Some("exec-1".to_string()), + tool_call_id: Some("call-1".to_string()), + tool_name: "add".to_string(), + arguments: json!({ "a": 2, "b": 3 }), + ..Default::default() + }], + ..Default::default() + }); + } + Ok(TurnModelResponse { + output: Some(json!({ "toolResult": request.tool_results[0].result })), + ..Default::default() + }) + }), + fixed_clock(), + fixed_ids(), + ); + + let result = runner + .run(run_request("session-1", "turn-1")) + .await + .unwrap(); + + assert_eq!(result.output, Some(json!({ "toolResult": 5 }))); + assert!(result.tool_results[0].success); + assert_eq!( + sink.turn_events() + .iter() + .map(|event| event.r#type.to_string()) + .collect::>(), + vec![ + "turn_start", + "llm_start", + "llm_complete", + "permission_requested", + "permission_completed", + "tool_execution_start", + "tool_execution_complete", + "tool_result", + "messages_updated", + "llm_start", + "llm_complete", + "turn_end" + ] + ); + let _ = fs::remove_file(path); +} + +#[tokio::test] +async fn reference_turn_runner_denied_permission_skips_execution() { + let path = trace_path("prompty-turn-deny"); + let sink = CollectingEventSink::new(); + let mut handlers = HashMap::new(); + handlers.insert( + "shell".to_string(), + Arc::new( + |_args: &Value, + _request: &HostToolRequest| + -> Result> { + panic!("should not execute") + }, + ) as Arc<_>, + ); + let runner = ReferenceTurnRunner::new( + sink.clone(), + JsonlEventJournalWriter::new(&path), + InMemoryCheckpointStore::new(), + DenyAllPermissionResolver, + FunctionHostToolExecutor::new(handlers), + Arc::new(|request: TurnModelRequest| { + if request.iteration == 0 { + return Ok(TurnModelResponse { + tool_requests: vec![HostToolRequest { + request_id: Some("exec-1".to_string()), + tool_name: "shell".to_string(), + ..Default::default() + }], + ..Default::default() + }); + } + Ok(TurnModelResponse { + output: Some(json!({ "denied": request.tool_results[0].error_kind })), + ..Default::default() + }) + }), + fixed_clock(), + fixed_ids(), + ); + + let result = runner + .run(run_request("session-1", "turn-1")) + .await + .unwrap(); + + assert_eq!( + result.output, + Some(json!({ "denied": "permission_denied" })) + ); + assert!( + !sink + .turn_events() + .iter() + .any(|event| event.r#type.to_string() == "tool_execution_start") + ); + let _ = fs::remove_file(path); +} + +#[tokio::test] +async fn reference_turn_runner_host_tool_failure_is_replayable() { + let path = trace_path("prompty-turn-fail"); + let mut handlers = HashMap::new(); + handlers.insert( + "fail".to_string(), + Arc::new( + |_args: &Value, + _request: &HostToolRequest| + -> Result> { + Err(Box::new(std::io::Error::other("boom"))) + }, + ) as Arc<_>, + ); + let runner = ReferenceTurnRunner::new( + CollectingEventSink::new(), + JsonlEventJournalWriter::new(&path), + InMemoryCheckpointStore::new(), + AllowAllPermissionResolver, + FunctionHostToolExecutor::new(handlers), + Arc::new(|request: TurnModelRequest| { + if request.iteration == 0 { + return Ok(TurnModelResponse { + tool_requests: vec![HostToolRequest { + request_id: Some("exec-1".to_string()), + tool_name: "fail".to_string(), + ..Default::default() + }], + ..Default::default() + }); + } + Ok(TurnModelResponse { + output: Some( + request.tool_results[0].to_value(&prompty::model::context::SaveContext::new()), + ), + ..Default::default() + }) + }), + fixed_clock(), + fixed_ids(), + ); + + let result = runner + .run(run_request("session-1", "turn-1")) + .await + .unwrap(); + + assert_eq!(result.output.as_ref().unwrap()["success"], false); + assert_eq!(result.output.as_ref().unwrap()["errorKind"], "exception"); + let _ = fs::remove_file(path); +} + +#[tokio::test] +async fn reference_turn_runner_deterministic_journal() { + async fn run_once(path: &std::path::Path) -> Vec { + let runner = ReferenceTurnRunner::new( + CollectingEventSink::new(), + JsonlEventJournalWriter::new(path), + InMemoryCheckpointStore::new(), + AllowAllPermissionResolver, + FunctionHostToolExecutor::default(), + Arc::new(|_request: TurnModelRequest| { + Ok(TurnModelResponse { + output: Some(json!("done")), + ..Default::default() + }) + }), + fixed_clock(), + fixed_ids(), + ); + runner + .run(run_request("session-1", "turn-1")) + .await + .unwrap(); + records(path) + } + + let first_path = trace_path("prompty-turn-first"); + let second_path = trace_path("prompty-turn-second"); + assert_eq!(run_once(&first_path).await, run_once(&second_path).await); + let _ = fs::remove_file(first_path); + let _ = fs::remove_file(second_path); +} + +#[tokio::test] +async fn reference_turn_runner_matches_shared_golden_replay_vectors() { + let vectors = replay_vectors(); + for scenario in vectors.scenarios { + let path = trace_path(&format!("prompty-replay-{}", scenario.name)); + let mut handlers = HashMap::new(); + handlers.insert( + "add".to_string(), + Arc::new( + |args: &Value, + _request: &HostToolRequest| + -> Result> { + Ok(json!( + args["a"].as_i64().unwrap() + args["b"].as_i64().unwrap() + )) + }, + ) as Arc<_>, + ); + handlers.insert( + "fail".to_string(), + Arc::new( + |_args: &Value, + _request: &HostToolRequest| + -> Result> { + Err(Box::new(std::io::Error::other("boom"))) + }, + ) as Arc<_>, + ); + let runner = ReferenceTurnRunner::new( + CollectingEventSink::new(), + JsonlEventJournalWriter::new(&path), + InMemoryCheckpointStore::new(), + ScenarioPermissionResolver { + approved: scenario.name != "permission_denied", + }, + FunctionHostToolExecutor::new(handlers), + model_for_scenario(&scenario.name), + Arc::new({ + let clock = vectors.clock.clone(); + move || clock.clone() + }), + fixed_ids(), + ); + runner + .run(RunTurnRequest { + session_id: vectors.session_id.clone(), + turn_id: vectors.turn_id.clone(), + inputs: scenario.inputs.unwrap_or_else(|| json!({})), + options: Some(TurnOptions { + max_iterations: scenario.max_iterations, + ..Default::default() + }), + }) + .await + .unwrap(); + + assert_eq!( + normalize_journal(records(&path)), + scenario.expected, + "{}", + scenario.name + ); + let _ = fs::remove_file(path); + } +} diff --git a/runtime/rust/prompty/tests/model/agent/guardrail_result_test.rs b/runtime/rust/prompty/tests/model/agent/guardrail_result_test.rs index 462e70dc..b1a8e6fb 100644 --- a/runtime/rust/prompty/tests/model/agent/guardrail_result_test.rs +++ b/runtime/rust/prompty/tests/model/agent/guardrail_result_test.rs @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. #![allow( unused_imports, diff --git a/runtime/rust/prompty/tests/model/agent/mod.rs b/runtime/rust/prompty/tests/model/agent/mod.rs index 26b1866e..c7d7ad39 100644 --- a/runtime/rust/prompty/tests/model/agent/mod.rs +++ b/runtime/rust/prompty/tests/model/agent/mod.rs @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. #![allow( unused_imports, diff --git a/runtime/rust/prompty/tests/model/agent/prompty_test.rs b/runtime/rust/prompty/tests/model/agent/prompty_test.rs index 5b85c643..f3f47d02 100644 --- a/runtime/rust/prompty/tests/model/agent/prompty_test.rs +++ b/runtime/rust/prompty/tests/model/agent/prompty_test.rs @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. #![allow( unused_imports, diff --git a/runtime/rust/prompty/tests/model/connection/connection_test.rs b/runtime/rust/prompty/tests/model/connection/connection_test.rs index c2c2c2bf..8d267f97 100644 --- a/runtime/rust/prompty/tests/model/connection/connection_test.rs +++ b/runtime/rust/prompty/tests/model/connection/connection_test.rs @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. #![allow( unused_imports, diff --git a/runtime/rust/prompty/tests/model/connection/mod.rs b/runtime/rust/prompty/tests/model/connection/mod.rs index fd60e9df..7edbe079 100644 --- a/runtime/rust/prompty/tests/model/connection/mod.rs +++ b/runtime/rust/prompty/tests/model/connection/mod.rs @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. #![allow( unused_imports, diff --git a/runtime/rust/prompty/tests/model/conversation/content_part_test.rs b/runtime/rust/prompty/tests/model/conversation/content_part_test.rs index 74221529..0eb3b128 100644 --- a/runtime/rust/prompty/tests/model/conversation/content_part_test.rs +++ b/runtime/rust/prompty/tests/model/conversation/content_part_test.rs @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. #![allow( unused_imports, diff --git a/runtime/rust/prompty/tests/model/conversation/message_test.rs b/runtime/rust/prompty/tests/model/conversation/message_test.rs index 293b666a..a241f55e 100644 --- a/runtime/rust/prompty/tests/model/conversation/message_test.rs +++ b/runtime/rust/prompty/tests/model/conversation/message_test.rs @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. #![allow( unused_imports, diff --git a/runtime/rust/prompty/tests/model/conversation/mod.rs b/runtime/rust/prompty/tests/model/conversation/mod.rs index 467f9b60..57cf9d46 100644 --- a/runtime/rust/prompty/tests/model/conversation/mod.rs +++ b/runtime/rust/prompty/tests/model/conversation/mod.rs @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. #![allow( unused_imports, diff --git a/runtime/rust/prompty/tests/model/conversation/thread_marker_test.rs b/runtime/rust/prompty/tests/model/conversation/thread_marker_test.rs index 30c3b0b9..e87587b8 100644 --- a/runtime/rust/prompty/tests/model/conversation/thread_marker_test.rs +++ b/runtime/rust/prompty/tests/model/conversation/thread_marker_test.rs @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. #![allow( unused_imports, diff --git a/runtime/rust/prompty/tests/model/conversation/tool_call_test.rs b/runtime/rust/prompty/tests/model/conversation/tool_call_test.rs index 5f95a39e..9a1c8bcd 100644 --- a/runtime/rust/prompty/tests/model/conversation/tool_call_test.rs +++ b/runtime/rust/prompty/tests/model/conversation/tool_call_test.rs @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. #![allow( unused_imports, diff --git a/runtime/rust/prompty/tests/model/conversation/tool_result_test.rs b/runtime/rust/prompty/tests/model/conversation/tool_result_test.rs index fb3d8779..f3a49b09 100644 --- a/runtime/rust/prompty/tests/model/conversation/tool_result_test.rs +++ b/runtime/rust/prompty/tests/model/conversation/tool_result_test.rs @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. #![allow( unused_imports, @@ -9,6 +10,7 @@ )] use prompty::model::ToolResult; +use prompty::model::ToolResultStatus; use prompty::model::context::{LoadContext, SaveContext}; #[test] @@ -20,7 +22,10 @@ fn test_tool_result_load_json() { "kind": "text", "value": "72°F and sunny" } - ] + ], + "errorKind": "missing_tool", + "errorMessage": "Tool 'get_weather' is not registered", + "durationMs": 42 } "####; let ctx = LoadContext::default(); @@ -31,7 +36,24 @@ fn test_tool_result_load_json() { result.err() ); let instance = result.unwrap(); - let _ = instance; // load succeeded, no scalar properties to validate + assert!( + instance.error_kind.is_some(), + "Expected error_kind to be Some" + ); + assert_eq!(instance.error_kind.as_ref().unwrap(), &"missing_tool"); + assert!( + instance.error_message.is_some(), + "Expected error_message to be Some" + ); + assert_eq!( + instance.error_message.as_ref().unwrap(), + &"Tool 'get_weather' is not registered" + ); + assert!( + instance.duration_ms.is_some(), + "Expected duration_ms to be Some" + ); + assert_eq!(instance.duration_ms.as_ref().unwrap(), &42.0); } #[test] @@ -40,6 +62,9 @@ fn test_tool_result_load_yaml() { parts: - kind: text value: 72°F and sunny +errorKind: missing_tool +errorMessage: Tool 'get_weather' is not registered +durationMs: 42 "####; let ctx = LoadContext::default(); @@ -50,7 +75,18 @@ parts: result.err() ); let instance = result.unwrap(); - let _ = instance; // load succeeded, no scalar properties to validate + assert!( + instance.error_kind.is_some(), + "Expected error_kind to be Some" + ); + assert!( + instance.error_message.is_some(), + "Expected error_message to be Some" + ); + assert!( + instance.duration_ms.is_some(), + "Expected duration_ms to be Some" + ); } #[test] @@ -62,7 +98,10 @@ fn test_tool_result_roundtrip() { "kind": "text", "value": "72°F and sunny" } - ] + ], + "errorKind": "missing_tool", + "errorMessage": "Tool 'get_weather' is not registered", + "durationMs": 42 } "####; let load_ctx = LoadContext::default(); diff --git a/runtime/rust/prompty/tests/model/core/file_not_found_error_test.rs b/runtime/rust/prompty/tests/model/core/file_not_found_error_test.rs index b0c3ac2b..ffac0c98 100644 --- a/runtime/rust/prompty/tests/model/core/file_not_found_error_test.rs +++ b/runtime/rust/prompty/tests/model/core/file_not_found_error_test.rs @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. #![allow( unused_imports, diff --git a/runtime/rust/prompty/tests/model/core/invoker_error_test.rs b/runtime/rust/prompty/tests/model/core/invoker_error_test.rs index bd9317e8..e22ac916 100644 --- a/runtime/rust/prompty/tests/model/core/invoker_error_test.rs +++ b/runtime/rust/prompty/tests/model/core/invoker_error_test.rs @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. #![allow( unused_imports, diff --git a/runtime/rust/prompty/tests/model/core/mod.rs b/runtime/rust/prompty/tests/model/core/mod.rs index 57c0ebca..761b53c2 100644 --- a/runtime/rust/prompty/tests/model/core/mod.rs +++ b/runtime/rust/prompty/tests/model/core/mod.rs @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. #![allow( unused_imports, diff --git a/runtime/rust/prompty/tests/model/core/property_test.rs b/runtime/rust/prompty/tests/model/core/property_test.rs index 865f37fb..ff146898 100644 --- a/runtime/rust/prompty/tests/model/core/property_test.rs +++ b/runtime/rust/prompty/tests/model/core/property_test.rs @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. #![allow( unused_imports, diff --git a/runtime/rust/prompty/tests/model/core/validation_error_test.rs b/runtime/rust/prompty/tests/model/core/validation_error_test.rs index 7d787508..93d1cad3 100644 --- a/runtime/rust/prompty/tests/model/core/validation_error_test.rs +++ b/runtime/rust/prompty/tests/model/core/validation_error_test.rs @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. #![allow( unused_imports, diff --git a/runtime/rust/prompty/tests/model/core/validation_result_test.rs b/runtime/rust/prompty/tests/model/core/validation_result_test.rs index 2dbcf748..6d823775 100644 --- a/runtime/rust/prompty/tests/model/core/validation_result_test.rs +++ b/runtime/rust/prompty/tests/model/core/validation_result_test.rs @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. #![allow( unused_imports, diff --git a/runtime/rust/prompty/tests/model/events/checkpoint_test.rs b/runtime/rust/prompty/tests/model/events/checkpoint_test.rs new file mode 100644 index 00000000..8f191240 --- /dev/null +++ b/runtime/rust/prompty/tests/model/events/checkpoint_test.rs @@ -0,0 +1,119 @@ +// +// Code generated by Typra emitter; DO NOT EDIT. + +#![allow( + unused_imports, + dead_code, + non_camel_case_types, + unused_variables, + clippy::all +)] + +use prompty::model::Checkpoint; +use prompty::model::context::{LoadContext, SaveContext}; + +#[test] +fn test_checkpoint_load_json() { + let json = r####" +{ + "id": "chk_abc123", + "sessionId": "sess_abc123", + "turnId": "turn_001", + "checkpointNumber": 3, + "title": "Added harness contracts", + "createdAt": "2026-06-09T20:00:00Z" +} +"####; + let ctx = LoadContext::default(); + let result = Checkpoint::from_json(json, &ctx); + assert!( + result.is_ok(), + "Failed to load from JSON: {:?}", + result.err() + ); + let instance = result.unwrap(); + assert!(instance.id.is_some(), "Expected id to be Some"); + assert_eq!(instance.id.as_ref().unwrap(), &"chk_abc123"); + assert!( + instance.session_id.is_some(), + "Expected session_id to be Some" + ); + assert_eq!(instance.session_id.as_ref().unwrap(), &"sess_abc123"); + assert!(instance.turn_id.is_some(), "Expected turn_id to be Some"); + assert_eq!(instance.turn_id.as_ref().unwrap(), &"turn_001"); + assert!( + instance.checkpoint_number.is_some(), + "Expected checkpoint_number to be Some" + ); + assert_eq!(instance.checkpoint_number.as_ref().unwrap(), &3); + assert_eq!(instance.title, "Added harness contracts"); + assert!( + instance.created_at.is_some(), + "Expected created_at to be Some" + ); + assert_eq!( + instance.created_at.as_ref().unwrap(), + &"2026-06-09T20:00:00Z" + ); +} + +#[test] +fn test_checkpoint_load_yaml() { + let yaml = r####" +id: chk_abc123 +sessionId: sess_abc123 +turnId: turn_001 +checkpointNumber: 3 +title: Added harness contracts +createdAt: "2026-06-09T20:00:00Z" + +"####; + let ctx = LoadContext::default(); + let result = Checkpoint::from_yaml(yaml, &ctx); + assert!( + result.is_ok(), + "Failed to load from YAML: {:?}", + result.err() + ); + let instance = result.unwrap(); + assert!(instance.id.is_some(), "Expected id to be Some"); + assert!( + instance.session_id.is_some(), + "Expected session_id to be Some" + ); + assert!(instance.turn_id.is_some(), "Expected turn_id to be Some"); + assert!( + instance.checkpoint_number.is_some(), + "Expected checkpoint_number to be Some" + ); + assert_eq!(instance.title, "Added harness contracts"); + assert!( + instance.created_at.is_some(), + "Expected created_at to be Some" + ); +} + +#[test] +fn test_checkpoint_roundtrip() { + let json = r####" +{ + "id": "chk_abc123", + "sessionId": "sess_abc123", + "turnId": "turn_001", + "checkpointNumber": 3, + "title": "Added harness contracts", + "createdAt": "2026-06-09T20:00:00Z" +} +"####; + let load_ctx = LoadContext::default(); + let result = Checkpoint::from_json(json, &load_ctx); + assert!(result.is_ok(), "Failed to load: {:?}", result.err()); + let instance = result.unwrap(); + let save_ctx = SaveContext::default(); + let json_output = instance.to_json(&save_ctx); + assert!( + json_output.is_ok(), + "Failed to serialize to JSON: {:?}", + json_output.err() + ); +} diff --git a/runtime/rust/prompty/tests/model/events/compaction_complete_payload_test.rs b/runtime/rust/prompty/tests/model/events/compaction_complete_payload_test.rs index a867689c..adcf4c54 100644 --- a/runtime/rust/prompty/tests/model/events/compaction_complete_payload_test.rs +++ b/runtime/rust/prompty/tests/model/events/compaction_complete_payload_test.rs @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. #![allow( unused_imports, @@ -16,7 +17,8 @@ fn test_compaction_complete_payload_load_json() { let json = r####" { "removed": 5, - "remaining": 3 + "remaining": 3, + "summaryLength": 1200 } "####; let ctx = LoadContext::default(); @@ -29,6 +31,11 @@ fn test_compaction_complete_payload_load_json() { let instance = result.unwrap(); assert_eq!(instance.removed, 5); assert_eq!(instance.remaining, 3); + assert!( + instance.summary_length.is_some(), + "Expected summary_length to be Some" + ); + assert_eq!(instance.summary_length.as_ref().unwrap(), &1200); } #[test] @@ -36,6 +43,7 @@ fn test_compaction_complete_payload_load_yaml() { let yaml = r####" removed: 5 remaining: 3 +summaryLength: 1200 "####; let ctx = LoadContext::default(); @@ -48,6 +56,10 @@ remaining: 3 let instance = result.unwrap(); assert_eq!(instance.removed, 5); assert_eq!(instance.remaining, 3); + assert!( + instance.summary_length.is_some(), + "Expected summary_length to be Some" + ); } #[test] @@ -55,7 +67,8 @@ fn test_compaction_complete_payload_roundtrip() { let json = r####" { "removed": 5, - "remaining": 3 + "remaining": 3, + "summaryLength": 1200 } "####; let load_ctx = LoadContext::default(); diff --git a/runtime/rust/prompty/tests/model/events/compaction_failed_payload_test.rs b/runtime/rust/prompty/tests/model/events/compaction_failed_payload_test.rs index 98236839..141562e0 100644 --- a/runtime/rust/prompty/tests/model/events/compaction_failed_payload_test.rs +++ b/runtime/rust/prompty/tests/model/events/compaction_failed_payload_test.rs @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. #![allow( unused_imports, diff --git a/runtime/rust/prompty/tests/model/events/compaction_start_payload_test.rs b/runtime/rust/prompty/tests/model/events/compaction_start_payload_test.rs new file mode 100644 index 00000000..2b46d1a1 --- /dev/null +++ b/runtime/rust/prompty/tests/model/events/compaction_start_payload_test.rs @@ -0,0 +1,68 @@ +// +// Code generated by Typra emitter; DO NOT EDIT. + +#![allow( + unused_imports, + dead_code, + non_camel_case_types, + unused_variables, + clippy::all +)] + +use prompty::model::CompactionStartPayload; +use prompty::model::context::{LoadContext, SaveContext}; + +#[test] +fn test_compaction_start_payload_load_json() { + let json = r####" +{ + "droppedCount": 5 +} +"####; + let ctx = LoadContext::default(); + let result = CompactionStartPayload::from_json(json, &ctx); + assert!( + result.is_ok(), + "Failed to load from JSON: {:?}", + result.err() + ); + let instance = result.unwrap(); + assert_eq!(instance.dropped_count, 5); +} + +#[test] +fn test_compaction_start_payload_load_yaml() { + let yaml = r####" +droppedCount: 5 + +"####; + let ctx = LoadContext::default(); + let result = CompactionStartPayload::from_yaml(yaml, &ctx); + assert!( + result.is_ok(), + "Failed to load from YAML: {:?}", + result.err() + ); + let instance = result.unwrap(); + assert_eq!(instance.dropped_count, 5); +} + +#[test] +fn test_compaction_start_payload_roundtrip() { + let json = r####" +{ + "droppedCount": 5 +} +"####; + let load_ctx = LoadContext::default(); + let result = CompactionStartPayload::from_json(json, &load_ctx); + assert!(result.is_ok(), "Failed to load: {:?}", result.err()); + let instance = result.unwrap(); + let save_ctx = SaveContext::default(); + let json_output = instance.to_json(&save_ctx); + assert!( + json_output.is_ok(), + "Failed to serialize to JSON: {:?}", + json_output.err() + ); +} diff --git a/runtime/rust/prompty/tests/model/events/done_event_payload_test.rs b/runtime/rust/prompty/tests/model/events/done_event_payload_test.rs index 23a08864..7faa654d 100644 --- a/runtime/rust/prompty/tests/model/events/done_event_payload_test.rs +++ b/runtime/rust/prompty/tests/model/events/done_event_payload_test.rs @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. #![allow( unused_imports, @@ -10,58 +11,3 @@ use prompty::model::DoneEventPayload; use prompty::model::context::{LoadContext, SaveContext}; - -#[test] -fn test_done_event_payload_load_json() { - let json = r####" -{ - "response": "The weather in Paris is 72°F and sunny." -} -"####; - let ctx = LoadContext::default(); - let result = DoneEventPayload::from_json(json, &ctx); - assert!( - result.is_ok(), - "Failed to load from JSON: {:?}", - result.err() - ); - let instance = result.unwrap(); - assert_eq!(instance.response, "The weather in Paris is 72°F and sunny."); -} - -#[test] -fn test_done_event_payload_load_yaml() { - let yaml = r####" -response: The weather in Paris is 72°F and sunny. - -"####; - let ctx = LoadContext::default(); - let result = DoneEventPayload::from_yaml(yaml, &ctx); - assert!( - result.is_ok(), - "Failed to load from YAML: {:?}", - result.err() - ); - let instance = result.unwrap(); - assert_eq!(instance.response, "The weather in Paris is 72°F and sunny."); -} - -#[test] -fn test_done_event_payload_roundtrip() { - let json = r####" -{ - "response": "The weather in Paris is 72°F and sunny." -} -"####; - let load_ctx = LoadContext::default(); - let result = DoneEventPayload::from_json(json, &load_ctx); - assert!(result.is_ok(), "Failed to load: {:?}", result.err()); - let instance = result.unwrap(); - let save_ctx = SaveContext::default(); - let json_output = instance.to_json(&save_ctx); - assert!( - json_output.is_ok(), - "Failed to serialize to JSON: {:?}", - json_output.err() - ); -} diff --git a/runtime/rust/prompty/tests/model/events/error_event_payload_test.rs b/runtime/rust/prompty/tests/model/events/error_event_payload_test.rs index 12420089..c48341be 100644 --- a/runtime/rust/prompty/tests/model/events/error_event_payload_test.rs +++ b/runtime/rust/prompty/tests/model/events/error_event_payload_test.rs @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. #![allow( unused_imports, @@ -15,7 +16,9 @@ use prompty::model::context::{LoadContext, SaveContext}; fn test_error_event_payload_load_json() { let json = r####" { - "message": "Rate limit exceeded" + "message": "Rate limit exceeded", + "errorKind": "rate_limit", + "phase": "llm" } "####; let ctx = LoadContext::default(); @@ -27,12 +30,21 @@ fn test_error_event_payload_load_json() { ); let instance = result.unwrap(); assert_eq!(instance.message, "Rate limit exceeded"); + assert!( + instance.error_kind.is_some(), + "Expected error_kind to be Some" + ); + assert_eq!(instance.error_kind.as_ref().unwrap(), &"rate_limit"); + assert!(instance.phase.is_some(), "Expected phase to be Some"); + assert_eq!(instance.phase.as_ref().unwrap(), &"llm"); } #[test] fn test_error_event_payload_load_yaml() { let yaml = r####" message: Rate limit exceeded +errorKind: rate_limit +phase: llm "####; let ctx = LoadContext::default(); @@ -44,13 +56,20 @@ message: Rate limit exceeded ); let instance = result.unwrap(); assert_eq!(instance.message, "Rate limit exceeded"); + assert!( + instance.error_kind.is_some(), + "Expected error_kind to be Some" + ); + assert!(instance.phase.is_some(), "Expected phase to be Some"); } #[test] fn test_error_event_payload_roundtrip() { let json = r####" { - "message": "Rate limit exceeded" + "message": "Rate limit exceeded", + "errorKind": "rate_limit", + "phase": "llm" } "####; let load_ctx = LoadContext::default(); diff --git a/runtime/rust/prompty/tests/model/events/harness_context_test.rs b/runtime/rust/prompty/tests/model/events/harness_context_test.rs new file mode 100644 index 00000000..fab7d7c0 --- /dev/null +++ b/runtime/rust/prompty/tests/model/events/harness_context_test.rs @@ -0,0 +1,75 @@ +// +// Code generated by Typra emitter; DO NOT EDIT. + +#![allow( + unused_imports, + dead_code, + non_camel_case_types, + unused_variables, + clippy::all +)] + +use prompty::model::HarnessContext; +use prompty::model::context::{LoadContext, SaveContext}; + +#[test] +fn test_harness_context_load_json() { + let json = r####" +{ + "cwd": "/workspace/project", + "gitRoot": "/workspace/project" +} +"####; + let ctx = LoadContext::default(); + let result = HarnessContext::from_json(json, &ctx); + assert!( + result.is_ok(), + "Failed to load from JSON: {:?}", + result.err() + ); + let instance = result.unwrap(); + assert!(instance.cwd.is_some(), "Expected cwd to be Some"); + assert_eq!(instance.cwd.as_ref().unwrap(), &"/workspace/project"); + assert!(instance.git_root.is_some(), "Expected git_root to be Some"); + assert_eq!(instance.git_root.as_ref().unwrap(), &"/workspace/project"); +} + +#[test] +fn test_harness_context_load_yaml() { + let yaml = r####" +cwd: /workspace/project +gitRoot: /workspace/project + +"####; + let ctx = LoadContext::default(); + let result = HarnessContext::from_yaml(yaml, &ctx); + assert!( + result.is_ok(), + "Failed to load from YAML: {:?}", + result.err() + ); + let instance = result.unwrap(); + assert!(instance.cwd.is_some(), "Expected cwd to be Some"); + assert!(instance.git_root.is_some(), "Expected git_root to be Some"); +} + +#[test] +fn test_harness_context_roundtrip() { + let json = r####" +{ + "cwd": "/workspace/project", + "gitRoot": "/workspace/project" +} +"####; + let load_ctx = LoadContext::default(); + let result = HarnessContext::from_json(json, &load_ctx); + assert!(result.is_ok(), "Failed to load: {:?}", result.err()); + let instance = result.unwrap(); + let save_ctx = SaveContext::default(); + let json_output = instance.to_json(&save_ctx); + assert!( + json_output.is_ok(), + "Failed to serialize to JSON: {:?}", + json_output.err() + ); +} diff --git a/runtime/rust/prompty/tests/model/events/hook_end_payload_test.rs b/runtime/rust/prompty/tests/model/events/hook_end_payload_test.rs new file mode 100644 index 00000000..9aadb782 --- /dev/null +++ b/runtime/rust/prompty/tests/model/events/hook_end_payload_test.rs @@ -0,0 +1,97 @@ +// +// Code generated by Typra emitter; DO NOT EDIT. + +#![allow( + unused_imports, + dead_code, + non_camel_case_types, + unused_variables, + clippy::all +)] + +use prompty::model::HookEndPayload; +use prompty::model::HookEndScope; +use prompty::model::context::{LoadContext, SaveContext}; + +#[test] +fn test_hook_end_payload_load_json() { + let json = r####" +{ + "hookInvocationId": "hook_abc123", + "hookType": "preToolUse", + "success": true, + "durationMs": 12, + "error": "hook failed" +} +"####; + let ctx = LoadContext::default(); + let result = HookEndPayload::from_json(json, &ctx); + assert!( + result.is_ok(), + "Failed to load from JSON: {:?}", + result.err() + ); + let instance = result.unwrap(); + assert_eq!(instance.hook_invocation_id, "hook_abc123"); + assert_eq!(instance.hook_type, "preToolUse"); + assert_eq!(instance.success, true); + assert!( + instance.duration_ms.is_some(), + "Expected duration_ms to be Some" + ); + assert_eq!(instance.duration_ms.as_ref().unwrap(), &12.0); + assert!(instance.error.is_some(), "Expected error to be Some"); + assert_eq!(instance.error.as_ref().unwrap(), &"hook failed"); +} + +#[test] +fn test_hook_end_payload_load_yaml() { + let yaml = r####" +hookInvocationId: hook_abc123 +hookType: preToolUse +success: true +durationMs: 12 +error: hook failed + +"####; + let ctx = LoadContext::default(); + let result = HookEndPayload::from_yaml(yaml, &ctx); + assert!( + result.is_ok(), + "Failed to load from YAML: {:?}", + result.err() + ); + let instance = result.unwrap(); + assert_eq!(instance.hook_invocation_id, "hook_abc123"); + assert_eq!(instance.hook_type, "preToolUse"); + assert_eq!(instance.success, true); + assert!( + instance.duration_ms.is_some(), + "Expected duration_ms to be Some" + ); + assert!(instance.error.is_some(), "Expected error to be Some"); +} + +#[test] +fn test_hook_end_payload_roundtrip() { + let json = r####" +{ + "hookInvocationId": "hook_abc123", + "hookType": "preToolUse", + "success": true, + "durationMs": 12, + "error": "hook failed" +} +"####; + let load_ctx = LoadContext::default(); + let result = HookEndPayload::from_json(json, &load_ctx); + assert!(result.is_ok(), "Failed to load: {:?}", result.err()); + let instance = result.unwrap(); + let save_ctx = SaveContext::default(); + let json_output = instance.to_json(&save_ctx); + assert!( + json_output.is_ok(), + "Failed to serialize to JSON: {:?}", + json_output.err() + ); +} diff --git a/runtime/rust/prompty/tests/model/events/hook_start_payload_test.rs b/runtime/rust/prompty/tests/model/events/hook_start_payload_test.rs new file mode 100644 index 00000000..3a62a2ad --- /dev/null +++ b/runtime/rust/prompty/tests/model/events/hook_start_payload_test.rs @@ -0,0 +1,74 @@ +// +// Code generated by Typra emitter; DO NOT EDIT. + +#![allow( + unused_imports, + dead_code, + non_camel_case_types, + unused_variables, + clippy::all +)] + +use prompty::model::HookStartPayload; +use prompty::model::HookStartScope; +use prompty::model::context::{LoadContext, SaveContext}; + +#[test] +fn test_hook_start_payload_load_json() { + let json = r####" +{ + "hookInvocationId": "hook_abc123", + "hookType": "preToolUse" +} +"####; + let ctx = LoadContext::default(); + let result = HookStartPayload::from_json(json, &ctx); + assert!( + result.is_ok(), + "Failed to load from JSON: {:?}", + result.err() + ); + let instance = result.unwrap(); + assert_eq!(instance.hook_invocation_id, "hook_abc123"); + assert_eq!(instance.hook_type, "preToolUse"); +} + +#[test] +fn test_hook_start_payload_load_yaml() { + let yaml = r####" +hookInvocationId: hook_abc123 +hookType: preToolUse + +"####; + let ctx = LoadContext::default(); + let result = HookStartPayload::from_yaml(yaml, &ctx); + assert!( + result.is_ok(), + "Failed to load from YAML: {:?}", + result.err() + ); + let instance = result.unwrap(); + assert_eq!(instance.hook_invocation_id, "hook_abc123"); + assert_eq!(instance.hook_type, "preToolUse"); +} + +#[test] +fn test_hook_start_payload_roundtrip() { + let json = r####" +{ + "hookInvocationId": "hook_abc123", + "hookType": "preToolUse" +} +"####; + let load_ctx = LoadContext::default(); + let result = HookStartPayload::from_json(json, &load_ctx); + assert!(result.is_ok(), "Failed to load: {:?}", result.err()); + let instance = result.unwrap(); + let save_ctx = SaveContext::default(); + let json_output = instance.to_json(&save_ctx); + assert!( + json_output.is_ok(), + "Failed to serialize to JSON: {:?}", + json_output.err() + ); +} diff --git a/runtime/rust/prompty/tests/model/events/host_tool_request_test.rs b/runtime/rust/prompty/tests/model/events/host_tool_request_test.rs new file mode 100644 index 00000000..b5803bff --- /dev/null +++ b/runtime/rust/prompty/tests/model/events/host_tool_request_test.rs @@ -0,0 +1,107 @@ +// +// Code generated by Typra emitter; DO NOT EDIT. + +#![allow( + unused_imports, + dead_code, + non_camel_case_types, + unused_variables, + clippy::all +)] + +use prompty::model::HostToolRequest; +use prompty::model::context::{LoadContext, SaveContext}; + +#[test] +fn test_host_tool_request_load_json() { + let json = r####" +{ + "requestId": "exec_abc123", + "toolCallId": "call_abc123", + "toolName": "powershell", + "workingDirectory": "/workspace/project" +} +"####; + let ctx = LoadContext::default(); + let result = HostToolRequest::from_json(json, &ctx); + assert!( + result.is_ok(), + "Failed to load from JSON: {:?}", + result.err() + ); + let instance = result.unwrap(); + assert!( + instance.request_id.is_some(), + "Expected request_id to be Some" + ); + assert_eq!(instance.request_id.as_ref().unwrap(), &"exec_abc123"); + assert!( + instance.tool_call_id.is_some(), + "Expected tool_call_id to be Some" + ); + assert_eq!(instance.tool_call_id.as_ref().unwrap(), &"call_abc123"); + assert_eq!(instance.tool_name, "powershell"); + assert!( + instance.working_directory.is_some(), + "Expected working_directory to be Some" + ); + assert_eq!( + instance.working_directory.as_ref().unwrap(), + &"/workspace/project" + ); +} + +#[test] +fn test_host_tool_request_load_yaml() { + let yaml = r####" +requestId: exec_abc123 +toolCallId: call_abc123 +toolName: powershell +workingDirectory: /workspace/project + +"####; + let ctx = LoadContext::default(); + let result = HostToolRequest::from_yaml(yaml, &ctx); + assert!( + result.is_ok(), + "Failed to load from YAML: {:?}", + result.err() + ); + let instance = result.unwrap(); + assert!( + instance.request_id.is_some(), + "Expected request_id to be Some" + ); + assert!( + instance.tool_call_id.is_some(), + "Expected tool_call_id to be Some" + ); + assert_eq!(instance.tool_name, "powershell"); + assert!( + instance.working_directory.is_some(), + "Expected working_directory to be Some" + ); +} + +#[test] +fn test_host_tool_request_roundtrip() { + let json = r####" +{ + "requestId": "exec_abc123", + "toolCallId": "call_abc123", + "toolName": "powershell", + "workingDirectory": "/workspace/project" +} +"####; + let load_ctx = LoadContext::default(); + let result = HostToolRequest::from_json(json, &load_ctx); + assert!(result.is_ok(), "Failed to load: {:?}", result.err()); + let instance = result.unwrap(); + let save_ctx = SaveContext::default(); + let json_output = instance.to_json(&save_ctx); + assert!( + json_output.is_ok(), + "Failed to serialize to JSON: {:?}", + json_output.err() + ); +} diff --git a/runtime/rust/prompty/tests/model/events/host_tool_result_test.rs b/runtime/rust/prompty/tests/model/events/host_tool_result_test.rs new file mode 100644 index 00000000..5c9692da --- /dev/null +++ b/runtime/rust/prompty/tests/model/events/host_tool_result_test.rs @@ -0,0 +1,133 @@ +// +// Code generated by Typra emitter; DO NOT EDIT. + +#![allow( + unused_imports, + dead_code, + non_camel_case_types, + unused_variables, + clippy::all +)] + +use prompty::model::HostToolResult; +use prompty::model::context::{LoadContext, SaveContext}; + +#[test] +fn test_host_tool_result_load_json() { + let json = r####" +{ + "requestId": "exec_abc123", + "toolCallId": "call_abc123", + "toolName": "powershell", + "success": true, + "exitCode": 0, + "durationMs": 250, + "errorKind": "timeout" +} +"####; + let ctx = LoadContext::default(); + let result = HostToolResult::from_json(json, &ctx); + assert!( + result.is_ok(), + "Failed to load from JSON: {:?}", + result.err() + ); + let instance = result.unwrap(); + assert!( + instance.request_id.is_some(), + "Expected request_id to be Some" + ); + assert_eq!(instance.request_id.as_ref().unwrap(), &"exec_abc123"); + assert!( + instance.tool_call_id.is_some(), + "Expected tool_call_id to be Some" + ); + assert_eq!(instance.tool_call_id.as_ref().unwrap(), &"call_abc123"); + assert_eq!(instance.tool_name, "powershell"); + assert_eq!(instance.success, true); + assert!( + instance.exit_code.is_some(), + "Expected exit_code to be Some" + ); + assert_eq!(instance.exit_code.as_ref().unwrap(), &0); + assert!( + instance.duration_ms.is_some(), + "Expected duration_ms to be Some" + ); + assert_eq!(instance.duration_ms.as_ref().unwrap(), &250.0); + assert!( + instance.error_kind.is_some(), + "Expected error_kind to be Some" + ); + assert_eq!(instance.error_kind.as_ref().unwrap(), &"timeout"); +} + +#[test] +fn test_host_tool_result_load_yaml() { + let yaml = r####" +requestId: exec_abc123 +toolCallId: call_abc123 +toolName: powershell +success: true +exitCode: 0 +durationMs: 250 +errorKind: timeout + +"####; + let ctx = LoadContext::default(); + let result = HostToolResult::from_yaml(yaml, &ctx); + assert!( + result.is_ok(), + "Failed to load from YAML: {:?}", + result.err() + ); + let instance = result.unwrap(); + assert!( + instance.request_id.is_some(), + "Expected request_id to be Some" + ); + assert!( + instance.tool_call_id.is_some(), + "Expected tool_call_id to be Some" + ); + assert_eq!(instance.tool_name, "powershell"); + assert_eq!(instance.success, true); + assert!( + instance.exit_code.is_some(), + "Expected exit_code to be Some" + ); + assert!( + instance.duration_ms.is_some(), + "Expected duration_ms to be Some" + ); + assert!( + instance.error_kind.is_some(), + "Expected error_kind to be Some" + ); +} + +#[test] +fn test_host_tool_result_roundtrip() { + let json = r####" +{ + "requestId": "exec_abc123", + "toolCallId": "call_abc123", + "toolName": "powershell", + "success": true, + "exitCode": 0, + "durationMs": 250, + "errorKind": "timeout" +} +"####; + let load_ctx = LoadContext::default(); + let result = HostToolResult::from_json(json, &load_ctx); + assert!(result.is_ok(), "Failed to load: {:?}", result.err()); + let instance = result.unwrap(); + let save_ctx = SaveContext::default(); + let json_output = instance.to_json(&save_ctx); + assert!( + json_output.is_ok(), + "Failed to serialize to JSON: {:?}", + json_output.err() + ); +} diff --git a/runtime/rust/prompty/tests/model/events/llm_complete_payload_test.rs b/runtime/rust/prompty/tests/model/events/llm_complete_payload_test.rs new file mode 100644 index 00000000..d8684275 --- /dev/null +++ b/runtime/rust/prompty/tests/model/events/llm_complete_payload_test.rs @@ -0,0 +1,99 @@ +// +// Code generated by Typra emitter; DO NOT EDIT. + +#![allow( + unused_imports, + dead_code, + non_camel_case_types, + unused_variables, + clippy::all +)] + +use prompty::model::LlmCompletePayload; +use prompty::model::context::{LoadContext, SaveContext}; + +#[test] +fn test_llm_complete_payload_load_json() { + let json = r####" +{ + "requestId": "req_abc123", + "serviceRequestId": "srv_abc123", + "durationMs": 820 +} +"####; + let ctx = LoadContext::default(); + let result = LlmCompletePayload::from_json(json, &ctx); + assert!( + result.is_ok(), + "Failed to load from JSON: {:?}", + result.err() + ); + let instance = result.unwrap(); + assert!( + instance.request_id.is_some(), + "Expected request_id to be Some" + ); + assert_eq!(instance.request_id.as_ref().unwrap(), &"req_abc123"); + assert!( + instance.service_request_id.is_some(), + "Expected service_request_id to be Some" + ); + assert_eq!(instance.service_request_id.as_ref().unwrap(), &"srv_abc123"); + assert!( + instance.duration_ms.is_some(), + "Expected duration_ms to be Some" + ); + assert_eq!(instance.duration_ms.as_ref().unwrap(), &820.0); +} + +#[test] +fn test_llm_complete_payload_load_yaml() { + let yaml = r####" +requestId: req_abc123 +serviceRequestId: srv_abc123 +durationMs: 820 + +"####; + let ctx = LoadContext::default(); + let result = LlmCompletePayload::from_yaml(yaml, &ctx); + assert!( + result.is_ok(), + "Failed to load from YAML: {:?}", + result.err() + ); + let instance = result.unwrap(); + assert!( + instance.request_id.is_some(), + "Expected request_id to be Some" + ); + assert!( + instance.service_request_id.is_some(), + "Expected service_request_id to be Some" + ); + assert!( + instance.duration_ms.is_some(), + "Expected duration_ms to be Some" + ); +} + +#[test] +fn test_llm_complete_payload_roundtrip() { + let json = r####" +{ + "requestId": "req_abc123", + "serviceRequestId": "srv_abc123", + "durationMs": 820 +} +"####; + let load_ctx = LoadContext::default(); + let result = LlmCompletePayload::from_json(json, &load_ctx); + assert!(result.is_ok(), "Failed to load: {:?}", result.err()); + let instance = result.unwrap(); + let save_ctx = SaveContext::default(); + let json_output = instance.to_json(&save_ctx); + assert!( + json_output.is_ok(), + "Failed to serialize to JSON: {:?}", + json_output.err() + ); +} diff --git a/runtime/rust/prompty/tests/model/events/llm_start_payload_test.rs b/runtime/rust/prompty/tests/model/events/llm_start_payload_test.rs new file mode 100644 index 00000000..d4896bae --- /dev/null +++ b/runtime/rust/prompty/tests/model/events/llm_start_payload_test.rs @@ -0,0 +1,93 @@ +// +// Code generated by Typra emitter; DO NOT EDIT. + +#![allow( + unused_imports, + dead_code, + non_camel_case_types, + unused_variables, + clippy::all +)] + +use prompty::model::LlmStartPayload; +use prompty::model::context::{LoadContext, SaveContext}; + +#[test] +fn test_llm_start_payload_load_json() { + let json = r####" +{ + "provider": "openai", + "modelId": "gpt-4o-mini", + "messageCount": 4, + "attempt": 0 +} +"####; + let ctx = LoadContext::default(); + let result = LlmStartPayload::from_json(json, &ctx); + assert!( + result.is_ok(), + "Failed to load from JSON: {:?}", + result.err() + ); + let instance = result.unwrap(); + assert!(instance.provider.is_some(), "Expected provider to be Some"); + assert_eq!(instance.provider.as_ref().unwrap(), &"openai"); + assert!(instance.model_id.is_some(), "Expected model_id to be Some"); + assert_eq!(instance.model_id.as_ref().unwrap(), &"gpt-4o-mini"); + assert!( + instance.message_count.is_some(), + "Expected message_count to be Some" + ); + assert_eq!(instance.message_count.as_ref().unwrap(), &4); + assert!(instance.attempt.is_some(), "Expected attempt to be Some"); + assert_eq!(instance.attempt.as_ref().unwrap(), &0); +} + +#[test] +fn test_llm_start_payload_load_yaml() { + let yaml = r####" +provider: openai +modelId: gpt-4o-mini +messageCount: 4 +attempt: 0 + +"####; + let ctx = LoadContext::default(); + let result = LlmStartPayload::from_yaml(yaml, &ctx); + assert!( + result.is_ok(), + "Failed to load from YAML: {:?}", + result.err() + ); + let instance = result.unwrap(); + assert!(instance.provider.is_some(), "Expected provider to be Some"); + assert!(instance.model_id.is_some(), "Expected model_id to be Some"); + assert!( + instance.message_count.is_some(), + "Expected message_count to be Some" + ); + assert!(instance.attempt.is_some(), "Expected attempt to be Some"); +} + +#[test] +fn test_llm_start_payload_roundtrip() { + let json = r####" +{ + "provider": "openai", + "modelId": "gpt-4o-mini", + "messageCount": 4, + "attempt": 0 +} +"####; + let load_ctx = LoadContext::default(); + let result = LlmStartPayload::from_json(json, &load_ctx); + assert!(result.is_ok(), "Failed to load: {:?}", result.err()); + let instance = result.unwrap(); + let save_ctx = SaveContext::default(); + let json_output = instance.to_json(&save_ctx); + assert!( + json_output.is_ok(), + "Failed to serialize to JSON: {:?}", + json_output.err() + ); +} diff --git a/runtime/rust/prompty/tests/model/events/messages_updated_payload_test.rs b/runtime/rust/prompty/tests/model/events/messages_updated_payload_test.rs index 5709537b..ff19fb47 100644 --- a/runtime/rust/prompty/tests/model/events/messages_updated_payload_test.rs +++ b/runtime/rust/prompty/tests/model/events/messages_updated_payload_test.rs @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. #![allow( unused_imports, @@ -10,3 +11,65 @@ use prompty::model::MessagesUpdatedPayload; use prompty::model::context::{LoadContext, SaveContext}; + +#[test] +fn test_messages_updated_payload_load_json() { + let json = r####" +{ + "reason": "tool_results", + "removed": 2 +} +"####; + let ctx = LoadContext::default(); + let result = MessagesUpdatedPayload::from_json(json, &ctx); + assert!( + result.is_ok(), + "Failed to load from JSON: {:?}", + result.err() + ); + let instance = result.unwrap(); + assert!(instance.reason.is_some(), "Expected reason to be Some"); + assert_eq!(instance.reason.as_ref().unwrap(), &"tool_results"); + assert!(instance.removed.is_some(), "Expected removed to be Some"); + assert_eq!(instance.removed.as_ref().unwrap(), &2); +} + +#[test] +fn test_messages_updated_payload_load_yaml() { + let yaml = r####" +reason: tool_results +removed: 2 + +"####; + let ctx = LoadContext::default(); + let result = MessagesUpdatedPayload::from_yaml(yaml, &ctx); + assert!( + result.is_ok(), + "Failed to load from YAML: {:?}", + result.err() + ); + let instance = result.unwrap(); + assert!(instance.reason.is_some(), "Expected reason to be Some"); + assert!(instance.removed.is_some(), "Expected removed to be Some"); +} + +#[test] +fn test_messages_updated_payload_roundtrip() { + let json = r####" +{ + "reason": "tool_results", + "removed": 2 +} +"####; + let load_ctx = LoadContext::default(); + let result = MessagesUpdatedPayload::from_json(json, &load_ctx); + assert!(result.is_ok(), "Failed to load: {:?}", result.err()); + let instance = result.unwrap(); + let save_ctx = SaveContext::default(); + let json_output = instance.to_json(&save_ctx); + assert!( + json_output.is_ok(), + "Failed to serialize to JSON: {:?}", + json_output.err() + ); +} diff --git a/runtime/rust/prompty/tests/model/events/mod.rs b/runtime/rust/prompty/tests/model/events/mod.rs index 0cb7b90e..24bf6570 100644 --- a/runtime/rust/prompty/tests/model/events/mod.rs +++ b/runtime/rust/prompty/tests/model/events/mod.rs @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. #![allow( unused_imports, @@ -8,14 +9,47 @@ clippy::all )] +mod checkpoint_test; mod compaction_complete_payload_test; mod compaction_failed_payload_test; +mod compaction_start_payload_test; mod done_event_payload_test; mod error_event_payload_test; +mod harness_context_test; +mod hook_end_payload_test; +mod hook_start_payload_test; +mod host_tool_request_test; +mod host_tool_result_test; +mod llm_complete_payload_test; +mod llm_start_payload_test; mod messages_updated_payload_test; +mod permission_completed_payload_test; +mod permission_decision_test; +mod permission_request_test; +mod permission_requested_payload_test; +mod redacted_field_test; +mod redaction_metadata_test; +mod retry_payload_test; +mod session_end_payload_test; +mod session_event_test; +mod session_file_ref_test; +mod session_ref_test; +mod session_start_payload_test; +mod session_summary_test; +mod session_trace_test; +mod session_warning_payload_test; mod status_event_payload_test; mod stream_chunk_test; mod thinking_event_payload_test; mod token_event_payload_test; +mod tool_call_complete_payload_test; mod tool_call_start_payload_test; +mod tool_execution_complete_payload_test; +mod tool_execution_start_payload_test; mod tool_result_payload_test; +mod trajectory_event_test; +mod turn_end_payload_test; +mod turn_event_test; +mod turn_start_payload_test; +mod turn_summary_test; +mod turn_trace_test; diff --git a/runtime/rust/prompty/tests/model/events/permission_completed_payload_test.rs b/runtime/rust/prompty/tests/model/events/permission_completed_payload_test.rs new file mode 100644 index 00000000..a60ffae4 --- /dev/null +++ b/runtime/rust/prompty/tests/model/events/permission_completed_payload_test.rs @@ -0,0 +1,103 @@ +// +// Code generated by Typra emitter; DO NOT EDIT. + +#![allow( + unused_imports, + dead_code, + non_camel_case_types, + unused_variables, + clippy::all +)] + +use prompty::model::PermissionCompletedPayload; +use prompty::model::context::{LoadContext, SaveContext}; + +#[test] +fn test_permission_completed_payload_load_json() { + let json = r####" +{ + "requestId": "perm_abc123", + "toolCallId": "call_abc123", + "permission": "tool.execute", + "approved": true, + "reason": "user_approved" +} +"####; + let ctx = LoadContext::default(); + let result = PermissionCompletedPayload::from_json(json, &ctx); + assert!( + result.is_ok(), + "Failed to load from JSON: {:?}", + result.err() + ); + let instance = result.unwrap(); + assert!( + instance.request_id.is_some(), + "Expected request_id to be Some" + ); + assert_eq!(instance.request_id.as_ref().unwrap(), &"perm_abc123"); + assert!( + instance.tool_call_id.is_some(), + "Expected tool_call_id to be Some" + ); + assert_eq!(instance.tool_call_id.as_ref().unwrap(), &"call_abc123"); + assert_eq!(instance.permission, "tool.execute"); + assert_eq!(instance.approved, true); + assert!(instance.reason.is_some(), "Expected reason to be Some"); + assert_eq!(instance.reason.as_ref().unwrap(), &"user_approved"); +} + +#[test] +fn test_permission_completed_payload_load_yaml() { + let yaml = r####" +requestId: perm_abc123 +toolCallId: call_abc123 +permission: tool.execute +approved: true +reason: user_approved + +"####; + let ctx = LoadContext::default(); + let result = PermissionCompletedPayload::from_yaml(yaml, &ctx); + assert!( + result.is_ok(), + "Failed to load from YAML: {:?}", + result.err() + ); + let instance = result.unwrap(); + assert!( + instance.request_id.is_some(), + "Expected request_id to be Some" + ); + assert!( + instance.tool_call_id.is_some(), + "Expected tool_call_id to be Some" + ); + assert_eq!(instance.permission, "tool.execute"); + assert_eq!(instance.approved, true); + assert!(instance.reason.is_some(), "Expected reason to be Some"); +} + +#[test] +fn test_permission_completed_payload_roundtrip() { + let json = r####" +{ + "requestId": "perm_abc123", + "toolCallId": "call_abc123", + "permission": "tool.execute", + "approved": true, + "reason": "user_approved" +} +"####; + let load_ctx = LoadContext::default(); + let result = PermissionCompletedPayload::from_json(json, &load_ctx); + assert!(result.is_ok(), "Failed to load: {:?}", result.err()); + let instance = result.unwrap(); + let save_ctx = SaveContext::default(); + let json_output = instance.to_json(&save_ctx); + assert!( + json_output.is_ok(), + "Failed to serialize to JSON: {:?}", + json_output.err() + ); +} diff --git a/runtime/rust/prompty/tests/model/events/permission_decision_test.rs b/runtime/rust/prompty/tests/model/events/permission_decision_test.rs new file mode 100644 index 00000000..2740a8c2 --- /dev/null +++ b/runtime/rust/prompty/tests/model/events/permission_decision_test.rs @@ -0,0 +1,103 @@ +// +// Code generated by Typra emitter; DO NOT EDIT. + +#![allow( + unused_imports, + dead_code, + non_camel_case_types, + unused_variables, + clippy::all +)] + +use prompty::model::PermissionDecision; +use prompty::model::context::{LoadContext, SaveContext}; + +#[test] +fn test_permission_decision_load_json() { + let json = r####" +{ + "requestId": "perm_abc123", + "toolCallId": "call_abc123", + "permission": "tool.execute", + "approved": true, + "reason": "user_approved" +} +"####; + let ctx = LoadContext::default(); + let result = PermissionDecision::from_json(json, &ctx); + assert!( + result.is_ok(), + "Failed to load from JSON: {:?}", + result.err() + ); + let instance = result.unwrap(); + assert!( + instance.request_id.is_some(), + "Expected request_id to be Some" + ); + assert_eq!(instance.request_id.as_ref().unwrap(), &"perm_abc123"); + assert!( + instance.tool_call_id.is_some(), + "Expected tool_call_id to be Some" + ); + assert_eq!(instance.tool_call_id.as_ref().unwrap(), &"call_abc123"); + assert_eq!(instance.permission, "tool.execute"); + assert_eq!(instance.approved, true); + assert!(instance.reason.is_some(), "Expected reason to be Some"); + assert_eq!(instance.reason.as_ref().unwrap(), &"user_approved"); +} + +#[test] +fn test_permission_decision_load_yaml() { + let yaml = r####" +requestId: perm_abc123 +toolCallId: call_abc123 +permission: tool.execute +approved: true +reason: user_approved + +"####; + let ctx = LoadContext::default(); + let result = PermissionDecision::from_yaml(yaml, &ctx); + assert!( + result.is_ok(), + "Failed to load from YAML: {:?}", + result.err() + ); + let instance = result.unwrap(); + assert!( + instance.request_id.is_some(), + "Expected request_id to be Some" + ); + assert!( + instance.tool_call_id.is_some(), + "Expected tool_call_id to be Some" + ); + assert_eq!(instance.permission, "tool.execute"); + assert_eq!(instance.approved, true); + assert!(instance.reason.is_some(), "Expected reason to be Some"); +} + +#[test] +fn test_permission_decision_roundtrip() { + let json = r####" +{ + "requestId": "perm_abc123", + "toolCallId": "call_abc123", + "permission": "tool.execute", + "approved": true, + "reason": "user_approved" +} +"####; + let load_ctx = LoadContext::default(); + let result = PermissionDecision::from_json(json, &load_ctx); + assert!(result.is_ok(), "Failed to load: {:?}", result.err()); + let instance = result.unwrap(); + let save_ctx = SaveContext::default(); + let json_output = instance.to_json(&save_ctx); + assert!( + json_output.is_ok(), + "Failed to serialize to JSON: {:?}", + json_output.err() + ); +} diff --git a/runtime/rust/prompty/tests/model/events/permission_request_test.rs b/runtime/rust/prompty/tests/model/events/permission_request_test.rs new file mode 100644 index 00000000..4257ec2d --- /dev/null +++ b/runtime/rust/prompty/tests/model/events/permission_request_test.rs @@ -0,0 +1,113 @@ +// +// Code generated by Typra emitter; DO NOT EDIT. + +#![allow( + unused_imports, + dead_code, + non_camel_case_types, + unused_variables, + clippy::all +)] + +use prompty::model::PermissionRequest; +use prompty::model::context::{LoadContext, SaveContext}; + +#[test] +fn test_permission_request_load_json() { + let json = r####" +{ + "requestId": "perm_abc123", + "toolCallId": "call_abc123", + "permission": "tool.execute", + "target": "shell", + "promptRequest": "Allow shell to run tests?" +} +"####; + let ctx = LoadContext::default(); + let result = PermissionRequest::from_json(json, &ctx); + assert!( + result.is_ok(), + "Failed to load from JSON: {:?}", + result.err() + ); + let instance = result.unwrap(); + assert!( + instance.request_id.is_some(), + "Expected request_id to be Some" + ); + assert_eq!(instance.request_id.as_ref().unwrap(), &"perm_abc123"); + assert!( + instance.tool_call_id.is_some(), + "Expected tool_call_id to be Some" + ); + assert_eq!(instance.tool_call_id.as_ref().unwrap(), &"call_abc123"); + assert_eq!(instance.permission, "tool.execute"); + assert!(instance.target.is_some(), "Expected target to be Some"); + assert_eq!(instance.target.as_ref().unwrap(), &"shell"); + assert!( + instance.prompt_request.is_some(), + "Expected prompt_request to be Some" + ); + assert_eq!( + instance.prompt_request.as_ref().unwrap(), + &"Allow shell to run tests?" + ); +} + +#[test] +fn test_permission_request_load_yaml() { + let yaml = r####" +requestId: perm_abc123 +toolCallId: call_abc123 +permission: tool.execute +target: shell +promptRequest: Allow shell to run tests? + +"####; + let ctx = LoadContext::default(); + let result = PermissionRequest::from_yaml(yaml, &ctx); + assert!( + result.is_ok(), + "Failed to load from YAML: {:?}", + result.err() + ); + let instance = result.unwrap(); + assert!( + instance.request_id.is_some(), + "Expected request_id to be Some" + ); + assert!( + instance.tool_call_id.is_some(), + "Expected tool_call_id to be Some" + ); + assert_eq!(instance.permission, "tool.execute"); + assert!(instance.target.is_some(), "Expected target to be Some"); + assert!( + instance.prompt_request.is_some(), + "Expected prompt_request to be Some" + ); +} + +#[test] +fn test_permission_request_roundtrip() { + let json = r####" +{ + "requestId": "perm_abc123", + "toolCallId": "call_abc123", + "permission": "tool.execute", + "target": "shell", + "promptRequest": "Allow shell to run tests?" +} +"####; + let load_ctx = LoadContext::default(); + let result = PermissionRequest::from_json(json, &load_ctx); + assert!(result.is_ok(), "Failed to load: {:?}", result.err()); + let instance = result.unwrap(); + let save_ctx = SaveContext::default(); + let json_output = instance.to_json(&save_ctx); + assert!( + json_output.is_ok(), + "Failed to serialize to JSON: {:?}", + json_output.err() + ); +} diff --git a/runtime/rust/prompty/tests/model/events/permission_requested_payload_test.rs b/runtime/rust/prompty/tests/model/events/permission_requested_payload_test.rs new file mode 100644 index 00000000..4a77781d --- /dev/null +++ b/runtime/rust/prompty/tests/model/events/permission_requested_payload_test.rs @@ -0,0 +1,113 @@ +// +// Code generated by Typra emitter; DO NOT EDIT. + +#![allow( + unused_imports, + dead_code, + non_camel_case_types, + unused_variables, + clippy::all +)] + +use prompty::model::PermissionRequestedPayload; +use prompty::model::context::{LoadContext, SaveContext}; + +#[test] +fn test_permission_requested_payload_load_json() { + let json = r####" +{ + "requestId": "perm_abc123", + "toolCallId": "call_abc123", + "permission": "tool.execute", + "target": "shell", + "promptRequest": "Allow shell to run tests?" +} +"####; + let ctx = LoadContext::default(); + let result = PermissionRequestedPayload::from_json(json, &ctx); + assert!( + result.is_ok(), + "Failed to load from JSON: {:?}", + result.err() + ); + let instance = result.unwrap(); + assert!( + instance.request_id.is_some(), + "Expected request_id to be Some" + ); + assert_eq!(instance.request_id.as_ref().unwrap(), &"perm_abc123"); + assert!( + instance.tool_call_id.is_some(), + "Expected tool_call_id to be Some" + ); + assert_eq!(instance.tool_call_id.as_ref().unwrap(), &"call_abc123"); + assert_eq!(instance.permission, "tool.execute"); + assert!(instance.target.is_some(), "Expected target to be Some"); + assert_eq!(instance.target.as_ref().unwrap(), &"shell"); + assert!( + instance.prompt_request.is_some(), + "Expected prompt_request to be Some" + ); + assert_eq!( + instance.prompt_request.as_ref().unwrap(), + &"Allow shell to run tests?" + ); +} + +#[test] +fn test_permission_requested_payload_load_yaml() { + let yaml = r####" +requestId: perm_abc123 +toolCallId: call_abc123 +permission: tool.execute +target: shell +promptRequest: Allow shell to run tests? + +"####; + let ctx = LoadContext::default(); + let result = PermissionRequestedPayload::from_yaml(yaml, &ctx); + assert!( + result.is_ok(), + "Failed to load from YAML: {:?}", + result.err() + ); + let instance = result.unwrap(); + assert!( + instance.request_id.is_some(), + "Expected request_id to be Some" + ); + assert!( + instance.tool_call_id.is_some(), + "Expected tool_call_id to be Some" + ); + assert_eq!(instance.permission, "tool.execute"); + assert!(instance.target.is_some(), "Expected target to be Some"); + assert!( + instance.prompt_request.is_some(), + "Expected prompt_request to be Some" + ); +} + +#[test] +fn test_permission_requested_payload_roundtrip() { + let json = r####" +{ + "requestId": "perm_abc123", + "toolCallId": "call_abc123", + "permission": "tool.execute", + "target": "shell", + "promptRequest": "Allow shell to run tests?" +} +"####; + let load_ctx = LoadContext::default(); + let result = PermissionRequestedPayload::from_json(json, &load_ctx); + assert!(result.is_ok(), "Failed to load: {:?}", result.err()); + let instance = result.unwrap(); + let save_ctx = SaveContext::default(); + let json_output = instance.to_json(&save_ctx); + assert!( + json_output.is_ok(), + "Failed to serialize to JSON: {:?}", + json_output.err() + ); +} diff --git a/runtime/rust/prompty/tests/model/events/redacted_field_test.rs b/runtime/rust/prompty/tests/model/events/redacted_field_test.rs new file mode 100644 index 00000000..f375c758 --- /dev/null +++ b/runtime/rust/prompty/tests/model/events/redacted_field_test.rs @@ -0,0 +1,80 @@ +// +// Code generated by Typra emitter; DO NOT EDIT. + +#![allow( + unused_imports, + dead_code, + non_camel_case_types, + unused_variables, + clippy::all +)] + +use prompty::model::RedactedField; +use prompty::model::RedactionMode; +use prompty::model::context::{LoadContext, SaveContext}; + +#[test] +fn test_redacted_field_load_json() { + let json = r####" +{ + "path": "$.arguments.apiKey", + "mode": "redacted", + "reason": "secret" +} +"####; + let ctx = LoadContext::default(); + let result = RedactedField::from_json(json, &ctx); + assert!( + result.is_ok(), + "Failed to load from JSON: {:?}", + result.err() + ); + let instance = result.unwrap(); + assert_eq!(instance.path, "$.arguments.apiKey"); + assert_eq!(instance.mode, RedactionMode::Redacted); + assert!(instance.reason.is_some(), "Expected reason to be Some"); + assert_eq!(instance.reason.as_ref().unwrap(), &"secret"); +} + +#[test] +fn test_redacted_field_load_yaml() { + let yaml = r####" +path: $.arguments.apiKey +mode: redacted +reason: secret + +"####; + let ctx = LoadContext::default(); + let result = RedactedField::from_yaml(yaml, &ctx); + assert!( + result.is_ok(), + "Failed to load from YAML: {:?}", + result.err() + ); + let instance = result.unwrap(); + assert_eq!(instance.path, "$.arguments.apiKey"); + assert_eq!(instance.mode, RedactionMode::Redacted); + assert!(instance.reason.is_some(), "Expected reason to be Some"); +} + +#[test] +fn test_redacted_field_roundtrip() { + let json = r####" +{ + "path": "$.arguments.apiKey", + "mode": "redacted", + "reason": "secret" +} +"####; + let load_ctx = LoadContext::default(); + let result = RedactedField::from_json(json, &load_ctx); + assert!(result.is_ok(), "Failed to load: {:?}", result.err()); + let instance = result.unwrap(); + let save_ctx = SaveContext::default(); + let json_output = instance.to_json(&save_ctx); + assert!( + json_output.is_ok(), + "Failed to serialize to JSON: {:?}", + json_output.err() + ); +} diff --git a/runtime/rust/prompty/tests/model/events/redaction_metadata_test.rs b/runtime/rust/prompty/tests/model/events/redaction_metadata_test.rs new file mode 100644 index 00000000..4ee0d184 --- /dev/null +++ b/runtime/rust/prompty/tests/model/events/redaction_metadata_test.rs @@ -0,0 +1,81 @@ +// +// Code generated by Typra emitter; DO NOT EDIT. + +#![allow( + unused_imports, + dead_code, + non_camel_case_types, + unused_variables, + clippy::all +)] + +use prompty::model::RedactionMetadata; +use prompty::model::context::{LoadContext, SaveContext}; + +#[test] +fn test_redaction_metadata_load_json() { + let json = r####" +{ + "sanitized": true, + "policy": "default-v1" +} +"####; + let ctx = LoadContext::default(); + let result = RedactionMetadata::from_json(json, &ctx); + assert!( + result.is_ok(), + "Failed to load from JSON: {:?}", + result.err() + ); + let instance = result.unwrap(); + assert!( + instance.sanitized.is_some(), + "Expected sanitized to be Some" + ); + assert_eq!(instance.sanitized.as_ref().unwrap(), &true); + assert!(instance.policy.is_some(), "Expected policy to be Some"); + assert_eq!(instance.policy.as_ref().unwrap(), &"default-v1"); +} + +#[test] +fn test_redaction_metadata_load_yaml() { + let yaml = r####" +sanitized: true +policy: default-v1 + +"####; + let ctx = LoadContext::default(); + let result = RedactionMetadata::from_yaml(yaml, &ctx); + assert!( + result.is_ok(), + "Failed to load from YAML: {:?}", + result.err() + ); + let instance = result.unwrap(); + assert!( + instance.sanitized.is_some(), + "Expected sanitized to be Some" + ); + assert!(instance.policy.is_some(), "Expected policy to be Some"); +} + +#[test] +fn test_redaction_metadata_roundtrip() { + let json = r####" +{ + "sanitized": true, + "policy": "default-v1" +} +"####; + let load_ctx = LoadContext::default(); + let result = RedactionMetadata::from_json(json, &load_ctx); + assert!(result.is_ok(), "Failed to load: {:?}", result.err()); + let instance = result.unwrap(); + let save_ctx = SaveContext::default(); + let json_output = instance.to_json(&save_ctx); + assert!( + json_output.is_ok(), + "Failed to serialize to JSON: {:?}", + json_output.err() + ); +} diff --git a/runtime/rust/prompty/tests/model/events/retry_payload_test.rs b/runtime/rust/prompty/tests/model/events/retry_payload_test.rs new file mode 100644 index 00000000..e9ad82c7 --- /dev/null +++ b/runtime/rust/prompty/tests/model/events/retry_payload_test.rs @@ -0,0 +1,97 @@ +// +// Code generated by Typra emitter; DO NOT EDIT. + +#![allow( + unused_imports, + dead_code, + non_camel_case_types, + unused_variables, + clippy::all +)] + +use prompty::model::RetryPayload; +use prompty::model::context::{LoadContext, SaveContext}; + +#[test] +fn test_retry_payload_load_json() { + let json = r####" +{ + "operation": "llm", + "attempt": 2, + "maxAttempts": 3, + "delayMs": 1250, + "reason": "rate_limit" +} +"####; + let ctx = LoadContext::default(); + let result = RetryPayload::from_json(json, &ctx); + assert!( + result.is_ok(), + "Failed to load from JSON: {:?}", + result.err() + ); + let instance = result.unwrap(); + assert_eq!(instance.operation, "llm"); + assert_eq!(instance.attempt, 2); + assert!( + instance.max_attempts.is_some(), + "Expected max_attempts to be Some" + ); + assert_eq!(instance.max_attempts.as_ref().unwrap(), &3); + assert!(instance.delay_ms.is_some(), "Expected delay_ms to be Some"); + assert_eq!(instance.delay_ms.as_ref().unwrap(), &1250.0); + assert!(instance.reason.is_some(), "Expected reason to be Some"); + assert_eq!(instance.reason.as_ref().unwrap(), &"rate_limit"); +} + +#[test] +fn test_retry_payload_load_yaml() { + let yaml = r####" +operation: llm +attempt: 2 +maxAttempts: 3 +delayMs: 1250 +reason: rate_limit + +"####; + let ctx = LoadContext::default(); + let result = RetryPayload::from_yaml(yaml, &ctx); + assert!( + result.is_ok(), + "Failed to load from YAML: {:?}", + result.err() + ); + let instance = result.unwrap(); + assert_eq!(instance.operation, "llm"); + assert_eq!(instance.attempt, 2); + assert!( + instance.max_attempts.is_some(), + "Expected max_attempts to be Some" + ); + assert!(instance.delay_ms.is_some(), "Expected delay_ms to be Some"); + assert!(instance.reason.is_some(), "Expected reason to be Some"); +} + +#[test] +fn test_retry_payload_roundtrip() { + let json = r####" +{ + "operation": "llm", + "attempt": 2, + "maxAttempts": 3, + "delayMs": 1250, + "reason": "rate_limit" +} +"####; + let load_ctx = LoadContext::default(); + let result = RetryPayload::from_json(json, &load_ctx); + assert!(result.is_ok(), "Failed to load: {:?}", result.err()); + let instance = result.unwrap(); + let save_ctx = SaveContext::default(); + let json_output = instance.to_json(&save_ctx); + assert!( + json_output.is_ok(), + "Failed to serialize to JSON: {:?}", + json_output.err() + ); +} diff --git a/runtime/rust/prompty/tests/model/events/session_end_payload_test.rs b/runtime/rust/prompty/tests/model/events/session_end_payload_test.rs new file mode 100644 index 00000000..e82565a5 --- /dev/null +++ b/runtime/rust/prompty/tests/model/events/session_end_payload_test.rs @@ -0,0 +1,103 @@ +// +// Code generated by Typra emitter; DO NOT EDIT. + +#![allow( + unused_imports, + dead_code, + non_camel_case_types, + unused_variables, + clippy::all +)] + +use prompty::model::SessionEndPayload; +use prompty::model::SessionEndStatus; +use prompty::model::context::{LoadContext, SaveContext}; + +#[test] +fn test_session_end_payload_load_json() { + let json = r####" +{ + "sessionId": "sess_abc123", + "status": "success", + "reason": "complete", + "durationMs": 12500 +} +"####; + let ctx = LoadContext::default(); + let result = SessionEndPayload::from_json(json, &ctx); + assert!( + result.is_ok(), + "Failed to load from JSON: {:?}", + result.err() + ); + let instance = result.unwrap(); + assert!( + instance.session_id.is_some(), + "Expected session_id to be Some" + ); + assert_eq!(instance.session_id.as_ref().unwrap(), &"sess_abc123"); + assert!(instance.status.is_some(), "Expected status to be Some"); + assert_eq!( + instance.status.as_ref().unwrap(), + &SessionEndStatus::Success + ); + assert!(instance.reason.is_some(), "Expected reason to be Some"); + assert_eq!(instance.reason.as_ref().unwrap(), &"complete"); + assert!( + instance.duration_ms.is_some(), + "Expected duration_ms to be Some" + ); + assert_eq!(instance.duration_ms.as_ref().unwrap(), &12500.0); +} + +#[test] +fn test_session_end_payload_load_yaml() { + let yaml = r####" +sessionId: sess_abc123 +status: success +reason: complete +durationMs: 12500 + +"####; + let ctx = LoadContext::default(); + let result = SessionEndPayload::from_yaml(yaml, &ctx); + assert!( + result.is_ok(), + "Failed to load from YAML: {:?}", + result.err() + ); + let instance = result.unwrap(); + assert!( + instance.session_id.is_some(), + "Expected session_id to be Some" + ); + assert!(instance.status.is_some(), "Expected status to be Some"); + assert!(instance.reason.is_some(), "Expected reason to be Some"); + assert!( + instance.duration_ms.is_some(), + "Expected duration_ms to be Some" + ); +} + +#[test] +fn test_session_end_payload_roundtrip() { + let json = r####" +{ + "sessionId": "sess_abc123", + "status": "success", + "reason": "complete", + "durationMs": 12500 +} +"####; + let load_ctx = LoadContext::default(); + let result = SessionEndPayload::from_json(json, &load_ctx); + assert!(result.is_ok(), "Failed to load: {:?}", result.err()); + let instance = result.unwrap(); + let save_ctx = SaveContext::default(); + let json_output = instance.to_json(&save_ctx); + assert!( + json_output.is_ok(), + "Failed to serialize to JSON: {:?}", + json_output.err() + ); +} diff --git a/runtime/rust/prompty/tests/model/events/session_event_test.rs b/runtime/rust/prompty/tests/model/events/session_event_test.rs new file mode 100644 index 00000000..8287d025 --- /dev/null +++ b/runtime/rust/prompty/tests/model/events/session_event_test.rs @@ -0,0 +1,110 @@ +// +// Code generated by Typra emitter; DO NOT EDIT. + +#![allow( + unused_imports, + dead_code, + non_camel_case_types, + unused_variables, + clippy::all +)] + +use prompty::model::SessionEvent; +use prompty::model::SessionEventType; +use prompty::model::context::{LoadContext, SaveContext}; + +#[test] +fn test_session_event_load_json() { + let json = r####" +{ + "id": "evt_abc123", + "timestamp": "2026-06-09T20:00:00Z", + "sessionId": "sess_abc123", + "turnId": "turn_001", + "parentId": "evt_parent", + "spanId": "span_hook_001" +} +"####; + let ctx = LoadContext::default(); + let result = SessionEvent::from_json(json, &ctx); + assert!( + result.is_ok(), + "Failed to load from JSON: {:?}", + result.err() + ); + let instance = result.unwrap(); + assert_eq!(instance.id, "evt_abc123"); + assert_eq!(instance.timestamp, "2026-06-09T20:00:00Z"); + assert!( + instance.session_id.is_some(), + "Expected session_id to be Some" + ); + assert_eq!(instance.session_id.as_ref().unwrap(), &"sess_abc123"); + assert!(instance.turn_id.is_some(), "Expected turn_id to be Some"); + assert_eq!(instance.turn_id.as_ref().unwrap(), &"turn_001"); + assert!( + instance.parent_id.is_some(), + "Expected parent_id to be Some" + ); + assert_eq!(instance.parent_id.as_ref().unwrap(), &"evt_parent"); + assert!(instance.span_id.is_some(), "Expected span_id to be Some"); + assert_eq!(instance.span_id.as_ref().unwrap(), &"span_hook_001"); +} + +#[test] +fn test_session_event_load_yaml() { + let yaml = r####" +id: evt_abc123 +timestamp: "2026-06-09T20:00:00Z" +sessionId: sess_abc123 +turnId: turn_001 +parentId: evt_parent +spanId: span_hook_001 + +"####; + let ctx = LoadContext::default(); + let result = SessionEvent::from_yaml(yaml, &ctx); + assert!( + result.is_ok(), + "Failed to load from YAML: {:?}", + result.err() + ); + let instance = result.unwrap(); + assert_eq!(instance.id, "evt_abc123"); + assert_eq!(instance.timestamp, "2026-06-09T20:00:00Z"); + assert!( + instance.session_id.is_some(), + "Expected session_id to be Some" + ); + assert!(instance.turn_id.is_some(), "Expected turn_id to be Some"); + assert!( + instance.parent_id.is_some(), + "Expected parent_id to be Some" + ); + assert!(instance.span_id.is_some(), "Expected span_id to be Some"); +} + +#[test] +fn test_session_event_roundtrip() { + let json = r####" +{ + "id": "evt_abc123", + "timestamp": "2026-06-09T20:00:00Z", + "sessionId": "sess_abc123", + "turnId": "turn_001", + "parentId": "evt_parent", + "spanId": "span_hook_001" +} +"####; + let load_ctx = LoadContext::default(); + let result = SessionEvent::from_json(json, &load_ctx); + assert!(result.is_ok(), "Failed to load: {:?}", result.err()); + let instance = result.unwrap(); + let save_ctx = SaveContext::default(); + let json_output = instance.to_json(&save_ctx); + assert!( + json_output.is_ok(), + "Failed to serialize to JSON: {:?}", + json_output.err() + ); +} diff --git a/runtime/rust/prompty/tests/model/events/session_file_ref_test.rs b/runtime/rust/prompty/tests/model/events/session_file_ref_test.rs new file mode 100644 index 00000000..06bad9f3 --- /dev/null +++ b/runtime/rust/prompty/tests/model/events/session_file_ref_test.rs @@ -0,0 +1,119 @@ +// +// Code generated by Typra emitter; DO NOT EDIT. + +#![allow( + unused_imports, + dead_code, + non_camel_case_types, + unused_variables, + clippy::all +)] + +use prompty::model::SessionFileRef; +use prompty::model::context::{LoadContext, SaveContext}; + +#[test] +fn test_session_file_ref_load_json() { + let json = r####" +{ + "sessionId": "sess_abc123", + "path": "src/index.ts", + "toolName": "view", + "turnIndex": 2, + "firstSeenAt": "2026-06-09T20:00:00Z" +} +"####; + let ctx = LoadContext::default(); + let result = SessionFileRef::from_json(json, &ctx); + assert!( + result.is_ok(), + "Failed to load from JSON: {:?}", + result.err() + ); + let instance = result.unwrap(); + assert!( + instance.session_id.is_some(), + "Expected session_id to be Some" + ); + assert_eq!(instance.session_id.as_ref().unwrap(), &"sess_abc123"); + assert_eq!(instance.path, "src/index.ts"); + assert!( + instance.tool_name.is_some(), + "Expected tool_name to be Some" + ); + assert_eq!(instance.tool_name.as_ref().unwrap(), &"view"); + assert!( + instance.turn_index.is_some(), + "Expected turn_index to be Some" + ); + assert_eq!(instance.turn_index.as_ref().unwrap(), &2); + assert!( + instance.first_seen_at.is_some(), + "Expected first_seen_at to be Some" + ); + assert_eq!( + instance.first_seen_at.as_ref().unwrap(), + &"2026-06-09T20:00:00Z" + ); +} + +#[test] +fn test_session_file_ref_load_yaml() { + let yaml = r####" +sessionId: sess_abc123 +path: src/index.ts +toolName: view +turnIndex: 2 +firstSeenAt: "2026-06-09T20:00:00Z" + +"####; + let ctx = LoadContext::default(); + let result = SessionFileRef::from_yaml(yaml, &ctx); + assert!( + result.is_ok(), + "Failed to load from YAML: {:?}", + result.err() + ); + let instance = result.unwrap(); + assert!( + instance.session_id.is_some(), + "Expected session_id to be Some" + ); + assert_eq!(instance.path, "src/index.ts"); + assert!( + instance.tool_name.is_some(), + "Expected tool_name to be Some" + ); + assert!( + instance.turn_index.is_some(), + "Expected turn_index to be Some" + ); + assert!( + instance.first_seen_at.is_some(), + "Expected first_seen_at to be Some" + ); +} + +#[test] +fn test_session_file_ref_roundtrip() { + let json = r####" +{ + "sessionId": "sess_abc123", + "path": "src/index.ts", + "toolName": "view", + "turnIndex": 2, + "firstSeenAt": "2026-06-09T20:00:00Z" +} +"####; + let load_ctx = LoadContext::default(); + let result = SessionFileRef::from_json(json, &load_ctx); + assert!(result.is_ok(), "Failed to load: {:?}", result.err()); + let instance = result.unwrap(); + let save_ctx = SaveContext::default(); + let json_output = instance.to_json(&save_ctx); + assert!( + json_output.is_ok(), + "Failed to serialize to JSON: {:?}", + json_output.err() + ); +} diff --git a/runtime/rust/prompty/tests/model/events/session_ref_test.rs b/runtime/rust/prompty/tests/model/events/session_ref_test.rs new file mode 100644 index 00000000..82fb4bb4 --- /dev/null +++ b/runtime/rust/prompty/tests/model/events/session_ref_test.rs @@ -0,0 +1,112 @@ +// +// Code generated by Typra emitter; DO NOT EDIT. + +#![allow( + unused_imports, + dead_code, + non_camel_case_types, + unused_variables, + clippy::all +)] + +use prompty::model::SessionRef; +use prompty::model::context::{LoadContext, SaveContext}; + +#[test] +fn test_session_ref_load_json() { + let json = r####" +{ + "sessionId": "sess_abc123", + "refType": "issue", + "refValue": "owner/repo#123", + "turnIndex": 2, + "createdAt": "2026-06-09T20:00:00Z" +} +"####; + let ctx = LoadContext::default(); + let result = SessionRef::from_json(json, &ctx); + assert!( + result.is_ok(), + "Failed to load from JSON: {:?}", + result.err() + ); + let instance = result.unwrap(); + assert!( + instance.session_id.is_some(), + "Expected session_id to be Some" + ); + assert_eq!(instance.session_id.as_ref().unwrap(), &"sess_abc123"); + assert_eq!(instance.ref_type, "issue"); + assert_eq!(instance.ref_value, "owner/repo#123"); + assert!( + instance.turn_index.is_some(), + "Expected turn_index to be Some" + ); + assert_eq!(instance.turn_index.as_ref().unwrap(), &2); + assert!( + instance.created_at.is_some(), + "Expected created_at to be Some" + ); + assert_eq!( + instance.created_at.as_ref().unwrap(), + &"2026-06-09T20:00:00Z" + ); +} + +#[test] +fn test_session_ref_load_yaml() { + let yaml = r####" +sessionId: sess_abc123 +refType: issue +refValue: "owner/repo#123" +turnIndex: 2 +createdAt: "2026-06-09T20:00:00Z" + +"####; + let ctx = LoadContext::default(); + let result = SessionRef::from_yaml(yaml, &ctx); + assert!( + result.is_ok(), + "Failed to load from YAML: {:?}", + result.err() + ); + let instance = result.unwrap(); + assert!( + instance.session_id.is_some(), + "Expected session_id to be Some" + ); + assert_eq!(instance.ref_type, "issue"); + assert_eq!(instance.ref_value, "owner/repo#123"); + assert!( + instance.turn_index.is_some(), + "Expected turn_index to be Some" + ); + assert!( + instance.created_at.is_some(), + "Expected created_at to be Some" + ); +} + +#[test] +fn test_session_ref_roundtrip() { + let json = r####" +{ + "sessionId": "sess_abc123", + "refType": "issue", + "refValue": "owner/repo#123", + "turnIndex": 2, + "createdAt": "2026-06-09T20:00:00Z" +} +"####; + let load_ctx = LoadContext::default(); + let result = SessionRef::from_json(json, &load_ctx); + assert!(result.is_ok(), "Failed to load: {:?}", result.err()); + let instance = result.unwrap(); + let save_ctx = SaveContext::default(); + let json_output = instance.to_json(&save_ctx); + assert!( + json_output.is_ok(), + "Failed to serialize to JSON: {:?}", + json_output.err() + ); +} diff --git a/runtime/rust/prompty/tests/model/events/session_start_payload_test.rs b/runtime/rust/prompty/tests/model/events/session_start_payload_test.rs new file mode 100644 index 00000000..8440d9d6 --- /dev/null +++ b/runtime/rust/prompty/tests/model/events/session_start_payload_test.rs @@ -0,0 +1,143 @@ +// +// Code generated by Typra emitter; DO NOT EDIT. + +#![allow( + unused_imports, + dead_code, + non_camel_case_types, + unused_variables, + clippy::all +)] + +use prompty::model::SessionStartPayload; +use prompty::model::context::{LoadContext, SaveContext}; + +#[test] +fn test_session_start_payload_load_json() { + let json = r####" +{ + "sessionId": "sess_abc123", + "schemaVersion": "1", + "producer": "prompty-agent", + "runtime": "typescript", + "promptyVersion": "2.0.0", + "startTime": "2026-06-09T20:00:00Z", + "selectedModel": "gpt-4o-mini", + "reasoningEffort": "medium" +} +"####; + let ctx = LoadContext::default(); + let result = SessionStartPayload::from_json(json, &ctx); + assert!( + result.is_ok(), + "Failed to load from JSON: {:?}", + result.err() + ); + let instance = result.unwrap(); + assert_eq!(instance.session_id, "sess_abc123"); + assert!( + instance.schema_version.is_some(), + "Expected schema_version to be Some" + ); + assert_eq!(instance.schema_version.as_ref().unwrap(), &"1"); + assert!(instance.producer.is_some(), "Expected producer to be Some"); + assert_eq!(instance.producer.as_ref().unwrap(), &"prompty-agent"); + assert!(instance.runtime.is_some(), "Expected runtime to be Some"); + assert_eq!(instance.runtime.as_ref().unwrap(), &"typescript"); + assert!( + instance.prompty_version.is_some(), + "Expected prompty_version to be Some" + ); + assert_eq!(instance.prompty_version.as_ref().unwrap(), &"2.0.0"); + assert!( + instance.start_time.is_some(), + "Expected start_time to be Some" + ); + assert_eq!( + instance.start_time.as_ref().unwrap(), + &"2026-06-09T20:00:00Z" + ); + assert!( + instance.selected_model.is_some(), + "Expected selected_model to be Some" + ); + assert_eq!(instance.selected_model.as_ref().unwrap(), &"gpt-4o-mini"); + assert!( + instance.reasoning_effort.is_some(), + "Expected reasoning_effort to be Some" + ); + assert_eq!(instance.reasoning_effort.as_ref().unwrap(), &"medium"); +} + +#[test] +fn test_session_start_payload_load_yaml() { + let yaml = r####" +sessionId: sess_abc123 +schemaVersion: "1" +producer: prompty-agent +runtime: typescript +promptyVersion: 2.0.0 +startTime: "2026-06-09T20:00:00Z" +selectedModel: gpt-4o-mini +reasoningEffort: medium + +"####; + let ctx = LoadContext::default(); + let result = SessionStartPayload::from_yaml(yaml, &ctx); + assert!( + result.is_ok(), + "Failed to load from YAML: {:?}", + result.err() + ); + let instance = result.unwrap(); + assert_eq!(instance.session_id, "sess_abc123"); + assert!( + instance.schema_version.is_some(), + "Expected schema_version to be Some" + ); + assert!(instance.producer.is_some(), "Expected producer to be Some"); + assert!(instance.runtime.is_some(), "Expected runtime to be Some"); + assert!( + instance.prompty_version.is_some(), + "Expected prompty_version to be Some" + ); + assert!( + instance.start_time.is_some(), + "Expected start_time to be Some" + ); + assert!( + instance.selected_model.is_some(), + "Expected selected_model to be Some" + ); + assert!( + instance.reasoning_effort.is_some(), + "Expected reasoning_effort to be Some" + ); +} + +#[test] +fn test_session_start_payload_roundtrip() { + let json = r####" +{ + "sessionId": "sess_abc123", + "schemaVersion": "1", + "producer": "prompty-agent", + "runtime": "typescript", + "promptyVersion": "2.0.0", + "startTime": "2026-06-09T20:00:00Z", + "selectedModel": "gpt-4o-mini", + "reasoningEffort": "medium" +} +"####; + let load_ctx = LoadContext::default(); + let result = SessionStartPayload::from_json(json, &load_ctx); + assert!(result.is_ok(), "Failed to load: {:?}", result.err()); + let instance = result.unwrap(); + let save_ctx = SaveContext::default(); + let json_output = instance.to_json(&save_ctx); + assert!( + json_output.is_ok(), + "Failed to serialize to JSON: {:?}", + json_output.err() + ); +} diff --git a/runtime/rust/prompty/tests/model/events/session_summary_test.rs b/runtime/rust/prompty/tests/model/events/session_summary_test.rs new file mode 100644 index 00000000..5c89287b --- /dev/null +++ b/runtime/rust/prompty/tests/model/events/session_summary_test.rs @@ -0,0 +1,108 @@ +// +// Code generated by Typra emitter; DO NOT EDIT. + +#![allow( + unused_imports, + dead_code, + non_camel_case_types, + unused_variables, + clippy::all +)] + +use prompty::model::SessionSummary; +use prompty::model::SessionSummaryStatus; +use prompty::model::context::{LoadContext, SaveContext}; + +#[test] +fn test_session_summary_load_json() { + let json = r####" +{ + "sessionId": "sess_abc123", + "status": "success", + "turns": 5, + "checkpoints": 2, + "durationMs": 12500 +} +"####; + let ctx = LoadContext::default(); + let result = SessionSummary::from_json(json, &ctx); + assert!( + result.is_ok(), + "Failed to load from JSON: {:?}", + result.err() + ); + let instance = result.unwrap(); + assert_eq!(instance.session_id, "sess_abc123"); + assert!(instance.status.is_some(), "Expected status to be Some"); + assert_eq!( + instance.status.as_ref().unwrap(), + &SessionSummaryStatus::Success + ); + assert!(instance.turns.is_some(), "Expected turns to be Some"); + assert_eq!(instance.turns.as_ref().unwrap(), &5); + assert!( + instance.checkpoints.is_some(), + "Expected checkpoints to be Some" + ); + assert_eq!(instance.checkpoints.as_ref().unwrap(), &2); + assert!( + instance.duration_ms.is_some(), + "Expected duration_ms to be Some" + ); + assert_eq!(instance.duration_ms.as_ref().unwrap(), &12500.0); +} + +#[test] +fn test_session_summary_load_yaml() { + let yaml = r####" +sessionId: sess_abc123 +status: success +turns: 5 +checkpoints: 2 +durationMs: 12500 + +"####; + let ctx = LoadContext::default(); + let result = SessionSummary::from_yaml(yaml, &ctx); + assert!( + result.is_ok(), + "Failed to load from YAML: {:?}", + result.err() + ); + let instance = result.unwrap(); + assert_eq!(instance.session_id, "sess_abc123"); + assert!(instance.status.is_some(), "Expected status to be Some"); + assert!(instance.turns.is_some(), "Expected turns to be Some"); + assert!( + instance.checkpoints.is_some(), + "Expected checkpoints to be Some" + ); + assert!( + instance.duration_ms.is_some(), + "Expected duration_ms to be Some" + ); +} + +#[test] +fn test_session_summary_roundtrip() { + let json = r####" +{ + "sessionId": "sess_abc123", + "status": "success", + "turns": 5, + "checkpoints": 2, + "durationMs": 12500 +} +"####; + let load_ctx = LoadContext::default(); + let result = SessionSummary::from_json(json, &load_ctx); + assert!(result.is_ok(), "Failed to load: {:?}", result.err()); + let instance = result.unwrap(); + let save_ctx = SaveContext::default(); + let json_output = instance.to_json(&save_ctx); + assert!( + json_output.is_ok(), + "Failed to serialize to JSON: {:?}", + json_output.err() + ); +} diff --git a/runtime/rust/prompty/tests/model/events/session_trace_test.rs b/runtime/rust/prompty/tests/model/events/session_trace_test.rs new file mode 100644 index 00000000..80d05c74 --- /dev/null +++ b/runtime/rust/prompty/tests/model/events/session_trace_test.rs @@ -0,0 +1,98 @@ +// +// Code generated by Typra emitter; DO NOT EDIT. + +#![allow( + unused_imports, + dead_code, + non_camel_case_types, + unused_variables, + clippy::all +)] + +use prompty::model::SessionTrace; +use prompty::model::context::{LoadContext, SaveContext}; + +#[test] +fn test_session_trace_load_json() { + let json = r####" +{ + "version": "1", + "runtime": "typescript", + "promptyVersion": "2.0.0", + "sessionId": "sess_abc123" +} +"####; + let ctx = LoadContext::default(); + let result = SessionTrace::from_json(json, &ctx); + assert!( + result.is_ok(), + "Failed to load from JSON: {:?}", + result.err() + ); + let instance = result.unwrap(); + assert_eq!(instance.version, "1"); + assert!(instance.runtime.is_some(), "Expected runtime to be Some"); + assert_eq!(instance.runtime.as_ref().unwrap(), &"typescript"); + assert!( + instance.prompty_version.is_some(), + "Expected prompty_version to be Some" + ); + assert_eq!(instance.prompty_version.as_ref().unwrap(), &"2.0.0"); + assert!( + instance.session_id.is_some(), + "Expected session_id to be Some" + ); + assert_eq!(instance.session_id.as_ref().unwrap(), &"sess_abc123"); +} + +#[test] +fn test_session_trace_load_yaml() { + let yaml = r####" +version: "1" +runtime: typescript +promptyVersion: 2.0.0 +sessionId: sess_abc123 + +"####; + let ctx = LoadContext::default(); + let result = SessionTrace::from_yaml(yaml, &ctx); + assert!( + result.is_ok(), + "Failed to load from YAML: {:?}", + result.err() + ); + let instance = result.unwrap(); + assert_eq!(instance.version, "1"); + assert!(instance.runtime.is_some(), "Expected runtime to be Some"); + assert!( + instance.prompty_version.is_some(), + "Expected prompty_version to be Some" + ); + assert!( + instance.session_id.is_some(), + "Expected session_id to be Some" + ); +} + +#[test] +fn test_session_trace_roundtrip() { + let json = r####" +{ + "version": "1", + "runtime": "typescript", + "promptyVersion": "2.0.0", + "sessionId": "sess_abc123" +} +"####; + let load_ctx = LoadContext::default(); + let result = SessionTrace::from_json(json, &load_ctx); + assert!(result.is_ok(), "Failed to load: {:?}", result.err()); + let instance = result.unwrap(); + let save_ctx = SaveContext::default(); + let json_output = instance.to_json(&save_ctx); + assert!( + json_output.is_ok(), + "Failed to serialize to JSON: {:?}", + json_output.err() + ); +} diff --git a/runtime/rust/prompty/tests/model/events/session_warning_payload_test.rs b/runtime/rust/prompty/tests/model/events/session_warning_payload_test.rs new file mode 100644 index 00000000..e451f490 --- /dev/null +++ b/runtime/rust/prompty/tests/model/events/session_warning_payload_test.rs @@ -0,0 +1,73 @@ +// +// Code generated by Typra emitter; DO NOT EDIT. + +#![allow( + unused_imports, + dead_code, + non_camel_case_types, + unused_variables, + clippy::all +)] + +use prompty::model::SessionWarningPayload; +use prompty::model::context::{LoadContext, SaveContext}; + +#[test] +fn test_session_warning_payload_load_json() { + let json = r####" +{ + "warningType": "remote", + "message": "Remote session disabled" +} +"####; + let ctx = LoadContext::default(); + let result = SessionWarningPayload::from_json(json, &ctx); + assert!( + result.is_ok(), + "Failed to load from JSON: {:?}", + result.err() + ); + let instance = result.unwrap(); + assert_eq!(instance.warning_type, "remote"); + assert_eq!(instance.message, "Remote session disabled"); +} + +#[test] +fn test_session_warning_payload_load_yaml() { + let yaml = r####" +warningType: remote +message: Remote session disabled + +"####; + let ctx = LoadContext::default(); + let result = SessionWarningPayload::from_yaml(yaml, &ctx); + assert!( + result.is_ok(), + "Failed to load from YAML: {:?}", + result.err() + ); + let instance = result.unwrap(); + assert_eq!(instance.warning_type, "remote"); + assert_eq!(instance.message, "Remote session disabled"); +} + +#[test] +fn test_session_warning_payload_roundtrip() { + let json = r####" +{ + "warningType": "remote", + "message": "Remote session disabled" +} +"####; + let load_ctx = LoadContext::default(); + let result = SessionWarningPayload::from_json(json, &load_ctx); + assert!(result.is_ok(), "Failed to load: {:?}", result.err()); + let instance = result.unwrap(); + let save_ctx = SaveContext::default(); + let json_output = instance.to_json(&save_ctx); + assert!( + json_output.is_ok(), + "Failed to serialize to JSON: {:?}", + json_output.err() + ); +} diff --git a/runtime/rust/prompty/tests/model/events/status_event_payload_test.rs b/runtime/rust/prompty/tests/model/events/status_event_payload_test.rs index 7bc141ec..2fcb936c 100644 --- a/runtime/rust/prompty/tests/model/events/status_event_payload_test.rs +++ b/runtime/rust/prompty/tests/model/events/status_event_payload_test.rs @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. #![allow( unused_imports, diff --git a/runtime/rust/prompty/tests/model/events/stream_chunk_test.rs b/runtime/rust/prompty/tests/model/events/stream_chunk_test.rs index 084d6b29..5e094934 100644 --- a/runtime/rust/prompty/tests/model/events/stream_chunk_test.rs +++ b/runtime/rust/prompty/tests/model/events/stream_chunk_test.rs @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. #![allow( unused_imports, diff --git a/runtime/rust/prompty/tests/model/events/thinking_event_payload_test.rs b/runtime/rust/prompty/tests/model/events/thinking_event_payload_test.rs index d2e7161e..b5e11822 100644 --- a/runtime/rust/prompty/tests/model/events/thinking_event_payload_test.rs +++ b/runtime/rust/prompty/tests/model/events/thinking_event_payload_test.rs @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. #![allow( unused_imports, diff --git a/runtime/rust/prompty/tests/model/events/token_event_payload_test.rs b/runtime/rust/prompty/tests/model/events/token_event_payload_test.rs index ef56b977..523ac67a 100644 --- a/runtime/rust/prompty/tests/model/events/token_event_payload_test.rs +++ b/runtime/rust/prompty/tests/model/events/token_event_payload_test.rs @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. #![allow( unused_imports, diff --git a/runtime/rust/prompty/tests/model/events/tool_call_complete_payload_test.rs b/runtime/rust/prompty/tests/model/events/tool_call_complete_payload_test.rs new file mode 100644 index 00000000..f1b7f804 --- /dev/null +++ b/runtime/rust/prompty/tests/model/events/tool_call_complete_payload_test.rs @@ -0,0 +1,103 @@ +// +// Code generated by Typra emitter; DO NOT EDIT. + +#![allow( + unused_imports, + dead_code, + non_camel_case_types, + unused_variables, + clippy::all +)] + +use prompty::model::ToolCallCompletePayload; +use prompty::model::context::{LoadContext, SaveContext}; + +#[test] +fn test_tool_call_complete_payload_load_json() { + let json = r####" +{ + "id": "call_abc123", + "name": "get_weather", + "success": true, + "durationMs": 42, + "errorKind": "timeout" +} +"####; + let ctx = LoadContext::default(); + let result = ToolCallCompletePayload::from_json(json, &ctx); + assert!( + result.is_ok(), + "Failed to load from JSON: {:?}", + result.err() + ); + let instance = result.unwrap(); + assert!(instance.id.is_some(), "Expected id to be Some"); + assert_eq!(instance.id.as_ref().unwrap(), &"call_abc123"); + assert_eq!(instance.name, "get_weather"); + assert_eq!(instance.success, true); + assert!( + instance.duration_ms.is_some(), + "Expected duration_ms to be Some" + ); + assert_eq!(instance.duration_ms.as_ref().unwrap(), &42.0); + assert!( + instance.error_kind.is_some(), + "Expected error_kind to be Some" + ); + assert_eq!(instance.error_kind.as_ref().unwrap(), &"timeout"); +} + +#[test] +fn test_tool_call_complete_payload_load_yaml() { + let yaml = r####" +id: call_abc123 +name: get_weather +success: true +durationMs: 42 +errorKind: timeout + +"####; + let ctx = LoadContext::default(); + let result = ToolCallCompletePayload::from_yaml(yaml, &ctx); + assert!( + result.is_ok(), + "Failed to load from YAML: {:?}", + result.err() + ); + let instance = result.unwrap(); + assert!(instance.id.is_some(), "Expected id to be Some"); + assert_eq!(instance.name, "get_weather"); + assert_eq!(instance.success, true); + assert!( + instance.duration_ms.is_some(), + "Expected duration_ms to be Some" + ); + assert!( + instance.error_kind.is_some(), + "Expected error_kind to be Some" + ); +} + +#[test] +fn test_tool_call_complete_payload_roundtrip() { + let json = r####" +{ + "id": "call_abc123", + "name": "get_weather", + "success": true, + "durationMs": 42, + "errorKind": "timeout" +} +"####; + let load_ctx = LoadContext::default(); + let result = ToolCallCompletePayload::from_json(json, &load_ctx); + assert!(result.is_ok(), "Failed to load: {:?}", result.err()); + let instance = result.unwrap(); + let save_ctx = SaveContext::default(); + let json_output = instance.to_json(&save_ctx); + assert!( + json_output.is_ok(), + "Failed to serialize to JSON: {:?}", + json_output.err() + ); +} diff --git a/runtime/rust/prompty/tests/model/events/tool_call_start_payload_test.rs b/runtime/rust/prompty/tests/model/events/tool_call_start_payload_test.rs index 84cf792a..bbcd5e72 100644 --- a/runtime/rust/prompty/tests/model/events/tool_call_start_payload_test.rs +++ b/runtime/rust/prompty/tests/model/events/tool_call_start_payload_test.rs @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. #![allow( unused_imports, @@ -15,6 +16,7 @@ use prompty::model::context::{LoadContext, SaveContext}; fn test_tool_call_start_payload_load_json() { let json = r####" { + "id": "call_abc123", "name": "get_weather", "arguments": "{\"city\": \"Paris\"}" } @@ -27,6 +29,8 @@ fn test_tool_call_start_payload_load_json() { result.err() ); let instance = result.unwrap(); + assert!(instance.id.is_some(), "Expected id to be Some"); + assert_eq!(instance.id.as_ref().unwrap(), &"call_abc123"); assert_eq!(instance.name, "get_weather"); assert_eq!(instance.arguments, "{\"city\": \"Paris\"}"); } @@ -34,6 +38,7 @@ fn test_tool_call_start_payload_load_json() { #[test] fn test_tool_call_start_payload_load_yaml() { let yaml = r####" +id: call_abc123 name: get_weather arguments: "{\"city\": \"Paris\"}" @@ -46,6 +51,7 @@ arguments: "{\"city\": \"Paris\"}" result.err() ); let instance = result.unwrap(); + assert!(instance.id.is_some(), "Expected id to be Some"); assert_eq!(instance.name, "get_weather"); assert_eq!(instance.arguments, "{\"city\": \"Paris\"}"); } @@ -54,6 +60,7 @@ arguments: "{\"city\": \"Paris\"}" fn test_tool_call_start_payload_roundtrip() { let json = r####" { + "id": "call_abc123", "name": "get_weather", "arguments": "{\"city\": \"Paris\"}" } diff --git a/runtime/rust/prompty/tests/model/events/tool_execution_complete_payload_test.rs b/runtime/rust/prompty/tests/model/events/tool_execution_complete_payload_test.rs new file mode 100644 index 00000000..21deacf0 --- /dev/null +++ b/runtime/rust/prompty/tests/model/events/tool_execution_complete_payload_test.rs @@ -0,0 +1,133 @@ +// +// Code generated by Typra emitter; DO NOT EDIT. + +#![allow( + unused_imports, + dead_code, + non_camel_case_types, + unused_variables, + clippy::all +)] + +use prompty::model::ToolExecutionCompletePayload; +use prompty::model::context::{LoadContext, SaveContext}; + +#[test] +fn test_tool_execution_complete_payload_load_json() { + let json = r####" +{ + "requestId": "exec_abc123", + "toolCallId": "call_abc123", + "toolName": "powershell", + "success": true, + "exitCode": 0, + "durationMs": 250, + "errorKind": "timeout" +} +"####; + let ctx = LoadContext::default(); + let result = ToolExecutionCompletePayload::from_json(json, &ctx); + assert!( + result.is_ok(), + "Failed to load from JSON: {:?}", + result.err() + ); + let instance = result.unwrap(); + assert!( + instance.request_id.is_some(), + "Expected request_id to be Some" + ); + assert_eq!(instance.request_id.as_ref().unwrap(), &"exec_abc123"); + assert!( + instance.tool_call_id.is_some(), + "Expected tool_call_id to be Some" + ); + assert_eq!(instance.tool_call_id.as_ref().unwrap(), &"call_abc123"); + assert_eq!(instance.tool_name, "powershell"); + assert_eq!(instance.success, true); + assert!( + instance.exit_code.is_some(), + "Expected exit_code to be Some" + ); + assert_eq!(instance.exit_code.as_ref().unwrap(), &0); + assert!( + instance.duration_ms.is_some(), + "Expected duration_ms to be Some" + ); + assert_eq!(instance.duration_ms.as_ref().unwrap(), &250.0); + assert!( + instance.error_kind.is_some(), + "Expected error_kind to be Some" + ); + assert_eq!(instance.error_kind.as_ref().unwrap(), &"timeout"); +} + +#[test] +fn test_tool_execution_complete_payload_load_yaml() { + let yaml = r####" +requestId: exec_abc123 +toolCallId: call_abc123 +toolName: powershell +success: true +exitCode: 0 +durationMs: 250 +errorKind: timeout + +"####; + let ctx = LoadContext::default(); + let result = ToolExecutionCompletePayload::from_yaml(yaml, &ctx); + assert!( + result.is_ok(), + "Failed to load from YAML: {:?}", + result.err() + ); + let instance = result.unwrap(); + assert!( + instance.request_id.is_some(), + "Expected request_id to be Some" + ); + assert!( + instance.tool_call_id.is_some(), + "Expected tool_call_id to be Some" + ); + assert_eq!(instance.tool_name, "powershell"); + assert_eq!(instance.success, true); + assert!( + instance.exit_code.is_some(), + "Expected exit_code to be Some" + ); + assert!( + instance.duration_ms.is_some(), + "Expected duration_ms to be Some" + ); + assert!( + instance.error_kind.is_some(), + "Expected error_kind to be Some" + ); +} + +#[test] +fn test_tool_execution_complete_payload_roundtrip() { + let json = r####" +{ + "requestId": "exec_abc123", + "toolCallId": "call_abc123", + "toolName": "powershell", + "success": true, + "exitCode": 0, + "durationMs": 250, + "errorKind": "timeout" +} +"####; + let load_ctx = LoadContext::default(); + let result = ToolExecutionCompletePayload::from_json(json, &load_ctx); + assert!(result.is_ok(), "Failed to load: {:?}", result.err()); + let instance = result.unwrap(); + let save_ctx = SaveContext::default(); + let json_output = instance.to_json(&save_ctx); + assert!( + json_output.is_ok(), + "Failed to serialize to JSON: {:?}", + json_output.err() + ); +} diff --git a/runtime/rust/prompty/tests/model/events/tool_execution_start_payload_test.rs b/runtime/rust/prompty/tests/model/events/tool_execution_start_payload_test.rs new file mode 100644 index 00000000..1c475e2d --- /dev/null +++ b/runtime/rust/prompty/tests/model/events/tool_execution_start_payload_test.rs @@ -0,0 +1,107 @@ +// +// Code generated by Typra emitter; DO NOT EDIT. + +#![allow( + unused_imports, + dead_code, + non_camel_case_types, + unused_variables, + clippy::all +)] + +use prompty::model::ToolExecutionStartPayload; +use prompty::model::context::{LoadContext, SaveContext}; + +#[test] +fn test_tool_execution_start_payload_load_json() { + let json = r####" +{ + "requestId": "exec_abc123", + "toolCallId": "call_abc123", + "toolName": "powershell", + "workingDirectory": "/workspace/project" +} +"####; + let ctx = LoadContext::default(); + let result = ToolExecutionStartPayload::from_json(json, &ctx); + assert!( + result.is_ok(), + "Failed to load from JSON: {:?}", + result.err() + ); + let instance = result.unwrap(); + assert!( + instance.request_id.is_some(), + "Expected request_id to be Some" + ); + assert_eq!(instance.request_id.as_ref().unwrap(), &"exec_abc123"); + assert!( + instance.tool_call_id.is_some(), + "Expected tool_call_id to be Some" + ); + assert_eq!(instance.tool_call_id.as_ref().unwrap(), &"call_abc123"); + assert_eq!(instance.tool_name, "powershell"); + assert!( + instance.working_directory.is_some(), + "Expected working_directory to be Some" + ); + assert_eq!( + instance.working_directory.as_ref().unwrap(), + &"/workspace/project" + ); +} + +#[test] +fn test_tool_execution_start_payload_load_yaml() { + let yaml = r####" +requestId: exec_abc123 +toolCallId: call_abc123 +toolName: powershell +workingDirectory: /workspace/project + +"####; + let ctx = LoadContext::default(); + let result = ToolExecutionStartPayload::from_yaml(yaml, &ctx); + assert!( + result.is_ok(), + "Failed to load from YAML: {:?}", + result.err() + ); + let instance = result.unwrap(); + assert!( + instance.request_id.is_some(), + "Expected request_id to be Some" + ); + assert!( + instance.tool_call_id.is_some(), + "Expected tool_call_id to be Some" + ); + assert_eq!(instance.tool_name, "powershell"); + assert!( + instance.working_directory.is_some(), + "Expected working_directory to be Some" + ); +} + +#[test] +fn test_tool_execution_start_payload_roundtrip() { + let json = r####" +{ + "requestId": "exec_abc123", + "toolCallId": "call_abc123", + "toolName": "powershell", + "workingDirectory": "/workspace/project" +} +"####; + let load_ctx = LoadContext::default(); + let result = ToolExecutionStartPayload::from_json(json, &load_ctx); + assert!(result.is_ok(), "Failed to load: {:?}", result.err()); + let instance = result.unwrap(); + let save_ctx = SaveContext::default(); + let json_output = instance.to_json(&save_ctx); + assert!( + json_output.is_ok(), + "Failed to serialize to JSON: {:?}", + json_output.err() + ); +} diff --git a/runtime/rust/prompty/tests/model/events/tool_result_payload_test.rs b/runtime/rust/prompty/tests/model/events/tool_result_payload_test.rs index 43de3468..b08f41a8 100644 --- a/runtime/rust/prompty/tests/model/events/tool_result_payload_test.rs +++ b/runtime/rust/prompty/tests/model/events/tool_result_payload_test.rs @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. #![allow( unused_imports, diff --git a/runtime/rust/prompty/tests/model/events/trajectory_event_test.rs b/runtime/rust/prompty/tests/model/events/trajectory_event_test.rs new file mode 100644 index 00000000..7cae8051 --- /dev/null +++ b/runtime/rust/prompty/tests/model/events/trajectory_event_test.rs @@ -0,0 +1,131 @@ +// +// Code generated by Typra emitter; DO NOT EDIT. + +#![allow( + unused_imports, + dead_code, + non_camel_case_types, + unused_variables, + clippy::all +)] + +use prompty::model::TrajectoryEvent; +use prompty::model::context::{LoadContext, SaveContext}; + +#[test] +fn test_trajectory_event_load_json() { + let json = r####" +{ + "id": "traj_abc123", + "sessionId": "sess_abc123", + "turnId": "turn_001", + "toolCallId": "call_abc123", + "turnIndex": 4, + "eventType": "command", + "createdAt": "2026-06-09T20:00:00Z" +} +"####; + let ctx = LoadContext::default(); + let result = TrajectoryEvent::from_json(json, &ctx); + assert!( + result.is_ok(), + "Failed to load from JSON: {:?}", + result.err() + ); + let instance = result.unwrap(); + assert!(instance.id.is_some(), "Expected id to be Some"); + assert_eq!(instance.id.as_ref().unwrap(), &"traj_abc123"); + assert!( + instance.session_id.is_some(), + "Expected session_id to be Some" + ); + assert_eq!(instance.session_id.as_ref().unwrap(), &"sess_abc123"); + assert!(instance.turn_id.is_some(), "Expected turn_id to be Some"); + assert_eq!(instance.turn_id.as_ref().unwrap(), &"turn_001"); + assert!( + instance.tool_call_id.is_some(), + "Expected tool_call_id to be Some" + ); + assert_eq!(instance.tool_call_id.as_ref().unwrap(), &"call_abc123"); + assert!( + instance.turn_index.is_some(), + "Expected turn_index to be Some" + ); + assert_eq!(instance.turn_index.as_ref().unwrap(), &4); + assert_eq!(instance.event_type, "command"); + assert!( + instance.created_at.is_some(), + "Expected created_at to be Some" + ); + assert_eq!( + instance.created_at.as_ref().unwrap(), + &"2026-06-09T20:00:00Z" + ); +} + +#[test] +fn test_trajectory_event_load_yaml() { + let yaml = r####" +id: traj_abc123 +sessionId: sess_abc123 +turnId: turn_001 +toolCallId: call_abc123 +turnIndex: 4 +eventType: command +createdAt: "2026-06-09T20:00:00Z" + +"####; + let ctx = LoadContext::default(); + let result = TrajectoryEvent::from_yaml(yaml, &ctx); + assert!( + result.is_ok(), + "Failed to load from YAML: {:?}", + result.err() + ); + let instance = result.unwrap(); + assert!(instance.id.is_some(), "Expected id to be Some"); + assert!( + instance.session_id.is_some(), + "Expected session_id to be Some" + ); + assert!(instance.turn_id.is_some(), "Expected turn_id to be Some"); + assert!( + instance.tool_call_id.is_some(), + "Expected tool_call_id to be Some" + ); + assert!( + instance.turn_index.is_some(), + "Expected turn_index to be Some" + ); + assert_eq!(instance.event_type, "command"); + assert!( + instance.created_at.is_some(), + "Expected created_at to be Some" + ); +} + +#[test] +fn test_trajectory_event_roundtrip() { + let json = r####" +{ + "id": "traj_abc123", + "sessionId": "sess_abc123", + "turnId": "turn_001", + "toolCallId": "call_abc123", + "turnIndex": 4, + "eventType": "command", + "createdAt": "2026-06-09T20:00:00Z" +} +"####; + let load_ctx = LoadContext::default(); + let result = TrajectoryEvent::from_json(json, &load_ctx); + assert!(result.is_ok(), "Failed to load: {:?}", result.err()); + let instance = result.unwrap(); + let save_ctx = SaveContext::default(); + let json_output = instance.to_json(&save_ctx); + assert!( + json_output.is_ok(), + "Failed to serialize to JSON: {:?}", + json_output.err() + ); +} diff --git a/runtime/rust/prompty/tests/model/events/turn_end_payload_test.rs b/runtime/rust/prompty/tests/model/events/turn_end_payload_test.rs new file mode 100644 index 00000000..e7323a2d --- /dev/null +++ b/runtime/rust/prompty/tests/model/events/turn_end_payload_test.rs @@ -0,0 +1,88 @@ +// +// Code generated by Typra emitter; DO NOT EDIT. + +#![allow( + unused_imports, + dead_code, + non_camel_case_types, + unused_variables, + clippy::all +)] + +use prompty::model::TurnEndPayload; +use prompty::model::TurnStatus; +use prompty::model::context::{LoadContext, SaveContext}; + +#[test] +fn test_turn_end_payload_load_json() { + let json = r####" +{ + "iterations": 2, + "durationMs": 1500 +} +"####; + let ctx = LoadContext::default(); + let result = TurnEndPayload::from_json(json, &ctx); + assert!( + result.is_ok(), + "Failed to load from JSON: {:?}", + result.err() + ); + let instance = result.unwrap(); + assert!( + instance.iterations.is_some(), + "Expected iterations to be Some" + ); + assert_eq!(instance.iterations.as_ref().unwrap(), &2); + assert!( + instance.duration_ms.is_some(), + "Expected duration_ms to be Some" + ); + assert_eq!(instance.duration_ms.as_ref().unwrap(), &1500.0); +} + +#[test] +fn test_turn_end_payload_load_yaml() { + let yaml = r####" +iterations: 2 +durationMs: 1500 + +"####; + let ctx = LoadContext::default(); + let result = TurnEndPayload::from_yaml(yaml, &ctx); + assert!( + result.is_ok(), + "Failed to load from YAML: {:?}", + result.err() + ); + let instance = result.unwrap(); + assert!( + instance.iterations.is_some(), + "Expected iterations to be Some" + ); + assert!( + instance.duration_ms.is_some(), + "Expected duration_ms to be Some" + ); +} + +#[test] +fn test_turn_end_payload_roundtrip() { + let json = r####" +{ + "iterations": 2, + "durationMs": 1500 +} +"####; + let load_ctx = LoadContext::default(); + let result = TurnEndPayload::from_json(json, &load_ctx); + assert!(result.is_ok(), "Failed to load: {:?}", result.err()); + let instance = result.unwrap(); + let save_ctx = SaveContext::default(); + let json_output = instance.to_json(&save_ctx); + assert!( + json_output.is_ok(), + "Failed to serialize to JSON: {:?}", + json_output.err() + ); +} diff --git a/runtime/rust/prompty/tests/model/events/turn_event_test.rs b/runtime/rust/prompty/tests/model/events/turn_event_test.rs new file mode 100644 index 00000000..127e9b0d --- /dev/null +++ b/runtime/rust/prompty/tests/model/events/turn_event_test.rs @@ -0,0 +1,110 @@ +// +// Code generated by Typra emitter; DO NOT EDIT. + +#![allow( + unused_imports, + dead_code, + non_camel_case_types, + unused_variables, + clippy::all +)] + +use prompty::model::TurnEvent; +use prompty::model::TurnEventType; +use prompty::model::context::{LoadContext, SaveContext}; + +#[test] +fn test_turn_event_load_json() { + let json = r####" +{ + "id": "evt_abc123", + "timestamp": "2026-06-09T20:00:00Z", + "turnId": "turn_001", + "iteration": 0, + "parentId": "evt_parent", + "spanId": "span_tool_001" +} +"####; + let ctx = LoadContext::default(); + let result = TurnEvent::from_json(json, &ctx); + assert!( + result.is_ok(), + "Failed to load from JSON: {:?}", + result.err() + ); + let instance = result.unwrap(); + assert_eq!(instance.id, "evt_abc123"); + assert_eq!(instance.timestamp, "2026-06-09T20:00:00Z"); + assert!(instance.turn_id.is_some(), "Expected turn_id to be Some"); + assert_eq!(instance.turn_id.as_ref().unwrap(), &"turn_001"); + assert!( + instance.iteration.is_some(), + "Expected iteration to be Some" + ); + assert_eq!(instance.iteration.as_ref().unwrap(), &0); + assert!( + instance.parent_id.is_some(), + "Expected parent_id to be Some" + ); + assert_eq!(instance.parent_id.as_ref().unwrap(), &"evt_parent"); + assert!(instance.span_id.is_some(), "Expected span_id to be Some"); + assert_eq!(instance.span_id.as_ref().unwrap(), &"span_tool_001"); +} + +#[test] +fn test_turn_event_load_yaml() { + let yaml = r####" +id: evt_abc123 +timestamp: "2026-06-09T20:00:00Z" +turnId: turn_001 +iteration: 0 +parentId: evt_parent +spanId: span_tool_001 + +"####; + let ctx = LoadContext::default(); + let result = TurnEvent::from_yaml(yaml, &ctx); + assert!( + result.is_ok(), + "Failed to load from YAML: {:?}", + result.err() + ); + let instance = result.unwrap(); + assert_eq!(instance.id, "evt_abc123"); + assert_eq!(instance.timestamp, "2026-06-09T20:00:00Z"); + assert!(instance.turn_id.is_some(), "Expected turn_id to be Some"); + assert!( + instance.iteration.is_some(), + "Expected iteration to be Some" + ); + assert!( + instance.parent_id.is_some(), + "Expected parent_id to be Some" + ); + assert!(instance.span_id.is_some(), "Expected span_id to be Some"); +} + +#[test] +fn test_turn_event_roundtrip() { + let json = r####" +{ + "id": "evt_abc123", + "timestamp": "2026-06-09T20:00:00Z", + "turnId": "turn_001", + "iteration": 0, + "parentId": "evt_parent", + "spanId": "span_tool_001" +} +"####; + let load_ctx = LoadContext::default(); + let result = TurnEvent::from_json(json, &load_ctx); + assert!(result.is_ok(), "Failed to load: {:?}", result.err()); + let instance = result.unwrap(); + let save_ctx = SaveContext::default(); + let json_output = instance.to_json(&save_ctx); + assert!( + json_output.is_ok(), + "Failed to serialize to JSON: {:?}", + json_output.err() + ); +} diff --git a/runtime/rust/prompty/tests/model/events/turn_start_payload_test.rs b/runtime/rust/prompty/tests/model/events/turn_start_payload_test.rs new file mode 100644 index 00000000..b7e9e816 --- /dev/null +++ b/runtime/rust/prompty/tests/model/events/turn_start_payload_test.rs @@ -0,0 +1,81 @@ +// +// Code generated by Typra emitter; DO NOT EDIT. + +#![allow( + unused_imports, + dead_code, + non_camel_case_types, + unused_variables, + clippy::all +)] + +use prompty::model::TurnStartPayload; +use prompty::model::context::{LoadContext, SaveContext}; + +#[test] +fn test_turn_start_payload_load_json() { + let json = r####" +{ + "agent": "weather-agent", + "maxIterations": 10 +} +"####; + let ctx = LoadContext::default(); + let result = TurnStartPayload::from_json(json, &ctx); + assert!( + result.is_ok(), + "Failed to load from JSON: {:?}", + result.err() + ); + let instance = result.unwrap(); + assert!(instance.agent.is_some(), "Expected agent to be Some"); + assert_eq!(instance.agent.as_ref().unwrap(), &"weather-agent"); + assert!( + instance.max_iterations.is_some(), + "Expected max_iterations to be Some" + ); + assert_eq!(instance.max_iterations.as_ref().unwrap(), &10); +} + +#[test] +fn test_turn_start_payload_load_yaml() { + let yaml = r####" +agent: weather-agent +maxIterations: 10 + +"####; + let ctx = LoadContext::default(); + let result = TurnStartPayload::from_yaml(yaml, &ctx); + assert!( + result.is_ok(), + "Failed to load from YAML: {:?}", + result.err() + ); + let instance = result.unwrap(); + assert!(instance.agent.is_some(), "Expected agent to be Some"); + assert!( + instance.max_iterations.is_some(), + "Expected max_iterations to be Some" + ); +} + +#[test] +fn test_turn_start_payload_roundtrip() { + let json = r####" +{ + "agent": "weather-agent", + "maxIterations": 10 +} +"####; + let load_ctx = LoadContext::default(); + let result = TurnStartPayload::from_json(json, &load_ctx); + assert!(result.is_ok(), "Failed to load: {:?}", result.err()); + let instance = result.unwrap(); + let save_ctx = SaveContext::default(); + let json_output = instance.to_json(&save_ctx); + assert!( + json_output.is_ok(), + "Failed to serialize to JSON: {:?}", + json_output.err() + ); +} diff --git a/runtime/rust/prompty/tests/model/events/turn_summary_test.rs b/runtime/rust/prompty/tests/model/events/turn_summary_test.rs new file mode 100644 index 00000000..a400787f --- /dev/null +++ b/runtime/rust/prompty/tests/model/events/turn_summary_test.rs @@ -0,0 +1,120 @@ +// +// Code generated by Typra emitter; DO NOT EDIT. + +#![allow( + unused_imports, + dead_code, + non_camel_case_types, + unused_variables, + clippy::all +)] + +use prompty::model::TurnSummary; +use prompty::model::context::{LoadContext, SaveContext}; + +#[test] +fn test_turn_summary_load_json() { + let json = r####" +{ + "turnId": "turn_001", + "status": "success", + "iterations": 2, + "llmCalls": 3, + "toolCalls": 2, + "retries": 1, + "durationMs": 2500 +} +"####; + let ctx = LoadContext::default(); + let result = TurnSummary::from_json(json, &ctx); + assert!( + result.is_ok(), + "Failed to load from JSON: {:?}", + result.err() + ); + let instance = result.unwrap(); + assert_eq!(instance.turn_id, "turn_001"); + assert_eq!(instance.status, "success"); + assert_eq!(instance.iterations, 2); + assert!( + instance.llm_calls.is_some(), + "Expected llm_calls to be Some" + ); + assert_eq!(instance.llm_calls.as_ref().unwrap(), &3); + assert!( + instance.tool_calls.is_some(), + "Expected tool_calls to be Some" + ); + assert_eq!(instance.tool_calls.as_ref().unwrap(), &2); + assert!(instance.retries.is_some(), "Expected retries to be Some"); + assert_eq!(instance.retries.as_ref().unwrap(), &1); + assert!( + instance.duration_ms.is_some(), + "Expected duration_ms to be Some" + ); + assert_eq!(instance.duration_ms.as_ref().unwrap(), &2500.0); +} + +#[test] +fn test_turn_summary_load_yaml() { + let yaml = r####" +turnId: turn_001 +status: success +iterations: 2 +llmCalls: 3 +toolCalls: 2 +retries: 1 +durationMs: 2500 + +"####; + let ctx = LoadContext::default(); + let result = TurnSummary::from_yaml(yaml, &ctx); + assert!( + result.is_ok(), + "Failed to load from YAML: {:?}", + result.err() + ); + let instance = result.unwrap(); + assert_eq!(instance.turn_id, "turn_001"); + assert_eq!(instance.status, "success"); + assert_eq!(instance.iterations, 2); + assert!( + instance.llm_calls.is_some(), + "Expected llm_calls to be Some" + ); + assert!( + instance.tool_calls.is_some(), + "Expected tool_calls to be Some" + ); + assert!(instance.retries.is_some(), "Expected retries to be Some"); + assert!( + instance.duration_ms.is_some(), + "Expected duration_ms to be Some" + ); +} + +#[test] +fn test_turn_summary_roundtrip() { + let json = r####" +{ + "turnId": "turn_001", + "status": "success", + "iterations": 2, + "llmCalls": 3, + "toolCalls": 2, + "retries": 1, + "durationMs": 2500 +} +"####; + let load_ctx = LoadContext::default(); + let result = TurnSummary::from_json(json, &load_ctx); + assert!(result.is_ok(), "Failed to load: {:?}", result.err()); + let instance = result.unwrap(); + let save_ctx = SaveContext::default(); + let json_output = instance.to_json(&save_ctx); + assert!( + json_output.is_ok(), + "Failed to serialize to JSON: {:?}", + json_output.err() + ); +} diff --git a/runtime/rust/prompty/tests/model/events/turn_trace_test.rs b/runtime/rust/prompty/tests/model/events/turn_trace_test.rs new file mode 100644 index 00000000..beab6efd --- /dev/null +++ b/runtime/rust/prompty/tests/model/events/turn_trace_test.rs @@ -0,0 +1,86 @@ +// +// Code generated by Typra emitter; DO NOT EDIT. + +#![allow( + unused_imports, + dead_code, + non_camel_case_types, + unused_variables, + clippy::all +)] + +use prompty::model::TurnTrace; +use prompty::model::context::{LoadContext, SaveContext}; + +#[test] +fn test_turn_trace_load_json() { + let json = r####" +{ + "version": "1", + "runtime": "typescript", + "promptyVersion": "2.0.0" +} +"####; + let ctx = LoadContext::default(); + let result = TurnTrace::from_json(json, &ctx); + assert!( + result.is_ok(), + "Failed to load from JSON: {:?}", + result.err() + ); + let instance = result.unwrap(); + assert_eq!(instance.version, "1"); + assert!(instance.runtime.is_some(), "Expected runtime to be Some"); + assert_eq!(instance.runtime.as_ref().unwrap(), &"typescript"); + assert!( + instance.prompty_version.is_some(), + "Expected prompty_version to be Some" + ); + assert_eq!(instance.prompty_version.as_ref().unwrap(), &"2.0.0"); +} + +#[test] +fn test_turn_trace_load_yaml() { + let yaml = r####" +version: "1" +runtime: typescript +promptyVersion: 2.0.0 + +"####; + let ctx = LoadContext::default(); + let result = TurnTrace::from_yaml(yaml, &ctx); + assert!( + result.is_ok(), + "Failed to load from YAML: {:?}", + result.err() + ); + let instance = result.unwrap(); + assert_eq!(instance.version, "1"); + assert!(instance.runtime.is_some(), "Expected runtime to be Some"); + assert!( + instance.prompty_version.is_some(), + "Expected prompty_version to be Some" + ); +} + +#[test] +fn test_turn_trace_roundtrip() { + let json = r####" +{ + "version": "1", + "runtime": "typescript", + "promptyVersion": "2.0.0" +} +"####; + let load_ctx = LoadContext::default(); + let result = TurnTrace::from_json(json, &load_ctx); + assert!(result.is_ok(), "Failed to load: {:?}", result.err()); + let instance = result.unwrap(); + let save_ctx = SaveContext::default(); + let json_output = instance.to_json(&save_ctx); + assert!( + json_output.is_ok(), + "Failed to serialize to JSON: {:?}", + json_output.err() + ); +} diff --git a/runtime/rust/prompty/tests/model/main.rs b/runtime/rust/prompty/tests/model/main.rs index 9f5d7fc9..fc601819 100644 --- a/runtime/rust/prompty/tests/model/main.rs +++ b/runtime/rust/prompty/tests/model/main.rs @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. #![allow( unused_imports, diff --git a/runtime/rust/prompty/tests/model/model/mod.rs b/runtime/rust/prompty/tests/model/model/mod.rs index 9f75a36a..411ab4a1 100644 --- a/runtime/rust/prompty/tests/model/model/mod.rs +++ b/runtime/rust/prompty/tests/model/model/mod.rs @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. #![allow( unused_imports, diff --git a/runtime/rust/prompty/tests/model/model/model_info_test.rs b/runtime/rust/prompty/tests/model/model/model_info_test.rs index 14d636fc..9e451027 100644 --- a/runtime/rust/prompty/tests/model/model/model_info_test.rs +++ b/runtime/rust/prompty/tests/model/model/model_info_test.rs @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. #![allow( unused_imports, diff --git a/runtime/rust/prompty/tests/model/model/model_options_test.rs b/runtime/rust/prompty/tests/model/model/model_options_test.rs index fa80b2fc..33fb8613 100644 --- a/runtime/rust/prompty/tests/model/model/model_options_test.rs +++ b/runtime/rust/prompty/tests/model/model/model_options_test.rs @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. #![allow( unused_imports, diff --git a/runtime/rust/prompty/tests/model/model/model_test.rs b/runtime/rust/prompty/tests/model/model/model_test.rs index a6145c16..1fe28628 100644 --- a/runtime/rust/prompty/tests/model/model/model_test.rs +++ b/runtime/rust/prompty/tests/model/model/model_test.rs @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. #![allow( unused_imports, diff --git a/runtime/rust/prompty/tests/model/model/token_usage_test.rs b/runtime/rust/prompty/tests/model/model/token_usage_test.rs index 7478c864..2234fc66 100644 --- a/runtime/rust/prompty/tests/model/model/token_usage_test.rs +++ b/runtime/rust/prompty/tests/model/model/token_usage_test.rs @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. #![allow( unused_imports, diff --git a/runtime/rust/prompty/tests/model/pipeline/compaction_config_test.rs b/runtime/rust/prompty/tests/model/pipeline/compaction_config_test.rs index 45ea60b5..23907e28 100644 --- a/runtime/rust/prompty/tests/model/pipeline/compaction_config_test.rs +++ b/runtime/rust/prompty/tests/model/pipeline/compaction_config_test.rs @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. #![allow( unused_imports, diff --git a/runtime/rust/prompty/tests/model/pipeline/mod.rs b/runtime/rust/prompty/tests/model/pipeline/mod.rs index 2cf05067..579edfad 100644 --- a/runtime/rust/prompty/tests/model/pipeline/mod.rs +++ b/runtime/rust/prompty/tests/model/pipeline/mod.rs @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. #![allow( unused_imports, @@ -9,4 +10,12 @@ )] mod compaction_config_test; +mod replay_journal_record_test; +mod replay_mismatch_test; +mod replay_verification_request_test; +mod replay_verification_result_test; +mod run_turn_request_test; +mod run_turn_result_test; +mod turn_model_request_test; +mod turn_model_response_test; mod turn_options_test; diff --git a/runtime/rust/prompty/tests/model/pipeline/replay_journal_record_test.rs b/runtime/rust/prompty/tests/model/pipeline/replay_journal_record_test.rs new file mode 100644 index 00000000..29f23135 --- /dev/null +++ b/runtime/rust/prompty/tests/model/pipeline/replay_journal_record_test.rs @@ -0,0 +1,15 @@ +// +// Code generated by Typra emitter; DO NOT EDIT. + +#![allow( + unused_imports, + dead_code, + non_camel_case_types, + unused_variables, + clippy::all +)] + +use prompty::model::ReplayJournalRecord; +use prompty::model::ReplayRecordKind; +use prompty::model::ReplayRecordStatus; +use prompty::model::context::{LoadContext, SaveContext}; diff --git a/runtime/rust/prompty/tests/model/pipeline/replay_mismatch_test.rs b/runtime/rust/prompty/tests/model/pipeline/replay_mismatch_test.rs new file mode 100644 index 00000000..4639b7a7 --- /dev/null +++ b/runtime/rust/prompty/tests/model/pipeline/replay_mismatch_test.rs @@ -0,0 +1,13 @@ +// +// Code generated by Typra emitter; DO NOT EDIT. + +#![allow( + unused_imports, + dead_code, + non_camel_case_types, + unused_variables, + clippy::all +)] + +use prompty::model::ReplayMismatch; +use prompty::model::context::{LoadContext, SaveContext}; diff --git a/runtime/rust/prompty/tests/model/pipeline/replay_verification_request_test.rs b/runtime/rust/prompty/tests/model/pipeline/replay_verification_request_test.rs new file mode 100644 index 00000000..0a6c55c7 --- /dev/null +++ b/runtime/rust/prompty/tests/model/pipeline/replay_verification_request_test.rs @@ -0,0 +1,13 @@ +// +// Code generated by Typra emitter; DO NOT EDIT. + +#![allow( + unused_imports, + dead_code, + non_camel_case_types, + unused_variables, + clippy::all +)] + +use prompty::model::ReplayVerificationRequest; +use prompty::model::context::{LoadContext, SaveContext}; diff --git a/runtime/rust/prompty/tests/model/pipeline/replay_verification_result_test.rs b/runtime/rust/prompty/tests/model/pipeline/replay_verification_result_test.rs new file mode 100644 index 00000000..20135d9a --- /dev/null +++ b/runtime/rust/prompty/tests/model/pipeline/replay_verification_result_test.rs @@ -0,0 +1,14 @@ +// +// Code generated by Typra emitter; DO NOT EDIT. + +#![allow( + unused_imports, + dead_code, + non_camel_case_types, + unused_variables, + clippy::all +)] + +use prompty::model::ReplayVerificationResult; +use prompty::model::ReplayVerificationStatus; +use prompty::model::context::{LoadContext, SaveContext}; diff --git a/runtime/rust/prompty/tests/model/pipeline/run_turn_request_test.rs b/runtime/rust/prompty/tests/model/pipeline/run_turn_request_test.rs new file mode 100644 index 00000000..bdc3863e --- /dev/null +++ b/runtime/rust/prompty/tests/model/pipeline/run_turn_request_test.rs @@ -0,0 +1,73 @@ +// +// Code generated by Typra emitter; DO NOT EDIT. + +#![allow( + unused_imports, + dead_code, + non_camel_case_types, + unused_variables, + clippy::all +)] + +use prompty::model::RunTurnRequest; +use prompty::model::context::{LoadContext, SaveContext}; + +#[test] +fn test_run_turn_request_load_json() { + let json = r####" +{ + "sessionId": "sess_abc123", + "turnId": "turn_abc123" +} +"####; + let ctx = LoadContext::default(); + let result = RunTurnRequest::from_json(json, &ctx); + assert!( + result.is_ok(), + "Failed to load from JSON: {:?}", + result.err() + ); + let instance = result.unwrap(); + assert_eq!(instance.session_id, "sess_abc123"); + assert_eq!(instance.turn_id, "turn_abc123"); +} + +#[test] +fn test_run_turn_request_load_yaml() { + let yaml = r####" +sessionId: sess_abc123 +turnId: turn_abc123 + +"####; + let ctx = LoadContext::default(); + let result = RunTurnRequest::from_yaml(yaml, &ctx); + assert!( + result.is_ok(), + "Failed to load from YAML: {:?}", + result.err() + ); + let instance = result.unwrap(); + assert_eq!(instance.session_id, "sess_abc123"); + assert_eq!(instance.turn_id, "turn_abc123"); +} + +#[test] +fn test_run_turn_request_roundtrip() { + let json = r####" +{ + "sessionId": "sess_abc123", + "turnId": "turn_abc123" +} +"####; + let load_ctx = LoadContext::default(); + let result = RunTurnRequest::from_json(json, &load_ctx); + assert!(result.is_ok(), "Failed to load: {:?}", result.err()); + let instance = result.unwrap(); + let save_ctx = SaveContext::default(); + let json_output = instance.to_json(&save_ctx); + assert!( + json_output.is_ok(), + "Failed to serialize to JSON: {:?}", + json_output.err() + ); +} diff --git a/runtime/rust/prompty/tests/model/pipeline/run_turn_result_test.rs b/runtime/rust/prompty/tests/model/pipeline/run_turn_result_test.rs new file mode 100644 index 00000000..46379584 --- /dev/null +++ b/runtime/rust/prompty/tests/model/pipeline/run_turn_result_test.rs @@ -0,0 +1,79 @@ +// +// Code generated by Typra emitter; DO NOT EDIT. + +#![allow( + unused_imports, + dead_code, + non_camel_case_types, + unused_variables, + clippy::all +)] + +use prompty::model::RunTurnResult; +use prompty::model::RunTurnStatus; +use prompty::model::context::{LoadContext, SaveContext}; + +#[test] +fn test_run_turn_result_load_json() { + let json = r####" +{ + "sessionId": "sess_abc123", + "turnId": "turn_abc123", + "iterations": 1 +} +"####; + let ctx = LoadContext::default(); + let result = RunTurnResult::from_json(json, &ctx); + assert!( + result.is_ok(), + "Failed to load from JSON: {:?}", + result.err() + ); + let instance = result.unwrap(); + assert_eq!(instance.session_id, "sess_abc123"); + assert_eq!(instance.turn_id, "turn_abc123"); + assert_eq!(instance.iterations, 1); +} + +#[test] +fn test_run_turn_result_load_yaml() { + let yaml = r####" +sessionId: sess_abc123 +turnId: turn_abc123 +iterations: 1 + +"####; + let ctx = LoadContext::default(); + let result = RunTurnResult::from_yaml(yaml, &ctx); + assert!( + result.is_ok(), + "Failed to load from YAML: {:?}", + result.err() + ); + let instance = result.unwrap(); + assert_eq!(instance.session_id, "sess_abc123"); + assert_eq!(instance.turn_id, "turn_abc123"); + assert_eq!(instance.iterations, 1); +} + +#[test] +fn test_run_turn_result_roundtrip() { + let json = r####" +{ + "sessionId": "sess_abc123", + "turnId": "turn_abc123", + "iterations": 1 +} +"####; + let load_ctx = LoadContext::default(); + let result = RunTurnResult::from_json(json, &load_ctx); + assert!(result.is_ok(), "Failed to load: {:?}", result.err()); + let instance = result.unwrap(); + let save_ctx = SaveContext::default(); + let json_output = instance.to_json(&save_ctx); + assert!( + json_output.is_ok(), + "Failed to serialize to JSON: {:?}", + json_output.err() + ); +} diff --git a/runtime/rust/prompty/tests/model/pipeline/turn_model_request_test.rs b/runtime/rust/prompty/tests/model/pipeline/turn_model_request_test.rs new file mode 100644 index 00000000..ecc3350f --- /dev/null +++ b/runtime/rust/prompty/tests/model/pipeline/turn_model_request_test.rs @@ -0,0 +1,78 @@ +// +// Code generated by Typra emitter; DO NOT EDIT. + +#![allow( + unused_imports, + dead_code, + non_camel_case_types, + unused_variables, + clippy::all +)] + +use prompty::model::TurnModelRequest; +use prompty::model::context::{LoadContext, SaveContext}; + +#[test] +fn test_turn_model_request_load_json() { + let json = r####" +{ + "sessionId": "sess_abc123", + "turnId": "turn_abc123", + "iteration": 0 +} +"####; + let ctx = LoadContext::default(); + let result = TurnModelRequest::from_json(json, &ctx); + assert!( + result.is_ok(), + "Failed to load from JSON: {:?}", + result.err() + ); + let instance = result.unwrap(); + assert_eq!(instance.session_id, "sess_abc123"); + assert_eq!(instance.turn_id, "turn_abc123"); + assert_eq!(instance.iteration, 0); +} + +#[test] +fn test_turn_model_request_load_yaml() { + let yaml = r####" +sessionId: sess_abc123 +turnId: turn_abc123 +iteration: 0 + +"####; + let ctx = LoadContext::default(); + let result = TurnModelRequest::from_yaml(yaml, &ctx); + assert!( + result.is_ok(), + "Failed to load from YAML: {:?}", + result.err() + ); + let instance = result.unwrap(); + assert_eq!(instance.session_id, "sess_abc123"); + assert_eq!(instance.turn_id, "turn_abc123"); + assert_eq!(instance.iteration, 0); +} + +#[test] +fn test_turn_model_request_roundtrip() { + let json = r####" +{ + "sessionId": "sess_abc123", + "turnId": "turn_abc123", + "iteration": 0 +} +"####; + let load_ctx = LoadContext::default(); + let result = TurnModelRequest::from_json(json, &load_ctx); + assert!(result.is_ok(), "Failed to load: {:?}", result.err()); + let instance = result.unwrap(); + let save_ctx = SaveContext::default(); + let json_output = instance.to_json(&save_ctx); + assert!( + json_output.is_ok(), + "Failed to serialize to JSON: {:?}", + json_output.err() + ); +} diff --git a/runtime/rust/prompty/tests/model/pipeline/turn_model_response_test.rs b/runtime/rust/prompty/tests/model/pipeline/turn_model_response_test.rs new file mode 100644 index 00000000..d3ff5d9e --- /dev/null +++ b/runtime/rust/prompty/tests/model/pipeline/turn_model_response_test.rs @@ -0,0 +1,13 @@ +// +// Code generated by Typra emitter; DO NOT EDIT. + +#![allow( + unused_imports, + dead_code, + non_camel_case_types, + unused_variables, + clippy::all +)] + +use prompty::model::TurnModelResponse; +use prompty::model::context::{LoadContext, SaveContext}; diff --git a/runtime/rust/prompty/tests/model/pipeline/turn_options_test.rs b/runtime/rust/prompty/tests/model/pipeline/turn_options_test.rs index 9edfd44b..791cad6c 100644 --- a/runtime/rust/prompty/tests/model/pipeline/turn_options_test.rs +++ b/runtime/rust/prompty/tests/model/pipeline/turn_options_test.rs @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. #![allow( unused_imports, diff --git a/runtime/rust/prompty/tests/model/streaming/mod.rs b/runtime/rust/prompty/tests/model/streaming/mod.rs index a2f22085..9d4cc27b 100644 --- a/runtime/rust/prompty/tests/model/streaming/mod.rs +++ b/runtime/rust/prompty/tests/model/streaming/mod.rs @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. #![allow( unused_imports, diff --git a/runtime/rust/prompty/tests/model/streaming/stream_options_test.rs b/runtime/rust/prompty/tests/model/streaming/stream_options_test.rs index 007c02d7..ffa2241a 100644 --- a/runtime/rust/prompty/tests/model/streaming/stream_options_test.rs +++ b/runtime/rust/prompty/tests/model/streaming/stream_options_test.rs @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. #![allow( unused_imports, diff --git a/runtime/rust/prompty/tests/model/template/format_config_test.rs b/runtime/rust/prompty/tests/model/template/format_config_test.rs index a002170a..34a89108 100644 --- a/runtime/rust/prompty/tests/model/template/format_config_test.rs +++ b/runtime/rust/prompty/tests/model/template/format_config_test.rs @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. #![allow( unused_imports, diff --git a/runtime/rust/prompty/tests/model/template/mod.rs b/runtime/rust/prompty/tests/model/template/mod.rs index 1a954115..3e98998d 100644 --- a/runtime/rust/prompty/tests/model/template/mod.rs +++ b/runtime/rust/prompty/tests/model/template/mod.rs @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. #![allow( unused_imports, diff --git a/runtime/rust/prompty/tests/model/template/parser_config_test.rs b/runtime/rust/prompty/tests/model/template/parser_config_test.rs index 085f4e2b..1785b6ce 100644 --- a/runtime/rust/prompty/tests/model/template/parser_config_test.rs +++ b/runtime/rust/prompty/tests/model/template/parser_config_test.rs @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. #![allow( unused_imports, diff --git a/runtime/rust/prompty/tests/model/template/template_test.rs b/runtime/rust/prompty/tests/model/template/template_test.rs index 63793ad3..bc34bfc8 100644 --- a/runtime/rust/prompty/tests/model/template/template_test.rs +++ b/runtime/rust/prompty/tests/model/template/template_test.rs @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. #![allow( unused_imports, diff --git a/runtime/rust/prompty/tests/model/tools/binding_test.rs b/runtime/rust/prompty/tests/model/tools/binding_test.rs index 9258b5db..d8f71fb1 100644 --- a/runtime/rust/prompty/tests/model/tools/binding_test.rs +++ b/runtime/rust/prompty/tests/model/tools/binding_test.rs @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. #![allow( unused_imports, diff --git a/runtime/rust/prompty/tests/model/tools/mcp_approval_mode_test.rs b/runtime/rust/prompty/tests/model/tools/mcp_approval_mode_test.rs index 945892ab..b20de138 100644 --- a/runtime/rust/prompty/tests/model/tools/mcp_approval_mode_test.rs +++ b/runtime/rust/prompty/tests/model/tools/mcp_approval_mode_test.rs @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. #![allow( unused_imports, diff --git a/runtime/rust/prompty/tests/model/tools/mod.rs b/runtime/rust/prompty/tests/model/tools/mod.rs index 4142e779..469bdc71 100644 --- a/runtime/rust/prompty/tests/model/tools/mod.rs +++ b/runtime/rust/prompty/tests/model/tools/mod.rs @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. #![allow( unused_imports, diff --git a/runtime/rust/prompty/tests/model/tools/tool_context_test.rs b/runtime/rust/prompty/tests/model/tools/tool_context_test.rs index 3fac90e7..80ebd6a0 100644 --- a/runtime/rust/prompty/tests/model/tools/tool_context_test.rs +++ b/runtime/rust/prompty/tests/model/tools/tool_context_test.rs @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. #![allow( unused_imports, diff --git a/runtime/rust/prompty/tests/model/tools/tool_dispatch_result_test.rs b/runtime/rust/prompty/tests/model/tools/tool_dispatch_result_test.rs index 1d0844d1..c3b7e792 100644 --- a/runtime/rust/prompty/tests/model/tools/tool_dispatch_result_test.rs +++ b/runtime/rust/prompty/tests/model/tools/tool_dispatch_result_test.rs @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. #![allow( unused_imports, diff --git a/runtime/rust/prompty/tests/model/tools/tool_test.rs b/runtime/rust/prompty/tests/model/tools/tool_test.rs index d493601b..1eb1648d 100644 --- a/runtime/rust/prompty/tests/model/tools/tool_test.rs +++ b/runtime/rust/prompty/tests/model/tools/tool_test.rs @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. #![allow( unused_imports, diff --git a/runtime/rust/prompty/tests/model/tracing/mod.rs b/runtime/rust/prompty/tests/model/tracing/mod.rs index bc7cf121..42c9ce80 100644 --- a/runtime/rust/prompty/tests/model/tracing/mod.rs +++ b/runtime/rust/prompty/tests/model/tracing/mod.rs @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. #![allow( unused_imports, diff --git a/runtime/rust/prompty/tests/model/tracing/trace_file_test.rs b/runtime/rust/prompty/tests/model/tracing/trace_file_test.rs index 27ad1a80..d4e3905f 100644 --- a/runtime/rust/prompty/tests/model/tracing/trace_file_test.rs +++ b/runtime/rust/prompty/tests/model/tracing/trace_file_test.rs @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. #![allow( unused_imports, diff --git a/runtime/rust/prompty/tests/model/tracing/trace_span_test.rs b/runtime/rust/prompty/tests/model/tracing/trace_span_test.rs index fd445f20..a6f04c1e 100644 --- a/runtime/rust/prompty/tests/model/tracing/trace_span_test.rs +++ b/runtime/rust/prompty/tests/model/tracing/trace_span_test.rs @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. #![allow( unused_imports, diff --git a/runtime/rust/prompty/tests/model/tracing/trace_time_test.rs b/runtime/rust/prompty/tests/model/tracing/trace_time_test.rs index 1bbd4ca2..2b03e1b0 100644 --- a/runtime/rust/prompty/tests/model/tracing/trace_time_test.rs +++ b/runtime/rust/prompty/tests/model/tracing/trace_time_test.rs @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. #![allow( unused_imports, diff --git a/runtime/rust/prompty/tests/model/wire/anthropic_image_block_test.rs b/runtime/rust/prompty/tests/model/wire/anthropic_image_block_test.rs index ea02d7d4..565836db 100644 --- a/runtime/rust/prompty/tests/model/wire/anthropic_image_block_test.rs +++ b/runtime/rust/prompty/tests/model/wire/anthropic_image_block_test.rs @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. #![allow( unused_imports, diff --git a/runtime/rust/prompty/tests/model/wire/anthropic_image_source_test.rs b/runtime/rust/prompty/tests/model/wire/anthropic_image_source_test.rs index c6e78973..089aa702 100644 --- a/runtime/rust/prompty/tests/model/wire/anthropic_image_source_test.rs +++ b/runtime/rust/prompty/tests/model/wire/anthropic_image_source_test.rs @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. #![allow( unused_imports, diff --git a/runtime/rust/prompty/tests/model/wire/anthropic_messages_request_test.rs b/runtime/rust/prompty/tests/model/wire/anthropic_messages_request_test.rs index e07ab03e..e18311e7 100644 --- a/runtime/rust/prompty/tests/model/wire/anthropic_messages_request_test.rs +++ b/runtime/rust/prompty/tests/model/wire/anthropic_messages_request_test.rs @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. #![allow( unused_imports, diff --git a/runtime/rust/prompty/tests/model/wire/anthropic_messages_response_test.rs b/runtime/rust/prompty/tests/model/wire/anthropic_messages_response_test.rs index d1ef73b1..7d5ef69e 100644 --- a/runtime/rust/prompty/tests/model/wire/anthropic_messages_response_test.rs +++ b/runtime/rust/prompty/tests/model/wire/anthropic_messages_response_test.rs @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. #![allow( unused_imports, diff --git a/runtime/rust/prompty/tests/model/wire/anthropic_text_block_test.rs b/runtime/rust/prompty/tests/model/wire/anthropic_text_block_test.rs index 2e0374dd..0f5bd4bc 100644 --- a/runtime/rust/prompty/tests/model/wire/anthropic_text_block_test.rs +++ b/runtime/rust/prompty/tests/model/wire/anthropic_text_block_test.rs @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. #![allow( unused_imports, diff --git a/runtime/rust/prompty/tests/model/wire/anthropic_tool_definition_test.rs b/runtime/rust/prompty/tests/model/wire/anthropic_tool_definition_test.rs index 333ca534..02c999b9 100644 --- a/runtime/rust/prompty/tests/model/wire/anthropic_tool_definition_test.rs +++ b/runtime/rust/prompty/tests/model/wire/anthropic_tool_definition_test.rs @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. #![allow( unused_imports, diff --git a/runtime/rust/prompty/tests/model/wire/anthropic_tool_result_block_test.rs b/runtime/rust/prompty/tests/model/wire/anthropic_tool_result_block_test.rs index 6bbf2dbf..b1b951a7 100644 --- a/runtime/rust/prompty/tests/model/wire/anthropic_tool_result_block_test.rs +++ b/runtime/rust/prompty/tests/model/wire/anthropic_tool_result_block_test.rs @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. #![allow( unused_imports, diff --git a/runtime/rust/prompty/tests/model/wire/anthropic_tool_use_block_test.rs b/runtime/rust/prompty/tests/model/wire/anthropic_tool_use_block_test.rs index bd0e410c..0b969e84 100644 --- a/runtime/rust/prompty/tests/model/wire/anthropic_tool_use_block_test.rs +++ b/runtime/rust/prompty/tests/model/wire/anthropic_tool_use_block_test.rs @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. #![allow( unused_imports, diff --git a/runtime/rust/prompty/tests/model/wire/anthropic_usage_test.rs b/runtime/rust/prompty/tests/model/wire/anthropic_usage_test.rs index 7772d548..a52e6af4 100644 --- a/runtime/rust/prompty/tests/model/wire/anthropic_usage_test.rs +++ b/runtime/rust/prompty/tests/model/wire/anthropic_usage_test.rs @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. #![allow( unused_imports, diff --git a/runtime/rust/prompty/tests/model/wire/anthropic_wire_message_test.rs b/runtime/rust/prompty/tests/model/wire/anthropic_wire_message_test.rs index c121c4cd..a54277bb 100644 --- a/runtime/rust/prompty/tests/model/wire/anthropic_wire_message_test.rs +++ b/runtime/rust/prompty/tests/model/wire/anthropic_wire_message_test.rs @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. #![allow( unused_imports, diff --git a/runtime/rust/prompty/tests/model/wire/mod.rs b/runtime/rust/prompty/tests/model/wire/mod.rs index 89b6ff98..fbacc623 100644 --- a/runtime/rust/prompty/tests/model/wire/mod.rs +++ b/runtime/rust/prompty/tests/model/wire/mod.rs @@ -1,4 +1,5 @@ -// Code generated by Prompty emitter; DO NOT EDIT. +// +// Code generated by Typra emitter; DO NOT EDIT. #![allow( unused_imports, diff --git a/runtime/rust/prompty/tests/parse_vectors.rs b/runtime/rust/prompty/tests/parse_vectors.rs index 7d95ef6a..55408422 100644 --- a/runtime/rust/prompty/tests/parse_vectors.rs +++ b/runtime/rust/prompty/tests/parse_vectors.rs @@ -7,7 +7,7 @@ use regex::Regex; use serde_json::Value; use prompty::parsers::parse_chat; -use prompty::types::{ContentPart, ContentPartKind, Message, Role}; +use prompty::types::{ContentPartKind, Message, Role}; /// Path to the parse vectors JSON file. fn vectors_path() -> PathBuf { diff --git a/runtime/typescript/packages/core/src/core/agent-events.ts b/runtime/typescript/packages/core/src/core/agent-events.ts index 9e57170a..cc44f030 100644 --- a/runtime/typescript/packages/core/src/core/agent-events.ts +++ b/runtime/typescript/packages/core/src/core/agent-events.ts @@ -5,9 +5,17 @@ /** Event types emitted during the agent loop. */ export type AgentEventType = + | "turn_start" + | "turn_end" + | "llm_start" + | "llm_complete" + | "retry" + | "permission_requested" + | "permission_completed" | "token" | "thinking" | "tool_call_start" + | "tool_call_complete" | "tool_result" | "status" | "messages_updated" @@ -32,7 +40,16 @@ export function emitEvent( ): void { if (!callback) return; try { - callback(eventType, data); + callback(eventType, { + ...data, + turnEvent: { + id: `evt_${globalThis.crypto?.randomUUID?.() ?? Math.random().toString(36).slice(2)}`, + type: eventType, + timestamp: new Date().toISOString(), + iteration: typeof data.iteration === "number" ? data.iteration : undefined, + payload: data, + }, + }); } catch (err) { // Swallow — event callbacks must not break the loop (§13.1) if (typeof globalThis.console?.debug === "function") { diff --git a/runtime/typescript/packages/core/src/core/pipeline.ts b/runtime/typescript/packages/core/src/core/pipeline.ts index 52206682..fec4e56d 100644 --- a/runtime/typescript/packages/core/src/core/pipeline.ts +++ b/runtime/typescript/packages/core/src/core/pipeline.ts @@ -61,6 +61,19 @@ const DEFAULT_PROVIDER = "openai"; const DEFAULT_MAX_ITERATIONS = 10; const DEFAULT_MAX_LLM_RETRIES = 3; +function emitFailedTurnEnd( + onEvent: EventCallback | undefined, + error: unknown, + iterations: number, + response?: unknown, +): void { + emitEvent(onEvent, "turn_end", { + iterations, + status: error instanceof CancelledError ? "cancelled" : "error", + ...(response !== undefined ? { response } : {}), + }); +} + // --------------------------------------------------------------------------- // ExecuteError (§9.10) // --------------------------------------------------------------------------- @@ -499,6 +512,12 @@ async function invokeWithRetry( emitEvent(onEvent, "status", { message: `LLM call failed, retrying (attempt ${attempts + 1}/${maxRetries})...`, }); + emitEvent(onEvent, "retry", { + operation: "llm", + attempt: attempts + 1, + maxAttempts: maxRetries, + reason: err instanceof Error ? err.message : String(err), + }); // Exponential backoff with jitter, capped at 60s (§9.10) // backoff = min(2^attempts + jitter(), 60) — values in seconds const backoffSecs = Math.min(Math.pow(2, attempts) + Math.random(), 60); @@ -666,11 +685,22 @@ export async function turn( const tools = options?.tools ?? {}; const hasTools = Object.keys(tools).length > 0; + const onEvent = options?.onEvent; + emitEvent(onEvent, "turn_start", { + agent: agent.name, + inputs, + maxIterations: options?.maxIterations ?? DEFAULT_MAX_ITERATIONS, + }); if (!hasTools) { // Simple mode: prepare → [extensions] → executor → [output guard] → process - let messages = await prepare(agent, inputs); - const onEvent = options?.onEvent; + let messages: Message[]; + try { + messages = await prepare(agent, inputs); + } catch (err) { + emitFailedTurnEnd(onEvent, err, 0); + throw err; + } // §13.5 — Drain steering messages if (options?.steering) { @@ -697,23 +727,50 @@ export async function turn( const result = options.guardrails.checkInput(messages); if (!result.allowed) { emitEvent(onEvent, "error", { message: `Input guardrail denied: ${result.reason}` }); + emitFailedTurnEnd(onEvent, new GuardrailError(result.reason ?? "Input guardrail denied"), 0); throw new GuardrailError(result.reason ?? "Input guardrail denied"); } if (result.rewrite) messages = result.rewrite; } // §13.2 — Check cancellation before LLM call - checkCancellation(options?.signal); + try { + checkCancellation(options?.signal); + } catch (err) { + emitEvent(onEvent, "cancelled", {}); + emitFailedTurnEnd(onEvent, err, 0); + throw err; + } const provider = resolveProvider(agent); const executor = getExecutor(provider); - const response = await executor.execute(agent, messages); + emitEvent(onEvent, "llm_start", { + provider, + modelId: agent.model?.id, + messageCount: messages.length, + attempt: 0, + }); + let response: unknown; + try { + response = await executor.execute(agent, messages); + } catch (err) { + emitFailedTurnEnd(onEvent, err, 0); + throw err; + } + emitEvent(onEvent, "llm_complete", {}); if (options?.raw) { emit("result", response); + emitEvent(onEvent, "turn_end", { iterations: 0, status: "success", response }); return response; } - const processed = await process(agent, response); + let processed: unknown; + try { + processed = await process(agent, response); + } catch (err) { + emitFailedTurnEnd(onEvent, err, 0, response); + throw err; + } // §13.4 — Output guardrail on final response if (options?.guardrails) { @@ -722,31 +779,39 @@ export async function turn( const gr = options.guardrails.checkOutput(assistantMsg); if (!gr.allowed) { emitEvent(onEvent, "error", { message: `Output guardrail denied: ${gr.reason}` }); + emitFailedTurnEnd(onEvent, new GuardrailError(gr.reason ?? "Output guardrail denied"), 0, processed); throw new GuardrailError(gr.reason ?? "Output guardrail denied"); } if (gr.rewrite !== undefined) { emit("result", gr.rewrite); emitEvent(onEvent, "done", { response: gr.rewrite, messages }); + emitEvent(onEvent, "turn_end", { iterations: 0, status: "success", response: gr.rewrite }); return gr.rewrite; } } emit("result", sanitizeValue("result", processed)); emitEvent(onEvent, "done", { response: processed, messages }); + emitEvent(onEvent, "turn_end", { iterations: 0, status: "success", response: processed }); return processed; } // Agent mode: prepare → [executor → toolCalls]* → executor → process const maxIterations = options?.maxIterations ?? DEFAULT_MAX_ITERATIONS; const maxLlmRetries = options?.maxLlmRetries ?? DEFAULT_MAX_LLM_RETRIES; - const onEvent = options?.onEvent; const signal = options?.signal; const contextBudget = options?.contextBudget; const guardrails = options?.guardrails; const steering = options?.steering; const parallelToolCalls = options?.parallelToolCalls ?? false; - let messages = await prepare(agent, inputs); + let messages: Message[]; + try { + messages = await prepare(agent, inputs); + } catch (err) { + emitFailedTurnEnd(onEvent, err, 0); + throw err; + } const parentInputs = inputs ?? {}; const provider = resolveProvider(agent); const executor = getExecutor(provider); @@ -760,6 +825,7 @@ export async function turn( checkCancellation(signal); } catch (err) { emitEvent(onEvent, "cancelled", {}); + emitFailedTurnEnd(onEvent, err, iteration); throw err; } @@ -790,6 +856,7 @@ export async function turn( const result = guardrails.checkInput(messages); if (!result.allowed) { emitEvent(onEvent, "error", { message: `Input guardrail denied: ${result.reason}` }); + emitFailedTurnEnd(onEvent, new GuardrailError(result.reason ?? "Input guardrail denied"), iteration); throw new GuardrailError(result.reason ?? "Input guardrail denied"); } if (result.rewrite) messages = result.rewrite; @@ -800,15 +867,36 @@ export async function turn( checkCancellation(signal); } catch (err) { emitEvent(onEvent, "cancelled", {}); + emitFailedTurnEnd(onEvent, err, iteration); throw err; } // Call LLM — §9.10: retry on transient failure - response = await invokeWithRetry(executor, agent, messages, maxLlmRetries, onEvent, signal); + emitEvent(onEvent, "llm_start", { + provider, + modelId: agent.model?.id, + messageCount: messages.length, + attempt: 0, + iteration, + }); + try { + response = await invokeWithRetry(executor, agent, messages, maxLlmRetries, onEvent, signal); + } catch (err) { + emitFailedTurnEnd(onEvent, err, iteration); + throw err; + } + emitEvent(onEvent, "llm_complete", { iteration }); // Streaming: consume the stream, extract tool calls from buffered chunks if (isAsyncIterable(response)) { - const { toolCalls, content } = await consumeStream(agent, response, onEvent); + let streamResult: Awaited>; + try { + streamResult = await consumeStream(agent, response, onEvent); + } catch (err) { + emitFailedTurnEnd(onEvent, err, iteration, response); + throw err; + } + const { toolCalls, content } = streamResult; // §13.4 — Output guardrail if (guardrails && content) { @@ -816,6 +904,7 @@ export async function turn( const gr = guardrails.checkOutput(assistantMsg); if (!gr.allowed) { emitEvent(onEvent, "error", { message: `Output guardrail denied: ${gr.reason}` }); + emitFailedTurnEnd(onEvent, new GuardrailError(gr.reason ?? "Output guardrail denied"), iteration, content); throw new GuardrailError(gr.reason ?? "Output guardrail denied"); } } @@ -824,27 +913,35 @@ export async function turn( emit("iterations", iteration); emit("result", content); emitEvent(onEvent, "done", { response: content, messages }); + emitEvent(onEvent, "turn_end", { iterations: iteration, status: "success", response: content }); return content; } iteration++; if (iteration > maxIterations) { + emitFailedTurnEnd(onEvent, new Error("Agent loop exceeded maxIterations"), iteration); throw new Error( `Agent loop exceeded maxIterations (${maxIterations}). ` + `The model kept requesting tool calls. Increase maxIterations or check your tools.`, ); } - const toolMessages = await traceSpan("toolCalls", async (toolEmit) => { - toolEmit("signature", "prompty.turn.toolCalls"); - toolEmit("description", `Tool call round ${iteration}`); - const result = await buildToolMessagesFromCallsWithExtensions( - toolCalls, content, tools, agent, parentInputs, toolEmit, - { onEvent, signal, guardrails, parallel: parallelToolCalls }, - ); - toolEmit("result", result.map((m) => ({ role: m.role, content: m.parts.map((p) => (p as { value?: string }).value ?? "").join(""), metadata: m.metadata }))); - return result; - }); + let toolMessages: Message[]; + try { + toolMessages = await traceSpan("toolCalls", async (toolEmit) => { + toolEmit("signature", "prompty.turn.toolCalls"); + toolEmit("description", `Tool call round ${iteration}`); + const result = await buildToolMessagesFromCallsWithExtensions( + toolCalls, content, tools, agent, parentInputs, toolEmit, + { onEvent, signal, guardrails, parallel: parallelToolCalls }, + ); + toolEmit("result", result.map((m) => ({ role: m.role, content: m.parts.map((p) => (p as { value?: string }).value ?? "").join(""), metadata: m.metadata }))); + return result; + }); + } catch (err) { + emitFailedTurnEnd(onEvent, err, iteration, content); + throw err; + } messages.push(...toolMessages); emitEvent(onEvent, "messages_updated", { messages }); @@ -853,25 +950,34 @@ export async function turn( // Non-streaming: check raw response for tool calls if (!hasToolCalls(response)) { - const finalResult = options?.raw ? response : await process(agent, response); + let finalResult: unknown; + try { + finalResult = options?.raw ? response : await process(agent, response); + } catch (err) { + emitFailedTurnEnd(onEvent, err, iteration, response); + throw err; + } if (guardrails) { const contentStr = typeof finalResult === "string" ? finalResult : JSON.stringify(finalResult); const assistantMsg = new Message({ role: "assistant", parts: [text(contentStr)] }); const gr = guardrails.checkOutput(assistantMsg); if (!gr.allowed) { emitEvent(onEvent, "error", { message: `Output guardrail denied: ${gr.reason}` }); + emitFailedTurnEnd(onEvent, new GuardrailError(gr.reason ?? "Output guardrail denied"), iteration, finalResult); throw new GuardrailError(gr.reason ?? "Output guardrail denied"); } if (gr.rewrite !== undefined) { emit("iterations", iteration); emit("result", gr.rewrite); emitEvent(onEvent, "done", { response: gr.rewrite, messages }); + emitEvent(onEvent, "turn_end", { iterations: iteration, status: "success", response: gr.rewrite }); return gr.rewrite; } } emit("iterations", iteration); emit("result", finalResult); emitEvent(onEvent, "done", { response: finalResult, messages }); + emitEvent(onEvent, "turn_end", { iterations: iteration, status: "success", response: finalResult }); return finalResult; } @@ -883,6 +989,7 @@ export async function turn( const gr = guardrails.checkOutput(assistantMsg); if (!gr.allowed) { emitEvent(onEvent, "error", { message: `Output guardrail denied: ${gr.reason}` }); + emitFailedTurnEnd(onEvent, new GuardrailError(gr.reason ?? "Output guardrail denied"), iteration, textContent); throw new GuardrailError(gr.reason ?? "Output guardrail denied"); } } @@ -890,22 +997,29 @@ export async function turn( iteration++; if (iteration > maxIterations) { + emitFailedTurnEnd(onEvent, new Error("Agent loop exceeded maxIterations"), iteration); throw new Error( `Agent loop exceeded maxIterations (${maxIterations}). ` + `The model kept requesting tool calls. Increase maxIterations or check your tools.`, ); } - const toolMessages = await traceSpan("toolCalls", async (toolEmit) => { - toolEmit("signature", "prompty.turn.toolCalls"); - toolEmit("description", `Tool call round ${iteration}`); - const result = await buildToolResultMessagesWithExtensions( - response, tools, agent, parentInputs, toolEmit, - { onEvent, signal, guardrails, parallel: parallelToolCalls }, - ); - toolEmit("result", result.map((m) => ({ role: m.role, content: m.parts.map((p) => (p as { value?: string }).value ?? "").join(""), metadata: m.metadata }))); - return result; - }); + let toolMessages: Message[]; + try { + toolMessages = await traceSpan("toolCalls", async (toolEmit) => { + toolEmit("signature", "prompty.turn.toolCalls"); + toolEmit("description", `Tool call round ${iteration}`); + const result = await buildToolResultMessagesWithExtensions( + response, tools, agent, parentInputs, toolEmit, + { onEvent, signal, guardrails, parallel: parallelToolCalls }, + ); + toolEmit("result", result.map((m) => ({ role: m.role, content: m.parts.map((p) => (p as { value?: string }).value ?? "").join(""), metadata: m.metadata }))); + return result; + }); + } catch (err) { + emitFailedTurnEnd(onEvent, err, iteration, response); + throw err; + } messages.push(...toolMessages); emitEvent(onEvent, "messages_updated", { messages }); @@ -1207,7 +1321,8 @@ async function dispatchOneToolWithExtensions( } // §13.1 — Emit tool_call_start - emitEvent(onEvent, "tool_call_start", { name: tc.name, arguments: tc.arguments }); + emitEvent(onEvent, "tool_call_start", { id: tc.id, name: tc.name, arguments: tc.arguments }); + const started = performance.now(); // §13.4 — Tool guardrail if (guardrails) { @@ -1216,6 +1331,14 @@ async function dispatchOneToolWithExtensions( if (!gr.allowed) { const deniedMsg = `Tool denied by guardrail: ${gr.reason}`; emitEvent(onEvent, "tool_result", { name: tc.name, result: deniedMsg }); + emitEvent(onEvent, "tool_call_complete", { + id: tc.id, + name: tc.name, + success: false, + result: deniedMsg, + durationMs: performance.now() - started, + errorKind: "guardrail_denied", + }); return deniedMsg; } if (gr.rewrite !== undefined) { @@ -1233,6 +1356,14 @@ async function dispatchOneToolWithExtensions( result = `Error: Tool '${tc.name}' received unparseable arguments`; emitEvent(onEvent, "error", { tool: tc.name, error: "Unparseable tool arguments" }); emitEvent(onEvent, "tool_result", { name: tc.name, result }); + emitEvent(onEvent, "tool_call_complete", { + id: tc.id, + name: tc.name, + success: false, + result, + durationMs: performance.now() - started, + errorKind: "invalid_arguments", + }); return result; } if (agent && parentInputs && typeof parsedArgs === "object" && parsedArgs !== null && !Array.isArray(parsedArgs)) { @@ -1257,6 +1388,15 @@ async function dispatchOneToolWithExtensions( // §13.1 — Emit tool_result emitEvent(onEvent, "tool_result", { name: tc.name, result }); + const success = !result.startsWith("Error:"); + emitEvent(onEvent, "tool_call_complete", { + id: tc.id, + name: tc.name, + success, + result, + durationMs: performance.now() - started, + errorKind: success ? undefined : "tool_error", + }); // §9.9 — Emit error event when tool result indicates failure if (result.startsWith("Error:")) { diff --git a/runtime/typescript/packages/core/src/harness/adapters.ts b/runtime/typescript/packages/core/src/harness/adapters.ts new file mode 100644 index 00000000..c00225b0 --- /dev/null +++ b/runtime/typescript/packages/core/src/harness/adapters.ts @@ -0,0 +1,180 @@ +// Copyright (c) Microsoft. All rights reserved. + +import { appendFileSync, mkdirSync } from "node:fs"; +import { dirname } from "node:path"; + +import { + Checkpoint, + HostToolRequest, + HostToolResult, + PermissionDecision, + PermissionRequest, + SessionEvent, + SessionSummary, + TurnEvent, + type CheckpointStore, + type EventJournalWriter, + type EventSink, + type HostToolExecutor, + type PermissionResolver, +} from "../model/index.js"; + +type JsonRecord = Record; +type ToolHandler = (args: JsonRecord, request: HostToolRequest) => unknown | Promise; + +function checkpointKey(sessionId: string, checkpointId: string): string { + return `${sessionId}\u0000${checkpointId}`; +} + +function requireCheckpointKey(checkpoint: Checkpoint): { sessionId: string; checkpointId: string } { + if (!checkpoint.sessionId) { + throw new Error("Checkpoint sessionId is required"); + } + if (!checkpoint.id) { + throw new Error("Checkpoint id is required"); + } + return { sessionId: checkpoint.sessionId, checkpointId: checkpoint.id }; +} + +function errorMessage(error: unknown): string { + return error instanceof Error ? error.message : String(error); +} + +/** Captures emitted turn and session events in memory. */ +export class CollectingEventSink implements EventSink { + readonly turnEvents: TurnEvent[] = []; + readonly sessionEvents: SessionEvent[] = []; + + emitTurn(turnEvent: TurnEvent): boolean { + this.turnEvents.push(turnEvent); + return true; + } + + emitSession(sessionEvent: SessionEvent): boolean { + this.sessionEvents.push(sessionEvent); + return true; + } +} + +/** Appends replayable event journal records as newline-delimited JSON. */ +export class JsonlEventJournalWriter implements EventJournalWriter { + private closed = false; + + constructor(private readonly path: string) { + mkdirSync(dirname(path), { recursive: true }); + } + + appendTurn(turnEvent: TurnEvent): boolean { + return this.write({ kind: "turn", event: turnEvent.save() }); + } + + appendSession(sessionEvent: SessionEvent): boolean { + return this.write({ kind: "session", event: sessionEvent.save() }); + } + + close(summary: SessionSummary | null): boolean { + if (summary) { + if (!this.write({ kind: "summary", summary: summary.save() })) { + return false; + } + } + this.closed = true; + return true; + } + + private write(record: JsonRecord): boolean { + if (this.closed) { + return false; + } + appendFileSync(this.path, `${JSON.stringify(record)}\n`, "utf8"); + return true; + } +} + +/** Stores checkpoints in memory by session and checkpoint identifier. */ +export class InMemoryCheckpointStore implements CheckpointStore { + private readonly checkpoints = new Map(); + + async save(checkpoint: Checkpoint): Promise { + const { sessionId, checkpointId } = requireCheckpointKey(checkpoint); + this.checkpoints.set(checkpointKey(sessionId, checkpointId), checkpoint); + return checkpoint; + } + + async load(sessionId: string, checkpointId: string): Promise { + return this.checkpoints.get(checkpointKey(sessionId, checkpointId)) ?? null; + } + + async listCheckpoints(sessionId: string): Promise { + return [...this.checkpoints.values()].filter((checkpoint) => checkpoint.sessionId === sessionId); + } +} + +/** Resolves every permission request as approved. */ +export class AllowAllPermissionResolver implements PermissionResolver { + async request(request: PermissionRequest): Promise { + return new PermissionDecision({ + requestId: request.requestId, + toolCallId: request.toolCallId, + permission: request.permission, + approved: true, + reason: "allow_all", + }); + } +} + +/** Resolves every permission request as denied. */ +export class DenyAllPermissionResolver implements PermissionResolver { + async request(request: PermissionRequest): Promise { + return new PermissionDecision({ + requestId: request.requestId, + toolCallId: request.toolCallId, + permission: request.permission, + approved: false, + reason: "deny_all", + }); + } +} + +/** Dispatches host tool requests to registered local functions. */ +export class FunctionHostToolExecutor implements HostToolExecutor { + constructor(private readonly handlers: Record) {} + + async execute(request: HostToolRequest): Promise { + const started = Date.now(); + const handler = this.handlers[request.toolName]; + if (!handler) { + return new HostToolResult({ + requestId: request.requestId, + toolCallId: request.toolCallId, + toolName: request.toolName, + success: false, + errorKind: "not_found", + result: { message: `No host tool registered for '${request.toolName}'` }, + durationMs: Date.now() - started, + }); + } + + try { + const result = await handler(request.arguments ?? {}, request); + return new HostToolResult({ + requestId: request.requestId, + toolCallId: request.toolCallId, + toolName: request.toolName, + success: true, + result, + durationMs: Date.now() - started, + }); + } catch (error) { + return new HostToolResult({ + requestId: request.requestId, + toolCallId: request.toolCallId, + toolName: request.toolName, + success: false, + errorKind: "exception", + result: { message: errorMessage(error) }, + durationMs: Date.now() - started, + }); + } + } +} diff --git a/runtime/typescript/packages/core/src/harness/index.ts b/runtime/typescript/packages/core/src/harness/index.ts new file mode 100644 index 00000000..4d97ae37 --- /dev/null +++ b/runtime/typescript/packages/core/src/harness/index.ts @@ -0,0 +1,24 @@ +export { + AllowAllPermissionResolver, + CollectingEventSink, + DenyAllPermissionResolver, + FunctionHostToolExecutor, + InMemoryCheckpointStore, + JsonlEventJournalWriter, +} from "./adapters.js"; + +export { + ReferenceTurnRunner, + RunTurnRequest, + RunTurnResult, + type TurnModelCallback, + TurnModelRequest, + TurnModelResponse, + type TurnRunnerDependencies, +} from "./turn-runner.js"; + +export { + ReferenceReplayVerifier, + ReplayVerificationRequest, + ReplayVerificationResult, +} from "./replay-verifier.js"; diff --git a/runtime/typescript/packages/core/src/harness/replay-verifier.ts b/runtime/typescript/packages/core/src/harness/replay-verifier.ts new file mode 100644 index 00000000..ee989f9c --- /dev/null +++ b/runtime/typescript/packages/core/src/harness/replay-verifier.ts @@ -0,0 +1,50 @@ +// Copyright (c) Microsoft. All rights reserved. + +import { + ReplayMismatch, + ReplayVerificationRequest, + ReplayVerificationResult, + type ReplayJournalRecord, +} from "../model/index.js"; + +export { ReplayVerificationRequest, ReplayVerificationResult }; + +function comparable(record: ReplayJournalRecord | undefined): string | undefined { + return record === undefined ? undefined : JSON.stringify(record.save()); +} + +/** Verifies an actual normalized replay journal against expected records. */ +export class ReferenceReplayVerifier { + verify(request: ReplayVerificationRequest): ReplayVerificationResult { + const expected = request.expected ?? []; + const actual = request.actual ?? []; + const max = Math.max(expected.length, actual.length); + const mismatches: ReplayMismatch[] = []; + + for (let index = 0; index < max; index += 1) { + const expectedRecord = expected[index]; + const actualRecord = actual[index]; + if (comparable(expectedRecord) !== comparable(actualRecord)) { + mismatches.push( + new ReplayMismatch({ + index, + expected: expectedRecord, + actual: actualRecord, + message: expectedRecord === undefined + ? "Unexpected extra replay record" + : actualRecord === undefined + ? "Missing replay record" + : "Replay record mismatch", + }), + ); + } + } + + return new ReplayVerificationResult({ + status: mismatches.length === 0 ? "passed" : "failed", + mismatches, + expectedCount: expected.length, + actualCount: actual.length, + }); + } +} diff --git a/runtime/typescript/packages/core/src/harness/turn-runner.ts b/runtime/typescript/packages/core/src/harness/turn-runner.ts new file mode 100644 index 00000000..a650c183 --- /dev/null +++ b/runtime/typescript/packages/core/src/harness/turn-runner.ts @@ -0,0 +1,242 @@ +// Copyright (c) Microsoft. All rights reserved. + +import { + Checkpoint, + HostToolRequest, + HostToolResult, + PermissionRequest, + RunTurnRequest, + RunTurnResult, + SessionEvent, + SessionSummary, + TurnEvent, + TurnModelRequest, + TurnModelResponse, + TurnOptions, + type CheckpointStore, + type EventJournalWriter, + type EventSink, + type HostToolExecutor, + type PermissionResolver, +} from "../model/index.js"; + +type JsonRecord = Record; + +export { RunTurnRequest, RunTurnResult, TurnModelRequest, TurnModelResponse }; + +export type TurnModelCallback = (request: TurnModelRequest) => TurnModelResponse | Promise; + +export interface TurnRunnerDependencies { + eventSink: EventSink; + journal: EventJournalWriter; + checkpointStore: CheckpointStore; + permissionResolver: PermissionResolver; + hostToolExecutor: HostToolExecutor; + invokeModel: TurnModelCallback; + now?: () => string; + nextId?: (prefix: string) => string; +} + +export class ReferenceTurnRunner { + private sequence = 0; + + constructor(private readonly dependencies: TurnRunnerDependencies) {} + + async run(request: RunTurnRequest): Promise { + const options = request.options ?? new TurnOptions(); + const inputs = request.inputs ?? {}; + const maxIterations = options.maxIterations ?? 10; + const checkpoints: Checkpoint[] = []; + const allToolResults: HostToolResult[] = []; + let pendingToolResults: HostToolResult[] = []; + let output: unknown; + let status: "success" | "error" = "success"; + let iterations = 0; + + this.recordSession("session_start", request.sessionId, request.turnId, { + sessionId: request.sessionId, + schemaVersion: "1", + }); + this.recordTurn("turn_start", request.turnId, 0, { + inputs, + maxIterations, + }); + + for (let iteration = 0; iteration < maxIterations; iteration += 1) { + iterations = iteration + 1; + this.recordTurn("llm_start", request.turnId, iteration, { + attempt: 0, + }); + + const modelResponse = await this.dependencies.invokeModel(new TurnModelRequest({ + sessionId: request.sessionId, + turnId: request.turnId, + iteration, + inputs, + options, + toolResults: pendingToolResults, + })); + + this.recordTurn("llm_complete", request.turnId, iteration, {}); + + const checkpoint = await this.saveCheckpoint(request.sessionId, request.turnId, iteration, modelResponse); + checkpoints.push(checkpoint); + + const toolRequests = modelResponse.toolRequests ?? []; + if (toolRequests.length === 0) { + output = modelResponse.output; + break; + } + + pendingToolResults = []; + for (const toolRequest of toolRequests) { + const toolResult = await this.resolveAndExecuteTool(request.turnId, iteration, toolRequest); + pendingToolResults.push(toolResult); + allToolResults.push(toolResult); + } + + this.recordTurn("messages_updated", request.turnId, iteration, { + toolResults: pendingToolResults.map((result) => result.save()), + }); + } + + if (output === undefined && pendingToolResults.length > 0) { + status = "error"; + output = { message: "Maximum turn iterations reached" }; + this.recordTurn("error", request.turnId, iterations, { + errorKind: "max_iterations", + message: "Maximum turn iterations reached", + }); + } + + this.recordTurn("turn_end", request.turnId, iterations, { + iterations, + status, + response: output, + }); + this.recordSession("session_end", request.sessionId, request.turnId, { + sessionId: request.sessionId, + status, + reason: "turn_complete", + }); + this.dependencies.journal.close( + new SessionSummary({ + sessionId: request.sessionId, + status, + turns: 1, + checkpoints: checkpoints.length, + }), + ); + + return new RunTurnResult({ + sessionId: request.sessionId, + turnId: request.turnId, + status, + output, + iterations, + toolResults: allToolResults, + checkpoints, + }); + } + + private async saveCheckpoint( + sessionId: string, + turnId: string, + iteration: number, + modelResponse: TurnModelResponse, + ): Promise { + const checkpoint = new Checkpoint({ + id: `${turnId}-checkpoint-${iteration}`, + sessionId, + turnId, + checkpointNumber: iteration + 1, + title: `Turn ${turnId} iteration ${iteration}`, + state: { + iteration, + output: modelResponse.output, + toolRequests: (modelResponse.toolRequests ?? []).map((toolRequest) => toolRequest.save()), + ...(modelResponse.checkpointState ?? {}), + }, + createdAt: this.timestamp(), + }); + const saved = await this.dependencies.checkpointStore.save(checkpoint); + this.recordSession("checkpoint_created", sessionId, turnId, { + checkpointId: saved.id, + checkpointNumber: saved.checkpointNumber, + }); + return saved; + } + + private async resolveAndExecuteTool( + turnId: string, + iteration: number, + toolRequest: HostToolRequest, + ): Promise { + const permission = new PermissionRequest({ + requestId: toolRequest.requestId ? `${toolRequest.requestId}-permission` : this.id("permission"), + toolCallId: toolRequest.toolCallId, + permission: "tool.execute", + target: toolRequest.toolName, + details: toolRequest.save(), + }); + + this.recordTurn("permission_requested", turnId, iteration, permission.save()); + const decision = await this.dependencies.permissionResolver.request(permission); + this.recordTurn("permission_completed", turnId, iteration, decision.save()); + + if (!decision.approved) { + return new HostToolResult({ + requestId: toolRequest.requestId, + toolCallId: toolRequest.toolCallId, + toolName: toolRequest.toolName, + success: false, + errorKind: "permission_denied", + result: { message: decision.reason ?? "Permission denied" }, + }); + } + + this.recordTurn("tool_execution_start", turnId, iteration, toolRequest.save()); + const result = await this.dependencies.hostToolExecutor.execute(toolRequest); + this.recordTurn("tool_execution_complete", turnId, iteration, result.save()); + this.recordTurn("tool_result", turnId, iteration, result.save()); + return result; + } + + private recordTurn(type: TurnEvent["type"], turnId: string, iteration: number, payload: JsonRecord): void { + const event = new TurnEvent({ + id: this.id("turn-event"), + type, + timestamp: this.timestamp(), + turnId, + iteration, + payload, + }); + this.dependencies.eventSink.emitTurn(event); + this.dependencies.journal.appendTurn(event); + } + + private recordSession(type: SessionEvent["type"], sessionId: string, turnId: string, payload: JsonRecord): void { + const event = new SessionEvent({ + id: this.id("session-event"), + type, + timestamp: this.timestamp(), + sessionId, + turnId, + payload, + }); + this.dependencies.eventSink.emitSession(event); + this.dependencies.journal.appendSession(event); + } + + private timestamp(): string { + return this.dependencies.now?.() ?? new Date().toISOString(); + } + + private id(prefix: string): string { + if (this.dependencies.nextId) { + return this.dependencies.nextId(prefix); + } + this.sequence += 1; + return `${prefix}-${this.sequence}`; + } +} diff --git a/runtime/typescript/packages/core/src/index.ts b/runtime/typescript/packages/core/src/index.ts index d33fae02..74c4da32 100644 --- a/runtime/typescript/packages/core/src/index.ts +++ b/runtime/typescript/packages/core/src/index.ts @@ -113,6 +113,29 @@ export { export { NunjucksRenderer, MustacheRenderer } from "./renderers/index.js"; export { PromptyChatParser } from "./parsers/index.js"; +// --------------------------------------------------------------------------- +// Harness reference adapters +// --------------------------------------------------------------------------- + +export { + AllowAllPermissionResolver, + CollectingEventSink, + DenyAllPermissionResolver, + FunctionHostToolExecutor, + InMemoryCheckpointStore, + JsonlEventJournalWriter, + ReferenceReplayVerifier, + ReferenceTurnRunner, + ReplayVerificationRequest, + ReplayVerificationResult, + RunTurnRequest, + RunTurnResult, + type TurnModelCallback, + TurnModelRequest, + TurnModelResponse, + type TurnRunnerDependencies, +} from "./harness/index.js"; + // --------------------------------------------------------------------------- // Tracing // --------------------------------------------------------------------------- @@ -166,6 +189,19 @@ export { PromptyTool, McpApprovalMode, Binding, + TurnEvent, + SessionEvent, + SessionSummary, + Checkpoint, + PermissionRequest, + PermissionDecision, + HostToolRequest, + HostToolResult, + type EventJournalWriter, + type EventSink, + type PermissionResolver, + type CheckpointStore, + type HostToolExecutor, } from "./model/index.js"; // Backward-compat aliases (will be removed in a future version) diff --git a/runtime/typescript/packages/core/src/model/agent/guardrail-result.ts b/runtime/typescript/packages/core/src/model/agent/guardrail-result.ts index be9c392e..7e43c7db 100644 --- a/runtime/typescript/packages/core/src/model/agent/guardrail-result.ts +++ b/runtime/typescript/packages/core/src/model/agent/guardrail-result.ts @@ -1,3 +1,4 @@ +// // Copyright (c) Microsoft. All rights reserved. // WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. diff --git a/runtime/typescript/packages/core/src/model/agent/index.ts b/runtime/typescript/packages/core/src/model/agent/index.ts index ffb7c225..3da1c268 100644 --- a/runtime/typescript/packages/core/src/model/agent/index.ts +++ b/runtime/typescript/packages/core/src/model/agent/index.ts @@ -1,3 +1,4 @@ +// // Copyright (c) Microsoft. All rights reserved. // WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. diff --git a/runtime/typescript/packages/core/src/model/agent/prompty.ts b/runtime/typescript/packages/core/src/model/agent/prompty.ts index f6de1772..cb750a68 100644 --- a/runtime/typescript/packages/core/src/model/agent/prompty.ts +++ b/runtime/typescript/packages/core/src/model/agent/prompty.ts @@ -1,3 +1,4 @@ +// // Copyright (c) Microsoft. All rights reserved. // WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. diff --git a/runtime/typescript/packages/core/src/model/connection/connection.ts b/runtime/typescript/packages/core/src/model/connection/connection.ts index 79085fb9..cbe015cf 100644 --- a/runtime/typescript/packages/core/src/model/connection/connection.ts +++ b/runtime/typescript/packages/core/src/model/connection/connection.ts @@ -1,3 +1,4 @@ +// // Copyright (c) Microsoft. All rights reserved. // WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. diff --git a/runtime/typescript/packages/core/src/model/connection/index.ts b/runtime/typescript/packages/core/src/model/connection/index.ts index 500f748a..2c312a1a 100644 --- a/runtime/typescript/packages/core/src/model/connection/index.ts +++ b/runtime/typescript/packages/core/src/model/connection/index.ts @@ -1,3 +1,4 @@ +// // Copyright (c) Microsoft. All rights reserved. // WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. diff --git a/runtime/typescript/packages/core/src/model/context.ts b/runtime/typescript/packages/core/src/model/context.ts index 639bb708..8307e52b 100644 --- a/runtime/typescript/packages/core/src/model/context.ts +++ b/runtime/typescript/packages/core/src/model/context.ts @@ -1,3 +1,4 @@ +// // Copyright (c) Microsoft. All rights reserved. // WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. diff --git a/runtime/typescript/packages/core/src/model/conversation/content-part.ts b/runtime/typescript/packages/core/src/model/conversation/content-part.ts index 4d448638..273eeada 100644 --- a/runtime/typescript/packages/core/src/model/conversation/content-part.ts +++ b/runtime/typescript/packages/core/src/model/conversation/content-part.ts @@ -1,3 +1,4 @@ +// // Copyright (c) Microsoft. All rights reserved. // WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. diff --git a/runtime/typescript/packages/core/src/model/conversation/index.ts b/runtime/typescript/packages/core/src/model/conversation/index.ts index 9443ad17..98832d99 100644 --- a/runtime/typescript/packages/core/src/model/conversation/index.ts +++ b/runtime/typescript/packages/core/src/model/conversation/index.ts @@ -1,3 +1,4 @@ +// // Copyright (c) Microsoft. All rights reserved. // WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. diff --git a/runtime/typescript/packages/core/src/model/conversation/message.ts b/runtime/typescript/packages/core/src/model/conversation/message.ts index 30ae67de..fc62988a 100644 --- a/runtime/typescript/packages/core/src/model/conversation/message.ts +++ b/runtime/typescript/packages/core/src/model/conversation/message.ts @@ -1,3 +1,4 @@ +// // Copyright (c) Microsoft. All rights reserved. // WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. diff --git a/runtime/typescript/packages/core/src/model/conversation/thread-marker.ts b/runtime/typescript/packages/core/src/model/conversation/thread-marker.ts index d52164b0..47d739d6 100644 --- a/runtime/typescript/packages/core/src/model/conversation/thread-marker.ts +++ b/runtime/typescript/packages/core/src/model/conversation/thread-marker.ts @@ -1,3 +1,4 @@ +// // Copyright (c) Microsoft. All rights reserved. // WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. diff --git a/runtime/typescript/packages/core/src/model/conversation/tool-call.ts b/runtime/typescript/packages/core/src/model/conversation/tool-call.ts index 385e85d3..c792f5a5 100644 --- a/runtime/typescript/packages/core/src/model/conversation/tool-call.ts +++ b/runtime/typescript/packages/core/src/model/conversation/tool-call.ts @@ -1,3 +1,4 @@ +// // Copyright (c) Microsoft. All rights reserved. // WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. diff --git a/runtime/typescript/packages/core/src/model/conversation/tool-result.ts b/runtime/typescript/packages/core/src/model/conversation/tool-result.ts index ad5d4fa1..6d6bc63b 100644 --- a/runtime/typescript/packages/core/src/model/conversation/tool-result.ts +++ b/runtime/typescript/packages/core/src/model/conversation/tool-result.ts @@ -1,16 +1,35 @@ +// // Copyright (c) Microsoft. All rights reserved. // WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. import { LoadContext, SaveContext } from "../context"; import { ContentPart, TextPart } from "./content-part"; +export type ToolResultStatus = "success" | "error" | "cancelled" | "timeout"; + export class ToolResult { static readonly shorthandProperty: string | undefined = undefined; parts: ContentPart[] = []; + status?: ToolResultStatus | undefined; + errorKind?: string | undefined; + errorMessage?: string | undefined; + durationMs?: number | undefined; constructor(init?: Partial) { this.parts = init?.parts ?? []; + if (init?.status !== undefined) { + this.status = init.status; + } + if (init?.errorKind !== undefined) { + this.errorKind = init.errorKind; + } + if (init?.errorMessage !== undefined) { + this.errorMessage = init.errorMessage; + } + if (init?.durationMs !== undefined) { + this.durationMs = init.durationMs; + } } //#region Load Methods @@ -31,6 +50,18 @@ export class ToolResult { context, ); } + if (data["status"] !== undefined && data["status"] !== null) { + instance.status = String(data["status"]) as ToolResultStatus; + } + if (data["errorKind"] !== undefined && data["errorKind"] !== null) { + instance.errorKind = String(data["errorKind"]); + } + if (data["errorMessage"] !== undefined && data["errorMessage"] !== null) { + instance.errorMessage = String(data["errorMessage"]); + } + if (data["durationMs"] !== undefined && data["durationMs"] !== null) { + instance.durationMs = Number(data["durationMs"]); + } if (context) { return context.processOutput(instance) as ToolResult; @@ -86,6 +117,18 @@ export class ToolResult { if (obj.parts !== undefined && obj.parts !== null) { result["parts"] = ToolResult.saveParts(obj.parts, context); } + if (obj.status !== undefined && obj.status !== null) { + result["status"] = obj.status; + } + if (obj.errorKind !== undefined && obj.errorKind !== null) { + result["errorKind"] = obj.errorKind; + } + if (obj.errorMessage !== undefined && obj.errorMessage !== null) { + result["errorMessage"] = obj.errorMessage; + } + if (obj.durationMs !== undefined && obj.durationMs !== null) { + result["durationMs"] = obj.durationMs; + } if (context) { return context.processDict(result); diff --git a/runtime/typescript/packages/core/src/model/core/file-not-found-error.ts b/runtime/typescript/packages/core/src/model/core/file-not-found-error.ts index 87efa82e..b2e91f10 100644 --- a/runtime/typescript/packages/core/src/model/core/file-not-found-error.ts +++ b/runtime/typescript/packages/core/src/model/core/file-not-found-error.ts @@ -1,3 +1,4 @@ +// // Copyright (c) Microsoft. All rights reserved. // WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. diff --git a/runtime/typescript/packages/core/src/model/core/index.ts b/runtime/typescript/packages/core/src/model/core/index.ts index 59175c21..e88baa26 100644 --- a/runtime/typescript/packages/core/src/model/core/index.ts +++ b/runtime/typescript/packages/core/src/model/core/index.ts @@ -1,3 +1,4 @@ +// // Copyright (c) Microsoft. All rights reserved. // WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. diff --git a/runtime/typescript/packages/core/src/model/core/invoker-error.ts b/runtime/typescript/packages/core/src/model/core/invoker-error.ts index 31680119..b4880b8b 100644 --- a/runtime/typescript/packages/core/src/model/core/invoker-error.ts +++ b/runtime/typescript/packages/core/src/model/core/invoker-error.ts @@ -1,3 +1,4 @@ +// // Copyright (c) Microsoft. All rights reserved. // WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. diff --git a/runtime/typescript/packages/core/src/model/core/property.ts b/runtime/typescript/packages/core/src/model/core/property.ts index 911893b0..e8a26334 100644 --- a/runtime/typescript/packages/core/src/model/core/property.ts +++ b/runtime/typescript/packages/core/src/model/core/property.ts @@ -1,3 +1,4 @@ +// // Copyright (c) Microsoft. All rights reserved. // WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. diff --git a/runtime/typescript/packages/core/src/model/core/validation-error.ts b/runtime/typescript/packages/core/src/model/core/validation-error.ts index 87f5ea6c..6717a801 100644 --- a/runtime/typescript/packages/core/src/model/core/validation-error.ts +++ b/runtime/typescript/packages/core/src/model/core/validation-error.ts @@ -1,3 +1,4 @@ +// // Copyright (c) Microsoft. All rights reserved. // WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. diff --git a/runtime/typescript/packages/core/src/model/core/validation-result.ts b/runtime/typescript/packages/core/src/model/core/validation-result.ts index a9f64878..757c4d86 100644 --- a/runtime/typescript/packages/core/src/model/core/validation-result.ts +++ b/runtime/typescript/packages/core/src/model/core/validation-result.ts @@ -1,3 +1,4 @@ +// // Copyright (c) Microsoft. All rights reserved. // WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. diff --git a/runtime/typescript/packages/core/src/model/events/checkpoint.ts b/runtime/typescript/packages/core/src/model/events/checkpoint.ts new file mode 100644 index 00000000..fbf80375 --- /dev/null +++ b/runtime/typescript/packages/core/src/model/events/checkpoint.ts @@ -0,0 +1,189 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. + +import { LoadContext, SaveContext } from "../context"; +import { RedactionMetadata } from "./redaction-metadata"; + +export class Checkpoint { + static readonly shorthandProperty: string | undefined = undefined; + + id?: string | undefined; + sessionId?: string | undefined; + turnId?: string | undefined; + checkpointNumber?: number | undefined; + title: string = ""; + overview?: string | undefined; + state?: Record | undefined; + summary?: string | undefined; + metadata?: Record | undefined; + createdAt?: string | undefined; + redaction?: RedactionMetadata | undefined; + + constructor(init?: Partial) { + if (init?.id !== undefined) { + this.id = init.id; + } + if (init?.sessionId !== undefined) { + this.sessionId = init.sessionId; + } + if (init?.turnId !== undefined) { + this.turnId = init.turnId; + } + if (init?.checkpointNumber !== undefined) { + this.checkpointNumber = init.checkpointNumber; + } + this.title = init?.title ?? ""; + if (init?.overview !== undefined) { + this.overview = init.overview; + } + if (init?.state !== undefined) { + this.state = init.state; + } + if (init?.summary !== undefined) { + this.summary = init.summary; + } + if (init?.metadata !== undefined) { + this.metadata = init.metadata; + } + if (init?.createdAt !== undefined) { + this.createdAt = init.createdAt; + } + if (init?.redaction !== undefined) { + this.redaction = init.redaction; + } + } + + //#region Load Methods + + static load( + data: Record, + context?: LoadContext, + ): Checkpoint { + if (context) { + data = context.processInput(data) as Record; + } + + const instance = new Checkpoint(); + + if (data["id"] !== undefined && data["id"] !== null) { + instance.id = String(data["id"]); + } + if (data["sessionId"] !== undefined && data["sessionId"] !== null) { + instance.sessionId = String(data["sessionId"]); + } + if (data["turnId"] !== undefined && data["turnId"] !== null) { + instance.turnId = String(data["turnId"]); + } + if ( + data["checkpointNumber"] !== undefined && + data["checkpointNumber"] !== null + ) { + instance.checkpointNumber = Number(data["checkpointNumber"]); + } + if (data["title"] !== undefined && data["title"] !== null) { + instance.title = String(data["title"]); + } + if (data["overview"] !== undefined && data["overview"] !== null) { + instance.overview = String(data["overview"]); + } + if (data["state"] !== undefined && data["state"] !== null) { + instance.state = data["state"] as Record; + } + if (data["summary"] !== undefined && data["summary"] !== null) { + instance.summary = String(data["summary"]); + } + if (data["metadata"] !== undefined && data["metadata"] !== null) { + instance.metadata = data["metadata"] as Record; + } + if (data["createdAt"] !== undefined && data["createdAt"] !== null) { + instance.createdAt = String(data["createdAt"]); + } + if (data["redaction"] !== undefined && data["redaction"] !== null) { + instance.redaction = RedactionMetadata.load( + data["redaction"] as Record, + context, + ); + } + + if (context) { + return context.processOutput(instance) as Checkpoint; + } + return instance; + } + + //#endregion + + //#region Save Methods + + save(context?: SaveContext): Record { + let obj: this = this; + if (context) { + obj = context.processObject(obj) as this; + } + + const result: Record = {}; + + if (obj.id !== undefined && obj.id !== null) { + result["id"] = obj.id; + } + if (obj.sessionId !== undefined && obj.sessionId !== null) { + result["sessionId"] = obj.sessionId; + } + if (obj.turnId !== undefined && obj.turnId !== null) { + result["turnId"] = obj.turnId; + } + if (obj.checkpointNumber !== undefined && obj.checkpointNumber !== null) { + result["checkpointNumber"] = obj.checkpointNumber; + } + if (obj.title !== undefined && obj.title !== null) { + result["title"] = obj.title; + } + if (obj.overview !== undefined && obj.overview !== null) { + result["overview"] = obj.overview; + } + if (obj.state !== undefined && obj.state !== null) { + result["state"] = obj.state; + } + if (obj.summary !== undefined && obj.summary !== null) { + result["summary"] = obj.summary; + } + if (obj.metadata !== undefined && obj.metadata !== null) { + result["metadata"] = obj.metadata; + } + if (obj.createdAt !== undefined && obj.createdAt !== null) { + result["createdAt"] = obj.createdAt; + } + if (obj.redaction !== undefined && obj.redaction !== null) { + result["redaction"] = obj.redaction.save(context); + } + + if (context) { + return context.processDict(result); + } + return result; + } + + toYaml(context?: SaveContext): string { + context = context ?? new SaveContext(); + return context.toYaml(this.save(context)); + } + + toJson(context?: SaveContext, indent: number = 2): string { + context = context ?? new SaveContext(); + return context.toJson(this.save(context), indent); + } + + static fromJson(json: string, context?: LoadContext): Checkpoint { + const data = JSON.parse(json); + return Checkpoint.load(data as Record, context); + } + + static fromYaml(yaml: string, context?: LoadContext): Checkpoint { + const { parse } = require("yaml"); + const data = parse(yaml); + return Checkpoint.load(data as Record, context); + } + + //#endregion +} diff --git a/runtime/typescript/packages/core/src/model/events/compaction-complete-payload.ts b/runtime/typescript/packages/core/src/model/events/compaction-complete-payload.ts index 245dbb51..0eca9fa3 100644 --- a/runtime/typescript/packages/core/src/model/events/compaction-complete-payload.ts +++ b/runtime/typescript/packages/core/src/model/events/compaction-complete-payload.ts @@ -1,3 +1,4 @@ +// // Copyright (c) Microsoft. All rights reserved. // WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. @@ -8,10 +9,14 @@ export class CompactionCompletePayload { removed: number = 0; remaining: number = 0; + summaryLength?: number | undefined; constructor(init?: Partial) { this.removed = init?.removed ?? 0; this.remaining = init?.remaining ?? 0; + if (init?.summaryLength !== undefined) { + this.summaryLength = init.summaryLength; + } } //#region Load Methods @@ -32,6 +37,9 @@ export class CompactionCompletePayload { if (data["remaining"] !== undefined && data["remaining"] !== null) { instance.remaining = Number(data["remaining"]); } + if (data["summaryLength"] !== undefined && data["summaryLength"] !== null) { + instance.summaryLength = Number(data["summaryLength"]); + } if (context) { return context.processOutput(instance) as CompactionCompletePayload; @@ -57,6 +65,9 @@ export class CompactionCompletePayload { if (obj.remaining !== undefined && obj.remaining !== null) { result["remaining"] = obj.remaining; } + if (obj.summaryLength !== undefined && obj.summaryLength !== null) { + result["summaryLength"] = obj.summaryLength; + } if (context) { return context.processDict(result); diff --git a/runtime/typescript/packages/core/src/model/events/compaction-failed-payload.ts b/runtime/typescript/packages/core/src/model/events/compaction-failed-payload.ts index 5baa16ec..fa6091ec 100644 --- a/runtime/typescript/packages/core/src/model/events/compaction-failed-payload.ts +++ b/runtime/typescript/packages/core/src/model/events/compaction-failed-payload.ts @@ -1,3 +1,4 @@ +// // Copyright (c) Microsoft. All rights reserved. // WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. diff --git a/runtime/typescript/packages/core/src/model/events/compaction-start-payload.ts b/runtime/typescript/packages/core/src/model/events/compaction-start-payload.ts new file mode 100644 index 00000000..136dfbeb --- /dev/null +++ b/runtime/typescript/packages/core/src/model/events/compaction-start-payload.ts @@ -0,0 +1,88 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. + +import { LoadContext, SaveContext } from "../context"; + +export class CompactionStartPayload { + static readonly shorthandProperty: string | undefined = undefined; + + droppedCount: number = 0; + + constructor(init?: Partial) { + this.droppedCount = init?.droppedCount ?? 0; + } + + //#region Load Methods + + static load( + data: Record, + context?: LoadContext, + ): CompactionStartPayload { + if (context) { + data = context.processInput(data) as Record; + } + + const instance = new CompactionStartPayload(); + + if (data["droppedCount"] !== undefined && data["droppedCount"] !== null) { + instance.droppedCount = Number(data["droppedCount"]); + } + + if (context) { + return context.processOutput(instance) as CompactionStartPayload; + } + return instance; + } + + //#endregion + + //#region Save Methods + + save(context?: SaveContext): Record { + let obj: this = this; + if (context) { + obj = context.processObject(obj) as this; + } + + const result: Record = {}; + + if (obj.droppedCount !== undefined && obj.droppedCount !== null) { + result["droppedCount"] = obj.droppedCount; + } + + if (context) { + return context.processDict(result); + } + return result; + } + + toYaml(context?: SaveContext): string { + context = context ?? new SaveContext(); + return context.toYaml(this.save(context)); + } + + toJson(context?: SaveContext, indent: number = 2): string { + context = context ?? new SaveContext(); + return context.toJson(this.save(context), indent); + } + + static fromJson(json: string, context?: LoadContext): CompactionStartPayload { + const data = JSON.parse(json); + return CompactionStartPayload.load( + data as Record, + context, + ); + } + + static fromYaml(yaml: string, context?: LoadContext): CompactionStartPayload { + const { parse } = require("yaml"); + const data = parse(yaml); + return CompactionStartPayload.load( + data as Record, + context, + ); + } + + //#endregion +} diff --git a/runtime/typescript/packages/core/src/model/events/done-event-payload.ts b/runtime/typescript/packages/core/src/model/events/done-event-payload.ts index e83f047f..4e3f3dec 100644 --- a/runtime/typescript/packages/core/src/model/events/done-event-payload.ts +++ b/runtime/typescript/packages/core/src/model/events/done-event-payload.ts @@ -1,3 +1,4 @@ +// // Copyright (c) Microsoft. All rights reserved. // WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. @@ -7,7 +8,7 @@ import { Message } from "../conversation/message"; export class DoneEventPayload { static readonly shorthandProperty: string | undefined = undefined; - response: string = ""; + response: unknown; messages: Message[] = []; constructor(init?: Partial) { @@ -28,7 +29,7 @@ export class DoneEventPayload { const instance = new DoneEventPayload(); if (data["response"] !== undefined && data["response"] !== null) { - instance.response = String(data["response"]); + instance.response = data["response"] as unknown; } if (data["messages"] !== undefined && data["messages"] !== null) { instance.messages = DoneEventPayload.loadMessages( diff --git a/runtime/typescript/packages/core/src/model/events/error-event-payload.ts b/runtime/typescript/packages/core/src/model/events/error-event-payload.ts index 8a83802f..65b73039 100644 --- a/runtime/typescript/packages/core/src/model/events/error-event-payload.ts +++ b/runtime/typescript/packages/core/src/model/events/error-event-payload.ts @@ -1,3 +1,4 @@ +// // Copyright (c) Microsoft. All rights reserved. // WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. @@ -7,9 +8,17 @@ export class ErrorEventPayload { static readonly shorthandProperty: string | undefined = undefined; message: string = ""; + errorKind?: string | undefined; + phase?: string | undefined; constructor(init?: Partial) { this.message = init?.message ?? ""; + if (init?.errorKind !== undefined) { + this.errorKind = init.errorKind; + } + if (init?.phase !== undefined) { + this.phase = init.phase; + } } //#region Load Methods @@ -27,6 +36,12 @@ export class ErrorEventPayload { if (data["message"] !== undefined && data["message"] !== null) { instance.message = String(data["message"]); } + if (data["errorKind"] !== undefined && data["errorKind"] !== null) { + instance.errorKind = String(data["errorKind"]); + } + if (data["phase"] !== undefined && data["phase"] !== null) { + instance.phase = String(data["phase"]); + } if (context) { return context.processOutput(instance) as ErrorEventPayload; @@ -49,6 +64,12 @@ export class ErrorEventPayload { if (obj.message !== undefined && obj.message !== null) { result["message"] = obj.message; } + if (obj.errorKind !== undefined && obj.errorKind !== null) { + result["errorKind"] = obj.errorKind; + } + if (obj.phase !== undefined && obj.phase !== null) { + result["phase"] = obj.phase; + } if (context) { return context.processDict(result); diff --git a/runtime/typescript/packages/core/src/model/events/harness-context.ts b/runtime/typescript/packages/core/src/model/events/harness-context.ts new file mode 100644 index 00000000..6bb6cb98 --- /dev/null +++ b/runtime/typescript/packages/core/src/model/events/harness-context.ts @@ -0,0 +1,104 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. + +import { LoadContext, SaveContext } from "../context"; + +export class HarnessContext { + static readonly shorthandProperty: string | undefined = undefined; + + cwd?: string | undefined; + gitRoot?: string | undefined; + metadata?: Record | undefined; + + constructor(init?: Partial) { + if (init?.cwd !== undefined) { + this.cwd = init.cwd; + } + if (init?.gitRoot !== undefined) { + this.gitRoot = init.gitRoot; + } + if (init?.metadata !== undefined) { + this.metadata = init.metadata; + } + } + + //#region Load Methods + + static load( + data: Record, + context?: LoadContext, + ): HarnessContext { + if (context) { + data = context.processInput(data) as Record; + } + + const instance = new HarnessContext(); + + if (data["cwd"] !== undefined && data["cwd"] !== null) { + instance.cwd = String(data["cwd"]); + } + if (data["gitRoot"] !== undefined && data["gitRoot"] !== null) { + instance.gitRoot = String(data["gitRoot"]); + } + if (data["metadata"] !== undefined && data["metadata"] !== null) { + instance.metadata = data["metadata"] as Record; + } + + if (context) { + return context.processOutput(instance) as HarnessContext; + } + return instance; + } + + //#endregion + + //#region Save Methods + + save(context?: SaveContext): Record { + let obj: this = this; + if (context) { + obj = context.processObject(obj) as this; + } + + const result: Record = {}; + + if (obj.cwd !== undefined && obj.cwd !== null) { + result["cwd"] = obj.cwd; + } + if (obj.gitRoot !== undefined && obj.gitRoot !== null) { + result["gitRoot"] = obj.gitRoot; + } + if (obj.metadata !== undefined && obj.metadata !== null) { + result["metadata"] = obj.metadata; + } + + if (context) { + return context.processDict(result); + } + return result; + } + + toYaml(context?: SaveContext): string { + context = context ?? new SaveContext(); + return context.toYaml(this.save(context)); + } + + toJson(context?: SaveContext, indent: number = 2): string { + context = context ?? new SaveContext(); + return context.toJson(this.save(context), indent); + } + + static fromJson(json: string, context?: LoadContext): HarnessContext { + const data = JSON.parse(json); + return HarnessContext.load(data as Record, context); + } + + static fromYaml(yaml: string, context?: LoadContext): HarnessContext { + const { parse } = require("yaml"); + const data = parse(yaml); + return HarnessContext.load(data as Record, context); + } + + //#endregion +} diff --git a/runtime/typescript/packages/core/src/model/events/hook-end-payload.ts b/runtime/typescript/packages/core/src/model/events/hook-end-payload.ts new file mode 100644 index 00000000..5091b533 --- /dev/null +++ b/runtime/typescript/packages/core/src/model/events/hook-end-payload.ts @@ -0,0 +1,157 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. + +import { LoadContext, SaveContext } from "../context"; +import { RedactionMetadata } from "./redaction-metadata"; + +export type HookEndScope = "turn" | "session"; + +export class HookEndPayload { + static readonly shorthandProperty: string | undefined = undefined; + + hookInvocationId: string = ""; + hookType: string = ""; + scope?: HookEndScope | undefined; + success: boolean = false; + output?: Record | undefined; + durationMs?: number | undefined; + error?: string | undefined; + redaction?: RedactionMetadata | undefined; + + constructor(init?: Partial) { + this.hookInvocationId = init?.hookInvocationId ?? ""; + this.hookType = init?.hookType ?? ""; + if (init?.scope !== undefined) { + this.scope = init.scope; + } + this.success = init?.success ?? false; + if (init?.output !== undefined) { + this.output = init.output; + } + if (init?.durationMs !== undefined) { + this.durationMs = init.durationMs; + } + if (init?.error !== undefined) { + this.error = init.error; + } + if (init?.redaction !== undefined) { + this.redaction = init.redaction; + } + } + + //#region Load Methods + + static load( + data: Record, + context?: LoadContext, + ): HookEndPayload { + if (context) { + data = context.processInput(data) as Record; + } + + const instance = new HookEndPayload(); + + if ( + data["hookInvocationId"] !== undefined && + data["hookInvocationId"] !== null + ) { + instance.hookInvocationId = String(data["hookInvocationId"]); + } + if (data["hookType"] !== undefined && data["hookType"] !== null) { + instance.hookType = String(data["hookType"]); + } + if (data["scope"] !== undefined && data["scope"] !== null) { + instance.scope = String(data["scope"]) as HookEndScope; + } + if (data["success"] !== undefined && data["success"] !== null) { + instance.success = Boolean(data["success"]); + } + if (data["output"] !== undefined && data["output"] !== null) { + instance.output = data["output"] as Record; + } + if (data["durationMs"] !== undefined && data["durationMs"] !== null) { + instance.durationMs = Number(data["durationMs"]); + } + if (data["error"] !== undefined && data["error"] !== null) { + instance.error = String(data["error"]); + } + if (data["redaction"] !== undefined && data["redaction"] !== null) { + instance.redaction = RedactionMetadata.load( + data["redaction"] as Record, + context, + ); + } + + if (context) { + return context.processOutput(instance) as HookEndPayload; + } + return instance; + } + + //#endregion + + //#region Save Methods + + save(context?: SaveContext): Record { + let obj: this = this; + if (context) { + obj = context.processObject(obj) as this; + } + + const result: Record = {}; + + if (obj.hookInvocationId !== undefined && obj.hookInvocationId !== null) { + result["hookInvocationId"] = obj.hookInvocationId; + } + if (obj.hookType !== undefined && obj.hookType !== null) { + result["hookType"] = obj.hookType; + } + if (obj.scope !== undefined && obj.scope !== null) { + result["scope"] = obj.scope; + } + if (obj.success !== undefined && obj.success !== null) { + result["success"] = obj.success; + } + if (obj.output !== undefined && obj.output !== null) { + result["output"] = obj.output; + } + if (obj.durationMs !== undefined && obj.durationMs !== null) { + result["durationMs"] = obj.durationMs; + } + if (obj.error !== undefined && obj.error !== null) { + result["error"] = obj.error; + } + if (obj.redaction !== undefined && obj.redaction !== null) { + result["redaction"] = obj.redaction.save(context); + } + + if (context) { + return context.processDict(result); + } + return result; + } + + toYaml(context?: SaveContext): string { + context = context ?? new SaveContext(); + return context.toYaml(this.save(context)); + } + + toJson(context?: SaveContext, indent: number = 2): string { + context = context ?? new SaveContext(); + return context.toJson(this.save(context), indent); + } + + static fromJson(json: string, context?: LoadContext): HookEndPayload { + const data = JSON.parse(json); + return HookEndPayload.load(data as Record, context); + } + + static fromYaml(yaml: string, context?: LoadContext): HookEndPayload { + const { parse } = require("yaml"); + const data = parse(yaml); + return HookEndPayload.load(data as Record, context); + } + + //#endregion +} diff --git a/runtime/typescript/packages/core/src/model/events/hook-start-payload.ts b/runtime/typescript/packages/core/src/model/events/hook-start-payload.ts new file mode 100644 index 00000000..e5cc1118 --- /dev/null +++ b/runtime/typescript/packages/core/src/model/events/hook-start-payload.ts @@ -0,0 +1,129 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. + +import { LoadContext, SaveContext } from "../context"; +import { RedactionMetadata } from "./redaction-metadata"; + +export type HookStartScope = "turn" | "session"; + +export class HookStartPayload { + static readonly shorthandProperty: string | undefined = undefined; + + hookInvocationId: string = ""; + hookType: string = ""; + scope?: HookStartScope | undefined; + input?: Record | undefined; + redaction?: RedactionMetadata | undefined; + + constructor(init?: Partial) { + this.hookInvocationId = init?.hookInvocationId ?? ""; + this.hookType = init?.hookType ?? ""; + if (init?.scope !== undefined) { + this.scope = init.scope; + } + if (init?.input !== undefined) { + this.input = init.input; + } + if (init?.redaction !== undefined) { + this.redaction = init.redaction; + } + } + + //#region Load Methods + + static load( + data: Record, + context?: LoadContext, + ): HookStartPayload { + if (context) { + data = context.processInput(data) as Record; + } + + const instance = new HookStartPayload(); + + if ( + data["hookInvocationId"] !== undefined && + data["hookInvocationId"] !== null + ) { + instance.hookInvocationId = String(data["hookInvocationId"]); + } + if (data["hookType"] !== undefined && data["hookType"] !== null) { + instance.hookType = String(data["hookType"]); + } + if (data["scope"] !== undefined && data["scope"] !== null) { + instance.scope = String(data["scope"]) as HookStartScope; + } + if (data["input"] !== undefined && data["input"] !== null) { + instance.input = data["input"] as Record; + } + if (data["redaction"] !== undefined && data["redaction"] !== null) { + instance.redaction = RedactionMetadata.load( + data["redaction"] as Record, + context, + ); + } + + if (context) { + return context.processOutput(instance) as HookStartPayload; + } + return instance; + } + + //#endregion + + //#region Save Methods + + save(context?: SaveContext): Record { + let obj: this = this; + if (context) { + obj = context.processObject(obj) as this; + } + + const result: Record = {}; + + if (obj.hookInvocationId !== undefined && obj.hookInvocationId !== null) { + result["hookInvocationId"] = obj.hookInvocationId; + } + if (obj.hookType !== undefined && obj.hookType !== null) { + result["hookType"] = obj.hookType; + } + if (obj.scope !== undefined && obj.scope !== null) { + result["scope"] = obj.scope; + } + if (obj.input !== undefined && obj.input !== null) { + result["input"] = obj.input; + } + if (obj.redaction !== undefined && obj.redaction !== null) { + result["redaction"] = obj.redaction.save(context); + } + + if (context) { + return context.processDict(result); + } + return result; + } + + toYaml(context?: SaveContext): string { + context = context ?? new SaveContext(); + return context.toYaml(this.save(context)); + } + + toJson(context?: SaveContext, indent: number = 2): string { + context = context ?? new SaveContext(); + return context.toJson(this.save(context), indent); + } + + static fromJson(json: string, context?: LoadContext): HookStartPayload { + const data = JSON.parse(json); + return HookStartPayload.load(data as Record, context); + } + + static fromYaml(yaml: string, context?: LoadContext): HookStartPayload { + const { parse } = require("yaml"); + const data = parse(yaml); + return HookStartPayload.load(data as Record, context); + } + + //#endregion +} diff --git a/runtime/typescript/packages/core/src/model/events/host-tool-request.ts b/runtime/typescript/packages/core/src/model/events/host-tool-request.ts new file mode 100644 index 00000000..16edbef2 --- /dev/null +++ b/runtime/typescript/packages/core/src/model/events/host-tool-request.ts @@ -0,0 +1,125 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. + +import { LoadContext, SaveContext } from "../context"; + +export class HostToolRequest { + static readonly shorthandProperty: string | undefined = undefined; + + requestId?: string | undefined; + toolCallId?: string | undefined; + toolName: string = ""; + arguments?: Record | undefined; + workingDirectory?: string | undefined; + + constructor(init?: Partial) { + if (init?.requestId !== undefined) { + this.requestId = init.requestId; + } + if (init?.toolCallId !== undefined) { + this.toolCallId = init.toolCallId; + } + this.toolName = init?.toolName ?? ""; + if (init?.arguments !== undefined) { + this.arguments = init.arguments; + } + if (init?.workingDirectory !== undefined) { + this.workingDirectory = init.workingDirectory; + } + } + + //#region Load Methods + + static load( + data: Record, + context?: LoadContext, + ): HostToolRequest { + if (context) { + data = context.processInput(data) as Record; + } + + const instance = new HostToolRequest(); + + if (data["requestId"] !== undefined && data["requestId"] !== null) { + instance.requestId = String(data["requestId"]); + } + if (data["toolCallId"] !== undefined && data["toolCallId"] !== null) { + instance.toolCallId = String(data["toolCallId"]); + } + if (data["toolName"] !== undefined && data["toolName"] !== null) { + instance.toolName = String(data["toolName"]); + } + if (data["arguments"] !== undefined && data["arguments"] !== null) { + instance.arguments = data["arguments"] as Record; + } + if ( + data["workingDirectory"] !== undefined && + data["workingDirectory"] !== null + ) { + instance.workingDirectory = String(data["workingDirectory"]); + } + + if (context) { + return context.processOutput(instance) as HostToolRequest; + } + return instance; + } + + //#endregion + + //#region Save Methods + + save(context?: SaveContext): Record { + let obj: this = this; + if (context) { + obj = context.processObject(obj) as this; + } + + const result: Record = {}; + + if (obj.requestId !== undefined && obj.requestId !== null) { + result["requestId"] = obj.requestId; + } + if (obj.toolCallId !== undefined && obj.toolCallId !== null) { + result["toolCallId"] = obj.toolCallId; + } + if (obj.toolName !== undefined && obj.toolName !== null) { + result["toolName"] = obj.toolName; + } + if (obj.arguments !== undefined && obj.arguments !== null) { + result["arguments"] = obj.arguments; + } + if (obj.workingDirectory !== undefined && obj.workingDirectory !== null) { + result["workingDirectory"] = obj.workingDirectory; + } + + if (context) { + return context.processDict(result); + } + return result; + } + + toYaml(context?: SaveContext): string { + context = context ?? new SaveContext(); + return context.toYaml(this.save(context)); + } + + toJson(context?: SaveContext, indent: number = 2): string { + context = context ?? new SaveContext(); + return context.toJson(this.save(context), indent); + } + + static fromJson(json: string, context?: LoadContext): HostToolRequest { + const data = JSON.parse(json); + return HostToolRequest.load(data as Record, context); + } + + static fromYaml(yaml: string, context?: LoadContext): HostToolRequest { + const { parse } = require("yaml"); + const data = parse(yaml); + return HostToolRequest.load(data as Record, context); + } + + //#endregion +} diff --git a/runtime/typescript/packages/core/src/model/events/host-tool-result.ts b/runtime/typescript/packages/core/src/model/events/host-tool-result.ts new file mode 100644 index 00000000..1a5628e4 --- /dev/null +++ b/runtime/typescript/packages/core/src/model/events/host-tool-result.ts @@ -0,0 +1,160 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. + +import { LoadContext, SaveContext } from "../context"; + +export class HostToolResult { + static readonly shorthandProperty: string | undefined = undefined; + + requestId?: string | undefined; + toolCallId?: string | undefined; + toolName: string = ""; + success: boolean = false; + result?: unknown | undefined; + exitCode?: number | undefined; + durationMs?: number | undefined; + errorKind?: string | undefined; + telemetry?: Record | undefined; + + constructor(init?: Partial) { + if (init?.requestId !== undefined) { + this.requestId = init.requestId; + } + if (init?.toolCallId !== undefined) { + this.toolCallId = init.toolCallId; + } + this.toolName = init?.toolName ?? ""; + this.success = init?.success ?? false; + if (init?.result !== undefined) { + this.result = init.result; + } + if (init?.exitCode !== undefined) { + this.exitCode = init.exitCode; + } + if (init?.durationMs !== undefined) { + this.durationMs = init.durationMs; + } + if (init?.errorKind !== undefined) { + this.errorKind = init.errorKind; + } + if (init?.telemetry !== undefined) { + this.telemetry = init.telemetry; + } + } + + //#region Load Methods + + static load( + data: Record, + context?: LoadContext, + ): HostToolResult { + if (context) { + data = context.processInput(data) as Record; + } + + const instance = new HostToolResult(); + + if (data["requestId"] !== undefined && data["requestId"] !== null) { + instance.requestId = String(data["requestId"]); + } + if (data["toolCallId"] !== undefined && data["toolCallId"] !== null) { + instance.toolCallId = String(data["toolCallId"]); + } + if (data["toolName"] !== undefined && data["toolName"] !== null) { + instance.toolName = String(data["toolName"]); + } + if (data["success"] !== undefined && data["success"] !== null) { + instance.success = Boolean(data["success"]); + } + if (data["result"] !== undefined && data["result"] !== null) { + instance.result = data["result"] as unknown; + } + if (data["exitCode"] !== undefined && data["exitCode"] !== null) { + instance.exitCode = Number(data["exitCode"]); + } + if (data["durationMs"] !== undefined && data["durationMs"] !== null) { + instance.durationMs = Number(data["durationMs"]); + } + if (data["errorKind"] !== undefined && data["errorKind"] !== null) { + instance.errorKind = String(data["errorKind"]); + } + if (data["telemetry"] !== undefined && data["telemetry"] !== null) { + instance.telemetry = data["telemetry"] as Record; + } + + if (context) { + return context.processOutput(instance) as HostToolResult; + } + return instance; + } + + //#endregion + + //#region Save Methods + + save(context?: SaveContext): Record { + let obj: this = this; + if (context) { + obj = context.processObject(obj) as this; + } + + const result: Record = {}; + + if (obj.requestId !== undefined && obj.requestId !== null) { + result["requestId"] = obj.requestId; + } + if (obj.toolCallId !== undefined && obj.toolCallId !== null) { + result["toolCallId"] = obj.toolCallId; + } + if (obj.toolName !== undefined && obj.toolName !== null) { + result["toolName"] = obj.toolName; + } + if (obj.success !== undefined && obj.success !== null) { + result["success"] = obj.success; + } + if (obj.result !== undefined && obj.result !== null) { + result["result"] = obj.result; + } + if (obj.exitCode !== undefined && obj.exitCode !== null) { + result["exitCode"] = obj.exitCode; + } + if (obj.durationMs !== undefined && obj.durationMs !== null) { + result["durationMs"] = obj.durationMs; + } + if (obj.errorKind !== undefined && obj.errorKind !== null) { + result["errorKind"] = obj.errorKind; + } + if (obj.telemetry !== undefined && obj.telemetry !== null) { + result["telemetry"] = obj.telemetry; + } + + if (context) { + return context.processDict(result); + } + return result; + } + + toYaml(context?: SaveContext): string { + context = context ?? new SaveContext(); + return context.toYaml(this.save(context)); + } + + toJson(context?: SaveContext, indent: number = 2): string { + context = context ?? new SaveContext(); + return context.toJson(this.save(context), indent); + } + + static fromJson(json: string, context?: LoadContext): HostToolResult { + const data = JSON.parse(json); + return HostToolResult.load(data as Record, context); + } + + static fromYaml(yaml: string, context?: LoadContext): HostToolResult { + const { parse } = require("yaml"); + const data = parse(yaml); + return HostToolResult.load(data as Record, context); + } + + //#endregion +} diff --git a/runtime/typescript/packages/core/src/model/events/index.ts b/runtime/typescript/packages/core/src/model/events/index.ts index 0025477f..65c48b22 100644 --- a/runtime/typescript/packages/core/src/model/events/index.ts +++ b/runtime/typescript/packages/core/src/model/events/index.ts @@ -1,16 +1,50 @@ +// // Copyright (c) Microsoft. All rights reserved. // WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. +export { HostToolResult } from "./host-tool-result"; +export { HostToolRequest } from "./host-tool-request"; +export { RedactedField } from "./redacted-field"; +export { RedactionMetadata } from "./redaction-metadata"; +export { Checkpoint } from "./checkpoint"; +export { TurnEvent } from "./turn-event"; +export { TurnStartPayload } from "./turn-start-payload"; +export { TurnEndPayload } from "./turn-end-payload"; +export { LlmStartPayload } from "./llm-start-payload"; +export { LlmCompletePayload } from "./llm-complete-payload"; +export { RetryPayload } from "./retry-payload"; +export { PermissionRequestedPayload } from "./permission-requested-payload"; +export { PermissionCompletedPayload } from "./permission-completed-payload"; +export { PermissionRequest } from "./permission-request"; +export { PermissionDecision } from "./permission-decision"; export { TokenEventPayload } from "./token-event-payload"; export { ThinkingEventPayload } from "./thinking-event-payload"; export { ToolCallStartPayload } from "./tool-call-start-payload"; +export { ToolCallCompletePayload } from "./tool-call-complete-payload"; +export { ToolExecutionStartPayload } from "./tool-execution-start-payload"; +export { ToolExecutionCompletePayload } from "./tool-execution-complete-payload"; +export { HookStartPayload } from "./hook-start-payload"; +export { HookEndPayload } from "./hook-end-payload"; export { ToolResultPayload } from "./tool-result-payload"; export { StatusEventPayload } from "./status-event-payload"; export { MessagesUpdatedPayload } from "./messages-updated-payload"; export { DoneEventPayload } from "./done-event-payload"; export { ErrorEventPayload } from "./error-event-payload"; +export { CompactionStartPayload } from "./compaction-start-payload"; export { CompactionCompletePayload } from "./compaction-complete-payload"; export { CompactionFailedPayload } from "./compaction-failed-payload"; +export { TurnSummary } from "./turn-summary"; +export { TurnTrace } from "./turn-trace"; +export { HarnessContext } from "./harness-context"; +export { SessionStartPayload } from "./session-start-payload"; +export { SessionEndPayload } from "./session-end-payload"; +export { SessionWarningPayload } from "./session-warning-payload"; +export { SessionEvent } from "./session-event"; +export { TrajectoryEvent } from "./trajectory-event"; +export { SessionFileRef } from "./session-file-ref"; +export { SessionRef } from "./session-ref"; +export { SessionSummary } from "./session-summary"; +export { SessionTrace } from "./session-trace"; export { StreamChunk, TextChunk, diff --git a/runtime/typescript/packages/core/src/model/events/llm-complete-payload.ts b/runtime/typescript/packages/core/src/model/events/llm-complete-payload.ts new file mode 100644 index 00000000..9aa1d6b3 --- /dev/null +++ b/runtime/typescript/packages/core/src/model/events/llm-complete-payload.ts @@ -0,0 +1,121 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. + +import { LoadContext, SaveContext } from "../context"; +import { TokenUsage } from "../model/token-usage"; + +export class LlmCompletePayload { + static readonly shorthandProperty: string | undefined = undefined; + + requestId?: string | undefined; + serviceRequestId?: string | undefined; + usage?: TokenUsage | undefined; + durationMs?: number | undefined; + + constructor(init?: Partial) { + if (init?.requestId !== undefined) { + this.requestId = init.requestId; + } + if (init?.serviceRequestId !== undefined) { + this.serviceRequestId = init.serviceRequestId; + } + if (init?.usage !== undefined) { + this.usage = init.usage; + } + if (init?.durationMs !== undefined) { + this.durationMs = init.durationMs; + } + } + + //#region Load Methods + + static load( + data: Record, + context?: LoadContext, + ): LlmCompletePayload { + if (context) { + data = context.processInput(data) as Record; + } + + const instance = new LlmCompletePayload(); + + if (data["requestId"] !== undefined && data["requestId"] !== null) { + instance.requestId = String(data["requestId"]); + } + if ( + data["serviceRequestId"] !== undefined && + data["serviceRequestId"] !== null + ) { + instance.serviceRequestId = String(data["serviceRequestId"]); + } + if (data["usage"] !== undefined && data["usage"] !== null) { + instance.usage = TokenUsage.load( + data["usage"] as Record, + context, + ); + } + if (data["durationMs"] !== undefined && data["durationMs"] !== null) { + instance.durationMs = Number(data["durationMs"]); + } + + if (context) { + return context.processOutput(instance) as LlmCompletePayload; + } + return instance; + } + + //#endregion + + //#region Save Methods + + save(context?: SaveContext): Record { + let obj: this = this; + if (context) { + obj = context.processObject(obj) as this; + } + + const result: Record = {}; + + if (obj.requestId !== undefined && obj.requestId !== null) { + result["requestId"] = obj.requestId; + } + if (obj.serviceRequestId !== undefined && obj.serviceRequestId !== null) { + result["serviceRequestId"] = obj.serviceRequestId; + } + if (obj.usage !== undefined && obj.usage !== null) { + result["usage"] = obj.usage.save(context); + } + if (obj.durationMs !== undefined && obj.durationMs !== null) { + result["durationMs"] = obj.durationMs; + } + + if (context) { + return context.processDict(result); + } + return result; + } + + toYaml(context?: SaveContext): string { + context = context ?? new SaveContext(); + return context.toYaml(this.save(context)); + } + + toJson(context?: SaveContext, indent: number = 2): string { + context = context ?? new SaveContext(); + return context.toJson(this.save(context), indent); + } + + static fromJson(json: string, context?: LoadContext): LlmCompletePayload { + const data = JSON.parse(json); + return LlmCompletePayload.load(data as Record, context); + } + + static fromYaml(yaml: string, context?: LoadContext): LlmCompletePayload { + const { parse } = require("yaml"); + const data = parse(yaml); + return LlmCompletePayload.load(data as Record, context); + } + + //#endregion +} diff --git a/runtime/typescript/packages/core/src/model/events/llm-start-payload.ts b/runtime/typescript/packages/core/src/model/events/llm-start-payload.ts new file mode 100644 index 00000000..5262733f --- /dev/null +++ b/runtime/typescript/packages/core/src/model/events/llm-start-payload.ts @@ -0,0 +1,114 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. + +import { LoadContext, SaveContext } from "../context"; + +export class LlmStartPayload { + static readonly shorthandProperty: string | undefined = undefined; + + provider?: string | undefined; + modelId?: string | undefined; + messageCount?: number | undefined; + attempt?: number | undefined; + + constructor(init?: Partial) { + if (init?.provider !== undefined) { + this.provider = init.provider; + } + if (init?.modelId !== undefined) { + this.modelId = init.modelId; + } + if (init?.messageCount !== undefined) { + this.messageCount = init.messageCount; + } + if (init?.attempt !== undefined) { + this.attempt = init.attempt; + } + } + + //#region Load Methods + + static load( + data: Record, + context?: LoadContext, + ): LlmStartPayload { + if (context) { + data = context.processInput(data) as Record; + } + + const instance = new LlmStartPayload(); + + if (data["provider"] !== undefined && data["provider"] !== null) { + instance.provider = String(data["provider"]); + } + if (data["modelId"] !== undefined && data["modelId"] !== null) { + instance.modelId = String(data["modelId"]); + } + if (data["messageCount"] !== undefined && data["messageCount"] !== null) { + instance.messageCount = Number(data["messageCount"]); + } + if (data["attempt"] !== undefined && data["attempt"] !== null) { + instance.attempt = Number(data["attempt"]); + } + + if (context) { + return context.processOutput(instance) as LlmStartPayload; + } + return instance; + } + + //#endregion + + //#region Save Methods + + save(context?: SaveContext): Record { + let obj: this = this; + if (context) { + obj = context.processObject(obj) as this; + } + + const result: Record = {}; + + if (obj.provider !== undefined && obj.provider !== null) { + result["provider"] = obj.provider; + } + if (obj.modelId !== undefined && obj.modelId !== null) { + result["modelId"] = obj.modelId; + } + if (obj.messageCount !== undefined && obj.messageCount !== null) { + result["messageCount"] = obj.messageCount; + } + if (obj.attempt !== undefined && obj.attempt !== null) { + result["attempt"] = obj.attempt; + } + + if (context) { + return context.processDict(result); + } + return result; + } + + toYaml(context?: SaveContext): string { + context = context ?? new SaveContext(); + return context.toYaml(this.save(context)); + } + + toJson(context?: SaveContext, indent: number = 2): string { + context = context ?? new SaveContext(); + return context.toJson(this.save(context), indent); + } + + static fromJson(json: string, context?: LoadContext): LlmStartPayload { + const data = JSON.parse(json); + return LlmStartPayload.load(data as Record, context); + } + + static fromYaml(yaml: string, context?: LoadContext): LlmStartPayload { + const { parse } = require("yaml"); + const data = parse(yaml); + return LlmStartPayload.load(data as Record, context); + } + + //#endregion +} diff --git a/runtime/typescript/packages/core/src/model/events/messages-updated-payload.ts b/runtime/typescript/packages/core/src/model/events/messages-updated-payload.ts index 2a6af48a..b014fb0e 100644 --- a/runtime/typescript/packages/core/src/model/events/messages-updated-payload.ts +++ b/runtime/typescript/packages/core/src/model/events/messages-updated-payload.ts @@ -1,3 +1,4 @@ +// // Copyright (c) Microsoft. All rights reserved. // WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. @@ -7,10 +8,24 @@ import { Message } from "../conversation/message"; export class MessagesUpdatedPayload { static readonly shorthandProperty: string | undefined = undefined; - messages: Message[] = []; + messages?: Message[] = []; + reason?: string | undefined; + appended?: Message[] = []; + removed?: number | undefined; constructor(init?: Partial) { - this.messages = init?.messages ?? []; + if (init?.messages !== undefined) { + this.messages = init.messages; + } + if (init?.reason !== undefined) { + this.reason = init.reason; + } + if (init?.appended !== undefined) { + this.appended = init.appended; + } + if (init?.removed !== undefined) { + this.removed = init.removed; + } } //#region Load Methods @@ -31,6 +46,18 @@ export class MessagesUpdatedPayload { context, ); } + if (data["reason"] !== undefined && data["reason"] !== null) { + instance.reason = String(data["reason"]); + } + if (data["appended"] !== undefined && data["appended"] !== null) { + instance.appended = MessagesUpdatedPayload.loadAppended( + data["appended"] as unknown[], + context, + ); + } + if (data["removed"] !== undefined && data["removed"] !== null) { + instance.removed = Number(data["removed"]); + } if (context) { return context.processOutput(instance) as MessagesUpdatedPayload; @@ -71,6 +98,39 @@ export class MessagesUpdatedPayload { return items.map((item) => item.save(context)); } + static loadAppended( + data: Record[] | unknown[], + context?: LoadContext, + ): Message[] { + if (!Array.isArray(data)) { + // Convert dict/object format to array format + const result: Record[] = []; + for (const [k, v] of Object.entries(data)) { + if (typeof v === "object" && v !== null && !Array.isArray(v)) { + result.push({ name: k, ...(v as Record) }); + } else { + result.push({ name: k, role: v }); + } + } + data = result; + } + return data.map((item) => + Message.load(item as Record, context), + ); + } + + static saveAppended( + items: Message[], + context?: SaveContext, + ): Record[] | Record { + if (!context) { + context = new SaveContext(); + } + + // This type doesn't have a 'name' property, so always use array format + return items.map((item) => item.save(context)); + } + //#endregion //#region Save Methods @@ -89,6 +149,18 @@ export class MessagesUpdatedPayload { context, ); } + if (obj.reason !== undefined && obj.reason !== null) { + result["reason"] = obj.reason; + } + if (obj.appended !== undefined && obj.appended !== null) { + result["appended"] = MessagesUpdatedPayload.saveAppended( + obj.appended, + context, + ); + } + if (obj.removed !== undefined && obj.removed !== null) { + result["removed"] = obj.removed; + } if (context) { return context.processDict(result); diff --git a/runtime/typescript/packages/core/src/model/events/permission-completed-payload.ts b/runtime/typescript/packages/core/src/model/events/permission-completed-payload.ts new file mode 100644 index 00000000..63a3dde1 --- /dev/null +++ b/runtime/typescript/packages/core/src/model/events/permission-completed-payload.ts @@ -0,0 +1,156 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. + +import { LoadContext, SaveContext } from "../context"; +import { RedactionMetadata } from "./redaction-metadata"; + +export class PermissionCompletedPayload { + static readonly shorthandProperty: string | undefined = undefined; + + requestId?: string | undefined; + toolCallId?: string | undefined; + permission: string = ""; + approved: boolean = false; + reason?: string | undefined; + result?: Record | undefined; + redaction?: RedactionMetadata | undefined; + + constructor(init?: Partial) { + if (init?.requestId !== undefined) { + this.requestId = init.requestId; + } + if (init?.toolCallId !== undefined) { + this.toolCallId = init.toolCallId; + } + this.permission = init?.permission ?? ""; + this.approved = init?.approved ?? false; + if (init?.reason !== undefined) { + this.reason = init.reason; + } + if (init?.result !== undefined) { + this.result = init.result; + } + if (init?.redaction !== undefined) { + this.redaction = init.redaction; + } + } + + //#region Load Methods + + static load( + data: Record, + context?: LoadContext, + ): PermissionCompletedPayload { + if (context) { + data = context.processInput(data) as Record; + } + + const instance = new PermissionCompletedPayload(); + + if (data["requestId"] !== undefined && data["requestId"] !== null) { + instance.requestId = String(data["requestId"]); + } + if (data["toolCallId"] !== undefined && data["toolCallId"] !== null) { + instance.toolCallId = String(data["toolCallId"]); + } + if (data["permission"] !== undefined && data["permission"] !== null) { + instance.permission = String(data["permission"]); + } + if (data["approved"] !== undefined && data["approved"] !== null) { + instance.approved = Boolean(data["approved"]); + } + if (data["reason"] !== undefined && data["reason"] !== null) { + instance.reason = String(data["reason"]); + } + if (data["result"] !== undefined && data["result"] !== null) { + instance.result = data["result"] as Record; + } + if (data["redaction"] !== undefined && data["redaction"] !== null) { + instance.redaction = RedactionMetadata.load( + data["redaction"] as Record, + context, + ); + } + + if (context) { + return context.processOutput(instance) as PermissionCompletedPayload; + } + return instance; + } + + //#endregion + + //#region Save Methods + + save(context?: SaveContext): Record { + let obj: this = this; + if (context) { + obj = context.processObject(obj) as this; + } + + const result: Record = {}; + + if (obj.requestId !== undefined && obj.requestId !== null) { + result["requestId"] = obj.requestId; + } + if (obj.toolCallId !== undefined && obj.toolCallId !== null) { + result["toolCallId"] = obj.toolCallId; + } + if (obj.permission !== undefined && obj.permission !== null) { + result["permission"] = obj.permission; + } + if (obj.approved !== undefined && obj.approved !== null) { + result["approved"] = obj.approved; + } + if (obj.reason !== undefined && obj.reason !== null) { + result["reason"] = obj.reason; + } + if (obj.result !== undefined && obj.result !== null) { + result["result"] = obj.result; + } + if (obj.redaction !== undefined && obj.redaction !== null) { + result["redaction"] = obj.redaction.save(context); + } + + if (context) { + return context.processDict(result); + } + return result; + } + + toYaml(context?: SaveContext): string { + context = context ?? new SaveContext(); + return context.toYaml(this.save(context)); + } + + toJson(context?: SaveContext, indent: number = 2): string { + context = context ?? new SaveContext(); + return context.toJson(this.save(context), indent); + } + + static fromJson( + json: string, + context?: LoadContext, + ): PermissionCompletedPayload { + const data = JSON.parse(json); + return PermissionCompletedPayload.load( + data as Record, + context, + ); + } + + static fromYaml( + yaml: string, + context?: LoadContext, + ): PermissionCompletedPayload { + const { parse } = require("yaml"); + const data = parse(yaml); + return PermissionCompletedPayload.load( + data as Record, + context, + ); + } + + //#endregion +} diff --git a/runtime/typescript/packages/core/src/model/events/permission-decision.ts b/runtime/typescript/packages/core/src/model/events/permission-decision.ts new file mode 100644 index 00000000..d818a600 --- /dev/null +++ b/runtime/typescript/packages/core/src/model/events/permission-decision.ts @@ -0,0 +1,130 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. + +import { LoadContext, SaveContext } from "../context"; + +export class PermissionDecision { + static readonly shorthandProperty: string | undefined = undefined; + + requestId?: string | undefined; + toolCallId?: string | undefined; + permission: string = ""; + approved: boolean = false; + reason?: string | undefined; + result?: Record | undefined; + + constructor(init?: Partial) { + if (init?.requestId !== undefined) { + this.requestId = init.requestId; + } + if (init?.toolCallId !== undefined) { + this.toolCallId = init.toolCallId; + } + this.permission = init?.permission ?? ""; + this.approved = init?.approved ?? false; + if (init?.reason !== undefined) { + this.reason = init.reason; + } + if (init?.result !== undefined) { + this.result = init.result; + } + } + + //#region Load Methods + + static load( + data: Record, + context?: LoadContext, + ): PermissionDecision { + if (context) { + data = context.processInput(data) as Record; + } + + const instance = new PermissionDecision(); + + if (data["requestId"] !== undefined && data["requestId"] !== null) { + instance.requestId = String(data["requestId"]); + } + if (data["toolCallId"] !== undefined && data["toolCallId"] !== null) { + instance.toolCallId = String(data["toolCallId"]); + } + if (data["permission"] !== undefined && data["permission"] !== null) { + instance.permission = String(data["permission"]); + } + if (data["approved"] !== undefined && data["approved"] !== null) { + instance.approved = Boolean(data["approved"]); + } + if (data["reason"] !== undefined && data["reason"] !== null) { + instance.reason = String(data["reason"]); + } + if (data["result"] !== undefined && data["result"] !== null) { + instance.result = data["result"] as Record; + } + + if (context) { + return context.processOutput(instance) as PermissionDecision; + } + return instance; + } + + //#endregion + + //#region Save Methods + + save(context?: SaveContext): Record { + let obj: this = this; + if (context) { + obj = context.processObject(obj) as this; + } + + const result: Record = {}; + + if (obj.requestId !== undefined && obj.requestId !== null) { + result["requestId"] = obj.requestId; + } + if (obj.toolCallId !== undefined && obj.toolCallId !== null) { + result["toolCallId"] = obj.toolCallId; + } + if (obj.permission !== undefined && obj.permission !== null) { + result["permission"] = obj.permission; + } + if (obj.approved !== undefined && obj.approved !== null) { + result["approved"] = obj.approved; + } + if (obj.reason !== undefined && obj.reason !== null) { + result["reason"] = obj.reason; + } + if (obj.result !== undefined && obj.result !== null) { + result["result"] = obj.result; + } + + if (context) { + return context.processDict(result); + } + return result; + } + + toYaml(context?: SaveContext): string { + context = context ?? new SaveContext(); + return context.toYaml(this.save(context)); + } + + toJson(context?: SaveContext, indent: number = 2): string { + context = context ?? new SaveContext(); + return context.toJson(this.save(context), indent); + } + + static fromJson(json: string, context?: LoadContext): PermissionDecision { + const data = JSON.parse(json); + return PermissionDecision.load(data as Record, context); + } + + static fromYaml(yaml: string, context?: LoadContext): PermissionDecision { + const { parse } = require("yaml"); + const data = parse(yaml); + return PermissionDecision.load(data as Record, context); + } + + //#endregion +} diff --git a/runtime/typescript/packages/core/src/model/events/permission-request.ts b/runtime/typescript/packages/core/src/model/events/permission-request.ts new file mode 100644 index 00000000..e3e25e7c --- /dev/null +++ b/runtime/typescript/packages/core/src/model/events/permission-request.ts @@ -0,0 +1,142 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. + +import { LoadContext, SaveContext } from "../context"; + +export class PermissionRequest { + static readonly shorthandProperty: string | undefined = undefined; + + requestId?: string | undefined; + toolCallId?: string | undefined; + permission: string = ""; + target?: string | undefined; + details?: Record | undefined; + promptRequest?: string | undefined; + policy?: Record | undefined; + + constructor(init?: Partial) { + if (init?.requestId !== undefined) { + this.requestId = init.requestId; + } + if (init?.toolCallId !== undefined) { + this.toolCallId = init.toolCallId; + } + this.permission = init?.permission ?? ""; + if (init?.target !== undefined) { + this.target = init.target; + } + if (init?.details !== undefined) { + this.details = init.details; + } + if (init?.promptRequest !== undefined) { + this.promptRequest = init.promptRequest; + } + if (init?.policy !== undefined) { + this.policy = init.policy; + } + } + + //#region Load Methods + + static load( + data: Record, + context?: LoadContext, + ): PermissionRequest { + if (context) { + data = context.processInput(data) as Record; + } + + const instance = new PermissionRequest(); + + if (data["requestId"] !== undefined && data["requestId"] !== null) { + instance.requestId = String(data["requestId"]); + } + if (data["toolCallId"] !== undefined && data["toolCallId"] !== null) { + instance.toolCallId = String(data["toolCallId"]); + } + if (data["permission"] !== undefined && data["permission"] !== null) { + instance.permission = String(data["permission"]); + } + if (data["target"] !== undefined && data["target"] !== null) { + instance.target = String(data["target"]); + } + if (data["details"] !== undefined && data["details"] !== null) { + instance.details = data["details"] as Record; + } + if (data["promptRequest"] !== undefined && data["promptRequest"] !== null) { + instance.promptRequest = String(data["promptRequest"]); + } + if (data["policy"] !== undefined && data["policy"] !== null) { + instance.policy = data["policy"] as Record; + } + + if (context) { + return context.processOutput(instance) as PermissionRequest; + } + return instance; + } + + //#endregion + + //#region Save Methods + + save(context?: SaveContext): Record { + let obj: this = this; + if (context) { + obj = context.processObject(obj) as this; + } + + const result: Record = {}; + + if (obj.requestId !== undefined && obj.requestId !== null) { + result["requestId"] = obj.requestId; + } + if (obj.toolCallId !== undefined && obj.toolCallId !== null) { + result["toolCallId"] = obj.toolCallId; + } + if (obj.permission !== undefined && obj.permission !== null) { + result["permission"] = obj.permission; + } + if (obj.target !== undefined && obj.target !== null) { + result["target"] = obj.target; + } + if (obj.details !== undefined && obj.details !== null) { + result["details"] = obj.details; + } + if (obj.promptRequest !== undefined && obj.promptRequest !== null) { + result["promptRequest"] = obj.promptRequest; + } + if (obj.policy !== undefined && obj.policy !== null) { + result["policy"] = obj.policy; + } + + if (context) { + return context.processDict(result); + } + return result; + } + + toYaml(context?: SaveContext): string { + context = context ?? new SaveContext(); + return context.toYaml(this.save(context)); + } + + toJson(context?: SaveContext, indent: number = 2): string { + context = context ?? new SaveContext(); + return context.toJson(this.save(context), indent); + } + + static fromJson(json: string, context?: LoadContext): PermissionRequest { + const data = JSON.parse(json); + return PermissionRequest.load(data as Record, context); + } + + static fromYaml(yaml: string, context?: LoadContext): PermissionRequest { + const { parse } = require("yaml"); + const data = parse(yaml); + return PermissionRequest.load(data as Record, context); + } + + //#endregion +} diff --git a/runtime/typescript/packages/core/src/model/events/permission-requested-payload.ts b/runtime/typescript/packages/core/src/model/events/permission-requested-payload.ts new file mode 100644 index 00000000..5d4d3b8f --- /dev/null +++ b/runtime/typescript/packages/core/src/model/events/permission-requested-payload.ts @@ -0,0 +1,168 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. + +import { LoadContext, SaveContext } from "../context"; +import { RedactionMetadata } from "./redaction-metadata"; + +export class PermissionRequestedPayload { + static readonly shorthandProperty: string | undefined = undefined; + + requestId?: string | undefined; + toolCallId?: string | undefined; + permission: string = ""; + target?: string | undefined; + details?: Record | undefined; + promptRequest?: string | undefined; + policy?: Record | undefined; + redaction?: RedactionMetadata | undefined; + + constructor(init?: Partial) { + if (init?.requestId !== undefined) { + this.requestId = init.requestId; + } + if (init?.toolCallId !== undefined) { + this.toolCallId = init.toolCallId; + } + this.permission = init?.permission ?? ""; + if (init?.target !== undefined) { + this.target = init.target; + } + if (init?.details !== undefined) { + this.details = init.details; + } + if (init?.promptRequest !== undefined) { + this.promptRequest = init.promptRequest; + } + if (init?.policy !== undefined) { + this.policy = init.policy; + } + if (init?.redaction !== undefined) { + this.redaction = init.redaction; + } + } + + //#region Load Methods + + static load( + data: Record, + context?: LoadContext, + ): PermissionRequestedPayload { + if (context) { + data = context.processInput(data) as Record; + } + + const instance = new PermissionRequestedPayload(); + + if (data["requestId"] !== undefined && data["requestId"] !== null) { + instance.requestId = String(data["requestId"]); + } + if (data["toolCallId"] !== undefined && data["toolCallId"] !== null) { + instance.toolCallId = String(data["toolCallId"]); + } + if (data["permission"] !== undefined && data["permission"] !== null) { + instance.permission = String(data["permission"]); + } + if (data["target"] !== undefined && data["target"] !== null) { + instance.target = String(data["target"]); + } + if (data["details"] !== undefined && data["details"] !== null) { + instance.details = data["details"] as Record; + } + if (data["promptRequest"] !== undefined && data["promptRequest"] !== null) { + instance.promptRequest = String(data["promptRequest"]); + } + if (data["policy"] !== undefined && data["policy"] !== null) { + instance.policy = data["policy"] as Record; + } + if (data["redaction"] !== undefined && data["redaction"] !== null) { + instance.redaction = RedactionMetadata.load( + data["redaction"] as Record, + context, + ); + } + + if (context) { + return context.processOutput(instance) as PermissionRequestedPayload; + } + return instance; + } + + //#endregion + + //#region Save Methods + + save(context?: SaveContext): Record { + let obj: this = this; + if (context) { + obj = context.processObject(obj) as this; + } + + const result: Record = {}; + + if (obj.requestId !== undefined && obj.requestId !== null) { + result["requestId"] = obj.requestId; + } + if (obj.toolCallId !== undefined && obj.toolCallId !== null) { + result["toolCallId"] = obj.toolCallId; + } + if (obj.permission !== undefined && obj.permission !== null) { + result["permission"] = obj.permission; + } + if (obj.target !== undefined && obj.target !== null) { + result["target"] = obj.target; + } + if (obj.details !== undefined && obj.details !== null) { + result["details"] = obj.details; + } + if (obj.promptRequest !== undefined && obj.promptRequest !== null) { + result["promptRequest"] = obj.promptRequest; + } + if (obj.policy !== undefined && obj.policy !== null) { + result["policy"] = obj.policy; + } + if (obj.redaction !== undefined && obj.redaction !== null) { + result["redaction"] = obj.redaction.save(context); + } + + if (context) { + return context.processDict(result); + } + return result; + } + + toYaml(context?: SaveContext): string { + context = context ?? new SaveContext(); + return context.toYaml(this.save(context)); + } + + toJson(context?: SaveContext, indent: number = 2): string { + context = context ?? new SaveContext(); + return context.toJson(this.save(context), indent); + } + + static fromJson( + json: string, + context?: LoadContext, + ): PermissionRequestedPayload { + const data = JSON.parse(json); + return PermissionRequestedPayload.load( + data as Record, + context, + ); + } + + static fromYaml( + yaml: string, + context?: LoadContext, + ): PermissionRequestedPayload { + const { parse } = require("yaml"); + const data = parse(yaml); + return PermissionRequestedPayload.load( + data as Record, + context, + ); + } + + //#endregion +} diff --git a/runtime/typescript/packages/core/src/model/events/redacted-field.ts b/runtime/typescript/packages/core/src/model/events/redacted-field.ts new file mode 100644 index 00000000..d9636cba --- /dev/null +++ b/runtime/typescript/packages/core/src/model/events/redacted-field.ts @@ -0,0 +1,107 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. + +import { LoadContext, SaveContext } from "../context"; + +export type RedactionMode = + | "none" + | "redacted" + | "hashed" + | "summary" + | "reference"; + +export class RedactedField { + static readonly shorthandProperty: string | undefined = undefined; + + path: string = ""; + mode: RedactionMode = "none"; + reason?: string | undefined; + + constructor(init?: Partial) { + this.path = init?.path ?? ""; + this.mode = init?.mode ?? "none"; + if (init?.reason !== undefined) { + this.reason = init.reason; + } + } + + //#region Load Methods + + static load( + data: Record, + context?: LoadContext, + ): RedactedField { + if (context) { + data = context.processInput(data) as Record; + } + + const instance = new RedactedField(); + + if (data["path"] !== undefined && data["path"] !== null) { + instance.path = String(data["path"]); + } + if (data["mode"] !== undefined && data["mode"] !== null) { + instance.mode = String(data["mode"]) as RedactionMode; + } + if (data["reason"] !== undefined && data["reason"] !== null) { + instance.reason = String(data["reason"]); + } + + if (context) { + return context.processOutput(instance) as RedactedField; + } + return instance; + } + + //#endregion + + //#region Save Methods + + save(context?: SaveContext): Record { + let obj: this = this; + if (context) { + obj = context.processObject(obj) as this; + } + + const result: Record = {}; + + if (obj.path !== undefined && obj.path !== null) { + result["path"] = obj.path; + } + if (obj.mode !== undefined && obj.mode !== null) { + result["mode"] = obj.mode; + } + if (obj.reason !== undefined && obj.reason !== null) { + result["reason"] = obj.reason; + } + + if (context) { + return context.processDict(result); + } + return result; + } + + toYaml(context?: SaveContext): string { + context = context ?? new SaveContext(); + return context.toYaml(this.save(context)); + } + + toJson(context?: SaveContext, indent: number = 2): string { + context = context ?? new SaveContext(); + return context.toJson(this.save(context), indent); + } + + static fromJson(json: string, context?: LoadContext): RedactedField { + const data = JSON.parse(json); + return RedactedField.load(data as Record, context); + } + + static fromYaml(yaml: string, context?: LoadContext): RedactedField { + const { parse } = require("yaml"); + const data = parse(yaml); + return RedactedField.load(data as Record, context); + } + + //#endregion +} diff --git a/runtime/typescript/packages/core/src/model/events/redaction-metadata.ts b/runtime/typescript/packages/core/src/model/events/redaction-metadata.ts new file mode 100644 index 00000000..c08fbb88 --- /dev/null +++ b/runtime/typescript/packages/core/src/model/events/redaction-metadata.ts @@ -0,0 +1,141 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. + +import { LoadContext, SaveContext } from "../context"; +import { RedactedField } from "./redacted-field"; + +export class RedactionMetadata { + static readonly shorthandProperty: string | undefined = undefined; + + sanitized?: boolean | undefined; + fields?: RedactedField[] = []; + policy?: string | undefined; + + constructor(init?: Partial) { + if (init?.sanitized !== undefined) { + this.sanitized = init.sanitized; + } + if (init?.fields !== undefined) { + this.fields = init.fields; + } + if (init?.policy !== undefined) { + this.policy = init.policy; + } + } + + //#region Load Methods + + static load( + data: Record, + context?: LoadContext, + ): RedactionMetadata { + if (context) { + data = context.processInput(data) as Record; + } + + const instance = new RedactionMetadata(); + + if (data["sanitized"] !== undefined && data["sanitized"] !== null) { + instance.sanitized = Boolean(data["sanitized"]); + } + if (data["fields"] !== undefined && data["fields"] !== null) { + instance.fields = RedactionMetadata.loadFields( + data["fields"] as unknown[], + context, + ); + } + if (data["policy"] !== undefined && data["policy"] !== null) { + instance.policy = String(data["policy"]); + } + + if (context) { + return context.processOutput(instance) as RedactionMetadata; + } + return instance; + } + + static loadFields( + data: Record[] | unknown[], + context?: LoadContext, + ): RedactedField[] { + if (!Array.isArray(data)) { + // Convert dict/object format to array format + const result: Record[] = []; + for (const [k, v] of Object.entries(data)) { + if (typeof v === "object" && v !== null && !Array.isArray(v)) { + result.push({ name: k, ...(v as Record) }); + } else { + result.push({ name: k, path: v }); + } + } + data = result; + } + return data.map((item) => + RedactedField.load(item as Record, context), + ); + } + + static saveFields( + items: RedactedField[], + context?: SaveContext, + ): Record[] | Record { + if (!context) { + context = new SaveContext(); + } + + // This type doesn't have a 'name' property, so always use array format + return items.map((item) => item.save(context)); + } + + //#endregion + + //#region Save Methods + + save(context?: SaveContext): Record { + let obj: this = this; + if (context) { + obj = context.processObject(obj) as this; + } + + const result: Record = {}; + + if (obj.sanitized !== undefined && obj.sanitized !== null) { + result["sanitized"] = obj.sanitized; + } + if (obj.fields !== undefined && obj.fields !== null) { + result["fields"] = RedactionMetadata.saveFields(obj.fields, context); + } + if (obj.policy !== undefined && obj.policy !== null) { + result["policy"] = obj.policy; + } + + if (context) { + return context.processDict(result); + } + return result; + } + + toYaml(context?: SaveContext): string { + context = context ?? new SaveContext(); + return context.toYaml(this.save(context)); + } + + toJson(context?: SaveContext, indent: number = 2): string { + context = context ?? new SaveContext(); + return context.toJson(this.save(context), indent); + } + + static fromJson(json: string, context?: LoadContext): RedactionMetadata { + const data = JSON.parse(json); + return RedactionMetadata.load(data as Record, context); + } + + static fromYaml(yaml: string, context?: LoadContext): RedactionMetadata { + const { parse } = require("yaml"); + const data = parse(yaml); + return RedactionMetadata.load(data as Record, context); + } + + //#endregion +} diff --git a/runtime/typescript/packages/core/src/model/events/retry-payload.ts b/runtime/typescript/packages/core/src/model/events/retry-payload.ts new file mode 100644 index 00000000..46470311 --- /dev/null +++ b/runtime/typescript/packages/core/src/model/events/retry-payload.ts @@ -0,0 +1,120 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. + +import { LoadContext, SaveContext } from "../context"; + +export class RetryPayload { + static readonly shorthandProperty: string | undefined = undefined; + + operation: string = ""; + attempt: number = 0; + maxAttempts?: number | undefined; + delayMs?: number | undefined; + reason?: string | undefined; + + constructor(init?: Partial) { + this.operation = init?.operation ?? ""; + this.attempt = init?.attempt ?? 0; + if (init?.maxAttempts !== undefined) { + this.maxAttempts = init.maxAttempts; + } + if (init?.delayMs !== undefined) { + this.delayMs = init.delayMs; + } + if (init?.reason !== undefined) { + this.reason = init.reason; + } + } + + //#region Load Methods + + static load( + data: Record, + context?: LoadContext, + ): RetryPayload { + if (context) { + data = context.processInput(data) as Record; + } + + const instance = new RetryPayload(); + + if (data["operation"] !== undefined && data["operation"] !== null) { + instance.operation = String(data["operation"]); + } + if (data["attempt"] !== undefined && data["attempt"] !== null) { + instance.attempt = Number(data["attempt"]); + } + if (data["maxAttempts"] !== undefined && data["maxAttempts"] !== null) { + instance.maxAttempts = Number(data["maxAttempts"]); + } + if (data["delayMs"] !== undefined && data["delayMs"] !== null) { + instance.delayMs = Number(data["delayMs"]); + } + if (data["reason"] !== undefined && data["reason"] !== null) { + instance.reason = String(data["reason"]); + } + + if (context) { + return context.processOutput(instance) as RetryPayload; + } + return instance; + } + + //#endregion + + //#region Save Methods + + save(context?: SaveContext): Record { + let obj: this = this; + if (context) { + obj = context.processObject(obj) as this; + } + + const result: Record = {}; + + if (obj.operation !== undefined && obj.operation !== null) { + result["operation"] = obj.operation; + } + if (obj.attempt !== undefined && obj.attempt !== null) { + result["attempt"] = obj.attempt; + } + if (obj.maxAttempts !== undefined && obj.maxAttempts !== null) { + result["maxAttempts"] = obj.maxAttempts; + } + if (obj.delayMs !== undefined && obj.delayMs !== null) { + result["delayMs"] = obj.delayMs; + } + if (obj.reason !== undefined && obj.reason !== null) { + result["reason"] = obj.reason; + } + + if (context) { + return context.processDict(result); + } + return result; + } + + toYaml(context?: SaveContext): string { + context = context ?? new SaveContext(); + return context.toYaml(this.save(context)); + } + + toJson(context?: SaveContext, indent: number = 2): string { + context = context ?? new SaveContext(); + return context.toJson(this.save(context), indent); + } + + static fromJson(json: string, context?: LoadContext): RetryPayload { + const data = JSON.parse(json); + return RetryPayload.load(data as Record, context); + } + + static fromYaml(yaml: string, context?: LoadContext): RetryPayload { + const { parse } = require("yaml"); + const data = parse(yaml); + return RetryPayload.load(data as Record, context); + } + + //#endregion +} diff --git a/runtime/typescript/packages/core/src/model/events/session-end-payload.ts b/runtime/typescript/packages/core/src/model/events/session-end-payload.ts new file mode 100644 index 00000000..16a29212 --- /dev/null +++ b/runtime/typescript/packages/core/src/model/events/session-end-payload.ts @@ -0,0 +1,120 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. + +import { LoadContext, SaveContext } from "../context"; + +export type SessionEndStatus = + | "success" + | "error" + | "cancelled" + | "interrupted"; + +export class SessionEndPayload { + static readonly shorthandProperty: string | undefined = undefined; + + sessionId?: string | undefined; + status?: SessionEndStatus | undefined; + reason?: string | undefined; + durationMs?: number | undefined; + + constructor(init?: Partial) { + if (init?.sessionId !== undefined) { + this.sessionId = init.sessionId; + } + if (init?.status !== undefined) { + this.status = init.status; + } + if (init?.reason !== undefined) { + this.reason = init.reason; + } + if (init?.durationMs !== undefined) { + this.durationMs = init.durationMs; + } + } + + //#region Load Methods + + static load( + data: Record, + context?: LoadContext, + ): SessionEndPayload { + if (context) { + data = context.processInput(data) as Record; + } + + const instance = new SessionEndPayload(); + + if (data["sessionId"] !== undefined && data["sessionId"] !== null) { + instance.sessionId = String(data["sessionId"]); + } + if (data["status"] !== undefined && data["status"] !== null) { + instance.status = String(data["status"]) as SessionEndStatus; + } + if (data["reason"] !== undefined && data["reason"] !== null) { + instance.reason = String(data["reason"]); + } + if (data["durationMs"] !== undefined && data["durationMs"] !== null) { + instance.durationMs = Number(data["durationMs"]); + } + + if (context) { + return context.processOutput(instance) as SessionEndPayload; + } + return instance; + } + + //#endregion + + //#region Save Methods + + save(context?: SaveContext): Record { + let obj: this = this; + if (context) { + obj = context.processObject(obj) as this; + } + + const result: Record = {}; + + if (obj.sessionId !== undefined && obj.sessionId !== null) { + result["sessionId"] = obj.sessionId; + } + if (obj.status !== undefined && obj.status !== null) { + result["status"] = obj.status; + } + if (obj.reason !== undefined && obj.reason !== null) { + result["reason"] = obj.reason; + } + if (obj.durationMs !== undefined && obj.durationMs !== null) { + result["durationMs"] = obj.durationMs; + } + + if (context) { + return context.processDict(result); + } + return result; + } + + toYaml(context?: SaveContext): string { + context = context ?? new SaveContext(); + return context.toYaml(this.save(context)); + } + + toJson(context?: SaveContext, indent: number = 2): string { + context = context ?? new SaveContext(); + return context.toJson(this.save(context), indent); + } + + static fromJson(json: string, context?: LoadContext): SessionEndPayload { + const data = JSON.parse(json); + return SessionEndPayload.load(data as Record, context); + } + + static fromYaml(yaml: string, context?: LoadContext): SessionEndPayload { + const { parse } = require("yaml"); + const data = parse(yaml); + return SessionEndPayload.load(data as Record, context); + } + + //#endregion +} diff --git a/runtime/typescript/packages/core/src/model/events/session-event.ts b/runtime/typescript/packages/core/src/model/events/session-event.ts new file mode 100644 index 00000000..bec2b5fb --- /dev/null +++ b/runtime/typescript/packages/core/src/model/events/session-event.ts @@ -0,0 +1,169 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. + +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 class SessionEvent { + static readonly shorthandProperty: string | undefined = undefined; + + id: string = ""; + type: SessionEventType = "session_start"; + timestamp: string = ""; + sessionId?: string | undefined; + turnId?: string | undefined; + parentId?: string | undefined; + spanId?: string | undefined; + payload: Record = {}; + redaction?: RedactionMetadata | undefined; + + constructor(init?: Partial) { + this.id = init?.id ?? ""; + this.type = init?.type ?? "session_start"; + this.timestamp = init?.timestamp ?? ""; + if (init?.sessionId !== undefined) { + this.sessionId = init.sessionId; + } + if (init?.turnId !== undefined) { + this.turnId = init.turnId; + } + if (init?.parentId !== undefined) { + this.parentId = init.parentId; + } + if (init?.spanId !== undefined) { + this.spanId = init.spanId; + } + this.payload = init?.payload ?? {}; + if (init?.redaction !== undefined) { + this.redaction = init.redaction; + } + } + + //#region Load Methods + + static load( + data: Record, + context?: LoadContext, + ): SessionEvent { + if (context) { + data = context.processInput(data) as Record; + } + + const instance = new SessionEvent(); + + if (data["id"] !== undefined && data["id"] !== null) { + instance.id = String(data["id"]); + } + if (data["type"] !== undefined && data["type"] !== null) { + instance.type = String(data["type"]) as SessionEventType; + } + if (data["timestamp"] !== undefined && data["timestamp"] !== null) { + instance.timestamp = String(data["timestamp"]); + } + if (data["sessionId"] !== undefined && data["sessionId"] !== null) { + instance.sessionId = String(data["sessionId"]); + } + if (data["turnId"] !== undefined && data["turnId"] !== null) { + instance.turnId = String(data["turnId"]); + } + if (data["parentId"] !== undefined && data["parentId"] !== null) { + instance.parentId = String(data["parentId"]); + } + if (data["spanId"] !== undefined && data["spanId"] !== null) { + instance.spanId = String(data["spanId"]); + } + if (data["payload"] !== undefined && data["payload"] !== null) { + instance.payload = data["payload"] as Record; + } + if (data["redaction"] !== undefined && data["redaction"] !== null) { + instance.redaction = RedactionMetadata.load( + data["redaction"] as Record, + context, + ); + } + + if (context) { + return context.processOutput(instance) as SessionEvent; + } + return instance; + } + + //#endregion + + //#region Save Methods + + save(context?: SaveContext): Record { + let obj: this = this; + if (context) { + obj = context.processObject(obj) as this; + } + + const result: Record = {}; + + if (obj.id !== undefined && obj.id !== null) { + result["id"] = obj.id; + } + if (obj.type !== undefined && obj.type !== null) { + result["type"] = obj.type; + } + if (obj.timestamp !== undefined && obj.timestamp !== null) { + result["timestamp"] = obj.timestamp; + } + if (obj.sessionId !== undefined && obj.sessionId !== null) { + result["sessionId"] = obj.sessionId; + } + if (obj.turnId !== undefined && obj.turnId !== null) { + result["turnId"] = obj.turnId; + } + if (obj.parentId !== undefined && obj.parentId !== null) { + result["parentId"] = obj.parentId; + } + if (obj.spanId !== undefined && obj.spanId !== null) { + result["spanId"] = obj.spanId; + } + if (obj.payload !== undefined && obj.payload !== null) { + result["payload"] = obj.payload; + } + if (obj.redaction !== undefined && obj.redaction !== null) { + result["redaction"] = obj.redaction.save(context); + } + + if (context) { + return context.processDict(result); + } + return result; + } + + toYaml(context?: SaveContext): string { + context = context ?? new SaveContext(); + return context.toYaml(this.save(context)); + } + + toJson(context?: SaveContext, indent: number = 2): string { + context = context ?? new SaveContext(); + return context.toJson(this.save(context), indent); + } + + static fromJson(json: string, context?: LoadContext): SessionEvent { + const data = JSON.parse(json); + return SessionEvent.load(data as Record, context); + } + + static fromYaml(yaml: string, context?: LoadContext): SessionEvent { + const { parse } = require("yaml"); + const data = parse(yaml); + return SessionEvent.load(data as Record, context); + } + + //#endregion +} diff --git a/runtime/typescript/packages/core/src/model/events/session-file-ref.ts b/runtime/typescript/packages/core/src/model/events/session-file-ref.ts new file mode 100644 index 00000000..558e3507 --- /dev/null +++ b/runtime/typescript/packages/core/src/model/events/session-file-ref.ts @@ -0,0 +1,122 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. + +import { LoadContext, SaveContext } from "../context"; + +export class SessionFileRef { + static readonly shorthandProperty: string | undefined = undefined; + + sessionId?: string | undefined; + path: string = ""; + toolName?: string | undefined; + turnIndex?: number | undefined; + firstSeenAt?: string | undefined; + + constructor(init?: Partial) { + if (init?.sessionId !== undefined) { + this.sessionId = init.sessionId; + } + this.path = init?.path ?? ""; + if (init?.toolName !== undefined) { + this.toolName = init.toolName; + } + if (init?.turnIndex !== undefined) { + this.turnIndex = init.turnIndex; + } + if (init?.firstSeenAt !== undefined) { + this.firstSeenAt = init.firstSeenAt; + } + } + + //#region Load Methods + + static load( + data: Record, + context?: LoadContext, + ): SessionFileRef { + if (context) { + data = context.processInput(data) as Record; + } + + const instance = new SessionFileRef(); + + if (data["sessionId"] !== undefined && data["sessionId"] !== null) { + instance.sessionId = String(data["sessionId"]); + } + if (data["path"] !== undefined && data["path"] !== null) { + instance.path = String(data["path"]); + } + if (data["toolName"] !== undefined && data["toolName"] !== null) { + instance.toolName = String(data["toolName"]); + } + if (data["turnIndex"] !== undefined && data["turnIndex"] !== null) { + instance.turnIndex = Number(data["turnIndex"]); + } + if (data["firstSeenAt"] !== undefined && data["firstSeenAt"] !== null) { + instance.firstSeenAt = String(data["firstSeenAt"]); + } + + if (context) { + return context.processOutput(instance) as SessionFileRef; + } + return instance; + } + + //#endregion + + //#region Save Methods + + save(context?: SaveContext): Record { + let obj: this = this; + if (context) { + obj = context.processObject(obj) as this; + } + + const result: Record = {}; + + if (obj.sessionId !== undefined && obj.sessionId !== null) { + result["sessionId"] = obj.sessionId; + } + if (obj.path !== undefined && obj.path !== null) { + result["path"] = obj.path; + } + if (obj.toolName !== undefined && obj.toolName !== null) { + result["toolName"] = obj.toolName; + } + if (obj.turnIndex !== undefined && obj.turnIndex !== null) { + result["turnIndex"] = obj.turnIndex; + } + if (obj.firstSeenAt !== undefined && obj.firstSeenAt !== null) { + result["firstSeenAt"] = obj.firstSeenAt; + } + + if (context) { + return context.processDict(result); + } + return result; + } + + toYaml(context?: SaveContext): string { + context = context ?? new SaveContext(); + return context.toYaml(this.save(context)); + } + + toJson(context?: SaveContext, indent: number = 2): string { + context = context ?? new SaveContext(); + return context.toJson(this.save(context), indent); + } + + static fromJson(json: string, context?: LoadContext): SessionFileRef { + const data = JSON.parse(json); + return SessionFileRef.load(data as Record, context); + } + + static fromYaml(yaml: string, context?: LoadContext): SessionFileRef { + const { parse } = require("yaml"); + const data = parse(yaml); + return SessionFileRef.load(data as Record, context); + } + + //#endregion +} diff --git a/runtime/typescript/packages/core/src/model/events/session-ref.ts b/runtime/typescript/packages/core/src/model/events/session-ref.ts new file mode 100644 index 00000000..5c30c5c5 --- /dev/null +++ b/runtime/typescript/packages/core/src/model/events/session-ref.ts @@ -0,0 +1,120 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. + +import { LoadContext, SaveContext } from "../context"; + +export class SessionRef { + static readonly shorthandProperty: string | undefined = undefined; + + sessionId?: string | undefined; + refType: string = ""; + refValue: string = ""; + turnIndex?: number | undefined; + createdAt?: string | undefined; + + constructor(init?: Partial) { + if (init?.sessionId !== undefined) { + this.sessionId = init.sessionId; + } + this.refType = init?.refType ?? ""; + this.refValue = init?.refValue ?? ""; + if (init?.turnIndex !== undefined) { + this.turnIndex = init.turnIndex; + } + if (init?.createdAt !== undefined) { + this.createdAt = init.createdAt; + } + } + + //#region Load Methods + + static load( + data: Record, + context?: LoadContext, + ): SessionRef { + if (context) { + data = context.processInput(data) as Record; + } + + const instance = new SessionRef(); + + if (data["sessionId"] !== undefined && data["sessionId"] !== null) { + instance.sessionId = String(data["sessionId"]); + } + if (data["refType"] !== undefined && data["refType"] !== null) { + instance.refType = String(data["refType"]); + } + if (data["refValue"] !== undefined && data["refValue"] !== null) { + instance.refValue = String(data["refValue"]); + } + if (data["turnIndex"] !== undefined && data["turnIndex"] !== null) { + instance.turnIndex = Number(data["turnIndex"]); + } + if (data["createdAt"] !== undefined && data["createdAt"] !== null) { + instance.createdAt = String(data["createdAt"]); + } + + if (context) { + return context.processOutput(instance) as SessionRef; + } + return instance; + } + + //#endregion + + //#region Save Methods + + save(context?: SaveContext): Record { + let obj: this = this; + if (context) { + obj = context.processObject(obj) as this; + } + + const result: Record = {}; + + if (obj.sessionId !== undefined && obj.sessionId !== null) { + result["sessionId"] = obj.sessionId; + } + if (obj.refType !== undefined && obj.refType !== null) { + result["refType"] = obj.refType; + } + if (obj.refValue !== undefined && obj.refValue !== null) { + result["refValue"] = obj.refValue; + } + if (obj.turnIndex !== undefined && obj.turnIndex !== null) { + result["turnIndex"] = obj.turnIndex; + } + if (obj.createdAt !== undefined && obj.createdAt !== null) { + result["createdAt"] = obj.createdAt; + } + + if (context) { + return context.processDict(result); + } + return result; + } + + toYaml(context?: SaveContext): string { + context = context ?? new SaveContext(); + return context.toYaml(this.save(context)); + } + + toJson(context?: SaveContext, indent: number = 2): string { + context = context ?? new SaveContext(); + return context.toJson(this.save(context), indent); + } + + static fromJson(json: string, context?: LoadContext): SessionRef { + const data = JSON.parse(json); + return SessionRef.load(data as Record, context); + } + + static fromYaml(yaml: string, context?: LoadContext): SessionRef { + const { parse } = require("yaml"); + const data = parse(yaml); + return SessionRef.load(data as Record, context); + } + + //#endregion +} diff --git a/runtime/typescript/packages/core/src/model/events/session-start-payload.ts b/runtime/typescript/packages/core/src/model/events/session-start-payload.ts new file mode 100644 index 00000000..dbba60b7 --- /dev/null +++ b/runtime/typescript/packages/core/src/model/events/session-start-payload.ts @@ -0,0 +1,172 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. + +import { LoadContext, SaveContext } from "../context"; +import { HarnessContext } from "./harness-context"; + +export class SessionStartPayload { + static readonly shorthandProperty: string | undefined = undefined; + + sessionId: string = ""; + schemaVersion?: string | undefined; + producer?: string | undefined; + runtime?: string | undefined; + promptyVersion?: string | undefined; + startTime?: string | undefined; + selectedModel?: string | undefined; + reasoningEffort?: string | undefined; + context?: HarnessContext | undefined; + + constructor(init?: Partial) { + this.sessionId = init?.sessionId ?? ""; + if (init?.schemaVersion !== undefined) { + this.schemaVersion = init.schemaVersion; + } + if (init?.producer !== undefined) { + this.producer = init.producer; + } + if (init?.runtime !== undefined) { + this.runtime = init.runtime; + } + if (init?.promptyVersion !== undefined) { + this.promptyVersion = init.promptyVersion; + } + if (init?.startTime !== undefined) { + this.startTime = init.startTime; + } + if (init?.selectedModel !== undefined) { + this.selectedModel = init.selectedModel; + } + if (init?.reasoningEffort !== undefined) { + this.reasoningEffort = init.reasoningEffort; + } + if (init?.context !== undefined) { + this.context = init.context; + } + } + + //#region Load Methods + + static load( + data: Record, + context?: LoadContext, + ): SessionStartPayload { + if (context) { + data = context.processInput(data) as Record; + } + + const instance = new SessionStartPayload(); + + if (data["sessionId"] !== undefined && data["sessionId"] !== null) { + instance.sessionId = String(data["sessionId"]); + } + if (data["schemaVersion"] !== undefined && data["schemaVersion"] !== null) { + instance.schemaVersion = String(data["schemaVersion"]); + } + if (data["producer"] !== undefined && data["producer"] !== null) { + instance.producer = String(data["producer"]); + } + if (data["runtime"] !== undefined && data["runtime"] !== null) { + instance.runtime = String(data["runtime"]); + } + if ( + data["promptyVersion"] !== undefined && + data["promptyVersion"] !== null + ) { + instance.promptyVersion = String(data["promptyVersion"]); + } + if (data["startTime"] !== undefined && data["startTime"] !== null) { + instance.startTime = String(data["startTime"]); + } + if (data["selectedModel"] !== undefined && data["selectedModel"] !== null) { + instance.selectedModel = String(data["selectedModel"]); + } + if ( + data["reasoningEffort"] !== undefined && + data["reasoningEffort"] !== null + ) { + instance.reasoningEffort = String(data["reasoningEffort"]); + } + if (data["context"] !== undefined && data["context"] !== null) { + instance.context = HarnessContext.load( + data["context"] as Record, + context, + ); + } + + if (context) { + return context.processOutput(instance) as SessionStartPayload; + } + return instance; + } + + //#endregion + + //#region Save Methods + + save(context?: SaveContext): Record { + let obj: this = this; + if (context) { + obj = context.processObject(obj) as this; + } + + const result: Record = {}; + + if (obj.sessionId !== undefined && obj.sessionId !== null) { + result["sessionId"] = obj.sessionId; + } + if (obj.schemaVersion !== undefined && obj.schemaVersion !== null) { + result["schemaVersion"] = obj.schemaVersion; + } + if (obj.producer !== undefined && obj.producer !== null) { + result["producer"] = obj.producer; + } + if (obj.runtime !== undefined && obj.runtime !== null) { + result["runtime"] = obj.runtime; + } + if (obj.promptyVersion !== undefined && obj.promptyVersion !== null) { + result["promptyVersion"] = obj.promptyVersion; + } + if (obj.startTime !== undefined && obj.startTime !== null) { + result["startTime"] = obj.startTime; + } + if (obj.selectedModel !== undefined && obj.selectedModel !== null) { + result["selectedModel"] = obj.selectedModel; + } + if (obj.reasoningEffort !== undefined && obj.reasoningEffort !== null) { + result["reasoningEffort"] = obj.reasoningEffort; + } + if (obj.context !== undefined && obj.context !== null) { + result["context"] = obj.context.save(context); + } + + if (context) { + return context.processDict(result); + } + return result; + } + + toYaml(context?: SaveContext): string { + context = context ?? new SaveContext(); + return context.toYaml(this.save(context)); + } + + toJson(context?: SaveContext, indent: number = 2): string { + context = context ?? new SaveContext(); + return context.toJson(this.save(context), indent); + } + + static fromJson(json: string, context?: LoadContext): SessionStartPayload { + const data = JSON.parse(json); + return SessionStartPayload.load(data as Record, context); + } + + static fromYaml(yaml: string, context?: LoadContext): SessionStartPayload { + const { parse } = require("yaml"); + const data = parse(yaml); + return SessionStartPayload.load(data as Record, context); + } + + //#endregion +} diff --git a/runtime/typescript/packages/core/src/model/events/session-summary.ts b/runtime/typescript/packages/core/src/model/events/session-summary.ts new file mode 100644 index 00000000..539050b4 --- /dev/null +++ b/runtime/typescript/packages/core/src/model/events/session-summary.ts @@ -0,0 +1,142 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. + +import { LoadContext, SaveContext } from "../context"; +import { TokenUsage } from "../model/token-usage"; + +export type SessionSummaryStatus = + | "success" + | "error" + | "cancelled" + | "interrupted"; + +export class SessionSummary { + static readonly shorthandProperty: string | undefined = undefined; + + sessionId: string = ""; + status?: SessionSummaryStatus | undefined; + turns?: number | undefined; + checkpoints?: number | undefined; + usage?: TokenUsage | undefined; + durationMs?: number | undefined; + + constructor(init?: Partial) { + this.sessionId = init?.sessionId ?? ""; + if (init?.status !== undefined) { + this.status = init.status; + } + if (init?.turns !== undefined) { + this.turns = init.turns; + } + if (init?.checkpoints !== undefined) { + this.checkpoints = init.checkpoints; + } + if (init?.usage !== undefined) { + this.usage = init.usage; + } + if (init?.durationMs !== undefined) { + this.durationMs = init.durationMs; + } + } + + //#region Load Methods + + static load( + data: Record, + context?: LoadContext, + ): SessionSummary { + if (context) { + data = context.processInput(data) as Record; + } + + const instance = new SessionSummary(); + + if (data["sessionId"] !== undefined && data["sessionId"] !== null) { + instance.sessionId = String(data["sessionId"]); + } + if (data["status"] !== undefined && data["status"] !== null) { + instance.status = String(data["status"]) as SessionSummaryStatus; + } + if (data["turns"] !== undefined && data["turns"] !== null) { + instance.turns = Number(data["turns"]); + } + if (data["checkpoints"] !== undefined && data["checkpoints"] !== null) { + instance.checkpoints = Number(data["checkpoints"]); + } + if (data["usage"] !== undefined && data["usage"] !== null) { + instance.usage = TokenUsage.load( + data["usage"] as Record, + context, + ); + } + if (data["durationMs"] !== undefined && data["durationMs"] !== null) { + instance.durationMs = Number(data["durationMs"]); + } + + if (context) { + return context.processOutput(instance) as SessionSummary; + } + return instance; + } + + //#endregion + + //#region Save Methods + + save(context?: SaveContext): Record { + let obj: this = this; + if (context) { + obj = context.processObject(obj) as this; + } + + const result: Record = {}; + + if (obj.sessionId !== undefined && obj.sessionId !== null) { + result["sessionId"] = obj.sessionId; + } + if (obj.status !== undefined && obj.status !== null) { + result["status"] = obj.status; + } + if (obj.turns !== undefined && obj.turns !== null) { + result["turns"] = obj.turns; + } + if (obj.checkpoints !== undefined && obj.checkpoints !== null) { + result["checkpoints"] = obj.checkpoints; + } + if (obj.usage !== undefined && obj.usage !== null) { + result["usage"] = obj.usage.save(context); + } + if (obj.durationMs !== undefined && obj.durationMs !== null) { + result["durationMs"] = obj.durationMs; + } + + if (context) { + return context.processDict(result); + } + return result; + } + + toYaml(context?: SaveContext): string { + context = context ?? new SaveContext(); + return context.toYaml(this.save(context)); + } + + toJson(context?: SaveContext, indent: number = 2): string { + context = context ?? new SaveContext(); + return context.toJson(this.save(context), indent); + } + + static fromJson(json: string, context?: LoadContext): SessionSummary { + const data = JSON.parse(json); + return SessionSummary.load(data as Record, context); + } + + static fromYaml(yaml: string, context?: LoadContext): SessionSummary { + const { parse } = require("yaml"); + const data = parse(yaml); + return SessionSummary.load(data as Record, context); + } + + //#endregion +} diff --git a/runtime/typescript/packages/core/src/model/events/session-trace.ts b/runtime/typescript/packages/core/src/model/events/session-trace.ts new file mode 100644 index 00000000..f2067a86 --- /dev/null +++ b/runtime/typescript/packages/core/src/model/events/session-trace.ts @@ -0,0 +1,412 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. + +import { LoadContext, SaveContext } from "../context"; +import { Checkpoint } from "./checkpoint"; +import { SessionEvent } from "./session-event"; +import { SessionFileRef } from "./session-file-ref"; +import { SessionRef } from "./session-ref"; +import { SessionSummary } from "./session-summary"; +import { TrajectoryEvent } from "./trajectory-event"; +import { TurnTrace } from "./turn-trace"; + +export class SessionTrace { + static readonly shorthandProperty: string | undefined = undefined; + + version: string = "1"; + runtime?: string | undefined; + promptyVersion?: string | undefined; + sessionId?: string | undefined; + events: SessionEvent[] = []; + turns?: TurnTrace[] = []; + checkpoints?: Checkpoint[] = []; + trajectory?: TrajectoryEvent[] = []; + files?: SessionFileRef[] = []; + refs?: SessionRef[] = []; + summary?: SessionSummary | undefined; + + constructor(init?: Partial) { + this.version = init?.version ?? "1"; + if (init?.runtime !== undefined) { + this.runtime = init.runtime; + } + if (init?.promptyVersion !== undefined) { + this.promptyVersion = init.promptyVersion; + } + if (init?.sessionId !== undefined) { + this.sessionId = init.sessionId; + } + this.events = init?.events ?? []; + if (init?.turns !== undefined) { + this.turns = init.turns; + } + if (init?.checkpoints !== undefined) { + this.checkpoints = init.checkpoints; + } + if (init?.trajectory !== undefined) { + this.trajectory = init.trajectory; + } + if (init?.files !== undefined) { + this.files = init.files; + } + if (init?.refs !== undefined) { + this.refs = init.refs; + } + if (init?.summary !== undefined) { + this.summary = init.summary; + } + } + + //#region Load Methods + + static load( + data: Record, + context?: LoadContext, + ): SessionTrace { + if (context) { + data = context.processInput(data) as Record; + } + + const instance = new SessionTrace(); + + if (data["version"] !== undefined && data["version"] !== null) { + instance.version = String(data["version"]); + } + if (data["runtime"] !== undefined && data["runtime"] !== null) { + instance.runtime = String(data["runtime"]); + } + if ( + data["promptyVersion"] !== undefined && + data["promptyVersion"] !== null + ) { + instance.promptyVersion = String(data["promptyVersion"]); + } + if (data["sessionId"] !== undefined && data["sessionId"] !== null) { + instance.sessionId = String(data["sessionId"]); + } + if (data["events"] !== undefined && data["events"] !== null) { + instance.events = SessionTrace.loadEvents( + data["events"] as unknown[], + context, + ); + } + if (data["turns"] !== undefined && data["turns"] !== null) { + instance.turns = SessionTrace.loadTurns( + data["turns"] as unknown[], + context, + ); + } + if (data["checkpoints"] !== undefined && data["checkpoints"] !== null) { + instance.checkpoints = SessionTrace.loadCheckpoints( + data["checkpoints"] as unknown[], + context, + ); + } + if (data["trajectory"] !== undefined && data["trajectory"] !== null) { + instance.trajectory = SessionTrace.loadTrajectory( + data["trajectory"] as unknown[], + context, + ); + } + if (data["files"] !== undefined && data["files"] !== null) { + instance.files = SessionTrace.loadFiles( + data["files"] as unknown[], + context, + ); + } + if (data["refs"] !== undefined && data["refs"] !== null) { + instance.refs = SessionTrace.loadRefs(data["refs"] as unknown[], context); + } + if (data["summary"] !== undefined && data["summary"] !== null) { + instance.summary = SessionSummary.load( + data["summary"] as Record, + context, + ); + } + + if (context) { + return context.processOutput(instance) as SessionTrace; + } + return instance; + } + + static loadEvents( + data: Record[] | unknown[], + context?: LoadContext, + ): SessionEvent[] { + if (!Array.isArray(data)) { + // Convert dict/object format to array format + const result: Record[] = []; + for (const [k, v] of Object.entries(data)) { + if (typeof v === "object" && v !== null && !Array.isArray(v)) { + result.push({ name: k, ...(v as Record) }); + } else { + result.push({ name: k, id: v }); + } + } + data = result; + } + return data.map((item) => + SessionEvent.load(item as Record, context), + ); + } + + static saveEvents( + items: SessionEvent[], + context?: SaveContext, + ): Record[] | Record { + if (!context) { + context = new SaveContext(); + } + + // This type doesn't have a 'name' property, so always use array format + return items.map((item) => item.save(context)); + } + + static loadTurns( + data: Record[] | unknown[], + context?: LoadContext, + ): TurnTrace[] { + if (!Array.isArray(data)) { + // Convert dict/object format to array format + const result: Record[] = []; + for (const [k, v] of Object.entries(data)) { + if (typeof v === "object" && v !== null && !Array.isArray(v)) { + result.push({ name: k, ...(v as Record) }); + } else { + result.push({ name: k, version: v }); + } + } + data = result; + } + return data.map((item) => + TurnTrace.load(item as Record, context), + ); + } + + static saveTurns( + items: TurnTrace[], + context?: SaveContext, + ): Record[] | Record { + if (!context) { + context = new SaveContext(); + } + + // This type doesn't have a 'name' property, so always use array format + return items.map((item) => item.save(context)); + } + + static loadCheckpoints( + data: Record[] | unknown[], + context?: LoadContext, + ): Checkpoint[] { + if (!Array.isArray(data)) { + // Convert dict/object format to array format + const result: Record[] = []; + for (const [k, v] of Object.entries(data)) { + if (typeof v === "object" && v !== null && !Array.isArray(v)) { + result.push({ name: k, ...(v as Record) }); + } else { + result.push({ name: k, id: v }); + } + } + data = result; + } + return data.map((item) => + Checkpoint.load(item as Record, context), + ); + } + + static saveCheckpoints( + items: Checkpoint[], + context?: SaveContext, + ): Record[] | Record { + if (!context) { + context = new SaveContext(); + } + + // This type doesn't have a 'name' property, so always use array format + return items.map((item) => item.save(context)); + } + + static loadTrajectory( + data: Record[] | unknown[], + context?: LoadContext, + ): TrajectoryEvent[] { + if (!Array.isArray(data)) { + // Convert dict/object format to array format + const result: Record[] = []; + for (const [k, v] of Object.entries(data)) { + if (typeof v === "object" && v !== null && !Array.isArray(v)) { + result.push({ name: k, ...(v as Record) }); + } else { + result.push({ name: k, id: v }); + } + } + data = result; + } + return data.map((item) => + TrajectoryEvent.load(item as Record, context), + ); + } + + static saveTrajectory( + items: TrajectoryEvent[], + context?: SaveContext, + ): Record[] | Record { + if (!context) { + context = new SaveContext(); + } + + // This type doesn't have a 'name' property, so always use array format + return items.map((item) => item.save(context)); + } + + static loadFiles( + data: Record[] | unknown[], + context?: LoadContext, + ): SessionFileRef[] { + if (!Array.isArray(data)) { + // Convert dict/object format to array format + const result: Record[] = []; + for (const [k, v] of Object.entries(data)) { + if (typeof v === "object" && v !== null && !Array.isArray(v)) { + result.push({ name: k, ...(v as Record) }); + } else { + result.push({ name: k, sessionId: v }); + } + } + data = result; + } + return data.map((item) => + SessionFileRef.load(item as Record, context), + ); + } + + static saveFiles( + items: SessionFileRef[], + context?: SaveContext, + ): Record[] | Record { + if (!context) { + context = new SaveContext(); + } + + // This type doesn't have a 'name' property, so always use array format + return items.map((item) => item.save(context)); + } + + static loadRefs( + data: Record[] | unknown[], + context?: LoadContext, + ): SessionRef[] { + if (!Array.isArray(data)) { + // Convert dict/object format to array format + const result: Record[] = []; + for (const [k, v] of Object.entries(data)) { + if (typeof v === "object" && v !== null && !Array.isArray(v)) { + result.push({ name: k, ...(v as Record) }); + } else { + result.push({ name: k, sessionId: v }); + } + } + data = result; + } + return data.map((item) => + SessionRef.load(item as Record, context), + ); + } + + static saveRefs( + items: SessionRef[], + context?: SaveContext, + ): Record[] | Record { + if (!context) { + context = new SaveContext(); + } + + // This type doesn't have a 'name' property, so always use array format + return items.map((item) => item.save(context)); + } + + //#endregion + + //#region Save Methods + + save(context?: SaveContext): Record { + let obj: this = this; + if (context) { + obj = context.processObject(obj) as this; + } + + const result: Record = {}; + + if (obj.version !== undefined && obj.version !== null) { + result["version"] = obj.version; + } + if (obj.runtime !== undefined && obj.runtime !== null) { + result["runtime"] = obj.runtime; + } + if (obj.promptyVersion !== undefined && obj.promptyVersion !== null) { + result["promptyVersion"] = obj.promptyVersion; + } + if (obj.sessionId !== undefined && obj.sessionId !== null) { + result["sessionId"] = obj.sessionId; + } + if (obj.events !== undefined && obj.events !== null) { + result["events"] = SessionTrace.saveEvents(obj.events, context); + } + if (obj.turns !== undefined && obj.turns !== null) { + result["turns"] = SessionTrace.saveTurns(obj.turns, context); + } + if (obj.checkpoints !== undefined && obj.checkpoints !== null) { + result["checkpoints"] = SessionTrace.saveCheckpoints( + obj.checkpoints, + context, + ); + } + if (obj.trajectory !== undefined && obj.trajectory !== null) { + result["trajectory"] = SessionTrace.saveTrajectory( + obj.trajectory, + context, + ); + } + if (obj.files !== undefined && obj.files !== null) { + result["files"] = SessionTrace.saveFiles(obj.files, context); + } + if (obj.refs !== undefined && obj.refs !== null) { + result["refs"] = SessionTrace.saveRefs(obj.refs, context); + } + if (obj.summary !== undefined && obj.summary !== null) { + result["summary"] = obj.summary.save(context); + } + + if (context) { + return context.processDict(result); + } + return result; + } + + toYaml(context?: SaveContext): string { + context = context ?? new SaveContext(); + return context.toYaml(this.save(context)); + } + + toJson(context?: SaveContext, indent: number = 2): string { + context = context ?? new SaveContext(); + return context.toJson(this.save(context), indent); + } + + static fromJson(json: string, context?: LoadContext): SessionTrace { + const data = JSON.parse(json); + return SessionTrace.load(data as Record, context); + } + + static fromYaml(yaml: string, context?: LoadContext): SessionTrace { + const { parse } = require("yaml"); + const data = parse(yaml); + return SessionTrace.load(data as Record, context); + } + + //#endregion +} diff --git a/runtime/typescript/packages/core/src/model/events/session-warning-payload.ts b/runtime/typescript/packages/core/src/model/events/session-warning-payload.ts new file mode 100644 index 00000000..5521086d --- /dev/null +++ b/runtime/typescript/packages/core/src/model/events/session-warning-payload.ts @@ -0,0 +1,100 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. + +import { LoadContext, SaveContext } from "../context"; + +export class SessionWarningPayload { + static readonly shorthandProperty: string | undefined = undefined; + + warningType: string = ""; + message: string = ""; + details?: Record | undefined; + + constructor(init?: Partial) { + this.warningType = init?.warningType ?? ""; + this.message = init?.message ?? ""; + if (init?.details !== undefined) { + this.details = init.details; + } + } + + //#region Load Methods + + static load( + data: Record, + context?: LoadContext, + ): SessionWarningPayload { + if (context) { + data = context.processInput(data) as Record; + } + + const instance = new SessionWarningPayload(); + + if (data["warningType"] !== undefined && data["warningType"] !== null) { + instance.warningType = String(data["warningType"]); + } + if (data["message"] !== undefined && data["message"] !== null) { + instance.message = String(data["message"]); + } + if (data["details"] !== undefined && data["details"] !== null) { + instance.details = data["details"] as Record; + } + + if (context) { + return context.processOutput(instance) as SessionWarningPayload; + } + return instance; + } + + //#endregion + + //#region Save Methods + + save(context?: SaveContext): Record { + let obj: this = this; + if (context) { + obj = context.processObject(obj) as this; + } + + const result: Record = {}; + + if (obj.warningType !== undefined && obj.warningType !== null) { + result["warningType"] = obj.warningType; + } + if (obj.message !== undefined && obj.message !== null) { + result["message"] = obj.message; + } + if (obj.details !== undefined && obj.details !== null) { + result["details"] = obj.details; + } + + if (context) { + return context.processDict(result); + } + return result; + } + + toYaml(context?: SaveContext): string { + context = context ?? new SaveContext(); + return context.toYaml(this.save(context)); + } + + toJson(context?: SaveContext, indent: number = 2): string { + context = context ?? new SaveContext(); + return context.toJson(this.save(context), indent); + } + + static fromJson(json: string, context?: LoadContext): SessionWarningPayload { + const data = JSON.parse(json); + return SessionWarningPayload.load(data as Record, context); + } + + static fromYaml(yaml: string, context?: LoadContext): SessionWarningPayload { + const { parse } = require("yaml"); + const data = parse(yaml); + return SessionWarningPayload.load(data as Record, context); + } + + //#endregion +} diff --git a/runtime/typescript/packages/core/src/model/events/status-event-payload.ts b/runtime/typescript/packages/core/src/model/events/status-event-payload.ts index 6d6cb367..8752ecdf 100644 --- a/runtime/typescript/packages/core/src/model/events/status-event-payload.ts +++ b/runtime/typescript/packages/core/src/model/events/status-event-payload.ts @@ -1,3 +1,4 @@ +// // Copyright (c) Microsoft. All rights reserved. // WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. diff --git a/runtime/typescript/packages/core/src/model/events/stream-chunk.ts b/runtime/typescript/packages/core/src/model/events/stream-chunk.ts index 444bbc1b..0b82aca3 100644 --- a/runtime/typescript/packages/core/src/model/events/stream-chunk.ts +++ b/runtime/typescript/packages/core/src/model/events/stream-chunk.ts @@ -1,3 +1,4 @@ +// // Copyright (c) Microsoft. All rights reserved. // WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. diff --git a/runtime/typescript/packages/core/src/model/events/thinking-event-payload.ts b/runtime/typescript/packages/core/src/model/events/thinking-event-payload.ts index 5bfa3d88..4e77e6f0 100644 --- a/runtime/typescript/packages/core/src/model/events/thinking-event-payload.ts +++ b/runtime/typescript/packages/core/src/model/events/thinking-event-payload.ts @@ -1,3 +1,4 @@ +// // Copyright (c) Microsoft. All rights reserved. // WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. diff --git a/runtime/typescript/packages/core/src/model/events/token-event-payload.ts b/runtime/typescript/packages/core/src/model/events/token-event-payload.ts index 740a04bd..b334e702 100644 --- a/runtime/typescript/packages/core/src/model/events/token-event-payload.ts +++ b/runtime/typescript/packages/core/src/model/events/token-event-payload.ts @@ -1,3 +1,4 @@ +// // Copyright (c) Microsoft. All rights reserved. // WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. diff --git a/runtime/typescript/packages/core/src/model/events/tool-call-complete-payload.ts b/runtime/typescript/packages/core/src/model/events/tool-call-complete-payload.ts new file mode 100644 index 00000000..18e2a870 --- /dev/null +++ b/runtime/typescript/packages/core/src/model/events/tool-call-complete-payload.ts @@ -0,0 +1,146 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. + +import { LoadContext, SaveContext } from "../context"; +import { ToolResult } from "../conversation/tool-result"; + +export class ToolCallCompletePayload { + static readonly shorthandProperty: string | undefined = undefined; + + id?: string | undefined; + name: string = ""; + success: boolean = false; + result?: ToolResult | undefined; + durationMs?: number | undefined; + errorKind?: string | undefined; + + constructor(init?: Partial) { + if (init?.id !== undefined) { + this.id = init.id; + } + this.name = init?.name ?? ""; + this.success = init?.success ?? false; + if (init?.result !== undefined) { + this.result = init.result; + } + if (init?.durationMs !== undefined) { + this.durationMs = init.durationMs; + } + if (init?.errorKind !== undefined) { + this.errorKind = init.errorKind; + } + } + + //#region Load Methods + + static load( + data: Record, + context?: LoadContext, + ): ToolCallCompletePayload { + if (context) { + data = context.processInput(data) as Record; + } + + const instance = new ToolCallCompletePayload(); + + if (data["id"] !== undefined && data["id"] !== null) { + instance.id = String(data["id"]); + } + if (data["name"] !== undefined && data["name"] !== null) { + instance.name = String(data["name"]); + } + if (data["success"] !== undefined && data["success"] !== null) { + instance.success = Boolean(data["success"]); + } + if (data["result"] !== undefined && data["result"] !== null) { + instance.result = ToolResult.load( + data["result"] as Record, + context, + ); + } + if (data["durationMs"] !== undefined && data["durationMs"] !== null) { + instance.durationMs = Number(data["durationMs"]); + } + if (data["errorKind"] !== undefined && data["errorKind"] !== null) { + instance.errorKind = String(data["errorKind"]); + } + + if (context) { + return context.processOutput(instance) as ToolCallCompletePayload; + } + return instance; + } + + //#endregion + + //#region Save Methods + + save(context?: SaveContext): Record { + let obj: this = this; + if (context) { + obj = context.processObject(obj) as this; + } + + const result: Record = {}; + + if (obj.id !== undefined && obj.id !== null) { + result["id"] = obj.id; + } + if (obj.name !== undefined && obj.name !== null) { + result["name"] = obj.name; + } + if (obj.success !== undefined && obj.success !== null) { + result["success"] = obj.success; + } + if (obj.result !== undefined && obj.result !== null) { + result["result"] = obj.result.save(context); + } + if (obj.durationMs !== undefined && obj.durationMs !== null) { + result["durationMs"] = obj.durationMs; + } + if (obj.errorKind !== undefined && obj.errorKind !== null) { + result["errorKind"] = obj.errorKind; + } + + if (context) { + return context.processDict(result); + } + return result; + } + + toYaml(context?: SaveContext): string { + context = context ?? new SaveContext(); + return context.toYaml(this.save(context)); + } + + toJson(context?: SaveContext, indent: number = 2): string { + context = context ?? new SaveContext(); + return context.toJson(this.save(context), indent); + } + + static fromJson( + json: string, + context?: LoadContext, + ): ToolCallCompletePayload { + const data = JSON.parse(json); + return ToolCallCompletePayload.load( + data as Record, + context, + ); + } + + static fromYaml( + yaml: string, + context?: LoadContext, + ): ToolCallCompletePayload { + const { parse } = require("yaml"); + const data = parse(yaml); + return ToolCallCompletePayload.load( + data as Record, + context, + ); + } + + //#endregion +} diff --git a/runtime/typescript/packages/core/src/model/events/tool-call-start-payload.ts b/runtime/typescript/packages/core/src/model/events/tool-call-start-payload.ts index 2d66b815..23ae073e 100644 --- a/runtime/typescript/packages/core/src/model/events/tool-call-start-payload.ts +++ b/runtime/typescript/packages/core/src/model/events/tool-call-start-payload.ts @@ -1,3 +1,4 @@ +// // Copyright (c) Microsoft. All rights reserved. // WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. @@ -6,10 +7,14 @@ import { LoadContext, SaveContext } from "../context"; export class ToolCallStartPayload { static readonly shorthandProperty: string | undefined = undefined; + id?: string | undefined; name: string = ""; arguments: string = ""; constructor(init?: Partial) { + if (init?.id !== undefined) { + this.id = init.id; + } this.name = init?.name ?? ""; this.arguments = init?.arguments ?? ""; } @@ -26,6 +31,9 @@ export class ToolCallStartPayload { const instance = new ToolCallStartPayload(); + if (data["id"] !== undefined && data["id"] !== null) { + instance.id = String(data["id"]); + } if (data["name"] !== undefined && data["name"] !== null) { instance.name = String(data["name"]); } @@ -51,6 +59,9 @@ export class ToolCallStartPayload { const result: Record = {}; + if (obj.id !== undefined && obj.id !== null) { + result["id"] = obj.id; + } if (obj.name !== undefined && obj.name !== null) { result["name"] = obj.name; } diff --git a/runtime/typescript/packages/core/src/model/events/tool-execution-complete-payload.ts b/runtime/typescript/packages/core/src/model/events/tool-execution-complete-payload.ts new file mode 100644 index 00000000..07e6edca --- /dev/null +++ b/runtime/typescript/packages/core/src/model/events/tool-execution-complete-payload.ts @@ -0,0 +1,186 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. + +import { LoadContext, SaveContext } from "../context"; +import { RedactionMetadata } from "./redaction-metadata"; + +export class ToolExecutionCompletePayload { + static readonly shorthandProperty: string | undefined = undefined; + + requestId?: string | undefined; + toolCallId?: string | undefined; + toolName: string = ""; + success: boolean = false; + result?: unknown | undefined; + exitCode?: number | undefined; + durationMs?: number | undefined; + errorKind?: string | undefined; + telemetry?: Record | undefined; + redaction?: RedactionMetadata | undefined; + + constructor(init?: Partial) { + if (init?.requestId !== undefined) { + this.requestId = init.requestId; + } + if (init?.toolCallId !== undefined) { + this.toolCallId = init.toolCallId; + } + this.toolName = init?.toolName ?? ""; + this.success = init?.success ?? false; + if (init?.result !== undefined) { + this.result = init.result; + } + if (init?.exitCode !== undefined) { + this.exitCode = init.exitCode; + } + if (init?.durationMs !== undefined) { + this.durationMs = init.durationMs; + } + if (init?.errorKind !== undefined) { + this.errorKind = init.errorKind; + } + if (init?.telemetry !== undefined) { + this.telemetry = init.telemetry; + } + if (init?.redaction !== undefined) { + this.redaction = init.redaction; + } + } + + //#region Load Methods + + static load( + data: Record, + context?: LoadContext, + ): ToolExecutionCompletePayload { + if (context) { + data = context.processInput(data) as Record; + } + + const instance = new ToolExecutionCompletePayload(); + + if (data["requestId"] !== undefined && data["requestId"] !== null) { + instance.requestId = String(data["requestId"]); + } + if (data["toolCallId"] !== undefined && data["toolCallId"] !== null) { + instance.toolCallId = String(data["toolCallId"]); + } + if (data["toolName"] !== undefined && data["toolName"] !== null) { + instance.toolName = String(data["toolName"]); + } + if (data["success"] !== undefined && data["success"] !== null) { + instance.success = Boolean(data["success"]); + } + if (data["result"] !== undefined && data["result"] !== null) { + instance.result = data["result"] as unknown; + } + if (data["exitCode"] !== undefined && data["exitCode"] !== null) { + instance.exitCode = Number(data["exitCode"]); + } + if (data["durationMs"] !== undefined && data["durationMs"] !== null) { + instance.durationMs = Number(data["durationMs"]); + } + if (data["errorKind"] !== undefined && data["errorKind"] !== null) { + instance.errorKind = String(data["errorKind"]); + } + if (data["telemetry"] !== undefined && data["telemetry"] !== null) { + instance.telemetry = data["telemetry"] as Record; + } + if (data["redaction"] !== undefined && data["redaction"] !== null) { + instance.redaction = RedactionMetadata.load( + data["redaction"] as Record, + context, + ); + } + + if (context) { + return context.processOutput(instance) as ToolExecutionCompletePayload; + } + return instance; + } + + //#endregion + + //#region Save Methods + + save(context?: SaveContext): Record { + let obj: this = this; + if (context) { + obj = context.processObject(obj) as this; + } + + const result: Record = {}; + + if (obj.requestId !== undefined && obj.requestId !== null) { + result["requestId"] = obj.requestId; + } + if (obj.toolCallId !== undefined && obj.toolCallId !== null) { + result["toolCallId"] = obj.toolCallId; + } + if (obj.toolName !== undefined && obj.toolName !== null) { + result["toolName"] = obj.toolName; + } + if (obj.success !== undefined && obj.success !== null) { + result["success"] = obj.success; + } + if (obj.result !== undefined && obj.result !== null) { + result["result"] = obj.result; + } + if (obj.exitCode !== undefined && obj.exitCode !== null) { + result["exitCode"] = obj.exitCode; + } + if (obj.durationMs !== undefined && obj.durationMs !== null) { + result["durationMs"] = obj.durationMs; + } + if (obj.errorKind !== undefined && obj.errorKind !== null) { + result["errorKind"] = obj.errorKind; + } + if (obj.telemetry !== undefined && obj.telemetry !== null) { + result["telemetry"] = obj.telemetry; + } + if (obj.redaction !== undefined && obj.redaction !== null) { + result["redaction"] = obj.redaction.save(context); + } + + if (context) { + return context.processDict(result); + } + return result; + } + + toYaml(context?: SaveContext): string { + context = context ?? new SaveContext(); + return context.toYaml(this.save(context)); + } + + toJson(context?: SaveContext, indent: number = 2): string { + context = context ?? new SaveContext(); + return context.toJson(this.save(context), indent); + } + + static fromJson( + json: string, + context?: LoadContext, + ): ToolExecutionCompletePayload { + const data = JSON.parse(json); + return ToolExecutionCompletePayload.load( + data as Record, + context, + ); + } + + static fromYaml( + yaml: string, + context?: LoadContext, + ): ToolExecutionCompletePayload { + const { parse } = require("yaml"); + const data = parse(yaml); + return ToolExecutionCompletePayload.load( + data as Record, + context, + ); + } + + //#endregion +} diff --git a/runtime/typescript/packages/core/src/model/events/tool-execution-start-payload.ts b/runtime/typescript/packages/core/src/model/events/tool-execution-start-payload.ts new file mode 100644 index 00000000..a3641573 --- /dev/null +++ b/runtime/typescript/packages/core/src/model/events/tool-execution-start-payload.ts @@ -0,0 +1,151 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. + +import { LoadContext, SaveContext } from "../context"; +import { RedactionMetadata } from "./redaction-metadata"; + +export class ToolExecutionStartPayload { + static readonly shorthandProperty: string | undefined = undefined; + + requestId?: string | undefined; + toolCallId?: string | undefined; + toolName: string = ""; + arguments?: Record | undefined; + workingDirectory?: string | undefined; + redaction?: RedactionMetadata | undefined; + + constructor(init?: Partial) { + if (init?.requestId !== undefined) { + this.requestId = init.requestId; + } + if (init?.toolCallId !== undefined) { + this.toolCallId = init.toolCallId; + } + this.toolName = init?.toolName ?? ""; + if (init?.arguments !== undefined) { + this.arguments = init.arguments; + } + if (init?.workingDirectory !== undefined) { + this.workingDirectory = init.workingDirectory; + } + if (init?.redaction !== undefined) { + this.redaction = init.redaction; + } + } + + //#region Load Methods + + static load( + data: Record, + context?: LoadContext, + ): ToolExecutionStartPayload { + if (context) { + data = context.processInput(data) as Record; + } + + const instance = new ToolExecutionStartPayload(); + + if (data["requestId"] !== undefined && data["requestId"] !== null) { + instance.requestId = String(data["requestId"]); + } + if (data["toolCallId"] !== undefined && data["toolCallId"] !== null) { + instance.toolCallId = String(data["toolCallId"]); + } + if (data["toolName"] !== undefined && data["toolName"] !== null) { + instance.toolName = String(data["toolName"]); + } + if (data["arguments"] !== undefined && data["arguments"] !== null) { + instance.arguments = data["arguments"] as Record; + } + if ( + data["workingDirectory"] !== undefined && + data["workingDirectory"] !== null + ) { + instance.workingDirectory = String(data["workingDirectory"]); + } + if (data["redaction"] !== undefined && data["redaction"] !== null) { + instance.redaction = RedactionMetadata.load( + data["redaction"] as Record, + context, + ); + } + + if (context) { + return context.processOutput(instance) as ToolExecutionStartPayload; + } + return instance; + } + + //#endregion + + //#region Save Methods + + save(context?: SaveContext): Record { + let obj: this = this; + if (context) { + obj = context.processObject(obj) as this; + } + + const result: Record = {}; + + if (obj.requestId !== undefined && obj.requestId !== null) { + result["requestId"] = obj.requestId; + } + if (obj.toolCallId !== undefined && obj.toolCallId !== null) { + result["toolCallId"] = obj.toolCallId; + } + if (obj.toolName !== undefined && obj.toolName !== null) { + result["toolName"] = obj.toolName; + } + if (obj.arguments !== undefined && obj.arguments !== null) { + result["arguments"] = obj.arguments; + } + if (obj.workingDirectory !== undefined && obj.workingDirectory !== null) { + result["workingDirectory"] = obj.workingDirectory; + } + if (obj.redaction !== undefined && obj.redaction !== null) { + result["redaction"] = obj.redaction.save(context); + } + + if (context) { + return context.processDict(result); + } + return result; + } + + toYaml(context?: SaveContext): string { + context = context ?? new SaveContext(); + return context.toYaml(this.save(context)); + } + + toJson(context?: SaveContext, indent: number = 2): string { + context = context ?? new SaveContext(); + return context.toJson(this.save(context), indent); + } + + static fromJson( + json: string, + context?: LoadContext, + ): ToolExecutionStartPayload { + const data = JSON.parse(json); + return ToolExecutionStartPayload.load( + data as Record, + context, + ); + } + + static fromYaml( + yaml: string, + context?: LoadContext, + ): ToolExecutionStartPayload { + const { parse } = require("yaml"); + const data = parse(yaml); + return ToolExecutionStartPayload.load( + data as Record, + context, + ); + } + + //#endregion +} diff --git a/runtime/typescript/packages/core/src/model/events/tool-result-payload.ts b/runtime/typescript/packages/core/src/model/events/tool-result-payload.ts index 9c06e9b8..450d8894 100644 --- a/runtime/typescript/packages/core/src/model/events/tool-result-payload.ts +++ b/runtime/typescript/packages/core/src/model/events/tool-result-payload.ts @@ -1,3 +1,4 @@ +// // Copyright (c) Microsoft. All rights reserved. // WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. diff --git a/runtime/typescript/packages/core/src/model/events/trajectory-event.ts b/runtime/typescript/packages/core/src/model/events/trajectory-event.ts new file mode 100644 index 00000000..f32a2a29 --- /dev/null +++ b/runtime/typescript/packages/core/src/model/events/trajectory-event.ts @@ -0,0 +1,166 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. + +import { LoadContext, SaveContext } from "../context"; +import { RedactionMetadata } from "./redaction-metadata"; + +export class TrajectoryEvent { + static readonly shorthandProperty: string | undefined = undefined; + + id?: string | undefined; + sessionId?: string | undefined; + turnId?: string | undefined; + toolCallId?: string | undefined; + turnIndex?: number | undefined; + eventType: string = ""; + data?: Record | undefined; + createdAt?: string | undefined; + redaction?: RedactionMetadata | undefined; + + constructor(init?: Partial) { + if (init?.id !== undefined) { + this.id = init.id; + } + if (init?.sessionId !== undefined) { + this.sessionId = init.sessionId; + } + if (init?.turnId !== undefined) { + this.turnId = init.turnId; + } + if (init?.toolCallId !== undefined) { + this.toolCallId = init.toolCallId; + } + if (init?.turnIndex !== undefined) { + this.turnIndex = init.turnIndex; + } + this.eventType = init?.eventType ?? ""; + if (init?.data !== undefined) { + this.data = init.data; + } + if (init?.createdAt !== undefined) { + this.createdAt = init.createdAt; + } + if (init?.redaction !== undefined) { + this.redaction = init.redaction; + } + } + + //#region Load Methods + + static load( + data: Record, + context?: LoadContext, + ): TrajectoryEvent { + if (context) { + data = context.processInput(data) as Record; + } + + const instance = new TrajectoryEvent(); + + if (data["id"] !== undefined && data["id"] !== null) { + instance.id = String(data["id"]); + } + if (data["sessionId"] !== undefined && data["sessionId"] !== null) { + instance.sessionId = String(data["sessionId"]); + } + if (data["turnId"] !== undefined && data["turnId"] !== null) { + instance.turnId = String(data["turnId"]); + } + if (data["toolCallId"] !== undefined && data["toolCallId"] !== null) { + instance.toolCallId = String(data["toolCallId"]); + } + if (data["turnIndex"] !== undefined && data["turnIndex"] !== null) { + instance.turnIndex = Number(data["turnIndex"]); + } + if (data["eventType"] !== undefined && data["eventType"] !== null) { + instance.eventType = String(data["eventType"]); + } + if (data["data"] !== undefined && data["data"] !== null) { + instance.data = data["data"] as Record; + } + if (data["createdAt"] !== undefined && data["createdAt"] !== null) { + instance.createdAt = String(data["createdAt"]); + } + if (data["redaction"] !== undefined && data["redaction"] !== null) { + instance.redaction = RedactionMetadata.load( + data["redaction"] as Record, + context, + ); + } + + if (context) { + return context.processOutput(instance) as TrajectoryEvent; + } + return instance; + } + + //#endregion + + //#region Save Methods + + save(context?: SaveContext): Record { + let obj: this = this; + if (context) { + obj = context.processObject(obj) as this; + } + + const result: Record = {}; + + if (obj.id !== undefined && obj.id !== null) { + result["id"] = obj.id; + } + if (obj.sessionId !== undefined && obj.sessionId !== null) { + result["sessionId"] = obj.sessionId; + } + if (obj.turnId !== undefined && obj.turnId !== null) { + result["turnId"] = obj.turnId; + } + if (obj.toolCallId !== undefined && obj.toolCallId !== null) { + result["toolCallId"] = obj.toolCallId; + } + if (obj.turnIndex !== undefined && obj.turnIndex !== null) { + result["turnIndex"] = obj.turnIndex; + } + if (obj.eventType !== undefined && obj.eventType !== null) { + result["eventType"] = obj.eventType; + } + if (obj.data !== undefined && obj.data !== null) { + result["data"] = obj.data; + } + if (obj.createdAt !== undefined && obj.createdAt !== null) { + result["createdAt"] = obj.createdAt; + } + if (obj.redaction !== undefined && obj.redaction !== null) { + result["redaction"] = obj.redaction.save(context); + } + + if (context) { + return context.processDict(result); + } + return result; + } + + toYaml(context?: SaveContext): string { + context = context ?? new SaveContext(); + return context.toYaml(this.save(context)); + } + + toJson(context?: SaveContext, indent: number = 2): string { + context = context ?? new SaveContext(); + return context.toJson(this.save(context), indent); + } + + static fromJson(json: string, context?: LoadContext): TrajectoryEvent { + const data = JSON.parse(json); + return TrajectoryEvent.load(data as Record, context); + } + + static fromYaml(yaml: string, context?: LoadContext): TrajectoryEvent { + const { parse } = require("yaml"); + const data = parse(yaml); + return TrajectoryEvent.load(data as Record, context); + } + + //#endregion +} diff --git a/runtime/typescript/packages/core/src/model/events/turn-end-payload.ts b/runtime/typescript/packages/core/src/model/events/turn-end-payload.ts new file mode 100644 index 00000000..2d3266c0 --- /dev/null +++ b/runtime/typescript/packages/core/src/model/events/turn-end-payload.ts @@ -0,0 +1,116 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. + +import { LoadContext, SaveContext } from "../context"; + +export type TurnStatus = "success" | "error" | "cancelled"; + +export class TurnEndPayload { + static readonly shorthandProperty: string | undefined = undefined; + + iterations?: number | undefined; + status?: TurnStatus | undefined; + response?: unknown | undefined; + durationMs?: number | undefined; + + constructor(init?: Partial) { + if (init?.iterations !== undefined) { + this.iterations = init.iterations; + } + if (init?.status !== undefined) { + this.status = init.status; + } + if (init?.response !== undefined) { + this.response = init.response; + } + if (init?.durationMs !== undefined) { + this.durationMs = init.durationMs; + } + } + + //#region Load Methods + + static load( + data: Record, + context?: LoadContext, + ): TurnEndPayload { + if (context) { + data = context.processInput(data) as Record; + } + + const instance = new TurnEndPayload(); + + if (data["iterations"] !== undefined && data["iterations"] !== null) { + instance.iterations = Number(data["iterations"]); + } + if (data["status"] !== undefined && data["status"] !== null) { + instance.status = String(data["status"]) as TurnStatus; + } + if (data["response"] !== undefined && data["response"] !== null) { + instance.response = data["response"] as unknown; + } + if (data["durationMs"] !== undefined && data["durationMs"] !== null) { + instance.durationMs = Number(data["durationMs"]); + } + + if (context) { + return context.processOutput(instance) as TurnEndPayload; + } + return instance; + } + + //#endregion + + //#region Save Methods + + save(context?: SaveContext): Record { + let obj: this = this; + if (context) { + obj = context.processObject(obj) as this; + } + + const result: Record = {}; + + if (obj.iterations !== undefined && obj.iterations !== null) { + result["iterations"] = obj.iterations; + } + if (obj.status !== undefined && obj.status !== null) { + result["status"] = obj.status; + } + if (obj.response !== undefined && obj.response !== null) { + result["response"] = obj.response; + } + if (obj.durationMs !== undefined && obj.durationMs !== null) { + result["durationMs"] = obj.durationMs; + } + + if (context) { + return context.processDict(result); + } + return result; + } + + toYaml(context?: SaveContext): string { + context = context ?? new SaveContext(); + return context.toYaml(this.save(context)); + } + + toJson(context?: SaveContext, indent: number = 2): string { + context = context ?? new SaveContext(); + return context.toJson(this.save(context), indent); + } + + static fromJson(json: string, context?: LoadContext): TurnEndPayload { + const data = JSON.parse(json); + return TurnEndPayload.load(data as Record, context); + } + + static fromYaml(yaml: string, context?: LoadContext): TurnEndPayload { + const { parse } = require("yaml"); + const data = parse(yaml); + return TurnEndPayload.load(data as Record, context); + } + + //#endregion +} diff --git a/runtime/typescript/packages/core/src/model/events/turn-event.ts b/runtime/typescript/packages/core/src/model/events/turn-event.ts new file mode 100644 index 00000000..8a68d6d5 --- /dev/null +++ b/runtime/typescript/packages/core/src/model/events/turn-event.ts @@ -0,0 +1,169 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. + +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 class TurnEvent { + static readonly shorthandProperty: string | undefined = undefined; + + id: string = ""; + type: TurnEventType = "turn_start"; + timestamp: string = ""; + turnId?: string | undefined; + iteration?: number | undefined; + parentId?: string | undefined; + spanId?: string | undefined; + payload: Record = {}; + + constructor(init?: Partial) { + this.id = init?.id ?? ""; + this.type = init?.type ?? "turn_start"; + this.timestamp = init?.timestamp ?? ""; + if (init?.turnId !== undefined) { + this.turnId = init.turnId; + } + if (init?.iteration !== undefined) { + this.iteration = init.iteration; + } + if (init?.parentId !== undefined) { + this.parentId = init.parentId; + } + if (init?.spanId !== undefined) { + this.spanId = init.spanId; + } + this.payload = init?.payload ?? {}; + } + + //#region Load Methods + + static load(data: Record, context?: LoadContext): TurnEvent { + if (context) { + data = context.processInput(data) as Record; + } + + const instance = new TurnEvent(); + + if (data["id"] !== undefined && data["id"] !== null) { + instance.id = String(data["id"]); + } + if (data["type"] !== undefined && data["type"] !== null) { + instance.type = String(data["type"]) as TurnEventType; + } + if (data["timestamp"] !== undefined && data["timestamp"] !== null) { + instance.timestamp = String(data["timestamp"]); + } + if (data["turnId"] !== undefined && data["turnId"] !== null) { + instance.turnId = String(data["turnId"]); + } + if (data["iteration"] !== undefined && data["iteration"] !== null) { + instance.iteration = Number(data["iteration"]); + } + if (data["parentId"] !== undefined && data["parentId"] !== null) { + instance.parentId = String(data["parentId"]); + } + if (data["spanId"] !== undefined && data["spanId"] !== null) { + instance.spanId = String(data["spanId"]); + } + if (data["payload"] !== undefined && data["payload"] !== null) { + instance.payload = data["payload"] as Record; + } + + if (context) { + return context.processOutput(instance) as TurnEvent; + } + return instance; + } + + //#endregion + + //#region Save Methods + + save(context?: SaveContext): Record { + let obj: this = this; + if (context) { + obj = context.processObject(obj) as this; + } + + const result: Record = {}; + + if (obj.id !== undefined && obj.id !== null) { + result["id"] = obj.id; + } + if (obj.type !== undefined && obj.type !== null) { + result["type"] = obj.type; + } + if (obj.timestamp !== undefined && obj.timestamp !== null) { + result["timestamp"] = obj.timestamp; + } + if (obj.turnId !== undefined && obj.turnId !== null) { + result["turnId"] = obj.turnId; + } + if (obj.iteration !== undefined && obj.iteration !== null) { + result["iteration"] = obj.iteration; + } + if (obj.parentId !== undefined && obj.parentId !== null) { + result["parentId"] = obj.parentId; + } + if (obj.spanId !== undefined && obj.spanId !== null) { + result["spanId"] = obj.spanId; + } + if (obj.payload !== undefined && obj.payload !== null) { + result["payload"] = obj.payload; + } + + if (context) { + return context.processDict(result); + } + return result; + } + + toYaml(context?: SaveContext): string { + context = context ?? new SaveContext(); + return context.toYaml(this.save(context)); + } + + toJson(context?: SaveContext, indent: number = 2): string { + context = context ?? new SaveContext(); + return context.toJson(this.save(context), indent); + } + + static fromJson(json: string, context?: LoadContext): TurnEvent { + const data = JSON.parse(json); + return TurnEvent.load(data as Record, context); + } + + static fromYaml(yaml: string, context?: LoadContext): TurnEvent { + const { parse } = require("yaml"); + const data = parse(yaml); + return TurnEvent.load(data as Record, context); + } + + //#endregion +} diff --git a/runtime/typescript/packages/core/src/model/events/turn-start-payload.ts b/runtime/typescript/packages/core/src/model/events/turn-start-payload.ts new file mode 100644 index 00000000..f2094217 --- /dev/null +++ b/runtime/typescript/packages/core/src/model/events/turn-start-payload.ts @@ -0,0 +1,104 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. + +import { LoadContext, SaveContext } from "../context"; + +export class TurnStartPayload { + static readonly shorthandProperty: string | undefined = undefined; + + agent?: string | undefined; + inputs?: Record | undefined; + maxIterations?: number | undefined; + + constructor(init?: Partial) { + if (init?.agent !== undefined) { + this.agent = init.agent; + } + if (init?.inputs !== undefined) { + this.inputs = init.inputs; + } + if (init?.maxIterations !== undefined) { + this.maxIterations = init.maxIterations; + } + } + + //#region Load Methods + + static load( + data: Record, + context?: LoadContext, + ): TurnStartPayload { + if (context) { + data = context.processInput(data) as Record; + } + + const instance = new TurnStartPayload(); + + if (data["agent"] !== undefined && data["agent"] !== null) { + instance.agent = String(data["agent"]); + } + if (data["inputs"] !== undefined && data["inputs"] !== null) { + instance.inputs = data["inputs"] as Record; + } + if (data["maxIterations"] !== undefined && data["maxIterations"] !== null) { + instance.maxIterations = Number(data["maxIterations"]); + } + + if (context) { + return context.processOutput(instance) as TurnStartPayload; + } + return instance; + } + + //#endregion + + //#region Save Methods + + save(context?: SaveContext): Record { + let obj: this = this; + if (context) { + obj = context.processObject(obj) as this; + } + + const result: Record = {}; + + if (obj.agent !== undefined && obj.agent !== null) { + result["agent"] = obj.agent; + } + if (obj.inputs !== undefined && obj.inputs !== null) { + result["inputs"] = obj.inputs; + } + if (obj.maxIterations !== undefined && obj.maxIterations !== null) { + result["maxIterations"] = obj.maxIterations; + } + + if (context) { + return context.processDict(result); + } + return result; + } + + toYaml(context?: SaveContext): string { + context = context ?? new SaveContext(); + return context.toYaml(this.save(context)); + } + + toJson(context?: SaveContext, indent: number = 2): string { + context = context ?? new SaveContext(); + return context.toJson(this.save(context), indent); + } + + static fromJson(json: string, context?: LoadContext): TurnStartPayload { + const data = JSON.parse(json); + return TurnStartPayload.load(data as Record, context); + } + + static fromYaml(yaml: string, context?: LoadContext): TurnStartPayload { + const { parse } = require("yaml"); + const data = parse(yaml); + return TurnStartPayload.load(data as Record, context); + } + + //#endregion +} diff --git a/runtime/typescript/packages/core/src/model/events/turn-summary.ts b/runtime/typescript/packages/core/src/model/events/turn-summary.ts new file mode 100644 index 00000000..1dce7ee2 --- /dev/null +++ b/runtime/typescript/packages/core/src/model/events/turn-summary.ts @@ -0,0 +1,152 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. + +import { LoadContext, SaveContext } from "../context"; +import { TokenUsage } from "../model/token-usage"; + +export class TurnSummary { + static readonly shorthandProperty: string | undefined = undefined; + + turnId: string = ""; + status: string = ""; + iterations: number = 0; + llmCalls?: number | undefined; + toolCalls?: number | undefined; + retries?: number | undefined; + usage?: TokenUsage | undefined; + durationMs?: number | undefined; + + constructor(init?: Partial) { + this.turnId = init?.turnId ?? ""; + this.status = init?.status ?? ""; + this.iterations = init?.iterations ?? 0; + if (init?.llmCalls !== undefined) { + this.llmCalls = init.llmCalls; + } + if (init?.toolCalls !== undefined) { + this.toolCalls = init.toolCalls; + } + if (init?.retries !== undefined) { + this.retries = init.retries; + } + if (init?.usage !== undefined) { + this.usage = init.usage; + } + if (init?.durationMs !== undefined) { + this.durationMs = init.durationMs; + } + } + + //#region Load Methods + + static load( + data: Record, + context?: LoadContext, + ): TurnSummary { + if (context) { + data = context.processInput(data) as Record; + } + + const instance = new TurnSummary(); + + if (data["turnId"] !== undefined && data["turnId"] !== null) { + instance.turnId = String(data["turnId"]); + } + if (data["status"] !== undefined && data["status"] !== null) { + instance.status = String(data["status"]); + } + if (data["iterations"] !== undefined && data["iterations"] !== null) { + instance.iterations = Number(data["iterations"]); + } + if (data["llmCalls"] !== undefined && data["llmCalls"] !== null) { + instance.llmCalls = Number(data["llmCalls"]); + } + if (data["toolCalls"] !== undefined && data["toolCalls"] !== null) { + instance.toolCalls = Number(data["toolCalls"]); + } + if (data["retries"] !== undefined && data["retries"] !== null) { + instance.retries = Number(data["retries"]); + } + if (data["usage"] !== undefined && data["usage"] !== null) { + instance.usage = TokenUsage.load( + data["usage"] as Record, + context, + ); + } + if (data["durationMs"] !== undefined && data["durationMs"] !== null) { + instance.durationMs = Number(data["durationMs"]); + } + + if (context) { + return context.processOutput(instance) as TurnSummary; + } + return instance; + } + + //#endregion + + //#region Save Methods + + save(context?: SaveContext): Record { + let obj: this = this; + if (context) { + obj = context.processObject(obj) as this; + } + + const result: Record = {}; + + if (obj.turnId !== undefined && obj.turnId !== null) { + result["turnId"] = obj.turnId; + } + if (obj.status !== undefined && obj.status !== null) { + result["status"] = obj.status; + } + if (obj.iterations !== undefined && obj.iterations !== null) { + result["iterations"] = obj.iterations; + } + if (obj.llmCalls !== undefined && obj.llmCalls !== null) { + result["llmCalls"] = obj.llmCalls; + } + if (obj.toolCalls !== undefined && obj.toolCalls !== null) { + result["toolCalls"] = obj.toolCalls; + } + if (obj.retries !== undefined && obj.retries !== null) { + result["retries"] = obj.retries; + } + if (obj.usage !== undefined && obj.usage !== null) { + result["usage"] = obj.usage.save(context); + } + if (obj.durationMs !== undefined && obj.durationMs !== null) { + result["durationMs"] = obj.durationMs; + } + + if (context) { + return context.processDict(result); + } + return result; + } + + toYaml(context?: SaveContext): string { + context = context ?? new SaveContext(); + return context.toYaml(this.save(context)); + } + + toJson(context?: SaveContext, indent: number = 2): string { + context = context ?? new SaveContext(); + return context.toJson(this.save(context), indent); + } + + static fromJson(json: string, context?: LoadContext): TurnSummary { + const data = JSON.parse(json); + return TurnSummary.load(data as Record, context); + } + + static fromYaml(yaml: string, context?: LoadContext): TurnSummary { + const { parse } = require("yaml"); + const data = parse(yaml); + return TurnSummary.load(data as Record, context); + } + + //#endregion +} diff --git a/runtime/typescript/packages/core/src/model/events/turn-trace.ts b/runtime/typescript/packages/core/src/model/events/turn-trace.ts new file mode 100644 index 00000000..786f954c --- /dev/null +++ b/runtime/typescript/packages/core/src/model/events/turn-trace.ts @@ -0,0 +1,161 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. + +import { LoadContext, SaveContext } from "../context"; +import { TurnEvent } from "./turn-event"; +import { TurnSummary } from "./turn-summary"; + +export class TurnTrace { + static readonly shorthandProperty: string | undefined = undefined; + + version: string = "1"; + runtime?: string | undefined; + promptyVersion?: string | undefined; + events: TurnEvent[] = []; + summary?: TurnSummary | undefined; + + constructor(init?: Partial) { + this.version = init?.version ?? "1"; + if (init?.runtime !== undefined) { + this.runtime = init.runtime; + } + if (init?.promptyVersion !== undefined) { + this.promptyVersion = init.promptyVersion; + } + this.events = init?.events ?? []; + if (init?.summary !== undefined) { + this.summary = init.summary; + } + } + + //#region Load Methods + + static load(data: Record, context?: LoadContext): TurnTrace { + if (context) { + data = context.processInput(data) as Record; + } + + const instance = new TurnTrace(); + + if (data["version"] !== undefined && data["version"] !== null) { + instance.version = String(data["version"]); + } + if (data["runtime"] !== undefined && data["runtime"] !== null) { + instance.runtime = String(data["runtime"]); + } + if ( + data["promptyVersion"] !== undefined && + data["promptyVersion"] !== null + ) { + instance.promptyVersion = String(data["promptyVersion"]); + } + if (data["events"] !== undefined && data["events"] !== null) { + instance.events = TurnTrace.loadEvents( + data["events"] as unknown[], + context, + ); + } + if (data["summary"] !== undefined && data["summary"] !== null) { + instance.summary = TurnSummary.load( + data["summary"] as Record, + context, + ); + } + + if (context) { + return context.processOutput(instance) as TurnTrace; + } + return instance; + } + + static loadEvents( + data: Record[] | unknown[], + context?: LoadContext, + ): TurnEvent[] { + if (!Array.isArray(data)) { + // Convert dict/object format to array format + const result: Record[] = []; + for (const [k, v] of Object.entries(data)) { + if (typeof v === "object" && v !== null && !Array.isArray(v)) { + result.push({ name: k, ...(v as Record) }); + } else { + result.push({ name: k, id: v }); + } + } + data = result; + } + return data.map((item) => + TurnEvent.load(item as Record, context), + ); + } + + static saveEvents( + items: TurnEvent[], + context?: SaveContext, + ): Record[] | Record { + if (!context) { + context = new SaveContext(); + } + + // This type doesn't have a 'name' property, so always use array format + return items.map((item) => item.save(context)); + } + + //#endregion + + //#region Save Methods + + save(context?: SaveContext): Record { + let obj: this = this; + if (context) { + obj = context.processObject(obj) as this; + } + + const result: Record = {}; + + if (obj.version !== undefined && obj.version !== null) { + result["version"] = obj.version; + } + if (obj.runtime !== undefined && obj.runtime !== null) { + result["runtime"] = obj.runtime; + } + if (obj.promptyVersion !== undefined && obj.promptyVersion !== null) { + result["promptyVersion"] = obj.promptyVersion; + } + if (obj.events !== undefined && obj.events !== null) { + result["events"] = TurnTrace.saveEvents(obj.events, context); + } + if (obj.summary !== undefined && obj.summary !== null) { + result["summary"] = obj.summary.save(context); + } + + if (context) { + return context.processDict(result); + } + return result; + } + + toYaml(context?: SaveContext): string { + context = context ?? new SaveContext(); + return context.toYaml(this.save(context)); + } + + toJson(context?: SaveContext, indent: number = 2): string { + context = context ?? new SaveContext(); + return context.toJson(this.save(context), indent); + } + + static fromJson(json: string, context?: LoadContext): TurnTrace { + const data = JSON.parse(json); + return TurnTrace.load(data as Record, context); + } + + static fromYaml(yaml: string, context?: LoadContext): TurnTrace { + const { parse } = require("yaml"); + const data = parse(yaml); + return TurnTrace.load(data as Record, context); + } + + //#endregion +} diff --git a/runtime/typescript/packages/core/src/model/index.ts b/runtime/typescript/packages/core/src/model/index.ts index f05c0984..256ef64d 100644 --- a/runtime/typescript/packages/core/src/model/index.ts +++ b/runtime/typescript/packages/core/src/model/index.ts @@ -1,3 +1,4 @@ +// // Copyright (c) Microsoft. All rights reserved. // WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. @@ -34,16 +35,49 @@ export { ValidationError } from "./core/validation-error"; export { FileNotFoundError } from "./core/file-not-found-error"; export { ValidationResult } from "./core/validation-result"; +export { HostToolResult } from "./events/host-tool-result"; +export { HostToolRequest } from "./events/host-tool-request"; +export { RedactedField } from "./events/redacted-field"; +export { RedactionMetadata } from "./events/redaction-metadata"; +export { Checkpoint } from "./events/checkpoint"; +export { TurnEvent } from "./events/turn-event"; +export { TurnStartPayload } from "./events/turn-start-payload"; +export { TurnEndPayload } from "./events/turn-end-payload"; +export { LlmStartPayload } from "./events/llm-start-payload"; +export { LlmCompletePayload } from "./events/llm-complete-payload"; +export { RetryPayload } from "./events/retry-payload"; +export { PermissionRequestedPayload } from "./events/permission-requested-payload"; +export { PermissionCompletedPayload } from "./events/permission-completed-payload"; +export { PermissionRequest } from "./events/permission-request"; +export { PermissionDecision } from "./events/permission-decision"; export { TokenEventPayload } from "./events/token-event-payload"; export { ThinkingEventPayload } from "./events/thinking-event-payload"; export { ToolCallStartPayload } from "./events/tool-call-start-payload"; +export { ToolCallCompletePayload } from "./events/tool-call-complete-payload"; +export { ToolExecutionStartPayload } from "./events/tool-execution-start-payload"; +export { ToolExecutionCompletePayload } from "./events/tool-execution-complete-payload"; +export { HookStartPayload } from "./events/hook-start-payload"; +export { HookEndPayload } from "./events/hook-end-payload"; export { ToolResultPayload } from "./events/tool-result-payload"; export { StatusEventPayload } from "./events/status-event-payload"; export { MessagesUpdatedPayload } from "./events/messages-updated-payload"; export { DoneEventPayload } from "./events/done-event-payload"; export { ErrorEventPayload } from "./events/error-event-payload"; +export { CompactionStartPayload } from "./events/compaction-start-payload"; export { CompactionCompletePayload } from "./events/compaction-complete-payload"; export { CompactionFailedPayload } from "./events/compaction-failed-payload"; +export { TurnSummary } from "./events/turn-summary"; +export { TurnTrace } from "./events/turn-trace"; +export { HarnessContext } from "./events/harness-context"; +export { SessionStartPayload } from "./events/session-start-payload"; +export { SessionEndPayload } from "./events/session-end-payload"; +export { SessionWarningPayload } from "./events/session-warning-payload"; +export { SessionEvent } from "./events/session-event"; +export { TrajectoryEvent } from "./events/trajectory-event"; +export { SessionFileRef } from "./events/session-file-ref"; +export { SessionRef } from "./events/session-ref"; +export { SessionSummary } from "./events/session-summary"; +export { SessionTrace } from "./events/session-trace"; export { StreamChunk, TextChunk, @@ -59,10 +93,23 @@ export { ModelInfo } from "./model/model-info"; export { CompactionConfig } from "./pipeline/compaction-config"; export { TurnOptions } from "./pipeline/turn-options"; +export { TurnModelRequest } from "./pipeline/turn-model-request"; +export { TurnModelResponse } from "./pipeline/turn-model-response"; +export { RunTurnRequest } from "./pipeline/run-turn-request"; +export { RunTurnResult } from "./pipeline/run-turn-result"; +export { ReplayJournalRecord } from "./pipeline/replay-journal-record"; +export { ReplayVerificationRequest } from "./pipeline/replay-verification-request"; +export { ReplayMismatch } from "./pipeline/replay-mismatch"; +export { ReplayVerificationResult } from "./pipeline/replay-verification-result"; export type { Renderer } from "./pipeline/renderer"; export type { Parser } from "./pipeline/parser"; export type { Executor } from "./pipeline/executor"; export type { Processor } from "./pipeline/processor"; +export type { EventSink } from "./pipeline/event-sink"; +export type { EventJournalWriter } from "./pipeline/event-journal-writer"; +export type { PermissionResolver } from "./pipeline/permission-resolver"; +export type { CheckpointStore } from "./pipeline/checkpoint-store"; +export type { HostToolExecutor } from "./pipeline/host-tool-executor"; export { StreamOptions } from "./streaming/stream-options"; diff --git a/runtime/typescript/packages/core/src/model/model/index.ts b/runtime/typescript/packages/core/src/model/model/index.ts index 535885a5..c93fa0e6 100644 --- a/runtime/typescript/packages/core/src/model/model/index.ts +++ b/runtime/typescript/packages/core/src/model/model/index.ts @@ -1,3 +1,4 @@ +// // Copyright (c) Microsoft. All rights reserved. // WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. diff --git a/runtime/typescript/packages/core/src/model/model/model-info.ts b/runtime/typescript/packages/core/src/model/model/model-info.ts index 60c9d6a5..e5d58117 100644 --- a/runtime/typescript/packages/core/src/model/model/model-info.ts +++ b/runtime/typescript/packages/core/src/model/model/model-info.ts @@ -1,3 +1,4 @@ +// // Copyright (c) Microsoft. All rights reserved. // WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. diff --git a/runtime/typescript/packages/core/src/model/model/model-options.ts b/runtime/typescript/packages/core/src/model/model/model-options.ts index 65868065..806ed03a 100644 --- a/runtime/typescript/packages/core/src/model/model/model-options.ts +++ b/runtime/typescript/packages/core/src/model/model/model-options.ts @@ -1,3 +1,4 @@ +// // Copyright (c) Microsoft. All rights reserved. // WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. diff --git a/runtime/typescript/packages/core/src/model/model/model.ts b/runtime/typescript/packages/core/src/model/model/model.ts index 60d05444..4d9bed76 100644 --- a/runtime/typescript/packages/core/src/model/model/model.ts +++ b/runtime/typescript/packages/core/src/model/model/model.ts @@ -1,3 +1,4 @@ +// // Copyright (c) Microsoft. All rights reserved. // WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. diff --git a/runtime/typescript/packages/core/src/model/model/token-usage.ts b/runtime/typescript/packages/core/src/model/model/token-usage.ts index 6965a646..c3ced855 100644 --- a/runtime/typescript/packages/core/src/model/model/token-usage.ts +++ b/runtime/typescript/packages/core/src/model/model/token-usage.ts @@ -1,3 +1,4 @@ +// // Copyright (c) Microsoft. All rights reserved. // WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. diff --git a/runtime/typescript/packages/core/src/model/pipeline/checkpoint-store.ts b/runtime/typescript/packages/core/src/model/pipeline/checkpoint-store.ts new file mode 100644 index 00000000..474fcec3 --- /dev/null +++ b/runtime/typescript/packages/core/src/model/pipeline/checkpoint-store.ts @@ -0,0 +1,15 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. + +import { Checkpoint } from "../events/checkpoint"; + +/** Stores and retrieves resumable session checkpoints. */ +export interface CheckpointStore { + /** Persist a session checkpoint and return the stored checkpoint */ + save(checkpoint: Checkpoint): Promise; + /** Load a checkpoint by session and checkpoint identifier */ + load(sessionId: string, checkpointId: string): Promise; + /** List checkpoints for a session */ + listCheckpoints(sessionId: string): Promise; +} diff --git a/runtime/typescript/packages/core/src/model/pipeline/compaction-config.ts b/runtime/typescript/packages/core/src/model/pipeline/compaction-config.ts index a94e4c2c..61bae55d 100644 --- a/runtime/typescript/packages/core/src/model/pipeline/compaction-config.ts +++ b/runtime/typescript/packages/core/src/model/pipeline/compaction-config.ts @@ -1,3 +1,4 @@ +// // Copyright (c) Microsoft. All rights reserved. // WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. diff --git a/runtime/typescript/packages/core/src/model/pipeline/event-journal-writer.ts b/runtime/typescript/packages/core/src/model/pipeline/event-journal-writer.ts new file mode 100644 index 00000000..9c2d3787 --- /dev/null +++ b/runtime/typescript/packages/core/src/model/pipeline/event-journal-writer.ts @@ -0,0 +1,17 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. + +import { SessionEvent } from "../events/session-event"; +import { SessionSummary } from "../events/session-summary"; +import { TurnEvent } from "../events/turn-event"; + +/** Persists typed events to a durable replay journal. */ +export interface EventJournalWriter { + /** Append a turn event to a durable replay journal */ + appendTurn(turnEvent: TurnEvent): boolean; + /** Append a session event to a durable replay journal */ + appendSession(sessionEvent: SessionEvent): boolean; + /** Finalize the journal with an optional session summary */ + close(summary: SessionSummary | null): boolean; +} diff --git a/runtime/typescript/packages/core/src/model/pipeline/event-sink.ts b/runtime/typescript/packages/core/src/model/pipeline/event-sink.ts new file mode 100644 index 00000000..6f2ff06d --- /dev/null +++ b/runtime/typescript/packages/core/src/model/pipeline/event-sink.ts @@ -0,0 +1,14 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. + +import { SessionEvent } from "../events/session-event"; +import { TurnEvent } from "../events/turn-event"; + +/** Receives typed turn and session events from a harness. */ +export interface EventSink { + /** Emit a typed turn event to a host sink */ + emitTurn(turnEvent: TurnEvent): boolean; + /** Emit a typed session event to a host sink */ + emitSession(sessionEvent: SessionEvent): boolean; +} diff --git a/runtime/typescript/packages/core/src/model/pipeline/executor.ts b/runtime/typescript/packages/core/src/model/pipeline/executor.ts index 548078fb..a288c30c 100644 --- a/runtime/typescript/packages/core/src/model/pipeline/executor.ts +++ b/runtime/typescript/packages/core/src/model/pipeline/executor.ts @@ -1,3 +1,4 @@ +// // Copyright (c) Microsoft. All rights reserved. // WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. diff --git a/runtime/typescript/packages/core/src/model/pipeline/host-tool-executor.ts b/runtime/typescript/packages/core/src/model/pipeline/host-tool-executor.ts new file mode 100644 index 00000000..a6f8a06f --- /dev/null +++ b/runtime/typescript/packages/core/src/model/pipeline/host-tool-executor.ts @@ -0,0 +1,12 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. + +import { HostToolRequest } from "../events/host-tool-request"; +import { HostToolResult } from "../events/host-tool-result"; + +/** Executes host tools after policy and permission checks. */ +export interface HostToolExecutor { + /** Execute a concrete host tool request and return its completion payload */ + execute(request: HostToolRequest): Promise; +} diff --git a/runtime/typescript/packages/core/src/model/pipeline/index.ts b/runtime/typescript/packages/core/src/model/pipeline/index.ts index 269fd9d4..f5dc03e8 100644 --- a/runtime/typescript/packages/core/src/model/pipeline/index.ts +++ b/runtime/typescript/packages/core/src/model/pipeline/index.ts @@ -1,9 +1,23 @@ +// // Copyright (c) Microsoft. All rights reserved. // WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. export { CompactionConfig } from "./compaction-config"; export { TurnOptions } from "./turn-options"; +export { TurnModelRequest } from "./turn-model-request"; +export { TurnModelResponse } from "./turn-model-response"; +export { RunTurnRequest } from "./run-turn-request"; +export { RunTurnResult } from "./run-turn-result"; +export { ReplayJournalRecord } from "./replay-journal-record"; +export { ReplayVerificationRequest } from "./replay-verification-request"; +export { ReplayMismatch } from "./replay-mismatch"; +export { ReplayVerificationResult } from "./replay-verification-result"; export type { Renderer } from "./renderer"; export type { Parser } from "./parser"; export type { Executor } from "./executor"; export type { Processor } from "./processor"; +export type { EventSink } from "./event-sink"; +export type { EventJournalWriter } from "./event-journal-writer"; +export type { PermissionResolver } from "./permission-resolver"; +export type { CheckpointStore } from "./checkpoint-store"; +export type { HostToolExecutor } from "./host-tool-executor"; diff --git a/runtime/typescript/packages/core/src/model/pipeline/parser.ts b/runtime/typescript/packages/core/src/model/pipeline/parser.ts index eb43685a..2649cf44 100644 --- a/runtime/typescript/packages/core/src/model/pipeline/parser.ts +++ b/runtime/typescript/packages/core/src/model/pipeline/parser.ts @@ -1,3 +1,4 @@ +// // Copyright (c) Microsoft. All rights reserved. // WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. diff --git a/runtime/typescript/packages/core/src/model/pipeline/permission-resolver.ts b/runtime/typescript/packages/core/src/model/pipeline/permission-resolver.ts new file mode 100644 index 00000000..4062162e --- /dev/null +++ b/runtime/typescript/packages/core/src/model/pipeline/permission-resolver.ts @@ -0,0 +1,12 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. + +import { PermissionDecision } from "../events/permission-decision"; +import { PermissionRequest } from "../events/permission-request"; + +/** Resolves host permission requests for potentially sensitive actions. */ +export interface PermissionResolver { + /** Resolve a host permission request */ + request(request: PermissionRequest): Promise; +} diff --git a/runtime/typescript/packages/core/src/model/pipeline/processor.ts b/runtime/typescript/packages/core/src/model/pipeline/processor.ts index a5c22796..4dec64a0 100644 --- a/runtime/typescript/packages/core/src/model/pipeline/processor.ts +++ b/runtime/typescript/packages/core/src/model/pipeline/processor.ts @@ -1,3 +1,4 @@ +// // Copyright (c) Microsoft. All rights reserved. // WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. diff --git a/runtime/typescript/packages/core/src/model/pipeline/renderer.ts b/runtime/typescript/packages/core/src/model/pipeline/renderer.ts index 70e1a89f..2a71a63c 100644 --- a/runtime/typescript/packages/core/src/model/pipeline/renderer.ts +++ b/runtime/typescript/packages/core/src/model/pipeline/renderer.ts @@ -1,3 +1,4 @@ +// // Copyright (c) Microsoft. All rights reserved. // WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. diff --git a/runtime/typescript/packages/core/src/model/pipeline/replay-journal-record.ts b/runtime/typescript/packages/core/src/model/pipeline/replay-journal-record.ts new file mode 100644 index 00000000..f7971dbf --- /dev/null +++ b/runtime/typescript/packages/core/src/model/pipeline/replay-journal-record.ts @@ -0,0 +1,195 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. + +import { LoadContext, SaveContext } from "../context"; + +export type ReplayRecordKind = "session" | "turn" | "summary"; +export type ReplayRecordStatus = "success" | "error" | "cancelled"; + +export class ReplayJournalRecord { + static readonly shorthandProperty: string | undefined = undefined; + + kind: ReplayRecordKind = "session"; + type?: string | undefined; + sessionId?: string | undefined; + turnId?: string | undefined; + iteration?: number | undefined; + status?: ReplayRecordStatus | undefined; + requestId?: string | undefined; + toolName?: string | undefined; + success?: boolean | undefined; + errorKind?: string | undefined; + turns?: number | undefined; + checkpoints?: number | undefined; + + constructor(init?: Partial) { + this.kind = init?.kind ?? "session"; + if (init?.type !== undefined) { + this.type = init.type; + } + if (init?.sessionId !== undefined) { + this.sessionId = init.sessionId; + } + if (init?.turnId !== undefined) { + this.turnId = init.turnId; + } + if (init?.iteration !== undefined) { + this.iteration = init.iteration; + } + if (init?.status !== undefined) { + this.status = init.status; + } + if (init?.requestId !== undefined) { + this.requestId = init.requestId; + } + if (init?.toolName !== undefined) { + this.toolName = init.toolName; + } + if (init?.success !== undefined) { + this.success = init.success; + } + if (init?.errorKind !== undefined) { + this.errorKind = init.errorKind; + } + if (init?.turns !== undefined) { + this.turns = init.turns; + } + if (init?.checkpoints !== undefined) { + this.checkpoints = init.checkpoints; + } + } + + //#region Load Methods + + static load( + data: Record, + context?: LoadContext, + ): ReplayJournalRecord { + if (context) { + data = context.processInput(data) as Record; + } + + const instance = new ReplayJournalRecord(); + + if (data["kind"] !== undefined && data["kind"] !== null) { + instance.kind = String(data["kind"]) as ReplayRecordKind; + } + if (data["type"] !== undefined && data["type"] !== null) { + instance.type = String(data["type"]); + } + if (data["sessionId"] !== undefined && data["sessionId"] !== null) { + instance.sessionId = String(data["sessionId"]); + } + if (data["turnId"] !== undefined && data["turnId"] !== null) { + instance.turnId = String(data["turnId"]); + } + if (data["iteration"] !== undefined && data["iteration"] !== null) { + instance.iteration = Number(data["iteration"]); + } + if (data["status"] !== undefined && data["status"] !== null) { + instance.status = String(data["status"]) as ReplayRecordStatus; + } + if (data["requestId"] !== undefined && data["requestId"] !== null) { + instance.requestId = String(data["requestId"]); + } + if (data["toolName"] !== undefined && data["toolName"] !== null) { + instance.toolName = String(data["toolName"]); + } + if (data["success"] !== undefined && data["success"] !== null) { + instance.success = Boolean(data["success"]); + } + if (data["errorKind"] !== undefined && data["errorKind"] !== null) { + instance.errorKind = String(data["errorKind"]); + } + if (data["turns"] !== undefined && data["turns"] !== null) { + instance.turns = Number(data["turns"]); + } + if (data["checkpoints"] !== undefined && data["checkpoints"] !== null) { + instance.checkpoints = Number(data["checkpoints"]); + } + + if (context) { + return context.processOutput(instance) as ReplayJournalRecord; + } + return instance; + } + + //#endregion + + //#region Save Methods + + save(context?: SaveContext): Record { + let obj: this = this; + if (context) { + obj = context.processObject(obj) as this; + } + + const result: Record = {}; + + if (obj.kind !== undefined && obj.kind !== null) { + result["kind"] = obj.kind; + } + if (obj.type !== undefined && obj.type !== null) { + result["type"] = obj.type; + } + if (obj.sessionId !== undefined && obj.sessionId !== null) { + result["sessionId"] = obj.sessionId; + } + if (obj.turnId !== undefined && obj.turnId !== null) { + result["turnId"] = obj.turnId; + } + if (obj.iteration !== undefined && obj.iteration !== null) { + result["iteration"] = obj.iteration; + } + if (obj.status !== undefined && obj.status !== null) { + result["status"] = obj.status; + } + if (obj.requestId !== undefined && obj.requestId !== null) { + result["requestId"] = obj.requestId; + } + if (obj.toolName !== undefined && obj.toolName !== null) { + result["toolName"] = obj.toolName; + } + if (obj.success !== undefined && obj.success !== null) { + result["success"] = obj.success; + } + if (obj.errorKind !== undefined && obj.errorKind !== null) { + result["errorKind"] = obj.errorKind; + } + if (obj.turns !== undefined && obj.turns !== null) { + result["turns"] = obj.turns; + } + if (obj.checkpoints !== undefined && obj.checkpoints !== null) { + result["checkpoints"] = obj.checkpoints; + } + + if (context) { + return context.processDict(result); + } + return result; + } + + toYaml(context?: SaveContext): string { + context = context ?? new SaveContext(); + return context.toYaml(this.save(context)); + } + + toJson(context?: SaveContext, indent: number = 2): string { + context = context ?? new SaveContext(); + return context.toJson(this.save(context), indent); + } + + static fromJson(json: string, context?: LoadContext): ReplayJournalRecord { + const data = JSON.parse(json); + return ReplayJournalRecord.load(data as Record, context); + } + + static fromYaml(yaml: string, context?: LoadContext): ReplayJournalRecord { + const { parse } = require("yaml"); + const data = parse(yaml); + return ReplayJournalRecord.load(data as Record, context); + } + + //#endregion +} diff --git a/runtime/typescript/packages/core/src/model/pipeline/replay-mismatch.ts b/runtime/typescript/packages/core/src/model/pipeline/replay-mismatch.ts new file mode 100644 index 00000000..4b844f0a --- /dev/null +++ b/runtime/typescript/packages/core/src/model/pipeline/replay-mismatch.ts @@ -0,0 +1,117 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. + +import { LoadContext, SaveContext } from "../context"; +import { ReplayJournalRecord } from "./replay-journal-record"; + +export class ReplayMismatch { + static readonly shorthandProperty: string | undefined = undefined; + + index: number = 0; + expected?: ReplayJournalRecord | undefined; + actual?: ReplayJournalRecord | undefined; + message: string = ""; + + constructor(init?: Partial) { + this.index = init?.index ?? 0; + if (init?.expected !== undefined) { + this.expected = init.expected; + } + if (init?.actual !== undefined) { + this.actual = init.actual; + } + this.message = init?.message ?? ""; + } + + //#region Load Methods + + static load( + data: Record, + context?: LoadContext, + ): ReplayMismatch { + if (context) { + data = context.processInput(data) as Record; + } + + const instance = new ReplayMismatch(); + + if (data["index"] !== undefined && data["index"] !== null) { + instance.index = Number(data["index"]); + } + if (data["expected"] !== undefined && data["expected"] !== null) { + instance.expected = ReplayJournalRecord.load( + data["expected"] as Record, + context, + ); + } + if (data["actual"] !== undefined && data["actual"] !== null) { + instance.actual = ReplayJournalRecord.load( + data["actual"] as Record, + context, + ); + } + if (data["message"] !== undefined && data["message"] !== null) { + instance.message = String(data["message"]); + } + + if (context) { + return context.processOutput(instance) as ReplayMismatch; + } + return instance; + } + + //#endregion + + //#region Save Methods + + save(context?: SaveContext): Record { + let obj: this = this; + if (context) { + obj = context.processObject(obj) as this; + } + + const result: Record = {}; + + if (obj.index !== undefined && obj.index !== null) { + result["index"] = obj.index; + } + if (obj.expected !== undefined && obj.expected !== null) { + result["expected"] = obj.expected.save(context); + } + if (obj.actual !== undefined && obj.actual !== null) { + result["actual"] = obj.actual.save(context); + } + if (obj.message !== undefined && obj.message !== null) { + result["message"] = obj.message; + } + + if (context) { + return context.processDict(result); + } + return result; + } + + toYaml(context?: SaveContext): string { + context = context ?? new SaveContext(); + return context.toYaml(this.save(context)); + } + + toJson(context?: SaveContext, indent: number = 2): string { + context = context ?? new SaveContext(); + return context.toJson(this.save(context), indent); + } + + static fromJson(json: string, context?: LoadContext): ReplayMismatch { + const data = JSON.parse(json); + return ReplayMismatch.load(data as Record, context); + } + + static fromYaml(yaml: string, context?: LoadContext): ReplayMismatch { + const { parse } = require("yaml"); + const data = parse(yaml); + return ReplayMismatch.load(data as Record, context); + } + + //#endregion +} diff --git a/runtime/typescript/packages/core/src/model/pipeline/replay-verification-request.ts b/runtime/typescript/packages/core/src/model/pipeline/replay-verification-request.ts new file mode 100644 index 00000000..07951579 --- /dev/null +++ b/runtime/typescript/packages/core/src/model/pipeline/replay-verification-request.ts @@ -0,0 +1,181 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. + +import { LoadContext, SaveContext } from "../context"; +import { ReplayJournalRecord } from "./replay-journal-record"; + +export class ReplayVerificationRequest { + static readonly shorthandProperty: string | undefined = undefined; + + expected: ReplayJournalRecord[] = []; + actual: ReplayJournalRecord[] = []; + + constructor(init?: Partial) { + this.expected = init?.expected ?? []; + this.actual = init?.actual ?? []; + } + + //#region Load Methods + + static load( + data: Record, + context?: LoadContext, + ): ReplayVerificationRequest { + if (context) { + data = context.processInput(data) as Record; + } + + const instance = new ReplayVerificationRequest(); + + if (data["expected"] !== undefined && data["expected"] !== null) { + instance.expected = ReplayVerificationRequest.loadExpected( + data["expected"] as unknown[], + context, + ); + } + if (data["actual"] !== undefined && data["actual"] !== null) { + instance.actual = ReplayVerificationRequest.loadActual( + data["actual"] as unknown[], + context, + ); + } + + if (context) { + return context.processOutput(instance) as ReplayVerificationRequest; + } + return instance; + } + + static loadExpected( + data: Record[] | unknown[], + context?: LoadContext, + ): ReplayJournalRecord[] { + if (!Array.isArray(data)) { + // Convert dict/object format to array format + const result: Record[] = []; + for (const [k, v] of Object.entries(data)) { + if (typeof v === "object" && v !== null && !Array.isArray(v)) { + result.push({ name: k, ...(v as Record) }); + } else { + result.push({ name: k, kind: v }); + } + } + data = result; + } + return data.map((item) => + ReplayJournalRecord.load(item as Record, context), + ); + } + + static saveExpected( + items: ReplayJournalRecord[], + context?: SaveContext, + ): Record[] | Record { + if (!context) { + context = new SaveContext(); + } + + // This type doesn't have a 'name' property, so always use array format + return items.map((item) => item.save(context)); + } + + static loadActual( + data: Record[] | unknown[], + context?: LoadContext, + ): ReplayJournalRecord[] { + if (!Array.isArray(data)) { + // Convert dict/object format to array format + const result: Record[] = []; + for (const [k, v] of Object.entries(data)) { + if (typeof v === "object" && v !== null && !Array.isArray(v)) { + result.push({ name: k, ...(v as Record) }); + } else { + result.push({ name: k, kind: v }); + } + } + data = result; + } + return data.map((item) => + ReplayJournalRecord.load(item as Record, context), + ); + } + + static saveActual( + items: ReplayJournalRecord[], + context?: SaveContext, + ): Record[] | Record { + if (!context) { + context = new SaveContext(); + } + + // This type doesn't have a 'name' property, so always use array format + return items.map((item) => item.save(context)); + } + + //#endregion + + //#region Save Methods + + save(context?: SaveContext): Record { + let obj: this = this; + if (context) { + obj = context.processObject(obj) as this; + } + + const result: Record = {}; + + if (obj.expected !== undefined && obj.expected !== null) { + result["expected"] = ReplayVerificationRequest.saveExpected( + obj.expected, + context, + ); + } + if (obj.actual !== undefined && obj.actual !== null) { + result["actual"] = ReplayVerificationRequest.saveActual( + obj.actual, + context, + ); + } + + if (context) { + return context.processDict(result); + } + return result; + } + + toYaml(context?: SaveContext): string { + context = context ?? new SaveContext(); + return context.toYaml(this.save(context)); + } + + toJson(context?: SaveContext, indent: number = 2): string { + context = context ?? new SaveContext(); + return context.toJson(this.save(context), indent); + } + + static fromJson( + json: string, + context?: LoadContext, + ): ReplayVerificationRequest { + const data = JSON.parse(json); + return ReplayVerificationRequest.load( + data as Record, + context, + ); + } + + static fromYaml( + yaml: string, + context?: LoadContext, + ): ReplayVerificationRequest { + const { parse } = require("yaml"); + const data = parse(yaml); + return ReplayVerificationRequest.load( + data as Record, + context, + ); + } + + //#endregion +} diff --git a/runtime/typescript/packages/core/src/model/pipeline/replay-verification-result.ts b/runtime/typescript/packages/core/src/model/pipeline/replay-verification-result.ts new file mode 100644 index 00000000..426c680e --- /dev/null +++ b/runtime/typescript/packages/core/src/model/pipeline/replay-verification-result.ts @@ -0,0 +1,162 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. + +import { LoadContext, SaveContext } from "../context"; +import { ReplayMismatch } from "./replay-mismatch"; + +export type ReplayVerificationStatus = "passed" | "failed"; + +export class ReplayVerificationResult { + static readonly shorthandProperty: string | undefined = undefined; + + status: ReplayVerificationStatus = "passed"; + mismatches?: ReplayMismatch[] = []; + expectedCount: number = 0; + actualCount: number = 0; + + constructor(init?: Partial) { + this.status = init?.status ?? "passed"; + if (init?.mismatches !== undefined) { + this.mismatches = init.mismatches; + } + this.expectedCount = init?.expectedCount ?? 0; + this.actualCount = init?.actualCount ?? 0; + } + + //#region Load Methods + + static load( + data: Record, + context?: LoadContext, + ): ReplayVerificationResult { + if (context) { + data = context.processInput(data) as Record; + } + + const instance = new ReplayVerificationResult(); + + if (data["status"] !== undefined && data["status"] !== null) { + instance.status = String(data["status"]) as ReplayVerificationStatus; + } + if (data["mismatches"] !== undefined && data["mismatches"] !== null) { + instance.mismatches = ReplayVerificationResult.loadMismatches( + data["mismatches"] as unknown[], + context, + ); + } + if (data["expectedCount"] !== undefined && data["expectedCount"] !== null) { + instance.expectedCount = Number(data["expectedCount"]); + } + if (data["actualCount"] !== undefined && data["actualCount"] !== null) { + instance.actualCount = Number(data["actualCount"]); + } + + if (context) { + return context.processOutput(instance) as ReplayVerificationResult; + } + return instance; + } + + static loadMismatches( + data: Record[] | unknown[], + context?: LoadContext, + ): ReplayMismatch[] { + if (!Array.isArray(data)) { + // Convert dict/object format to array format + const result: Record[] = []; + for (const [k, v] of Object.entries(data)) { + if (typeof v === "object" && v !== null && !Array.isArray(v)) { + result.push({ name: k, ...(v as Record) }); + } else { + result.push({ name: k, index: v }); + } + } + data = result; + } + return data.map((item) => + ReplayMismatch.load(item as Record, context), + ); + } + + static saveMismatches( + items: ReplayMismatch[], + context?: SaveContext, + ): Record[] | Record { + if (!context) { + context = new SaveContext(); + } + + // This type doesn't have a 'name' property, so always use array format + return items.map((item) => item.save(context)); + } + + //#endregion + + //#region Save Methods + + save(context?: SaveContext): Record { + let obj: this = this; + if (context) { + obj = context.processObject(obj) as this; + } + + const result: Record = {}; + + if (obj.status !== undefined && obj.status !== null) { + result["status"] = obj.status; + } + if (obj.mismatches !== undefined && obj.mismatches !== null) { + result["mismatches"] = ReplayVerificationResult.saveMismatches( + obj.mismatches, + context, + ); + } + if (obj.expectedCount !== undefined && obj.expectedCount !== null) { + result["expectedCount"] = obj.expectedCount; + } + if (obj.actualCount !== undefined && obj.actualCount !== null) { + result["actualCount"] = obj.actualCount; + } + + if (context) { + return context.processDict(result); + } + return result; + } + + toYaml(context?: SaveContext): string { + context = context ?? new SaveContext(); + return context.toYaml(this.save(context)); + } + + toJson(context?: SaveContext, indent: number = 2): string { + context = context ?? new SaveContext(); + return context.toJson(this.save(context), indent); + } + + static fromJson( + json: string, + context?: LoadContext, + ): ReplayVerificationResult { + const data = JSON.parse(json); + return ReplayVerificationResult.load( + data as Record, + context, + ); + } + + static fromYaml( + yaml: string, + context?: LoadContext, + ): ReplayVerificationResult { + const { parse } = require("yaml"); + const data = parse(yaml); + return ReplayVerificationResult.load( + data as Record, + context, + ); + } + + //#endregion +} diff --git a/runtime/typescript/packages/core/src/model/pipeline/run-turn-request.ts b/runtime/typescript/packages/core/src/model/pipeline/run-turn-request.ts new file mode 100644 index 00000000..e4a95ad8 --- /dev/null +++ b/runtime/typescript/packages/core/src/model/pipeline/run-turn-request.ts @@ -0,0 +1,114 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. + +import { LoadContext, SaveContext } from "../context"; +import { TurnOptions } from "./turn-options"; + +export class RunTurnRequest { + static readonly shorthandProperty: string | undefined = undefined; + + sessionId: string = ""; + turnId: string = ""; + inputs?: Record | undefined; + options?: TurnOptions | undefined; + + constructor(init?: Partial) { + this.sessionId = init?.sessionId ?? ""; + this.turnId = init?.turnId ?? ""; + if (init?.inputs !== undefined) { + this.inputs = init.inputs; + } + if (init?.options !== undefined) { + this.options = init.options; + } + } + + //#region Load Methods + + static load( + data: Record, + context?: LoadContext, + ): RunTurnRequest { + if (context) { + data = context.processInput(data) as Record; + } + + const instance = new RunTurnRequest(); + + if (data["sessionId"] !== undefined && data["sessionId"] !== null) { + instance.sessionId = String(data["sessionId"]); + } + if (data["turnId"] !== undefined && data["turnId"] !== null) { + instance.turnId = String(data["turnId"]); + } + if (data["inputs"] !== undefined && data["inputs"] !== null) { + instance.inputs = data["inputs"] as Record; + } + if (data["options"] !== undefined && data["options"] !== null) { + instance.options = TurnOptions.load( + data["options"] as Record, + context, + ); + } + + if (context) { + return context.processOutput(instance) as RunTurnRequest; + } + return instance; + } + + //#endregion + + //#region Save Methods + + save(context?: SaveContext): Record { + let obj: this = this; + if (context) { + obj = context.processObject(obj) as this; + } + + const result: Record = {}; + + if (obj.sessionId !== undefined && obj.sessionId !== null) { + result["sessionId"] = obj.sessionId; + } + if (obj.turnId !== undefined && obj.turnId !== null) { + result["turnId"] = obj.turnId; + } + if (obj.inputs !== undefined && obj.inputs !== null) { + result["inputs"] = obj.inputs; + } + if (obj.options !== undefined && obj.options !== null) { + result["options"] = obj.options.save(context); + } + + if (context) { + return context.processDict(result); + } + return result; + } + + toYaml(context?: SaveContext): string { + context = context ?? new SaveContext(); + return context.toYaml(this.save(context)); + } + + toJson(context?: SaveContext, indent: number = 2): string { + context = context ?? new SaveContext(); + return context.toJson(this.save(context), indent); + } + + static fromJson(json: string, context?: LoadContext): RunTurnRequest { + const data = JSON.parse(json); + return RunTurnRequest.load(data as Record, context); + } + + static fromYaml(yaml: string, context?: LoadContext): RunTurnRequest { + const { parse } = require("yaml"); + const data = parse(yaml); + return RunTurnRequest.load(data as Record, context); + } + + //#endregion +} diff --git a/runtime/typescript/packages/core/src/model/pipeline/run-turn-result.ts b/runtime/typescript/packages/core/src/model/pipeline/run-turn-result.ts new file mode 100644 index 00000000..5067c074 --- /dev/null +++ b/runtime/typescript/packages/core/src/model/pipeline/run-turn-result.ts @@ -0,0 +1,218 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. + +import { LoadContext, SaveContext } from "../context"; +import { Checkpoint } from "../events/checkpoint"; +import { HostToolResult } from "../events/host-tool-result"; + +export type RunTurnStatus = "success" | "error" | "cancelled"; + +export class RunTurnResult { + static readonly shorthandProperty: string | undefined = undefined; + + sessionId: string = ""; + turnId: string = ""; + status: RunTurnStatus = "success"; + output?: unknown | undefined; + iterations: number = 0; + toolResults?: HostToolResult[] = []; + checkpoints?: Checkpoint[] = []; + + constructor(init?: Partial) { + this.sessionId = init?.sessionId ?? ""; + this.turnId = init?.turnId ?? ""; + this.status = init?.status ?? "success"; + if (init?.output !== undefined) { + this.output = init.output; + } + this.iterations = init?.iterations ?? 0; + if (init?.toolResults !== undefined) { + this.toolResults = init.toolResults; + } + if (init?.checkpoints !== undefined) { + this.checkpoints = init.checkpoints; + } + } + + //#region Load Methods + + static load( + data: Record, + context?: LoadContext, + ): RunTurnResult { + if (context) { + data = context.processInput(data) as Record; + } + + const instance = new RunTurnResult(); + + if (data["sessionId"] !== undefined && data["sessionId"] !== null) { + instance.sessionId = String(data["sessionId"]); + } + if (data["turnId"] !== undefined && data["turnId"] !== null) { + instance.turnId = String(data["turnId"]); + } + if (data["status"] !== undefined && data["status"] !== null) { + instance.status = String(data["status"]) as RunTurnStatus; + } + if (data["output"] !== undefined && data["output"] !== null) { + instance.output = data["output"] as unknown; + } + if (data["iterations"] !== undefined && data["iterations"] !== null) { + instance.iterations = Number(data["iterations"]); + } + if (data["toolResults"] !== undefined && data["toolResults"] !== null) { + instance.toolResults = RunTurnResult.loadToolResults( + data["toolResults"] as unknown[], + context, + ); + } + if (data["checkpoints"] !== undefined && data["checkpoints"] !== null) { + instance.checkpoints = RunTurnResult.loadCheckpoints( + data["checkpoints"] as unknown[], + context, + ); + } + + if (context) { + return context.processOutput(instance) as RunTurnResult; + } + return instance; + } + + static loadToolResults( + data: Record[] | unknown[], + context?: LoadContext, + ): HostToolResult[] { + if (!Array.isArray(data)) { + // Convert dict/object format to array format + const result: Record[] = []; + for (const [k, v] of Object.entries(data)) { + if (typeof v === "object" && v !== null && !Array.isArray(v)) { + result.push({ name: k, ...(v as Record) }); + } else { + result.push({ name: k, requestId: v }); + } + } + data = result; + } + return data.map((item) => + HostToolResult.load(item as Record, context), + ); + } + + static saveToolResults( + items: HostToolResult[], + context?: SaveContext, + ): Record[] | Record { + if (!context) { + context = new SaveContext(); + } + + // This type doesn't have a 'name' property, so always use array format + return items.map((item) => item.save(context)); + } + + static loadCheckpoints( + data: Record[] | unknown[], + context?: LoadContext, + ): Checkpoint[] { + if (!Array.isArray(data)) { + // Convert dict/object format to array format + const result: Record[] = []; + for (const [k, v] of Object.entries(data)) { + if (typeof v === "object" && v !== null && !Array.isArray(v)) { + result.push({ name: k, ...(v as Record) }); + } else { + result.push({ name: k, id: v }); + } + } + data = result; + } + return data.map((item) => + Checkpoint.load(item as Record, context), + ); + } + + static saveCheckpoints( + items: Checkpoint[], + context?: SaveContext, + ): Record[] | Record { + if (!context) { + context = new SaveContext(); + } + + // This type doesn't have a 'name' property, so always use array format + return items.map((item) => item.save(context)); + } + + //#endregion + + //#region Save Methods + + save(context?: SaveContext): Record { + let obj: this = this; + if (context) { + obj = context.processObject(obj) as this; + } + + const result: Record = {}; + + if (obj.sessionId !== undefined && obj.sessionId !== null) { + result["sessionId"] = obj.sessionId; + } + if (obj.turnId !== undefined && obj.turnId !== null) { + result["turnId"] = obj.turnId; + } + if (obj.status !== undefined && obj.status !== null) { + result["status"] = obj.status; + } + if (obj.output !== undefined && obj.output !== null) { + result["output"] = obj.output; + } + if (obj.iterations !== undefined && obj.iterations !== null) { + result["iterations"] = obj.iterations; + } + if (obj.toolResults !== undefined && obj.toolResults !== null) { + result["toolResults"] = RunTurnResult.saveToolResults( + obj.toolResults, + context, + ); + } + if (obj.checkpoints !== undefined && obj.checkpoints !== null) { + result["checkpoints"] = RunTurnResult.saveCheckpoints( + obj.checkpoints, + context, + ); + } + + if (context) { + return context.processDict(result); + } + return result; + } + + toYaml(context?: SaveContext): string { + context = context ?? new SaveContext(); + return context.toYaml(this.save(context)); + } + + toJson(context?: SaveContext, indent: number = 2): string { + context = context ?? new SaveContext(); + return context.toJson(this.save(context), indent); + } + + static fromJson(json: string, context?: LoadContext): RunTurnResult { + const data = JSON.parse(json); + return RunTurnResult.load(data as Record, context); + } + + static fromYaml(yaml: string, context?: LoadContext): RunTurnResult { + const { parse } = require("yaml"); + const data = parse(yaml); + return RunTurnResult.load(data as Record, context); + } + + //#endregion +} diff --git a/runtime/typescript/packages/core/src/model/pipeline/turn-model-request.ts b/runtime/typescript/packages/core/src/model/pipeline/turn-model-request.ts new file mode 100644 index 00000000..3adee64d --- /dev/null +++ b/runtime/typescript/packages/core/src/model/pipeline/turn-model-request.ts @@ -0,0 +1,172 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. + +import { LoadContext, SaveContext } from "../context"; +import { HostToolResult } from "../events/host-tool-result"; +import { TurnOptions } from "./turn-options"; + +export class TurnModelRequest { + static readonly shorthandProperty: string | undefined = undefined; + + sessionId: string = ""; + turnId: string = ""; + iteration: number = 0; + inputs?: Record | undefined; + options?: TurnOptions | undefined; + toolResults?: HostToolResult[] = []; + + constructor(init?: Partial) { + this.sessionId = init?.sessionId ?? ""; + this.turnId = init?.turnId ?? ""; + this.iteration = init?.iteration ?? 0; + if (init?.inputs !== undefined) { + this.inputs = init.inputs; + } + if (init?.options !== undefined) { + this.options = init.options; + } + if (init?.toolResults !== undefined) { + this.toolResults = init.toolResults; + } + } + + //#region Load Methods + + static load( + data: Record, + context?: LoadContext, + ): TurnModelRequest { + if (context) { + data = context.processInput(data) as Record; + } + + const instance = new TurnModelRequest(); + + if (data["sessionId"] !== undefined && data["sessionId"] !== null) { + instance.sessionId = String(data["sessionId"]); + } + if (data["turnId"] !== undefined && data["turnId"] !== null) { + instance.turnId = String(data["turnId"]); + } + if (data["iteration"] !== undefined && data["iteration"] !== null) { + instance.iteration = Number(data["iteration"]); + } + if (data["inputs"] !== undefined && data["inputs"] !== null) { + instance.inputs = data["inputs"] as Record; + } + if (data["options"] !== undefined && data["options"] !== null) { + instance.options = TurnOptions.load( + data["options"] as Record, + context, + ); + } + if (data["toolResults"] !== undefined && data["toolResults"] !== null) { + instance.toolResults = TurnModelRequest.loadToolResults( + data["toolResults"] as unknown[], + context, + ); + } + + if (context) { + return context.processOutput(instance) as TurnModelRequest; + } + return instance; + } + + static loadToolResults( + data: Record[] | unknown[], + context?: LoadContext, + ): HostToolResult[] { + if (!Array.isArray(data)) { + // Convert dict/object format to array format + const result: Record[] = []; + for (const [k, v] of Object.entries(data)) { + if (typeof v === "object" && v !== null && !Array.isArray(v)) { + result.push({ name: k, ...(v as Record) }); + } else { + result.push({ name: k, requestId: v }); + } + } + data = result; + } + return data.map((item) => + HostToolResult.load(item as Record, context), + ); + } + + static saveToolResults( + items: HostToolResult[], + context?: SaveContext, + ): Record[] | Record { + if (!context) { + context = new SaveContext(); + } + + // This type doesn't have a 'name' property, so always use array format + return items.map((item) => item.save(context)); + } + + //#endregion + + //#region Save Methods + + save(context?: SaveContext): Record { + let obj: this = this; + if (context) { + obj = context.processObject(obj) as this; + } + + const result: Record = {}; + + if (obj.sessionId !== undefined && obj.sessionId !== null) { + result["sessionId"] = obj.sessionId; + } + if (obj.turnId !== undefined && obj.turnId !== null) { + result["turnId"] = obj.turnId; + } + if (obj.iteration !== undefined && obj.iteration !== null) { + result["iteration"] = obj.iteration; + } + if (obj.inputs !== undefined && obj.inputs !== null) { + result["inputs"] = obj.inputs; + } + if (obj.options !== undefined && obj.options !== null) { + result["options"] = obj.options.save(context); + } + if (obj.toolResults !== undefined && obj.toolResults !== null) { + result["toolResults"] = TurnModelRequest.saveToolResults( + obj.toolResults, + context, + ); + } + + if (context) { + return context.processDict(result); + } + return result; + } + + toYaml(context?: SaveContext): string { + context = context ?? new SaveContext(); + return context.toYaml(this.save(context)); + } + + toJson(context?: SaveContext, indent: number = 2): string { + context = context ?? new SaveContext(); + return context.toJson(this.save(context), indent); + } + + static fromJson(json: string, context?: LoadContext): TurnModelRequest { + const data = JSON.parse(json); + return TurnModelRequest.load(data as Record, context); + } + + static fromYaml(yaml: string, context?: LoadContext): TurnModelRequest { + const { parse } = require("yaml"); + const data = parse(yaml); + return TurnModelRequest.load(data as Record, context); + } + + //#endregion +} diff --git a/runtime/typescript/packages/core/src/model/pipeline/turn-model-response.ts b/runtime/typescript/packages/core/src/model/pipeline/turn-model-response.ts new file mode 100644 index 00000000..0ce7bbd8 --- /dev/null +++ b/runtime/typescript/packages/core/src/model/pipeline/turn-model-response.ts @@ -0,0 +1,150 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. + +import { LoadContext, SaveContext } from "../context"; +import { HostToolRequest } from "../events/host-tool-request"; + +export class TurnModelResponse { + static readonly shorthandProperty: string | undefined = undefined; + + output?: unknown | undefined; + toolRequests?: HostToolRequest[] = []; + checkpointState?: Record | undefined; + + constructor(init?: Partial) { + if (init?.output !== undefined) { + this.output = init.output; + } + if (init?.toolRequests !== undefined) { + this.toolRequests = init.toolRequests; + } + if (init?.checkpointState !== undefined) { + this.checkpointState = init.checkpointState; + } + } + + //#region Load Methods + + static load( + data: Record, + context?: LoadContext, + ): TurnModelResponse { + if (context) { + data = context.processInput(data) as Record; + } + + const instance = new TurnModelResponse(); + + if (data["output"] !== undefined && data["output"] !== null) { + instance.output = data["output"] as unknown; + } + if (data["toolRequests"] !== undefined && data["toolRequests"] !== null) { + instance.toolRequests = TurnModelResponse.loadToolRequests( + data["toolRequests"] as unknown[], + context, + ); + } + if ( + data["checkpointState"] !== undefined && + data["checkpointState"] !== null + ) { + instance.checkpointState = data["checkpointState"] as Record< + string, + unknown + >; + } + + if (context) { + return context.processOutput(instance) as TurnModelResponse; + } + return instance; + } + + static loadToolRequests( + data: Record[] | unknown[], + context?: LoadContext, + ): HostToolRequest[] { + if (!Array.isArray(data)) { + // Convert dict/object format to array format + const result: Record[] = []; + for (const [k, v] of Object.entries(data)) { + if (typeof v === "object" && v !== null && !Array.isArray(v)) { + result.push({ name: k, ...(v as Record) }); + } else { + result.push({ name: k, requestId: v }); + } + } + data = result; + } + return data.map((item) => + HostToolRequest.load(item as Record, context), + ); + } + + static saveToolRequests( + items: HostToolRequest[], + context?: SaveContext, + ): Record[] | Record { + if (!context) { + context = new SaveContext(); + } + + // This type doesn't have a 'name' property, so always use array format + return items.map((item) => item.save(context)); + } + + //#endregion + + //#region Save Methods + + save(context?: SaveContext): Record { + let obj: this = this; + if (context) { + obj = context.processObject(obj) as this; + } + + const result: Record = {}; + + if (obj.output !== undefined && obj.output !== null) { + result["output"] = obj.output; + } + if (obj.toolRequests !== undefined && obj.toolRequests !== null) { + result["toolRequests"] = TurnModelResponse.saveToolRequests( + obj.toolRequests, + context, + ); + } + if (obj.checkpointState !== undefined && obj.checkpointState !== null) { + result["checkpointState"] = obj.checkpointState; + } + + if (context) { + return context.processDict(result); + } + return result; + } + + toYaml(context?: SaveContext): string { + context = context ?? new SaveContext(); + return context.toYaml(this.save(context)); + } + + toJson(context?: SaveContext, indent: number = 2): string { + context = context ?? new SaveContext(); + return context.toJson(this.save(context), indent); + } + + static fromJson(json: string, context?: LoadContext): TurnModelResponse { + const data = JSON.parse(json); + return TurnModelResponse.load(data as Record, context); + } + + static fromYaml(yaml: string, context?: LoadContext): TurnModelResponse { + const { parse } = require("yaml"); + const data = parse(yaml); + return TurnModelResponse.load(data as Record, context); + } + + //#endregion +} diff --git a/runtime/typescript/packages/core/src/model/pipeline/turn-options.ts b/runtime/typescript/packages/core/src/model/pipeline/turn-options.ts index 5628f610..e6ef5b45 100644 --- a/runtime/typescript/packages/core/src/model/pipeline/turn-options.ts +++ b/runtime/typescript/packages/core/src/model/pipeline/turn-options.ts @@ -1,3 +1,4 @@ +// // Copyright (c) Microsoft. All rights reserved. // WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. diff --git a/runtime/typescript/packages/core/src/model/streaming/index.ts b/runtime/typescript/packages/core/src/model/streaming/index.ts index 931f8a82..382b53fc 100644 --- a/runtime/typescript/packages/core/src/model/streaming/index.ts +++ b/runtime/typescript/packages/core/src/model/streaming/index.ts @@ -1,3 +1,4 @@ +// // Copyright (c) Microsoft. All rights reserved. // WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. diff --git a/runtime/typescript/packages/core/src/model/streaming/stream-options.ts b/runtime/typescript/packages/core/src/model/streaming/stream-options.ts index f5f78005..e1763b43 100644 --- a/runtime/typescript/packages/core/src/model/streaming/stream-options.ts +++ b/runtime/typescript/packages/core/src/model/streaming/stream-options.ts @@ -1,3 +1,4 @@ +// // Copyright (c) Microsoft. All rights reserved. // WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. diff --git a/runtime/typescript/packages/core/src/model/template/format-config.ts b/runtime/typescript/packages/core/src/model/template/format-config.ts index 69741caa..3a8050bb 100644 --- a/runtime/typescript/packages/core/src/model/template/format-config.ts +++ b/runtime/typescript/packages/core/src/model/template/format-config.ts @@ -1,3 +1,4 @@ +// // Copyright (c) Microsoft. All rights reserved. // WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. diff --git a/runtime/typescript/packages/core/src/model/template/index.ts b/runtime/typescript/packages/core/src/model/template/index.ts index ba6ed5f4..fc96d87d 100644 --- a/runtime/typescript/packages/core/src/model/template/index.ts +++ b/runtime/typescript/packages/core/src/model/template/index.ts @@ -1,3 +1,4 @@ +// // Copyright (c) Microsoft. All rights reserved. // WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. diff --git a/runtime/typescript/packages/core/src/model/template/parser-config.ts b/runtime/typescript/packages/core/src/model/template/parser-config.ts index 9d2012dd..b676e890 100644 --- a/runtime/typescript/packages/core/src/model/template/parser-config.ts +++ b/runtime/typescript/packages/core/src/model/template/parser-config.ts @@ -1,3 +1,4 @@ +// // Copyright (c) Microsoft. All rights reserved. // WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. diff --git a/runtime/typescript/packages/core/src/model/template/template.ts b/runtime/typescript/packages/core/src/model/template/template.ts index d619c31f..e54d16be 100644 --- a/runtime/typescript/packages/core/src/model/template/template.ts +++ b/runtime/typescript/packages/core/src/model/template/template.ts @@ -1,3 +1,4 @@ +// // Copyright (c) Microsoft. All rights reserved. // WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. diff --git a/runtime/typescript/packages/core/src/model/tools/binding.ts b/runtime/typescript/packages/core/src/model/tools/binding.ts index 4dff4650..378f5b94 100644 --- a/runtime/typescript/packages/core/src/model/tools/binding.ts +++ b/runtime/typescript/packages/core/src/model/tools/binding.ts @@ -1,3 +1,4 @@ +// // Copyright (c) Microsoft. All rights reserved. // WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. diff --git a/runtime/typescript/packages/core/src/model/tools/index.ts b/runtime/typescript/packages/core/src/model/tools/index.ts index 6ee2a844..49dd0ac3 100644 --- a/runtime/typescript/packages/core/src/model/tools/index.ts +++ b/runtime/typescript/packages/core/src/model/tools/index.ts @@ -1,3 +1,4 @@ +// // Copyright (c) Microsoft. All rights reserved. // WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. diff --git a/runtime/typescript/packages/core/src/model/tools/mcp-approval-mode.ts b/runtime/typescript/packages/core/src/model/tools/mcp-approval-mode.ts index 33ac01a8..54169e0e 100644 --- a/runtime/typescript/packages/core/src/model/tools/mcp-approval-mode.ts +++ b/runtime/typescript/packages/core/src/model/tools/mcp-approval-mode.ts @@ -1,3 +1,4 @@ +// // Copyright (c) Microsoft. All rights reserved. // WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. diff --git a/runtime/typescript/packages/core/src/model/tools/tool-context.ts b/runtime/typescript/packages/core/src/model/tools/tool-context.ts index a893b47c..a8b63fe1 100644 --- a/runtime/typescript/packages/core/src/model/tools/tool-context.ts +++ b/runtime/typescript/packages/core/src/model/tools/tool-context.ts @@ -1,3 +1,4 @@ +// // Copyright (c) Microsoft. All rights reserved. // WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. diff --git a/runtime/typescript/packages/core/src/model/tools/tool-dispatch-result.ts b/runtime/typescript/packages/core/src/model/tools/tool-dispatch-result.ts index 9fdb93b9..3250bcdf 100644 --- a/runtime/typescript/packages/core/src/model/tools/tool-dispatch-result.ts +++ b/runtime/typescript/packages/core/src/model/tools/tool-dispatch-result.ts @@ -1,3 +1,4 @@ +// // Copyright (c) Microsoft. All rights reserved. // WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. diff --git a/runtime/typescript/packages/core/src/model/tools/tool.ts b/runtime/typescript/packages/core/src/model/tools/tool.ts index 26f180ba..87ddffc3 100644 --- a/runtime/typescript/packages/core/src/model/tools/tool.ts +++ b/runtime/typescript/packages/core/src/model/tools/tool.ts @@ -1,3 +1,4 @@ +// // Copyright (c) Microsoft. All rights reserved. // WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. diff --git a/runtime/typescript/packages/core/src/model/tracing/index.ts b/runtime/typescript/packages/core/src/model/tracing/index.ts index 2135ee97..e535f8fc 100644 --- a/runtime/typescript/packages/core/src/model/tracing/index.ts +++ b/runtime/typescript/packages/core/src/model/tracing/index.ts @@ -1,3 +1,4 @@ +// // Copyright (c) Microsoft. All rights reserved. // WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. diff --git a/runtime/typescript/packages/core/src/model/tracing/trace-file.ts b/runtime/typescript/packages/core/src/model/tracing/trace-file.ts index 082468ec..eabf02a1 100644 --- a/runtime/typescript/packages/core/src/model/tracing/trace-file.ts +++ b/runtime/typescript/packages/core/src/model/tracing/trace-file.ts @@ -1,3 +1,4 @@ +// // Copyright (c) Microsoft. All rights reserved. // WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. diff --git a/runtime/typescript/packages/core/src/model/tracing/trace-span.ts b/runtime/typescript/packages/core/src/model/tracing/trace-span.ts index 15891a30..5fd017be 100644 --- a/runtime/typescript/packages/core/src/model/tracing/trace-span.ts +++ b/runtime/typescript/packages/core/src/model/tracing/trace-span.ts @@ -1,3 +1,4 @@ +// // Copyright (c) Microsoft. All rights reserved. // WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. diff --git a/runtime/typescript/packages/core/src/model/tracing/trace-time.ts b/runtime/typescript/packages/core/src/model/tracing/trace-time.ts index 552d00f4..be24b71e 100644 --- a/runtime/typescript/packages/core/src/model/tracing/trace-time.ts +++ b/runtime/typescript/packages/core/src/model/tracing/trace-time.ts @@ -1,3 +1,4 @@ +// // Copyright (c) Microsoft. All rights reserved. // WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. diff --git a/runtime/typescript/packages/core/src/model/wire/anthropic-image-block.ts b/runtime/typescript/packages/core/src/model/wire/anthropic-image-block.ts index 8a2ab857..ec2df219 100644 --- a/runtime/typescript/packages/core/src/model/wire/anthropic-image-block.ts +++ b/runtime/typescript/packages/core/src/model/wire/anthropic-image-block.ts @@ -1,3 +1,4 @@ +// // Copyright (c) Microsoft. All rights reserved. // WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. diff --git a/runtime/typescript/packages/core/src/model/wire/anthropic-image-source.ts b/runtime/typescript/packages/core/src/model/wire/anthropic-image-source.ts index 0580d2ca..319c38c9 100644 --- a/runtime/typescript/packages/core/src/model/wire/anthropic-image-source.ts +++ b/runtime/typescript/packages/core/src/model/wire/anthropic-image-source.ts @@ -1,3 +1,4 @@ +// // Copyright (c) Microsoft. All rights reserved. // WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. diff --git a/runtime/typescript/packages/core/src/model/wire/anthropic-messages-request.ts b/runtime/typescript/packages/core/src/model/wire/anthropic-messages-request.ts index f40b274b..8dde01a6 100644 --- a/runtime/typescript/packages/core/src/model/wire/anthropic-messages-request.ts +++ b/runtime/typescript/packages/core/src/model/wire/anthropic-messages-request.ts @@ -1,3 +1,4 @@ +// // Copyright (c) Microsoft. All rights reserved. // WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. diff --git a/runtime/typescript/packages/core/src/model/wire/anthropic-messages-response.ts b/runtime/typescript/packages/core/src/model/wire/anthropic-messages-response.ts index cd688326..50fd78b8 100644 --- a/runtime/typescript/packages/core/src/model/wire/anthropic-messages-response.ts +++ b/runtime/typescript/packages/core/src/model/wire/anthropic-messages-response.ts @@ -1,3 +1,4 @@ +// // Copyright (c) Microsoft. All rights reserved. // WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. diff --git a/runtime/typescript/packages/core/src/model/wire/anthropic-text-block.ts b/runtime/typescript/packages/core/src/model/wire/anthropic-text-block.ts index b0010085..e441b5c0 100644 --- a/runtime/typescript/packages/core/src/model/wire/anthropic-text-block.ts +++ b/runtime/typescript/packages/core/src/model/wire/anthropic-text-block.ts @@ -1,3 +1,4 @@ +// // Copyright (c) Microsoft. All rights reserved. // WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. diff --git a/runtime/typescript/packages/core/src/model/wire/anthropic-tool-definition.ts b/runtime/typescript/packages/core/src/model/wire/anthropic-tool-definition.ts index 1c7c518f..07f9017d 100644 --- a/runtime/typescript/packages/core/src/model/wire/anthropic-tool-definition.ts +++ b/runtime/typescript/packages/core/src/model/wire/anthropic-tool-definition.ts @@ -1,3 +1,4 @@ +// // Copyright (c) Microsoft. All rights reserved. // WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. diff --git a/runtime/typescript/packages/core/src/model/wire/anthropic-tool-result-block.ts b/runtime/typescript/packages/core/src/model/wire/anthropic-tool-result-block.ts index ccaee3e4..b51ccced 100644 --- a/runtime/typescript/packages/core/src/model/wire/anthropic-tool-result-block.ts +++ b/runtime/typescript/packages/core/src/model/wire/anthropic-tool-result-block.ts @@ -1,3 +1,4 @@ +// // Copyright (c) Microsoft. All rights reserved. // WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. diff --git a/runtime/typescript/packages/core/src/model/wire/anthropic-tool-use-block.ts b/runtime/typescript/packages/core/src/model/wire/anthropic-tool-use-block.ts index d2d1e7b2..1a21976a 100644 --- a/runtime/typescript/packages/core/src/model/wire/anthropic-tool-use-block.ts +++ b/runtime/typescript/packages/core/src/model/wire/anthropic-tool-use-block.ts @@ -1,3 +1,4 @@ +// // Copyright (c) Microsoft. All rights reserved. // WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. diff --git a/runtime/typescript/packages/core/src/model/wire/anthropic-usage.ts b/runtime/typescript/packages/core/src/model/wire/anthropic-usage.ts index da25a95b..532de4a8 100644 --- a/runtime/typescript/packages/core/src/model/wire/anthropic-usage.ts +++ b/runtime/typescript/packages/core/src/model/wire/anthropic-usage.ts @@ -1,3 +1,4 @@ +// // Copyright (c) Microsoft. All rights reserved. // WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. diff --git a/runtime/typescript/packages/core/src/model/wire/anthropic-wire-message.ts b/runtime/typescript/packages/core/src/model/wire/anthropic-wire-message.ts index 4e83619d..6415c2cf 100644 --- a/runtime/typescript/packages/core/src/model/wire/anthropic-wire-message.ts +++ b/runtime/typescript/packages/core/src/model/wire/anthropic-wire-message.ts @@ -1,3 +1,4 @@ +// // Copyright (c) Microsoft. All rights reserved. // WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. diff --git a/runtime/typescript/packages/core/src/model/wire/index.ts b/runtime/typescript/packages/core/src/model/wire/index.ts index 8fdcc319..a4f800c2 100644 --- a/runtime/typescript/packages/core/src/model/wire/index.ts +++ b/runtime/typescript/packages/core/src/model/wire/index.ts @@ -1,3 +1,4 @@ +// // Copyright (c) Microsoft. All rights reserved. // WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. diff --git a/runtime/typescript/packages/core/tests/agent-extensions.test.ts b/runtime/typescript/packages/core/tests/agent-extensions.test.ts index 8a97f930..eab76f92 100644 --- a/runtime/typescript/packages/core/tests/agent-extensions.test.ts +++ b/runtime/typescript/packages/core/tests/agent-extensions.test.ts @@ -25,7 +25,19 @@ describe("emitEvent", () => { const data = { iteration: 1 }; emitEvent(cb, "status", data); expect(cb).toHaveBeenCalledOnce(); - expect(cb).toHaveBeenCalledWith("status", data); + expect(cb).toHaveBeenCalledWith( + "status", + expect.objectContaining({ + iteration: 1, + turnEvent: expect.objectContaining({ + id: expect.any(String), + type: "status", + timestamp: expect.any(String), + iteration: 1, + payload: data, + }), + }), + ); }); it("silently swallows exceptions from callback", () => { diff --git a/runtime/typescript/packages/core/tests/harness/adapters.test.ts b/runtime/typescript/packages/core/tests/harness/adapters.test.ts new file mode 100644 index 00000000..013af758 --- /dev/null +++ b/runtime/typescript/packages/core/tests/harness/adapters.test.ts @@ -0,0 +1,164 @@ +import { mkdtempSync, readFileSync, rmSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; + +import { + AllowAllPermissionResolver, + Checkpoint, + CollectingEventSink, + DenyAllPermissionResolver, + FunctionHostToolExecutor, + HostToolRequest, + InMemoryCheckpointStore, + JsonlEventJournalWriter, + PermissionRequest, + SessionEvent, + SessionSummary, + TurnEvent, +} from "../../src/index"; + +function turnEvent(): TurnEvent { + return new TurnEvent({ + id: "turn-event", + type: "turn_start", + timestamp: "2026-06-10T00:00:00Z", + payload: { phase: "start" }, + }); +} + +function sessionEvent(): SessionEvent { + return new SessionEvent({ + id: "session-event", + type: "session_start", + timestamp: "2026-06-10T00:00:00Z", + sessionId: "session-1", + payload: { phase: "start" }, + }); +} + +describe("harness reference adapters", () => { + it("collects emitted events in order", () => { + const sink = new CollectingEventSink(); + + expect(sink.emitTurn(turnEvent())).toBe(true); + expect(sink.emitSession(sessionEvent())).toBe(true); + + expect(sink.turnEvents.map((event) => event.id)).toEqual(["turn-event"]); + expect(sink.sessionEvents.map((event) => event.id)).toEqual(["session-event"]); + }); + + it("writes JSONL event journal records", () => { + const dir = mkdtempSync(join(tmpdir(), "prompty-trace-")); + try { + const path = join(dir, "trace.jsonl"); + const writer = new JsonlEventJournalWriter(path); + + writer.appendTurn(turnEvent()); + writer.appendSession(sessionEvent()); + writer.close(new SessionSummary({ sessionId: "session-1", turns: 1 })); + + const lines = readFileSync(path, "utf8").trim().split("\n").map((line) => JSON.parse(line)); + expect(lines.map((line) => line.kind)).toEqual(["turn", "session", "summary"]); + expect(lines[0].event.id).toBe("turn-event"); + expect(lines[1].event.id).toBe("session-event"); + expect(lines[2].summary.sessionId).toBe("session-1"); + } finally { + rmSync(dir, { recursive: true, force: true }); + } + }); + + it("returns false when writing after trace close", () => { + const dir = mkdtempSync(join(tmpdir(), "prompty-trace-")); + try { + const writer = new JsonlEventJournalWriter(join(dir, "trace.jsonl")); + + expect(writer.close(null)).toBe(true); + expect(writer.appendTurn(turnEvent())).toBe(false); + } finally { + rmSync(dir, { recursive: true, force: true }); + } + }); + + it("stores checkpoints by session and checkpoint id", async () => { + const store = new InMemoryCheckpointStore(); + const checkpoint = new Checkpoint({ id: "checkpoint-1", sessionId: "session-1", title: "First" }); + + await expect(store.save(checkpoint)).resolves.toBe(checkpoint); + await expect(store.load("session-1", "checkpoint-1")).resolves.toBe(checkpoint); + await expect(store.load("session-1", "missing")).resolves.toBeNull(); + await expect(store.listCheckpoints("session-1")).resolves.toEqual([checkpoint]); + }); + + it("requires checkpoint identifiers when saving", async () => { + const store = new InMemoryCheckpointStore(); + + await expect(store.save(new Checkpoint({ id: "checkpoint-1", title: "Missing session" }))).rejects.toThrow( + "sessionId", + ); + await expect(store.save(new Checkpoint({ sessionId: "session-1", title: "Missing id" }))).rejects.toThrow("id"); + }); + + it("resolves allow-all and deny-all permissions", async () => { + const request = new PermissionRequest({ + requestId: "permission-1", + toolCallId: "tool-call-1", + permission: "tool.execute", + }); + + await expect(new AllowAllPermissionResolver().request(request)).resolves.toMatchObject({ + requestId: "permission-1", + toolCallId: "tool-call-1", + permission: "tool.execute", + approved: true, + reason: "allow_all", + }); + await expect(new DenyAllPermissionResolver().request(request)).resolves.toMatchObject({ + approved: false, + reason: "deny_all", + }); + }); + + it("executes registered host tool functions", async () => { + const executor = new FunctionHostToolExecutor({ + add: (args) => Number(args.a) + Number(args.b), + }); + + await expect( + executor.execute(new HostToolRequest({ requestId: "exec-1", toolName: "add", arguments: { a: 2, b: 3 } })), + ).resolves.toMatchObject({ + requestId: "exec-1", + toolName: "add", + success: true, + result: 5, + }); + }); + + it("passes empty arguments to host tool functions when omitted", async () => { + const executor = new FunctionHostToolExecutor({ + count: (args) => Object.keys(args).length, + }); + + await expect(executor.execute(new HostToolRequest({ toolName: "count" }))).resolves.toMatchObject({ + success: true, + result: 0, + }); + }); + + it("returns failed host tool results for missing or throwing handlers", async () => { + const executor = new FunctionHostToolExecutor({ + fail: () => { + throw new Error("boom"); + }, + }); + + await expect(executor.execute(new HostToolRequest({ toolName: "missing" }))).resolves.toMatchObject({ + success: false, + errorKind: "not_found", + }); + await expect(executor.execute(new HostToolRequest({ toolName: "fail" }))).resolves.toMatchObject({ + success: false, + errorKind: "exception", + result: { message: "boom" }, + }); + }); +}); diff --git a/runtime/typescript/packages/core/tests/harness/replay-verifier.test.ts b/runtime/typescript/packages/core/tests/harness/replay-verifier.test.ts new file mode 100644 index 00000000..44fdf808 --- /dev/null +++ b/runtime/typescript/packages/core/tests/harness/replay-verifier.test.ts @@ -0,0 +1,43 @@ +import { + ReferenceReplayVerifier, +} from "../../src/index"; +import { + ReplayJournalRecord, + ReplayVerificationRequest, +} from "../../src/model/index"; + +describe("ReferenceReplayVerifier", () => { + it("passes identical normalized replay records", () => { + const record = new ReplayJournalRecord({ + kind: "turn", + type: "turn_end", + turnId: "turn-1", + iteration: 1, + status: "success", + }); + + const result = new ReferenceReplayVerifier().verify( + new ReplayVerificationRequest({ expected: [record], actual: [record] }), + ); + + expect(result.status).toBe("passed"); + expect(result.mismatches).toEqual([]); + expect(result.expectedCount).toBe(1); + expect(result.actualCount).toBe(1); + }); + + it("reports mismatches with generated contract types", () => { + const result = new ReferenceReplayVerifier().verify( + new ReplayVerificationRequest({ + expected: [new ReplayJournalRecord({ kind: "summary", sessionId: "session-1", status: "success" })], + actual: [new ReplayJournalRecord({ kind: "summary", sessionId: "session-1", status: "error" })], + }), + ); + + expect(result.status).toBe("failed"); + expect(result.mismatches?.[0]).toMatchObject({ + index: 0, + message: "Replay record mismatch", + }); + }); +}); diff --git a/runtime/typescript/packages/core/tests/harness/turn-runner.test.ts b/runtime/typescript/packages/core/tests/harness/turn-runner.test.ts new file mode 100644 index 00000000..2421e0ac --- /dev/null +++ b/runtime/typescript/packages/core/tests/harness/turn-runner.test.ts @@ -0,0 +1,362 @@ +import { mkdtempSync, readFileSync, rmSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; + +import { + AllowAllPermissionResolver, + CollectingEventSink, + DenyAllPermissionResolver, + FunctionHostToolExecutor, + HostToolRequest, + InMemoryCheckpointStore, + JsonlEventJournalWriter, + ReferenceTurnRunner, + type HostToolExecutor, +} from "../../src/index"; +import { TurnOptions } from "../../src/model/index"; + +function fixedIds(): (prefix: string) => string { + let index = 0; + return (prefix: string) => `${prefix}-${++index}`; +} + +function journalRecords(path: string): unknown[] { + return readFileSync(path, "utf8").trim().split("\n").map((line) => JSON.parse(line)); +} + +type ReplayVector = { + version: number; + clock: string; + sessionId: string; + turnId: string; + scenarios: Array<{ + name: string; + inputs?: Record; + maxIterations?: number; + expected: string[]; + }>; +}; + +function replayVectors(): ReplayVector { + const vectors = JSON.parse( + readFileSync(new URL("../../../../../../spec/vectors/harness/replay_vectors.json", import.meta.url), "utf8"), + ); + if (vectors.version !== 1) { + throw new Error(`Unsupported replay vector version ${vectors.version}`); + } + return vectors; +} + +function normalizeJournal(records: unknown[]): string[] { + return records.map((record: any) => { + if (record.kind === "summary") { + const summary = record.summary; + return `summary:${summary.sessionId}:${summary.status}:turns=${summary.turns}:checkpoints=${summary.checkpoints}`; + } + + const event = record.event; + if (record.kind === "session") { + if (event.type === "session_end") { + return `session:${event.type}:${event.sessionId}:${event.turnId}:${event.payload.status}`; + } + return `session:${event.type}:${event.sessionId}:${event.turnId}`; + } + + const payload = event.payload ?? {}; + switch (event.type) { + case "permission_requested": + return `turn:${event.type}:${event.iteration}:${payload.requestId}`; + case "permission_completed": + return `turn:${event.type}:${event.iteration}:${payload.approved}`; + case "tool_execution_start": + return `turn:${event.type}:${event.iteration}:${payload.toolName}`; + case "tool_execution_complete": + case "tool_result": + return [ + `turn:${event.type}:${event.iteration}:${payload.toolName}:${payload.success}`, + payload.errorKind, + ].filter(Boolean).join(":"); + case "error": + return `turn:${event.type}:${event.iteration}:${payload.errorKind}`; + case "turn_end": + return `turn:${event.type}:${event.iteration}:${payload.status}`; + default: + return `turn:${event.type}:${event.iteration}`; + } + }); +} + +function modelForScenario(name: string): (request: any) => unknown { + return (request) => { + if (name === "no_tool") { + return { + output: { text: `hello ${request.inputs.name}` }, + checkpointState: { stable: true }, + }; + } + if (request.iteration === 0) { + const toolName = name === "tool_failure" ? "fail" : "add"; + return { + toolRequests: [ + new HostToolRequest({ + requestId: "exec-1", + toolCallId: "call-1", + toolName, + arguments: { a: 2, b: 3 }, + }), + ], + }; + } + return { output: { toolResult: request.toolResults[0]?.result, errorKind: request.toolResults[0]?.errorKind } }; + }; +} + +describe("ReferenceTurnRunner", () => { + it("emits, journals, and checkpoints a deterministic single turn in order", async () => { + const dir = mkdtempSync(join(tmpdir(), "prompty-turn-")); + try { + const journalPath = join(dir, "trace.jsonl"); + const sink = new CollectingEventSink(); + const checkpointStore = new InMemoryCheckpointStore(); + const runner = new ReferenceTurnRunner({ + eventSink: sink, + journal: new JsonlEventJournalWriter(journalPath), + checkpointStore, + permissionResolver: new AllowAllPermissionResolver(), + hostToolExecutor: new FunctionHostToolExecutor({}), + invokeModel: (request) => + ({ + output: { text: `hello ${request.inputs.name}` }, + checkpointState: { stable: true }, + }), + now: () => "2026-06-28T00:00:00Z", + nextId: fixedIds(), + }); + + const result = await runner.run({ + sessionId: "session-1", + turnId: "turn-1", + inputs: { name: "Ada" }, + options: new TurnOptions({ maxIterations: 3 }), + }); + + expect(result).toMatchObject({ + sessionId: "session-1", + turnId: "turn-1", + status: "success", + iterations: 1, + output: { text: "hello Ada" }, + }); + expect(sink.sessionEvents.map((event) => event.type)).toEqual([ + "session_start", + "checkpoint_created", + "session_end", + ]); + expect(sink.turnEvents.map((event) => event.type)).toEqual([ + "turn_start", + "llm_start", + "llm_complete", + "turn_end", + ]); + await expect(checkpointStore.load("session-1", "turn-1-checkpoint-0")).resolves.toMatchObject({ + state: { stable: true, output: { text: "hello Ada" } }, + }); + expect(journalRecords(journalPath).map((record: any) => record.kind)).toEqual([ + "session", + "turn", + "turn", + "turn", + "session", + "turn", + "session", + "summary", + ]); + } finally { + rmSync(dir, { recursive: true, force: true }); + } + }); + + it("requests permission and executes host tools before the final model response", async () => { + const dir = mkdtempSync(join(tmpdir(), "prompty-turn-tool-")); + try { + const sink = new CollectingEventSink(); + const runner = new ReferenceTurnRunner({ + eventSink: sink, + journal: new JsonlEventJournalWriter(join(dir, "trace.jsonl")), + checkpointStore: new InMemoryCheckpointStore(), + permissionResolver: new AllowAllPermissionResolver(), + hostToolExecutor: new FunctionHostToolExecutor({ + add: (args) => Number(args.a) + Number(args.b), + }), + invokeModel: (request) => { + if (request.iteration === 0) { + return { + toolRequests: [ + new HostToolRequest({ + requestId: "exec-1", + toolCallId: "call-1", + toolName: "add", + arguments: { a: 2, b: 3 }, + }), + ], + }; + } + return { output: { toolResult: request.toolResults[0]?.result } }; + }, + now: () => "2026-06-28T00:00:00Z", + nextId: fixedIds(), + }); + + const result = await runner.run({ sessionId: "session-1", turnId: "turn-1" }); + + expect(result.output).toEqual({ toolResult: 5 }); + expect(result.toolResults).toHaveLength(1); + expect(result.toolResults[0]).toMatchObject({ success: true, result: 5 }); + expect(sink.turnEvents.map((event) => event.type)).toEqual([ + "turn_start", + "llm_start", + "llm_complete", + "permission_requested", + "permission_completed", + "tool_execution_start", + "tool_execution_complete", + "tool_result", + "messages_updated", + "llm_start", + "llm_complete", + "turn_end", + ]); + } finally { + rmSync(dir, { recursive: true, force: true }); + } + }); + + it("records denied permission without executing the host tool", async () => { + const dir = mkdtempSync(join(tmpdir(), "prompty-turn-deny-")); + try { + const sink = new CollectingEventSink(); + const executor: HostToolExecutor = { + execute: async () => { + throw new Error("should not execute"); + }, + }; + const runner = new ReferenceTurnRunner({ + eventSink: sink, + journal: new JsonlEventJournalWriter(join(dir, "trace.jsonl")), + checkpointStore: new InMemoryCheckpointStore(), + permissionResolver: new DenyAllPermissionResolver(), + hostToolExecutor: executor, + invokeModel: (request) => + request.iteration === 0 + ? { + toolRequests: [new HostToolRequest({ requestId: "exec-1", toolName: "shell" })], + } + : { output: { denied: request.toolResults[0]?.errorKind } }, + now: () => "2026-06-28T00:00:00Z", + nextId: fixedIds(), + }); + + const result = await runner.run({ sessionId: "session-1", turnId: "turn-1" }); + + expect(result.output).toEqual({ denied: "permission_denied" }); + expect(result.toolResults[0]).toMatchObject({ success: false, errorKind: "permission_denied" }); + expect(sink.turnEvents.map((event) => event.type)).not.toContain("tool_execution_start"); + } finally { + rmSync(dir, { recursive: true, force: true }); + } + }); + + it("surfaces host tool failure as replayable tool results", async () => { + const dir = mkdtempSync(join(tmpdir(), "prompty-turn-fail-")); + try { + const runner = new ReferenceTurnRunner({ + eventSink: new CollectingEventSink(), + journal: new JsonlEventJournalWriter(join(dir, "trace.jsonl")), + checkpointStore: new InMemoryCheckpointStore(), + permissionResolver: new AllowAllPermissionResolver(), + hostToolExecutor: new FunctionHostToolExecutor({ + fail: () => { + throw new Error("boom"); + }, + }), + invokeModel: (request) => + request.iteration === 0 + ? { + toolRequests: [new HostToolRequest({ requestId: "exec-1", toolName: "fail" })], + } + : { output: request.toolResults[0]?.save() }, + now: () => "2026-06-28T00:00:00Z", + nextId: fixedIds(), + }); + + const result = await runner.run({ sessionId: "session-1", turnId: "turn-1" }); + + expect(result.output).toMatchObject({ success: false, errorKind: "exception", result: { message: "boom" } }); + } finally { + rmSync(dir, { recursive: true, force: true }); + } + }); + + it("produces deterministic journal records when clock and ids are injected", async () => { + async function runOnce(path: string): Promise { + const runner = new ReferenceTurnRunner({ + eventSink: new CollectingEventSink(), + journal: new JsonlEventJournalWriter(path), + checkpointStore: new InMemoryCheckpointStore(), + permissionResolver: new AllowAllPermissionResolver(), + hostToolExecutor: new FunctionHostToolExecutor({}), + invokeModel: () => ({ output: "done" }), + now: () => "2026-06-28T00:00:00Z", + nextId: fixedIds(), + }); + await runner.run({ sessionId: "session-1", turnId: "turn-1" }); + return journalRecords(path); + } + + const dir = mkdtempSync(join(tmpdir(), "prompty-turn-deterministic-")); + try { + await expect(runOnce(join(dir, "first.jsonl"))).resolves.toEqual(await runOnce(join(dir, "second.jsonl"))); + } finally { + rmSync(dir, { recursive: true, force: true }); + } + }); + + it("matches shared golden replay journal vectors", async () => { + const vectors = replayVectors(); + + for (const scenario of vectors.scenarios) { + const dir = mkdtempSync(join(tmpdir(), `prompty-replay-${scenario.name}-`)); + try { + const runner = new ReferenceTurnRunner({ + eventSink: new CollectingEventSink(), + journal: new JsonlEventJournalWriter(join(dir, "trace.jsonl")), + checkpointStore: new InMemoryCheckpointStore(), + permissionResolver: + scenario.name === "permission_denied" + ? new DenyAllPermissionResolver() + : new AllowAllPermissionResolver(), + hostToolExecutor: new FunctionHostToolExecutor({ + add: (args) => Number(args.a) + Number(args.b), + fail: () => { + throw new Error("boom"); + }, + }), + invokeModel: modelForScenario(scenario.name), + now: () => vectors.clock, + nextId: fixedIds(), + }); + + await runner.run({ + sessionId: vectors.sessionId, + turnId: vectors.turnId, + inputs: scenario.inputs, + options: new TurnOptions({ maxIterations: scenario.maxIterations }), + }); + + expect(normalizeJournal(journalRecords(join(dir, "trace.jsonl"))), scenario.name).toEqual(scenario.expected); + } finally { + rmSync(dir, { recursive: true, force: true }); + } + } + }); +}); diff --git a/runtime/typescript/packages/core/tests/model/agent/guardrail-result.test.ts b/runtime/typescript/packages/core/tests/model/agent/guardrail-result.test.ts index feb766b9..8c7de005 100644 --- a/runtime/typescript/packages/core/tests/model/agent/guardrail-result.test.ts +++ b/runtime/typescript/packages/core/tests/model/agent/guardrail-result.test.ts @@ -1,3 +1,4 @@ +// // Copyright (c) Microsoft. All rights reserved. // WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. diff --git a/runtime/typescript/packages/core/tests/model/agent/prompty.test.ts b/runtime/typescript/packages/core/tests/model/agent/prompty.test.ts index 8adf4d85..401cb72e 100644 --- a/runtime/typescript/packages/core/tests/model/agent/prompty.test.ts +++ b/runtime/typescript/packages/core/tests/model/agent/prompty.test.ts @@ -1,3 +1,4 @@ +// // Copyright (c) Microsoft. All rights reserved. // WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. diff --git a/runtime/typescript/packages/core/tests/model/connection/anonymous-connection.test.ts b/runtime/typescript/packages/core/tests/model/connection/anonymous-connection.test.ts index 3459285d..57c5bcda 100644 --- a/runtime/typescript/packages/core/tests/model/connection/anonymous-connection.test.ts +++ b/runtime/typescript/packages/core/tests/model/connection/anonymous-connection.test.ts @@ -1,3 +1,4 @@ +// // Copyright (c) Microsoft. All rights reserved. // WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. diff --git a/runtime/typescript/packages/core/tests/model/connection/api-key-connection.test.ts b/runtime/typescript/packages/core/tests/model/connection/api-key-connection.test.ts index 1fd78c51..b72270a7 100644 --- a/runtime/typescript/packages/core/tests/model/connection/api-key-connection.test.ts +++ b/runtime/typescript/packages/core/tests/model/connection/api-key-connection.test.ts @@ -1,3 +1,4 @@ +// // Copyright (c) Microsoft. All rights reserved. // WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. diff --git a/runtime/typescript/packages/core/tests/model/connection/connection.test.ts b/runtime/typescript/packages/core/tests/model/connection/connection.test.ts index 50c60abf..193aa0bd 100644 --- a/runtime/typescript/packages/core/tests/model/connection/connection.test.ts +++ b/runtime/typescript/packages/core/tests/model/connection/connection.test.ts @@ -1,3 +1,4 @@ +// // Copyright (c) Microsoft. All rights reserved. // WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. diff --git a/runtime/typescript/packages/core/tests/model/connection/foundry-connection.test.ts b/runtime/typescript/packages/core/tests/model/connection/foundry-connection.test.ts index e3fac545..b691b3fe 100644 --- a/runtime/typescript/packages/core/tests/model/connection/foundry-connection.test.ts +++ b/runtime/typescript/packages/core/tests/model/connection/foundry-connection.test.ts @@ -1,3 +1,4 @@ +// // Copyright (c) Microsoft. All rights reserved. // WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. diff --git a/runtime/typescript/packages/core/tests/model/connection/o-auth-connection.test.ts b/runtime/typescript/packages/core/tests/model/connection/o-auth-connection.test.ts index 6cc08cd9..6119454b 100644 --- a/runtime/typescript/packages/core/tests/model/connection/o-auth-connection.test.ts +++ b/runtime/typescript/packages/core/tests/model/connection/o-auth-connection.test.ts @@ -1,3 +1,4 @@ +// // Copyright (c) Microsoft. All rights reserved. // WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. diff --git a/runtime/typescript/packages/core/tests/model/connection/reference-connection.test.ts b/runtime/typescript/packages/core/tests/model/connection/reference-connection.test.ts index 999ca922..4b46d0f5 100644 --- a/runtime/typescript/packages/core/tests/model/connection/reference-connection.test.ts +++ b/runtime/typescript/packages/core/tests/model/connection/reference-connection.test.ts @@ -1,3 +1,4 @@ +// // Copyright (c) Microsoft. All rights reserved. // WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. diff --git a/runtime/typescript/packages/core/tests/model/connection/remote-connection.test.ts b/runtime/typescript/packages/core/tests/model/connection/remote-connection.test.ts index 5c876360..bbc29a6c 100644 --- a/runtime/typescript/packages/core/tests/model/connection/remote-connection.test.ts +++ b/runtime/typescript/packages/core/tests/model/connection/remote-connection.test.ts @@ -1,3 +1,4 @@ +// // Copyright (c) Microsoft. All rights reserved. // WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. diff --git a/runtime/typescript/packages/core/tests/model/conversation/audio-part.test.ts b/runtime/typescript/packages/core/tests/model/conversation/audio-part.test.ts index 4f3bd5b0..348fc839 100644 --- a/runtime/typescript/packages/core/tests/model/conversation/audio-part.test.ts +++ b/runtime/typescript/packages/core/tests/model/conversation/audio-part.test.ts @@ -1,3 +1,4 @@ +// // Copyright (c) Microsoft. All rights reserved. // WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. diff --git a/runtime/typescript/packages/core/tests/model/conversation/content-part.test.ts b/runtime/typescript/packages/core/tests/model/conversation/content-part.test.ts index 3a6e3b4d..ecd3cece 100644 --- a/runtime/typescript/packages/core/tests/model/conversation/content-part.test.ts +++ b/runtime/typescript/packages/core/tests/model/conversation/content-part.test.ts @@ -1,3 +1,4 @@ +// // Copyright (c) Microsoft. All rights reserved. // WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. diff --git a/runtime/typescript/packages/core/tests/model/conversation/file-part.test.ts b/runtime/typescript/packages/core/tests/model/conversation/file-part.test.ts index 29893087..c83c5ee5 100644 --- a/runtime/typescript/packages/core/tests/model/conversation/file-part.test.ts +++ b/runtime/typescript/packages/core/tests/model/conversation/file-part.test.ts @@ -1,3 +1,4 @@ +// // Copyright (c) Microsoft. All rights reserved. // WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. diff --git a/runtime/typescript/packages/core/tests/model/conversation/image-part.test.ts b/runtime/typescript/packages/core/tests/model/conversation/image-part.test.ts index 4fa02ad4..827cbe23 100644 --- a/runtime/typescript/packages/core/tests/model/conversation/image-part.test.ts +++ b/runtime/typescript/packages/core/tests/model/conversation/image-part.test.ts @@ -1,3 +1,4 @@ +// // Copyright (c) Microsoft. All rights reserved. // WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. diff --git a/runtime/typescript/packages/core/tests/model/conversation/message.test.ts b/runtime/typescript/packages/core/tests/model/conversation/message.test.ts index badb3277..3816ada6 100644 --- a/runtime/typescript/packages/core/tests/model/conversation/message.test.ts +++ b/runtime/typescript/packages/core/tests/model/conversation/message.test.ts @@ -1,3 +1,4 @@ +// // Copyright (c) Microsoft. All rights reserved. // WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. diff --git a/runtime/typescript/packages/core/tests/model/conversation/text-part.test.ts b/runtime/typescript/packages/core/tests/model/conversation/text-part.test.ts index 366212d4..1c80f3e0 100644 --- a/runtime/typescript/packages/core/tests/model/conversation/text-part.test.ts +++ b/runtime/typescript/packages/core/tests/model/conversation/text-part.test.ts @@ -1,3 +1,4 @@ +// // Copyright (c) Microsoft. All rights reserved. // WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. diff --git a/runtime/typescript/packages/core/tests/model/conversation/thread-marker.test.ts b/runtime/typescript/packages/core/tests/model/conversation/thread-marker.test.ts index a478cb0c..2f57c898 100644 --- a/runtime/typescript/packages/core/tests/model/conversation/thread-marker.test.ts +++ b/runtime/typescript/packages/core/tests/model/conversation/thread-marker.test.ts @@ -1,3 +1,4 @@ +// // Copyright (c) Microsoft. All rights reserved. // WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. diff --git a/runtime/typescript/packages/core/tests/model/conversation/tool-call.test.ts b/runtime/typescript/packages/core/tests/model/conversation/tool-call.test.ts index b0141892..eac12b40 100644 --- a/runtime/typescript/packages/core/tests/model/conversation/tool-call.test.ts +++ b/runtime/typescript/packages/core/tests/model/conversation/tool-call.test.ts @@ -1,3 +1,4 @@ +// // Copyright (c) Microsoft. All rights reserved. // WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. diff --git a/runtime/typescript/packages/core/tests/model/conversation/tool-result.test.ts b/runtime/typescript/packages/core/tests/model/conversation/tool-result.test.ts index e747abfd..8f74c02e 100644 --- a/runtime/typescript/packages/core/tests/model/conversation/tool-result.test.ts +++ b/runtime/typescript/packages/core/tests/model/conversation/tool-result.test.ts @@ -1,3 +1,4 @@ +// // Copyright (c) Microsoft. All rights reserved. // WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. @@ -18,31 +19,47 @@ describe("ToolResult", () => { describe("JSON serialization", () => { it("should load from JSON - example 1", () => { - const json = `{\n "parts": [\n {\n "kind": "text",\n "value": "72°F and sunny"\n }\n ]\n}`; + const json = `{\n "parts": [\n {\n "kind": "text",\n "value": "72°F and sunny"\n }\n ],\n "errorKind": "missing_tool",\n "errorMessage": "Tool 'get_weather' is not registered",\n "durationMs": 42\n}`; const instance = ToolResult.fromJson(json); expect(instance).toBeDefined(); + expect(instance.errorKind).toEqual("missing_tool"); + expect(instance.errorMessage).toEqual( + "Tool 'get_weather' is not registered", + ); + expect(instance.durationMs).toEqual(42); }); it("should round-trip JSON - example 1", () => { - const json = `{\n "parts": [\n {\n "kind": "text",\n "value": "72°F and sunny"\n }\n ]\n}`; + const json = `{\n "parts": [\n {\n "kind": "text",\n "value": "72°F and sunny"\n }\n ],\n "errorKind": "missing_tool",\n "errorMessage": "Tool 'get_weather' is not registered",\n "durationMs": 42\n}`; const instance = ToolResult.fromJson(json); const output = instance.toJson(); const reloaded = ToolResult.fromJson(output); + expect(reloaded.errorKind).toEqual(instance.errorKind); + expect(reloaded.errorMessage).toEqual(instance.errorMessage); + expect(reloaded.durationMs).toEqual(instance.durationMs); }); }); describe("YAML serialization", () => { it("should load from YAML - example 1", () => { - const yaml = `parts:\n - kind: text\n value: 72°F and sunny\n`; + const yaml = `parts:\n - kind: text\n value: 72°F and sunny\nerrorKind: missing_tool\nerrorMessage: Tool 'get_weather' is not registered\ndurationMs: 42\n`; const instance = ToolResult.fromYaml(yaml); expect(instance).toBeDefined(); + expect(instance.errorKind).toEqual("missing_tool"); + expect(instance.errorMessage).toEqual( + "Tool 'get_weather' is not registered", + ); + expect(instance.durationMs).toEqual(42); }); it("should round-trip YAML - example 1", () => { - const yaml = `parts:\n - kind: text\n value: 72°F and sunny\n`; + const yaml = `parts:\n - kind: text\n value: 72°F and sunny\nerrorKind: missing_tool\nerrorMessage: Tool 'get_weather' is not registered\ndurationMs: 42\n`; const instance = ToolResult.fromYaml(yaml); const output = instance.toYaml(); const reloaded = ToolResult.fromYaml(output); + expect(reloaded.errorKind).toEqual(instance.errorKind); + expect(reloaded.errorMessage).toEqual(instance.errorMessage); + expect(reloaded.durationMs).toEqual(instance.durationMs); }); }); diff --git a/runtime/typescript/packages/core/tests/model/core/array-property.test.ts b/runtime/typescript/packages/core/tests/model/core/array-property.test.ts index d83e43f7..08e11b9b 100644 --- a/runtime/typescript/packages/core/tests/model/core/array-property.test.ts +++ b/runtime/typescript/packages/core/tests/model/core/array-property.test.ts @@ -1,3 +1,4 @@ +// // Copyright (c) Microsoft. All rights reserved. // WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. diff --git a/runtime/typescript/packages/core/tests/model/core/file-not-found-error.test.ts b/runtime/typescript/packages/core/tests/model/core/file-not-found-error.test.ts index 8e811b81..e4e5036f 100644 --- a/runtime/typescript/packages/core/tests/model/core/file-not-found-error.test.ts +++ b/runtime/typescript/packages/core/tests/model/core/file-not-found-error.test.ts @@ -1,3 +1,4 @@ +// // Copyright (c) Microsoft. All rights reserved. // WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. diff --git a/runtime/typescript/packages/core/tests/model/core/invoker-error.test.ts b/runtime/typescript/packages/core/tests/model/core/invoker-error.test.ts index 3699b903..248de8ad 100644 --- a/runtime/typescript/packages/core/tests/model/core/invoker-error.test.ts +++ b/runtime/typescript/packages/core/tests/model/core/invoker-error.test.ts @@ -1,3 +1,4 @@ +// // Copyright (c) Microsoft. All rights reserved. // WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. diff --git a/runtime/typescript/packages/core/tests/model/core/object-property.test.ts b/runtime/typescript/packages/core/tests/model/core/object-property.test.ts index 51702532..09d9567a 100644 --- a/runtime/typescript/packages/core/tests/model/core/object-property.test.ts +++ b/runtime/typescript/packages/core/tests/model/core/object-property.test.ts @@ -1,3 +1,4 @@ +// // Copyright (c) Microsoft. All rights reserved. // WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. diff --git a/runtime/typescript/packages/core/tests/model/core/property.test.ts b/runtime/typescript/packages/core/tests/model/core/property.test.ts index c94e9337..2925a486 100644 --- a/runtime/typescript/packages/core/tests/model/core/property.test.ts +++ b/runtime/typescript/packages/core/tests/model/core/property.test.ts @@ -1,3 +1,4 @@ +// // Copyright (c) Microsoft. All rights reserved. // WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. diff --git a/runtime/typescript/packages/core/tests/model/core/validation-error.test.ts b/runtime/typescript/packages/core/tests/model/core/validation-error.test.ts index 0ac7ef99..fde328f0 100644 --- a/runtime/typescript/packages/core/tests/model/core/validation-error.test.ts +++ b/runtime/typescript/packages/core/tests/model/core/validation-error.test.ts @@ -1,3 +1,4 @@ +// // Copyright (c) Microsoft. All rights reserved. // WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. diff --git a/runtime/typescript/packages/core/tests/model/core/validation-result.test.ts b/runtime/typescript/packages/core/tests/model/core/validation-result.test.ts index 539cb2dd..41ea55c0 100644 --- a/runtime/typescript/packages/core/tests/model/core/validation-result.test.ts +++ b/runtime/typescript/packages/core/tests/model/core/validation-result.test.ts @@ -1,3 +1,4 @@ +// // Copyright (c) Microsoft. All rights reserved. // WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. diff --git a/runtime/typescript/packages/core/tests/model/events/checkpoint.test.ts b/runtime/typescript/packages/core/tests/model/events/checkpoint.test.ts new file mode 100644 index 00000000..26c11255 --- /dev/null +++ b/runtime/typescript/packages/core/tests/model/events/checkpoint.test.ts @@ -0,0 +1,88 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. + +import { Checkpoint } from "../../../src/model/index"; + +describe("Checkpoint", () => { + describe("construction", () => { + it("should create a new instance with defaults", () => { + const instance = new Checkpoint(); + expect(instance).toBeDefined(); + }); + + it("should create a new instance with partial initialization", () => { + const instance = new Checkpoint({}); + expect(instance).toBeDefined(); + }); + }); + + describe("JSON serialization", () => { + it("should load from JSON - example 1", () => { + const json = `{\n "id": "chk_abc123",\n "sessionId": "sess_abc123",\n "turnId": "turn_001",\n "checkpointNumber": 3,\n "title": "Added harness contracts",\n "createdAt": "2026-06-09T20:00:00Z"\n}`; + const instance = Checkpoint.fromJson(json); + expect(instance).toBeDefined(); + expect(instance.id).toEqual("chk_abc123"); + expect(instance.sessionId).toEqual("sess_abc123"); + expect(instance.turnId).toEqual("turn_001"); + expect(instance.checkpointNumber).toEqual(3); + expect(instance.title).toEqual("Added harness contracts"); + expect(instance.createdAt).toEqual("2026-06-09T20:00:00Z"); + }); + + it("should round-trip JSON - example 1", () => { + const json = `{\n "id": "chk_abc123",\n "sessionId": "sess_abc123",\n "turnId": "turn_001",\n "checkpointNumber": 3,\n "title": "Added harness contracts",\n "createdAt": "2026-06-09T20:00:00Z"\n}`; + const instance = Checkpoint.fromJson(json); + const output = instance.toJson(); + const reloaded = Checkpoint.fromJson(output); + expect(reloaded.id).toEqual(instance.id); + expect(reloaded.sessionId).toEqual(instance.sessionId); + expect(reloaded.turnId).toEqual(instance.turnId); + expect(reloaded.checkpointNumber).toEqual(instance.checkpointNumber); + expect(reloaded.title).toEqual(instance.title); + expect(reloaded.createdAt).toEqual(instance.createdAt); + }); + }); + + describe("YAML serialization", () => { + it("should load from YAML - example 1", () => { + const yaml = `id: chk_abc123\nsessionId: sess_abc123\nturnId: turn_001\ncheckpointNumber: 3\ntitle: Added harness contracts\ncreatedAt: "2026-06-09T20:00:00Z"\n`; + const instance = Checkpoint.fromYaml(yaml); + expect(instance).toBeDefined(); + expect(instance.id).toEqual("chk_abc123"); + expect(instance.sessionId).toEqual("sess_abc123"); + expect(instance.turnId).toEqual("turn_001"); + expect(instance.checkpointNumber).toEqual(3); + expect(instance.title).toEqual("Added harness contracts"); + expect(instance.createdAt).toEqual("2026-06-09T20:00:00Z"); + }); + + it("should round-trip YAML - example 1", () => { + const yaml = `id: chk_abc123\nsessionId: sess_abc123\nturnId: turn_001\ncheckpointNumber: 3\ntitle: Added harness contracts\ncreatedAt: "2026-06-09T20:00:00Z"\n`; + const instance = Checkpoint.fromYaml(yaml); + const output = instance.toYaml(); + const reloaded = Checkpoint.fromYaml(output); + expect(reloaded.id).toEqual(instance.id); + expect(reloaded.sessionId).toEqual(instance.sessionId); + expect(reloaded.turnId).toEqual(instance.turnId); + expect(reloaded.checkpointNumber).toEqual(instance.checkpointNumber); + expect(reloaded.title).toEqual(instance.title); + expect(reloaded.createdAt).toEqual(instance.createdAt); + }); + }); + + describe("load and save", () => { + it("should load from dictionary", () => { + const data: Record = {}; + const instance = Checkpoint.load(data); + expect(instance).toBeDefined(); + }); + + it("should save to dictionary", () => { + const instance = new Checkpoint(); + const data = instance.save(); + expect(data).toBeDefined(); + expect(typeof data).toBe("object"); + }); + }); +}); diff --git a/runtime/typescript/packages/core/tests/model/events/compaction-complete-payload.test.ts b/runtime/typescript/packages/core/tests/model/events/compaction-complete-payload.test.ts index 73e06ab7..f8f56d98 100644 --- a/runtime/typescript/packages/core/tests/model/events/compaction-complete-payload.test.ts +++ b/runtime/typescript/packages/core/tests/model/events/compaction-complete-payload.test.ts @@ -1,3 +1,4 @@ +// // Copyright (c) Microsoft. All rights reserved. // WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. @@ -18,39 +19,43 @@ describe("CompactionCompletePayload", () => { describe("JSON serialization", () => { it("should load from JSON - example 1", () => { - const json = `{\n "removed": 5,\n "remaining": 3\n}`; + const json = `{\n "removed": 5,\n "remaining": 3,\n "summaryLength": 1200\n}`; const instance = CompactionCompletePayload.fromJson(json); expect(instance).toBeDefined(); expect(instance.removed).toEqual(5); expect(instance.remaining).toEqual(3); + expect(instance.summaryLength).toEqual(1200); }); it("should round-trip JSON - example 1", () => { - const json = `{\n "removed": 5,\n "remaining": 3\n}`; + const json = `{\n "removed": 5,\n "remaining": 3,\n "summaryLength": 1200\n}`; const instance = CompactionCompletePayload.fromJson(json); const output = instance.toJson(); const reloaded = CompactionCompletePayload.fromJson(output); expect(reloaded.removed).toEqual(instance.removed); expect(reloaded.remaining).toEqual(instance.remaining); + expect(reloaded.summaryLength).toEqual(instance.summaryLength); }); }); describe("YAML serialization", () => { it("should load from YAML - example 1", () => { - const yaml = `removed: 5\nremaining: 3\n`; + const yaml = `removed: 5\nremaining: 3\nsummaryLength: 1200\n`; const instance = CompactionCompletePayload.fromYaml(yaml); expect(instance).toBeDefined(); expect(instance.removed).toEqual(5); expect(instance.remaining).toEqual(3); + expect(instance.summaryLength).toEqual(1200); }); it("should round-trip YAML - example 1", () => { - const yaml = `removed: 5\nremaining: 3\n`; + const yaml = `removed: 5\nremaining: 3\nsummaryLength: 1200\n`; const instance = CompactionCompletePayload.fromYaml(yaml); const output = instance.toYaml(); const reloaded = CompactionCompletePayload.fromYaml(output); expect(reloaded.removed).toEqual(instance.removed); expect(reloaded.remaining).toEqual(instance.remaining); + expect(reloaded.summaryLength).toEqual(instance.summaryLength); }); }); diff --git a/runtime/typescript/packages/core/tests/model/events/compaction-failed-payload.test.ts b/runtime/typescript/packages/core/tests/model/events/compaction-failed-payload.test.ts index 1f0ac0a5..211dad76 100644 --- a/runtime/typescript/packages/core/tests/model/events/compaction-failed-payload.test.ts +++ b/runtime/typescript/packages/core/tests/model/events/compaction-failed-payload.test.ts @@ -1,3 +1,4 @@ +// // Copyright (c) Microsoft. All rights reserved. // WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. diff --git a/runtime/typescript/packages/core/tests/model/events/compaction-start-payload.test.ts b/runtime/typescript/packages/core/tests/model/events/compaction-start-payload.test.ts new file mode 100644 index 00000000..d4b87fb2 --- /dev/null +++ b/runtime/typescript/packages/core/tests/model/events/compaction-start-payload.test.ts @@ -0,0 +1,68 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. + +import { CompactionStartPayload } from "../../../src/model/index"; + +describe("CompactionStartPayload", () => { + describe("construction", () => { + it("should create a new instance with defaults", () => { + const instance = new CompactionStartPayload(); + expect(instance).toBeDefined(); + }); + + it("should create a new instance with partial initialization", () => { + const instance = new CompactionStartPayload({}); + expect(instance).toBeDefined(); + }); + }); + + describe("JSON serialization", () => { + it("should load from JSON - example 1", () => { + const json = `{\n "droppedCount": 5\n}`; + const instance = CompactionStartPayload.fromJson(json); + expect(instance).toBeDefined(); + expect(instance.droppedCount).toEqual(5); + }); + + it("should round-trip JSON - example 1", () => { + const json = `{\n "droppedCount": 5\n}`; + const instance = CompactionStartPayload.fromJson(json); + const output = instance.toJson(); + const reloaded = CompactionStartPayload.fromJson(output); + expect(reloaded.droppedCount).toEqual(instance.droppedCount); + }); + }); + + describe("YAML serialization", () => { + it("should load from YAML - example 1", () => { + const yaml = `droppedCount: 5\n`; + const instance = CompactionStartPayload.fromYaml(yaml); + expect(instance).toBeDefined(); + expect(instance.droppedCount).toEqual(5); + }); + + it("should round-trip YAML - example 1", () => { + const yaml = `droppedCount: 5\n`; + const instance = CompactionStartPayload.fromYaml(yaml); + const output = instance.toYaml(); + const reloaded = CompactionStartPayload.fromYaml(output); + expect(reloaded.droppedCount).toEqual(instance.droppedCount); + }); + }); + + describe("load and save", () => { + it("should load from dictionary", () => { + const data: Record = {}; + const instance = CompactionStartPayload.load(data); + expect(instance).toBeDefined(); + }); + + it("should save to dictionary", () => { + const instance = new CompactionStartPayload(); + const data = instance.save(); + expect(data).toBeDefined(); + expect(typeof data).toBe("object"); + }); + }); +}); diff --git a/runtime/typescript/packages/core/tests/model/events/done-event-payload.test.ts b/runtime/typescript/packages/core/tests/model/events/done-event-payload.test.ts index d0812504..6ff23372 100644 --- a/runtime/typescript/packages/core/tests/model/events/done-event-payload.test.ts +++ b/runtime/typescript/packages/core/tests/model/events/done-event-payload.test.ts @@ -1,3 +1,4 @@ +// // Copyright (c) Microsoft. All rights reserved. // WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. @@ -16,44 +17,6 @@ describe("DoneEventPayload", () => { }); }); - describe("JSON serialization", () => { - it("should load from JSON - example 1", () => { - const json = `{\n "response": "The weather in Paris is 72°F and sunny."\n}`; - const instance = DoneEventPayload.fromJson(json); - expect(instance).toBeDefined(); - expect(instance.response).toEqual( - "The weather in Paris is 72°F and sunny.", - ); - }); - - it("should round-trip JSON - example 1", () => { - const json = `{\n "response": "The weather in Paris is 72°F and sunny."\n}`; - const instance = DoneEventPayload.fromJson(json); - const output = instance.toJson(); - const reloaded = DoneEventPayload.fromJson(output); - expect(reloaded.response).toEqual(instance.response); - }); - }); - - describe("YAML serialization", () => { - it("should load from YAML - example 1", () => { - const yaml = `response: The weather in Paris is 72°F and sunny.\n`; - const instance = DoneEventPayload.fromYaml(yaml); - expect(instance).toBeDefined(); - expect(instance.response).toEqual( - "The weather in Paris is 72°F and sunny.", - ); - }); - - it("should round-trip YAML - example 1", () => { - const yaml = `response: The weather in Paris is 72°F and sunny.\n`; - const instance = DoneEventPayload.fromYaml(yaml); - const output = instance.toYaml(); - const reloaded = DoneEventPayload.fromYaml(output); - expect(reloaded.response).toEqual(instance.response); - }); - }); - describe("load and save", () => { it("should load from dictionary", () => { const data: Record = {}; diff --git a/runtime/typescript/packages/core/tests/model/events/error-chunk.test.ts b/runtime/typescript/packages/core/tests/model/events/error-chunk.test.ts index 33526f5b..b0980c7f 100644 --- a/runtime/typescript/packages/core/tests/model/events/error-chunk.test.ts +++ b/runtime/typescript/packages/core/tests/model/events/error-chunk.test.ts @@ -1,3 +1,4 @@ +// // Copyright (c) Microsoft. All rights reserved. // WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. diff --git a/runtime/typescript/packages/core/tests/model/events/error-event-payload.test.ts b/runtime/typescript/packages/core/tests/model/events/error-event-payload.test.ts index f105f005..2dee9181 100644 --- a/runtime/typescript/packages/core/tests/model/events/error-event-payload.test.ts +++ b/runtime/typescript/packages/core/tests/model/events/error-event-payload.test.ts @@ -1,3 +1,4 @@ +// // Copyright (c) Microsoft. All rights reserved. // WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. @@ -18,35 +19,43 @@ describe("ErrorEventPayload", () => { describe("JSON serialization", () => { it("should load from JSON - example 1", () => { - const json = `{\n "message": "Rate limit exceeded"\n}`; + const json = `{\n "message": "Rate limit exceeded",\n "errorKind": "rate_limit",\n "phase": "llm"\n}`; const instance = ErrorEventPayload.fromJson(json); expect(instance).toBeDefined(); expect(instance.message).toEqual("Rate limit exceeded"); + expect(instance.errorKind).toEqual("rate_limit"); + expect(instance.phase).toEqual("llm"); }); it("should round-trip JSON - example 1", () => { - const json = `{\n "message": "Rate limit exceeded"\n}`; + const json = `{\n "message": "Rate limit exceeded",\n "errorKind": "rate_limit",\n "phase": "llm"\n}`; const instance = ErrorEventPayload.fromJson(json); const output = instance.toJson(); const reloaded = ErrorEventPayload.fromJson(output); expect(reloaded.message).toEqual(instance.message); + expect(reloaded.errorKind).toEqual(instance.errorKind); + expect(reloaded.phase).toEqual(instance.phase); }); }); describe("YAML serialization", () => { it("should load from YAML - example 1", () => { - const yaml = `message: Rate limit exceeded\n`; + const yaml = `message: Rate limit exceeded\nerrorKind: rate_limit\nphase: llm\n`; const instance = ErrorEventPayload.fromYaml(yaml); expect(instance).toBeDefined(); expect(instance.message).toEqual("Rate limit exceeded"); + expect(instance.errorKind).toEqual("rate_limit"); + expect(instance.phase).toEqual("llm"); }); it("should round-trip YAML - example 1", () => { - const yaml = `message: Rate limit exceeded\n`; + const yaml = `message: Rate limit exceeded\nerrorKind: rate_limit\nphase: llm\n`; const instance = ErrorEventPayload.fromYaml(yaml); const output = instance.toYaml(); const reloaded = ErrorEventPayload.fromYaml(output); expect(reloaded.message).toEqual(instance.message); + expect(reloaded.errorKind).toEqual(instance.errorKind); + expect(reloaded.phase).toEqual(instance.phase); }); }); diff --git a/runtime/typescript/packages/core/tests/model/events/harness-context.test.ts b/runtime/typescript/packages/core/tests/model/events/harness-context.test.ts new file mode 100644 index 00000000..da18d798 --- /dev/null +++ b/runtime/typescript/packages/core/tests/model/events/harness-context.test.ts @@ -0,0 +1,72 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. + +import { HarnessContext } from "../../../src/model/index"; + +describe("HarnessContext", () => { + describe("construction", () => { + it("should create a new instance with defaults", () => { + const instance = new HarnessContext(); + expect(instance).toBeDefined(); + }); + + it("should create a new instance with partial initialization", () => { + const instance = new HarnessContext({}); + expect(instance).toBeDefined(); + }); + }); + + describe("JSON serialization", () => { + it("should load from JSON - example 1", () => { + const json = `{\n "cwd": "/workspace/project",\n "gitRoot": "/workspace/project"\n}`; + const instance = HarnessContext.fromJson(json); + expect(instance).toBeDefined(); + expect(instance.cwd).toEqual("/workspace/project"); + expect(instance.gitRoot).toEqual("/workspace/project"); + }); + + it("should round-trip JSON - example 1", () => { + const json = `{\n "cwd": "/workspace/project",\n "gitRoot": "/workspace/project"\n}`; + const instance = HarnessContext.fromJson(json); + const output = instance.toJson(); + const reloaded = HarnessContext.fromJson(output); + expect(reloaded.cwd).toEqual(instance.cwd); + expect(reloaded.gitRoot).toEqual(instance.gitRoot); + }); + }); + + describe("YAML serialization", () => { + it("should load from YAML - example 1", () => { + const yaml = `cwd: /workspace/project\ngitRoot: /workspace/project\n`; + const instance = HarnessContext.fromYaml(yaml); + expect(instance).toBeDefined(); + expect(instance.cwd).toEqual("/workspace/project"); + expect(instance.gitRoot).toEqual("/workspace/project"); + }); + + it("should round-trip YAML - example 1", () => { + const yaml = `cwd: /workspace/project\ngitRoot: /workspace/project\n`; + const instance = HarnessContext.fromYaml(yaml); + const output = instance.toYaml(); + const reloaded = HarnessContext.fromYaml(output); + expect(reloaded.cwd).toEqual(instance.cwd); + expect(reloaded.gitRoot).toEqual(instance.gitRoot); + }); + }); + + describe("load and save", () => { + it("should load from dictionary", () => { + const data: Record = {}; + const instance = HarnessContext.load(data); + expect(instance).toBeDefined(); + }); + + it("should save to dictionary", () => { + const instance = new HarnessContext(); + const data = instance.save(); + expect(data).toBeDefined(); + expect(typeof data).toBe("object"); + }); + }); +}); diff --git a/runtime/typescript/packages/core/tests/model/events/hook-end-payload.test.ts b/runtime/typescript/packages/core/tests/model/events/hook-end-payload.test.ts new file mode 100644 index 00000000..645d8b0e --- /dev/null +++ b/runtime/typescript/packages/core/tests/model/events/hook-end-payload.test.ts @@ -0,0 +1,84 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. + +import { HookEndPayload } from "../../../src/model/index"; + +describe("HookEndPayload", () => { + describe("construction", () => { + it("should create a new instance with defaults", () => { + const instance = new HookEndPayload(); + expect(instance).toBeDefined(); + }); + + it("should create a new instance with partial initialization", () => { + const instance = new HookEndPayload({}); + expect(instance).toBeDefined(); + }); + }); + + describe("JSON serialization", () => { + it("should load from JSON - example 1", () => { + const json = `{\n "hookInvocationId": "hook_abc123",\n "hookType": "preToolUse",\n "success": true,\n "durationMs": 12,\n "error": "hook failed"\n}`; + const instance = HookEndPayload.fromJson(json); + expect(instance).toBeDefined(); + expect(instance.hookInvocationId).toEqual("hook_abc123"); + expect(instance.hookType).toEqual("preToolUse"); + expect(instance.success).toEqual(true); + expect(instance.durationMs).toEqual(12); + expect(instance.error).toEqual("hook failed"); + }); + + it("should round-trip JSON - example 1", () => { + const json = `{\n "hookInvocationId": "hook_abc123",\n "hookType": "preToolUse",\n "success": true,\n "durationMs": 12,\n "error": "hook failed"\n}`; + const instance = HookEndPayload.fromJson(json); + const output = instance.toJson(); + const reloaded = HookEndPayload.fromJson(output); + expect(reloaded.hookInvocationId).toEqual(instance.hookInvocationId); + expect(reloaded.hookType).toEqual(instance.hookType); + expect(reloaded.success).toEqual(instance.success); + expect(reloaded.durationMs).toEqual(instance.durationMs); + expect(reloaded.error).toEqual(instance.error); + }); + }); + + describe("YAML serialization", () => { + it("should load from YAML - example 1", () => { + const yaml = `hookInvocationId: hook_abc123\nhookType: preToolUse\nsuccess: true\ndurationMs: 12\nerror: hook failed\n`; + const instance = HookEndPayload.fromYaml(yaml); + expect(instance).toBeDefined(); + expect(instance.hookInvocationId).toEqual("hook_abc123"); + expect(instance.hookType).toEqual("preToolUse"); + expect(instance.success).toEqual(true); + expect(instance.durationMs).toEqual(12); + expect(instance.error).toEqual("hook failed"); + }); + + it("should round-trip YAML - example 1", () => { + const yaml = `hookInvocationId: hook_abc123\nhookType: preToolUse\nsuccess: true\ndurationMs: 12\nerror: hook failed\n`; + const instance = HookEndPayload.fromYaml(yaml); + const output = instance.toYaml(); + const reloaded = HookEndPayload.fromYaml(output); + expect(reloaded.hookInvocationId).toEqual(instance.hookInvocationId); + expect(reloaded.hookType).toEqual(instance.hookType); + expect(reloaded.success).toEqual(instance.success); + expect(reloaded.durationMs).toEqual(instance.durationMs); + expect(reloaded.error).toEqual(instance.error); + }); + }); + + describe("load and save", () => { + it("should load from dictionary", () => { + const data: Record = {}; + const instance = HookEndPayload.load(data); + expect(instance).toBeDefined(); + }); + + it("should save to dictionary", () => { + const instance = new HookEndPayload(); + const data = instance.save(); + expect(data).toBeDefined(); + expect(typeof data).toBe("object"); + }); + }); +}); diff --git a/runtime/typescript/packages/core/tests/model/events/hook-start-payload.test.ts b/runtime/typescript/packages/core/tests/model/events/hook-start-payload.test.ts new file mode 100644 index 00000000..77b0ac3c --- /dev/null +++ b/runtime/typescript/packages/core/tests/model/events/hook-start-payload.test.ts @@ -0,0 +1,72 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. + +import { HookStartPayload } from "../../../src/model/index"; + +describe("HookStartPayload", () => { + describe("construction", () => { + it("should create a new instance with defaults", () => { + const instance = new HookStartPayload(); + expect(instance).toBeDefined(); + }); + + it("should create a new instance with partial initialization", () => { + const instance = new HookStartPayload({}); + expect(instance).toBeDefined(); + }); + }); + + describe("JSON serialization", () => { + it("should load from JSON - example 1", () => { + const json = `{\n "hookInvocationId": "hook_abc123",\n "hookType": "preToolUse"\n}`; + const instance = HookStartPayload.fromJson(json); + expect(instance).toBeDefined(); + expect(instance.hookInvocationId).toEqual("hook_abc123"); + expect(instance.hookType).toEqual("preToolUse"); + }); + + it("should round-trip JSON - example 1", () => { + const json = `{\n "hookInvocationId": "hook_abc123",\n "hookType": "preToolUse"\n}`; + const instance = HookStartPayload.fromJson(json); + const output = instance.toJson(); + const reloaded = HookStartPayload.fromJson(output); + expect(reloaded.hookInvocationId).toEqual(instance.hookInvocationId); + expect(reloaded.hookType).toEqual(instance.hookType); + }); + }); + + describe("YAML serialization", () => { + it("should load from YAML - example 1", () => { + const yaml = `hookInvocationId: hook_abc123\nhookType: preToolUse\n`; + const instance = HookStartPayload.fromYaml(yaml); + expect(instance).toBeDefined(); + expect(instance.hookInvocationId).toEqual("hook_abc123"); + expect(instance.hookType).toEqual("preToolUse"); + }); + + it("should round-trip YAML - example 1", () => { + const yaml = `hookInvocationId: hook_abc123\nhookType: preToolUse\n`; + const instance = HookStartPayload.fromYaml(yaml); + const output = instance.toYaml(); + const reloaded = HookStartPayload.fromYaml(output); + expect(reloaded.hookInvocationId).toEqual(instance.hookInvocationId); + expect(reloaded.hookType).toEqual(instance.hookType); + }); + }); + + describe("load and save", () => { + it("should load from dictionary", () => { + const data: Record = {}; + const instance = HookStartPayload.load(data); + expect(instance).toBeDefined(); + }); + + it("should save to dictionary", () => { + const instance = new HookStartPayload(); + const data = instance.save(); + expect(data).toBeDefined(); + expect(typeof data).toBe("object"); + }); + }); +}); diff --git a/runtime/typescript/packages/core/tests/model/events/host-tool-request.test.ts b/runtime/typescript/packages/core/tests/model/events/host-tool-request.test.ts new file mode 100644 index 00000000..74acbfa4 --- /dev/null +++ b/runtime/typescript/packages/core/tests/model/events/host-tool-request.test.ts @@ -0,0 +1,80 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. + +import { HostToolRequest } from "../../../src/model/index"; + +describe("HostToolRequest", () => { + describe("construction", () => { + it("should create a new instance with defaults", () => { + const instance = new HostToolRequest(); + expect(instance).toBeDefined(); + }); + + it("should create a new instance with partial initialization", () => { + const instance = new HostToolRequest({}); + expect(instance).toBeDefined(); + }); + }); + + describe("JSON serialization", () => { + it("should load from JSON - example 1", () => { + const json = `{\n "requestId": "exec_abc123",\n "toolCallId": "call_abc123",\n "toolName": "powershell",\n "workingDirectory": "/workspace/project"\n}`; + const instance = HostToolRequest.fromJson(json); + expect(instance).toBeDefined(); + expect(instance.requestId).toEqual("exec_abc123"); + expect(instance.toolCallId).toEqual("call_abc123"); + expect(instance.toolName).toEqual("powershell"); + expect(instance.workingDirectory).toEqual("/workspace/project"); + }); + + it("should round-trip JSON - example 1", () => { + const json = `{\n "requestId": "exec_abc123",\n "toolCallId": "call_abc123",\n "toolName": "powershell",\n "workingDirectory": "/workspace/project"\n}`; + const instance = HostToolRequest.fromJson(json); + const output = instance.toJson(); + const reloaded = HostToolRequest.fromJson(output); + expect(reloaded.requestId).toEqual(instance.requestId); + expect(reloaded.toolCallId).toEqual(instance.toolCallId); + expect(reloaded.toolName).toEqual(instance.toolName); + expect(reloaded.workingDirectory).toEqual(instance.workingDirectory); + }); + }); + + describe("YAML serialization", () => { + it("should load from YAML - example 1", () => { + const yaml = `requestId: exec_abc123\ntoolCallId: call_abc123\ntoolName: powershell\nworkingDirectory: /workspace/project\n`; + const instance = HostToolRequest.fromYaml(yaml); + expect(instance).toBeDefined(); + expect(instance.requestId).toEqual("exec_abc123"); + expect(instance.toolCallId).toEqual("call_abc123"); + expect(instance.toolName).toEqual("powershell"); + expect(instance.workingDirectory).toEqual("/workspace/project"); + }); + + it("should round-trip YAML - example 1", () => { + const yaml = `requestId: exec_abc123\ntoolCallId: call_abc123\ntoolName: powershell\nworkingDirectory: /workspace/project\n`; + const instance = HostToolRequest.fromYaml(yaml); + const output = instance.toYaml(); + const reloaded = HostToolRequest.fromYaml(output); + expect(reloaded.requestId).toEqual(instance.requestId); + expect(reloaded.toolCallId).toEqual(instance.toolCallId); + expect(reloaded.toolName).toEqual(instance.toolName); + expect(reloaded.workingDirectory).toEqual(instance.workingDirectory); + }); + }); + + describe("load and save", () => { + it("should load from dictionary", () => { + const data: Record = {}; + const instance = HostToolRequest.load(data); + expect(instance).toBeDefined(); + }); + + it("should save to dictionary", () => { + const instance = new HostToolRequest(); + const data = instance.save(); + expect(data).toBeDefined(); + expect(typeof data).toBe("object"); + }); + }); +}); diff --git a/runtime/typescript/packages/core/tests/model/events/host-tool-result.test.ts b/runtime/typescript/packages/core/tests/model/events/host-tool-result.test.ts new file mode 100644 index 00000000..95c431cd --- /dev/null +++ b/runtime/typescript/packages/core/tests/model/events/host-tool-result.test.ts @@ -0,0 +1,92 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. + +import { HostToolResult } from "../../../src/model/index"; + +describe("HostToolResult", () => { + describe("construction", () => { + it("should create a new instance with defaults", () => { + const instance = new HostToolResult(); + expect(instance).toBeDefined(); + }); + + it("should create a new instance with partial initialization", () => { + const instance = new HostToolResult({}); + expect(instance).toBeDefined(); + }); + }); + + describe("JSON serialization", () => { + it("should load from JSON - example 1", () => { + const json = `{\n "requestId": "exec_abc123",\n "toolCallId": "call_abc123",\n "toolName": "powershell",\n "success": true,\n "exitCode": 0,\n "durationMs": 250,\n "errorKind": "timeout"\n}`; + const instance = HostToolResult.fromJson(json); + expect(instance).toBeDefined(); + expect(instance.requestId).toEqual("exec_abc123"); + expect(instance.toolCallId).toEqual("call_abc123"); + expect(instance.toolName).toEqual("powershell"); + expect(instance.success).toEqual(true); + expect(instance.exitCode).toEqual(0); + expect(instance.durationMs).toEqual(250); + expect(instance.errorKind).toEqual("timeout"); + }); + + it("should round-trip JSON - example 1", () => { + const json = `{\n "requestId": "exec_abc123",\n "toolCallId": "call_abc123",\n "toolName": "powershell",\n "success": true,\n "exitCode": 0,\n "durationMs": 250,\n "errorKind": "timeout"\n}`; + const instance = HostToolResult.fromJson(json); + const output = instance.toJson(); + const reloaded = HostToolResult.fromJson(output); + expect(reloaded.requestId).toEqual(instance.requestId); + expect(reloaded.toolCallId).toEqual(instance.toolCallId); + expect(reloaded.toolName).toEqual(instance.toolName); + expect(reloaded.success).toEqual(instance.success); + expect(reloaded.exitCode).toEqual(instance.exitCode); + expect(reloaded.durationMs).toEqual(instance.durationMs); + expect(reloaded.errorKind).toEqual(instance.errorKind); + }); + }); + + describe("YAML serialization", () => { + it("should load from YAML - example 1", () => { + const yaml = `requestId: exec_abc123\ntoolCallId: call_abc123\ntoolName: powershell\nsuccess: true\nexitCode: 0\ndurationMs: 250\nerrorKind: timeout\n`; + const instance = HostToolResult.fromYaml(yaml); + expect(instance).toBeDefined(); + expect(instance.requestId).toEqual("exec_abc123"); + expect(instance.toolCallId).toEqual("call_abc123"); + expect(instance.toolName).toEqual("powershell"); + expect(instance.success).toEqual(true); + expect(instance.exitCode).toEqual(0); + expect(instance.durationMs).toEqual(250); + expect(instance.errorKind).toEqual("timeout"); + }); + + it("should round-trip YAML - example 1", () => { + const yaml = `requestId: exec_abc123\ntoolCallId: call_abc123\ntoolName: powershell\nsuccess: true\nexitCode: 0\ndurationMs: 250\nerrorKind: timeout\n`; + const instance = HostToolResult.fromYaml(yaml); + const output = instance.toYaml(); + const reloaded = HostToolResult.fromYaml(output); + expect(reloaded.requestId).toEqual(instance.requestId); + expect(reloaded.toolCallId).toEqual(instance.toolCallId); + expect(reloaded.toolName).toEqual(instance.toolName); + expect(reloaded.success).toEqual(instance.success); + expect(reloaded.exitCode).toEqual(instance.exitCode); + expect(reloaded.durationMs).toEqual(instance.durationMs); + expect(reloaded.errorKind).toEqual(instance.errorKind); + }); + }); + + describe("load and save", () => { + it("should load from dictionary", () => { + const data: Record = {}; + const instance = HostToolResult.load(data); + expect(instance).toBeDefined(); + }); + + it("should save to dictionary", () => { + const instance = new HostToolResult(); + const data = instance.save(); + expect(data).toBeDefined(); + expect(typeof data).toBe("object"); + }); + }); +}); diff --git a/runtime/typescript/packages/core/tests/model/events/llm-complete-payload.test.ts b/runtime/typescript/packages/core/tests/model/events/llm-complete-payload.test.ts new file mode 100644 index 00000000..23953899 --- /dev/null +++ b/runtime/typescript/packages/core/tests/model/events/llm-complete-payload.test.ts @@ -0,0 +1,76 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. + +import { LlmCompletePayload } from "../../../src/model/index"; + +describe("LlmCompletePayload", () => { + describe("construction", () => { + it("should create a new instance with defaults", () => { + const instance = new LlmCompletePayload(); + expect(instance).toBeDefined(); + }); + + it("should create a new instance with partial initialization", () => { + const instance = new LlmCompletePayload({}); + expect(instance).toBeDefined(); + }); + }); + + describe("JSON serialization", () => { + it("should load from JSON - example 1", () => { + const json = `{\n "requestId": "req_abc123",\n "serviceRequestId": "srv_abc123",\n "durationMs": 820\n}`; + const instance = LlmCompletePayload.fromJson(json); + expect(instance).toBeDefined(); + expect(instance.requestId).toEqual("req_abc123"); + expect(instance.serviceRequestId).toEqual("srv_abc123"); + expect(instance.durationMs).toEqual(820); + }); + + it("should round-trip JSON - example 1", () => { + const json = `{\n "requestId": "req_abc123",\n "serviceRequestId": "srv_abc123",\n "durationMs": 820\n}`; + const instance = LlmCompletePayload.fromJson(json); + const output = instance.toJson(); + const reloaded = LlmCompletePayload.fromJson(output); + expect(reloaded.requestId).toEqual(instance.requestId); + expect(reloaded.serviceRequestId).toEqual(instance.serviceRequestId); + expect(reloaded.durationMs).toEqual(instance.durationMs); + }); + }); + + describe("YAML serialization", () => { + it("should load from YAML - example 1", () => { + const yaml = `requestId: req_abc123\nserviceRequestId: srv_abc123\ndurationMs: 820\n`; + const instance = LlmCompletePayload.fromYaml(yaml); + expect(instance).toBeDefined(); + expect(instance.requestId).toEqual("req_abc123"); + expect(instance.serviceRequestId).toEqual("srv_abc123"); + expect(instance.durationMs).toEqual(820); + }); + + it("should round-trip YAML - example 1", () => { + const yaml = `requestId: req_abc123\nserviceRequestId: srv_abc123\ndurationMs: 820\n`; + const instance = LlmCompletePayload.fromYaml(yaml); + const output = instance.toYaml(); + const reloaded = LlmCompletePayload.fromYaml(output); + expect(reloaded.requestId).toEqual(instance.requestId); + expect(reloaded.serviceRequestId).toEqual(instance.serviceRequestId); + expect(reloaded.durationMs).toEqual(instance.durationMs); + }); + }); + + describe("load and save", () => { + it("should load from dictionary", () => { + const data: Record = {}; + const instance = LlmCompletePayload.load(data); + expect(instance).toBeDefined(); + }); + + it("should save to dictionary", () => { + const instance = new LlmCompletePayload(); + const data = instance.save(); + expect(data).toBeDefined(); + expect(typeof data).toBe("object"); + }); + }); +}); diff --git a/runtime/typescript/packages/core/tests/model/events/llm-start-payload.test.ts b/runtime/typescript/packages/core/tests/model/events/llm-start-payload.test.ts new file mode 100644 index 00000000..bb7a0c6b --- /dev/null +++ b/runtime/typescript/packages/core/tests/model/events/llm-start-payload.test.ts @@ -0,0 +1,80 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. + +import { LlmStartPayload } from "../../../src/model/index"; + +describe("LlmStartPayload", () => { + describe("construction", () => { + it("should create a new instance with defaults", () => { + const instance = new LlmStartPayload(); + expect(instance).toBeDefined(); + }); + + it("should create a new instance with partial initialization", () => { + const instance = new LlmStartPayload({}); + expect(instance).toBeDefined(); + }); + }); + + describe("JSON serialization", () => { + it("should load from JSON - example 1", () => { + const json = `{\n "provider": "openai",\n "modelId": "gpt-4o-mini",\n "messageCount": 4,\n "attempt": 0\n}`; + const instance = LlmStartPayload.fromJson(json); + expect(instance).toBeDefined(); + expect(instance.provider).toEqual("openai"); + expect(instance.modelId).toEqual("gpt-4o-mini"); + expect(instance.messageCount).toEqual(4); + expect(instance.attempt).toEqual(0); + }); + + it("should round-trip JSON - example 1", () => { + const json = `{\n "provider": "openai",\n "modelId": "gpt-4o-mini",\n "messageCount": 4,\n "attempt": 0\n}`; + const instance = LlmStartPayload.fromJson(json); + const output = instance.toJson(); + const reloaded = LlmStartPayload.fromJson(output); + expect(reloaded.provider).toEqual(instance.provider); + expect(reloaded.modelId).toEqual(instance.modelId); + expect(reloaded.messageCount).toEqual(instance.messageCount); + expect(reloaded.attempt).toEqual(instance.attempt); + }); + }); + + describe("YAML serialization", () => { + it("should load from YAML - example 1", () => { + const yaml = `provider: openai\nmodelId: gpt-4o-mini\nmessageCount: 4\nattempt: 0\n`; + const instance = LlmStartPayload.fromYaml(yaml); + expect(instance).toBeDefined(); + expect(instance.provider).toEqual("openai"); + expect(instance.modelId).toEqual("gpt-4o-mini"); + expect(instance.messageCount).toEqual(4); + expect(instance.attempt).toEqual(0); + }); + + it("should round-trip YAML - example 1", () => { + const yaml = `provider: openai\nmodelId: gpt-4o-mini\nmessageCount: 4\nattempt: 0\n`; + const instance = LlmStartPayload.fromYaml(yaml); + const output = instance.toYaml(); + const reloaded = LlmStartPayload.fromYaml(output); + expect(reloaded.provider).toEqual(instance.provider); + expect(reloaded.modelId).toEqual(instance.modelId); + expect(reloaded.messageCount).toEqual(instance.messageCount); + expect(reloaded.attempt).toEqual(instance.attempt); + }); + }); + + describe("load and save", () => { + it("should load from dictionary", () => { + const data: Record = {}; + const instance = LlmStartPayload.load(data); + expect(instance).toBeDefined(); + }); + + it("should save to dictionary", () => { + const instance = new LlmStartPayload(); + const data = instance.save(); + expect(data).toBeDefined(); + expect(typeof data).toBe("object"); + }); + }); +}); diff --git a/runtime/typescript/packages/core/tests/model/events/messages-updated-payload.test.ts b/runtime/typescript/packages/core/tests/model/events/messages-updated-payload.test.ts index 57c00cff..08df2b50 100644 --- a/runtime/typescript/packages/core/tests/model/events/messages-updated-payload.test.ts +++ b/runtime/typescript/packages/core/tests/model/events/messages-updated-payload.test.ts @@ -1,3 +1,4 @@ +// // Copyright (c) Microsoft. All rights reserved. // WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. @@ -16,6 +17,44 @@ describe("MessagesUpdatedPayload", () => { }); }); + describe("JSON serialization", () => { + it("should load from JSON - example 1", () => { + const json = `{\n "reason": "tool_results",\n "removed": 2\n}`; + const instance = MessagesUpdatedPayload.fromJson(json); + expect(instance).toBeDefined(); + expect(instance.reason).toEqual("tool_results"); + expect(instance.removed).toEqual(2); + }); + + it("should round-trip JSON - example 1", () => { + const json = `{\n "reason": "tool_results",\n "removed": 2\n}`; + const instance = MessagesUpdatedPayload.fromJson(json); + const output = instance.toJson(); + const reloaded = MessagesUpdatedPayload.fromJson(output); + expect(reloaded.reason).toEqual(instance.reason); + expect(reloaded.removed).toEqual(instance.removed); + }); + }); + + describe("YAML serialization", () => { + it("should load from YAML - example 1", () => { + const yaml = `reason: tool_results\nremoved: 2\n`; + const instance = MessagesUpdatedPayload.fromYaml(yaml); + expect(instance).toBeDefined(); + expect(instance.reason).toEqual("tool_results"); + expect(instance.removed).toEqual(2); + }); + + it("should round-trip YAML - example 1", () => { + const yaml = `reason: tool_results\nremoved: 2\n`; + const instance = MessagesUpdatedPayload.fromYaml(yaml); + const output = instance.toYaml(); + const reloaded = MessagesUpdatedPayload.fromYaml(output); + expect(reloaded.reason).toEqual(instance.reason); + expect(reloaded.removed).toEqual(instance.removed); + }); + }); + describe("load and save", () => { it("should load from dictionary", () => { const data: Record = {}; diff --git a/runtime/typescript/packages/core/tests/model/events/permission-completed-payload.test.ts b/runtime/typescript/packages/core/tests/model/events/permission-completed-payload.test.ts new file mode 100644 index 00000000..84e2b7ff --- /dev/null +++ b/runtime/typescript/packages/core/tests/model/events/permission-completed-payload.test.ts @@ -0,0 +1,84 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. + +import { PermissionCompletedPayload } from "../../../src/model/index"; + +describe("PermissionCompletedPayload", () => { + describe("construction", () => { + it("should create a new instance with defaults", () => { + const instance = new PermissionCompletedPayload(); + expect(instance).toBeDefined(); + }); + + it("should create a new instance with partial initialization", () => { + const instance = new PermissionCompletedPayload({}); + expect(instance).toBeDefined(); + }); + }); + + describe("JSON serialization", () => { + it("should load from JSON - example 1", () => { + const json = `{\n "requestId": "perm_abc123",\n "toolCallId": "call_abc123",\n "permission": "tool.execute",\n "approved": true,\n "reason": "user_approved"\n}`; + const instance = PermissionCompletedPayload.fromJson(json); + expect(instance).toBeDefined(); + expect(instance.requestId).toEqual("perm_abc123"); + expect(instance.toolCallId).toEqual("call_abc123"); + expect(instance.permission).toEqual("tool.execute"); + expect(instance.approved).toEqual(true); + expect(instance.reason).toEqual("user_approved"); + }); + + it("should round-trip JSON - example 1", () => { + const json = `{\n "requestId": "perm_abc123",\n "toolCallId": "call_abc123",\n "permission": "tool.execute",\n "approved": true,\n "reason": "user_approved"\n}`; + const instance = PermissionCompletedPayload.fromJson(json); + const output = instance.toJson(); + const reloaded = PermissionCompletedPayload.fromJson(output); + expect(reloaded.requestId).toEqual(instance.requestId); + expect(reloaded.toolCallId).toEqual(instance.toolCallId); + expect(reloaded.permission).toEqual(instance.permission); + expect(reloaded.approved).toEqual(instance.approved); + expect(reloaded.reason).toEqual(instance.reason); + }); + }); + + describe("YAML serialization", () => { + it("should load from YAML - example 1", () => { + const yaml = `requestId: perm_abc123\ntoolCallId: call_abc123\npermission: tool.execute\napproved: true\nreason: user_approved\n`; + const instance = PermissionCompletedPayload.fromYaml(yaml); + expect(instance).toBeDefined(); + expect(instance.requestId).toEqual("perm_abc123"); + expect(instance.toolCallId).toEqual("call_abc123"); + expect(instance.permission).toEqual("tool.execute"); + expect(instance.approved).toEqual(true); + expect(instance.reason).toEqual("user_approved"); + }); + + it("should round-trip YAML - example 1", () => { + const yaml = `requestId: perm_abc123\ntoolCallId: call_abc123\npermission: tool.execute\napproved: true\nreason: user_approved\n`; + const instance = PermissionCompletedPayload.fromYaml(yaml); + const output = instance.toYaml(); + const reloaded = PermissionCompletedPayload.fromYaml(output); + expect(reloaded.requestId).toEqual(instance.requestId); + expect(reloaded.toolCallId).toEqual(instance.toolCallId); + expect(reloaded.permission).toEqual(instance.permission); + expect(reloaded.approved).toEqual(instance.approved); + expect(reloaded.reason).toEqual(instance.reason); + }); + }); + + describe("load and save", () => { + it("should load from dictionary", () => { + const data: Record = {}; + const instance = PermissionCompletedPayload.load(data); + expect(instance).toBeDefined(); + }); + + it("should save to dictionary", () => { + const instance = new PermissionCompletedPayload(); + const data = instance.save(); + expect(data).toBeDefined(); + expect(typeof data).toBe("object"); + }); + }); +}); diff --git a/runtime/typescript/packages/core/tests/model/events/permission-decision.test.ts b/runtime/typescript/packages/core/tests/model/events/permission-decision.test.ts new file mode 100644 index 00000000..2644d421 --- /dev/null +++ b/runtime/typescript/packages/core/tests/model/events/permission-decision.test.ts @@ -0,0 +1,84 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. + +import { PermissionDecision } from "../../../src/model/index"; + +describe("PermissionDecision", () => { + describe("construction", () => { + it("should create a new instance with defaults", () => { + const instance = new PermissionDecision(); + expect(instance).toBeDefined(); + }); + + it("should create a new instance with partial initialization", () => { + const instance = new PermissionDecision({}); + expect(instance).toBeDefined(); + }); + }); + + describe("JSON serialization", () => { + it("should load from JSON - example 1", () => { + const json = `{\n "requestId": "perm_abc123",\n "toolCallId": "call_abc123",\n "permission": "tool.execute",\n "approved": true,\n "reason": "user_approved"\n}`; + const instance = PermissionDecision.fromJson(json); + expect(instance).toBeDefined(); + expect(instance.requestId).toEqual("perm_abc123"); + expect(instance.toolCallId).toEqual("call_abc123"); + expect(instance.permission).toEqual("tool.execute"); + expect(instance.approved).toEqual(true); + expect(instance.reason).toEqual("user_approved"); + }); + + it("should round-trip JSON - example 1", () => { + const json = `{\n "requestId": "perm_abc123",\n "toolCallId": "call_abc123",\n "permission": "tool.execute",\n "approved": true,\n "reason": "user_approved"\n}`; + const instance = PermissionDecision.fromJson(json); + const output = instance.toJson(); + const reloaded = PermissionDecision.fromJson(output); + expect(reloaded.requestId).toEqual(instance.requestId); + expect(reloaded.toolCallId).toEqual(instance.toolCallId); + expect(reloaded.permission).toEqual(instance.permission); + expect(reloaded.approved).toEqual(instance.approved); + expect(reloaded.reason).toEqual(instance.reason); + }); + }); + + describe("YAML serialization", () => { + it("should load from YAML - example 1", () => { + const yaml = `requestId: perm_abc123\ntoolCallId: call_abc123\npermission: tool.execute\napproved: true\nreason: user_approved\n`; + const instance = PermissionDecision.fromYaml(yaml); + expect(instance).toBeDefined(); + expect(instance.requestId).toEqual("perm_abc123"); + expect(instance.toolCallId).toEqual("call_abc123"); + expect(instance.permission).toEqual("tool.execute"); + expect(instance.approved).toEqual(true); + expect(instance.reason).toEqual("user_approved"); + }); + + it("should round-trip YAML - example 1", () => { + const yaml = `requestId: perm_abc123\ntoolCallId: call_abc123\npermission: tool.execute\napproved: true\nreason: user_approved\n`; + const instance = PermissionDecision.fromYaml(yaml); + const output = instance.toYaml(); + const reloaded = PermissionDecision.fromYaml(output); + expect(reloaded.requestId).toEqual(instance.requestId); + expect(reloaded.toolCallId).toEqual(instance.toolCallId); + expect(reloaded.permission).toEqual(instance.permission); + expect(reloaded.approved).toEqual(instance.approved); + expect(reloaded.reason).toEqual(instance.reason); + }); + }); + + describe("load and save", () => { + it("should load from dictionary", () => { + const data: Record = {}; + const instance = PermissionDecision.load(data); + expect(instance).toBeDefined(); + }); + + it("should save to dictionary", () => { + const instance = new PermissionDecision(); + const data = instance.save(); + expect(data).toBeDefined(); + expect(typeof data).toBe("object"); + }); + }); +}); diff --git a/runtime/typescript/packages/core/tests/model/events/permission-request.test.ts b/runtime/typescript/packages/core/tests/model/events/permission-request.test.ts new file mode 100644 index 00000000..aea9ef96 --- /dev/null +++ b/runtime/typescript/packages/core/tests/model/events/permission-request.test.ts @@ -0,0 +1,84 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. + +import { PermissionRequest } from "../../../src/model/index"; + +describe("PermissionRequest", () => { + describe("construction", () => { + it("should create a new instance with defaults", () => { + const instance = new PermissionRequest(); + expect(instance).toBeDefined(); + }); + + it("should create a new instance with partial initialization", () => { + const instance = new PermissionRequest({}); + expect(instance).toBeDefined(); + }); + }); + + describe("JSON serialization", () => { + it("should load from JSON - example 1", () => { + const json = `{\n "requestId": "perm_abc123",\n "toolCallId": "call_abc123",\n "permission": "tool.execute",\n "target": "shell",\n "promptRequest": "Allow shell to run tests?"\n}`; + const instance = PermissionRequest.fromJson(json); + expect(instance).toBeDefined(); + expect(instance.requestId).toEqual("perm_abc123"); + expect(instance.toolCallId).toEqual("call_abc123"); + expect(instance.permission).toEqual("tool.execute"); + expect(instance.target).toEqual("shell"); + expect(instance.promptRequest).toEqual("Allow shell to run tests?"); + }); + + it("should round-trip JSON - example 1", () => { + const json = `{\n "requestId": "perm_abc123",\n "toolCallId": "call_abc123",\n "permission": "tool.execute",\n "target": "shell",\n "promptRequest": "Allow shell to run tests?"\n}`; + const instance = PermissionRequest.fromJson(json); + const output = instance.toJson(); + const reloaded = PermissionRequest.fromJson(output); + expect(reloaded.requestId).toEqual(instance.requestId); + expect(reloaded.toolCallId).toEqual(instance.toolCallId); + expect(reloaded.permission).toEqual(instance.permission); + expect(reloaded.target).toEqual(instance.target); + expect(reloaded.promptRequest).toEqual(instance.promptRequest); + }); + }); + + describe("YAML serialization", () => { + it("should load from YAML - example 1", () => { + const yaml = `requestId: perm_abc123\ntoolCallId: call_abc123\npermission: tool.execute\ntarget: shell\npromptRequest: Allow shell to run tests?\n`; + const instance = PermissionRequest.fromYaml(yaml); + expect(instance).toBeDefined(); + expect(instance.requestId).toEqual("perm_abc123"); + expect(instance.toolCallId).toEqual("call_abc123"); + expect(instance.permission).toEqual("tool.execute"); + expect(instance.target).toEqual("shell"); + expect(instance.promptRequest).toEqual("Allow shell to run tests?"); + }); + + it("should round-trip YAML - example 1", () => { + const yaml = `requestId: perm_abc123\ntoolCallId: call_abc123\npermission: tool.execute\ntarget: shell\npromptRequest: Allow shell to run tests?\n`; + const instance = PermissionRequest.fromYaml(yaml); + const output = instance.toYaml(); + const reloaded = PermissionRequest.fromYaml(output); + expect(reloaded.requestId).toEqual(instance.requestId); + expect(reloaded.toolCallId).toEqual(instance.toolCallId); + expect(reloaded.permission).toEqual(instance.permission); + expect(reloaded.target).toEqual(instance.target); + expect(reloaded.promptRequest).toEqual(instance.promptRequest); + }); + }); + + describe("load and save", () => { + it("should load from dictionary", () => { + const data: Record = {}; + const instance = PermissionRequest.load(data); + expect(instance).toBeDefined(); + }); + + it("should save to dictionary", () => { + const instance = new PermissionRequest(); + const data = instance.save(); + expect(data).toBeDefined(); + expect(typeof data).toBe("object"); + }); + }); +}); diff --git a/runtime/typescript/packages/core/tests/model/events/permission-requested-payload.test.ts b/runtime/typescript/packages/core/tests/model/events/permission-requested-payload.test.ts new file mode 100644 index 00000000..540865ee --- /dev/null +++ b/runtime/typescript/packages/core/tests/model/events/permission-requested-payload.test.ts @@ -0,0 +1,84 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. + +import { PermissionRequestedPayload } from "../../../src/model/index"; + +describe("PermissionRequestedPayload", () => { + describe("construction", () => { + it("should create a new instance with defaults", () => { + const instance = new PermissionRequestedPayload(); + expect(instance).toBeDefined(); + }); + + it("should create a new instance with partial initialization", () => { + const instance = new PermissionRequestedPayload({}); + expect(instance).toBeDefined(); + }); + }); + + describe("JSON serialization", () => { + it("should load from JSON - example 1", () => { + const json = `{\n "requestId": "perm_abc123",\n "toolCallId": "call_abc123",\n "permission": "tool.execute",\n "target": "shell",\n "promptRequest": "Allow shell to run tests?"\n}`; + const instance = PermissionRequestedPayload.fromJson(json); + expect(instance).toBeDefined(); + expect(instance.requestId).toEqual("perm_abc123"); + expect(instance.toolCallId).toEqual("call_abc123"); + expect(instance.permission).toEqual("tool.execute"); + expect(instance.target).toEqual("shell"); + expect(instance.promptRequest).toEqual("Allow shell to run tests?"); + }); + + it("should round-trip JSON - example 1", () => { + const json = `{\n "requestId": "perm_abc123",\n "toolCallId": "call_abc123",\n "permission": "tool.execute",\n "target": "shell",\n "promptRequest": "Allow shell to run tests?"\n}`; + const instance = PermissionRequestedPayload.fromJson(json); + const output = instance.toJson(); + const reloaded = PermissionRequestedPayload.fromJson(output); + expect(reloaded.requestId).toEqual(instance.requestId); + expect(reloaded.toolCallId).toEqual(instance.toolCallId); + expect(reloaded.permission).toEqual(instance.permission); + expect(reloaded.target).toEqual(instance.target); + expect(reloaded.promptRequest).toEqual(instance.promptRequest); + }); + }); + + describe("YAML serialization", () => { + it("should load from YAML - example 1", () => { + const yaml = `requestId: perm_abc123\ntoolCallId: call_abc123\npermission: tool.execute\ntarget: shell\npromptRequest: Allow shell to run tests?\n`; + const instance = PermissionRequestedPayload.fromYaml(yaml); + expect(instance).toBeDefined(); + expect(instance.requestId).toEqual("perm_abc123"); + expect(instance.toolCallId).toEqual("call_abc123"); + expect(instance.permission).toEqual("tool.execute"); + expect(instance.target).toEqual("shell"); + expect(instance.promptRequest).toEqual("Allow shell to run tests?"); + }); + + it("should round-trip YAML - example 1", () => { + const yaml = `requestId: perm_abc123\ntoolCallId: call_abc123\npermission: tool.execute\ntarget: shell\npromptRequest: Allow shell to run tests?\n`; + const instance = PermissionRequestedPayload.fromYaml(yaml); + const output = instance.toYaml(); + const reloaded = PermissionRequestedPayload.fromYaml(output); + expect(reloaded.requestId).toEqual(instance.requestId); + expect(reloaded.toolCallId).toEqual(instance.toolCallId); + expect(reloaded.permission).toEqual(instance.permission); + expect(reloaded.target).toEqual(instance.target); + expect(reloaded.promptRequest).toEqual(instance.promptRequest); + }); + }); + + describe("load and save", () => { + it("should load from dictionary", () => { + const data: Record = {}; + const instance = PermissionRequestedPayload.load(data); + expect(instance).toBeDefined(); + }); + + it("should save to dictionary", () => { + const instance = new PermissionRequestedPayload(); + const data = instance.save(); + expect(data).toBeDefined(); + expect(typeof data).toBe("object"); + }); + }); +}); diff --git a/runtime/typescript/packages/core/tests/model/events/redacted-field.test.ts b/runtime/typescript/packages/core/tests/model/events/redacted-field.test.ts new file mode 100644 index 00000000..72c58484 --- /dev/null +++ b/runtime/typescript/packages/core/tests/model/events/redacted-field.test.ts @@ -0,0 +1,76 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. + +import { RedactedField } from "../../../src/model/index"; + +describe("RedactedField", () => { + describe("construction", () => { + it("should create a new instance with defaults", () => { + const instance = new RedactedField(); + expect(instance).toBeDefined(); + }); + + it("should create a new instance with partial initialization", () => { + const instance = new RedactedField({}); + expect(instance).toBeDefined(); + }); + }); + + describe("JSON serialization", () => { + it("should load from JSON - example 1", () => { + const json = `{\n "path": "$.arguments.apiKey",\n "mode": "redacted",\n "reason": "secret"\n}`; + const instance = RedactedField.fromJson(json); + expect(instance).toBeDefined(); + expect(instance.path).toEqual("$.arguments.apiKey"); + expect(instance.mode).toEqual("redacted"); + expect(instance.reason).toEqual("secret"); + }); + + it("should round-trip JSON - example 1", () => { + const json = `{\n "path": "$.arguments.apiKey",\n "mode": "redacted",\n "reason": "secret"\n}`; + const instance = RedactedField.fromJson(json); + const output = instance.toJson(); + const reloaded = RedactedField.fromJson(output); + expect(reloaded.path).toEqual(instance.path); + expect(reloaded.mode).toEqual(instance.mode); + expect(reloaded.reason).toEqual(instance.reason); + }); + }); + + describe("YAML serialization", () => { + it("should load from YAML - example 1", () => { + const yaml = `path: $.arguments.apiKey\nmode: redacted\nreason: secret\n`; + const instance = RedactedField.fromYaml(yaml); + expect(instance).toBeDefined(); + expect(instance.path).toEqual("$.arguments.apiKey"); + expect(instance.mode).toEqual("redacted"); + expect(instance.reason).toEqual("secret"); + }); + + it("should round-trip YAML - example 1", () => { + const yaml = `path: $.arguments.apiKey\nmode: redacted\nreason: secret\n`; + const instance = RedactedField.fromYaml(yaml); + const output = instance.toYaml(); + const reloaded = RedactedField.fromYaml(output); + expect(reloaded.path).toEqual(instance.path); + expect(reloaded.mode).toEqual(instance.mode); + expect(reloaded.reason).toEqual(instance.reason); + }); + }); + + describe("load and save", () => { + it("should load from dictionary", () => { + const data: Record = {}; + const instance = RedactedField.load(data); + expect(instance).toBeDefined(); + }); + + it("should save to dictionary", () => { + const instance = new RedactedField(); + const data = instance.save(); + expect(data).toBeDefined(); + expect(typeof data).toBe("object"); + }); + }); +}); diff --git a/runtime/typescript/packages/core/tests/model/events/redaction-metadata.test.ts b/runtime/typescript/packages/core/tests/model/events/redaction-metadata.test.ts new file mode 100644 index 00000000..aaa2d75b --- /dev/null +++ b/runtime/typescript/packages/core/tests/model/events/redaction-metadata.test.ts @@ -0,0 +1,72 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. + +import { RedactionMetadata } from "../../../src/model/index"; + +describe("RedactionMetadata", () => { + describe("construction", () => { + it("should create a new instance with defaults", () => { + const instance = new RedactionMetadata(); + expect(instance).toBeDefined(); + }); + + it("should create a new instance with partial initialization", () => { + const instance = new RedactionMetadata({}); + expect(instance).toBeDefined(); + }); + }); + + describe("JSON serialization", () => { + it("should load from JSON - example 1", () => { + const json = `{\n "sanitized": true,\n "policy": "default-v1"\n}`; + const instance = RedactionMetadata.fromJson(json); + expect(instance).toBeDefined(); + expect(instance.sanitized).toEqual(true); + expect(instance.policy).toEqual("default-v1"); + }); + + it("should round-trip JSON - example 1", () => { + const json = `{\n "sanitized": true,\n "policy": "default-v1"\n}`; + const instance = RedactionMetadata.fromJson(json); + const output = instance.toJson(); + const reloaded = RedactionMetadata.fromJson(output); + expect(reloaded.sanitized).toEqual(instance.sanitized); + expect(reloaded.policy).toEqual(instance.policy); + }); + }); + + describe("YAML serialization", () => { + it("should load from YAML - example 1", () => { + const yaml = `sanitized: true\npolicy: default-v1\n`; + const instance = RedactionMetadata.fromYaml(yaml); + expect(instance).toBeDefined(); + expect(instance.sanitized).toEqual(true); + expect(instance.policy).toEqual("default-v1"); + }); + + it("should round-trip YAML - example 1", () => { + const yaml = `sanitized: true\npolicy: default-v1\n`; + const instance = RedactionMetadata.fromYaml(yaml); + const output = instance.toYaml(); + const reloaded = RedactionMetadata.fromYaml(output); + expect(reloaded.sanitized).toEqual(instance.sanitized); + expect(reloaded.policy).toEqual(instance.policy); + }); + }); + + describe("load and save", () => { + it("should load from dictionary", () => { + const data: Record = {}; + const instance = RedactionMetadata.load(data); + expect(instance).toBeDefined(); + }); + + it("should save to dictionary", () => { + const instance = new RedactionMetadata(); + const data = instance.save(); + expect(data).toBeDefined(); + expect(typeof data).toBe("object"); + }); + }); +}); diff --git a/runtime/typescript/packages/core/tests/model/events/retry-payload.test.ts b/runtime/typescript/packages/core/tests/model/events/retry-payload.test.ts new file mode 100644 index 00000000..ef954f14 --- /dev/null +++ b/runtime/typescript/packages/core/tests/model/events/retry-payload.test.ts @@ -0,0 +1,84 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. + +import { RetryPayload } from "../../../src/model/index"; + +describe("RetryPayload", () => { + describe("construction", () => { + it("should create a new instance with defaults", () => { + const instance = new RetryPayload(); + expect(instance).toBeDefined(); + }); + + it("should create a new instance with partial initialization", () => { + const instance = new RetryPayload({}); + expect(instance).toBeDefined(); + }); + }); + + describe("JSON serialization", () => { + it("should load from JSON - example 1", () => { + const json = `{\n "operation": "llm",\n "attempt": 2,\n "maxAttempts": 3,\n "delayMs": 1250,\n "reason": "rate_limit"\n}`; + const instance = RetryPayload.fromJson(json); + expect(instance).toBeDefined(); + expect(instance.operation).toEqual("llm"); + expect(instance.attempt).toEqual(2); + expect(instance.maxAttempts).toEqual(3); + expect(instance.delayMs).toEqual(1250); + expect(instance.reason).toEqual("rate_limit"); + }); + + it("should round-trip JSON - example 1", () => { + const json = `{\n "operation": "llm",\n "attempt": 2,\n "maxAttempts": 3,\n "delayMs": 1250,\n "reason": "rate_limit"\n}`; + const instance = RetryPayload.fromJson(json); + const output = instance.toJson(); + const reloaded = RetryPayload.fromJson(output); + expect(reloaded.operation).toEqual(instance.operation); + expect(reloaded.attempt).toEqual(instance.attempt); + expect(reloaded.maxAttempts).toEqual(instance.maxAttempts); + expect(reloaded.delayMs).toEqual(instance.delayMs); + expect(reloaded.reason).toEqual(instance.reason); + }); + }); + + describe("YAML serialization", () => { + it("should load from YAML - example 1", () => { + const yaml = `operation: llm\nattempt: 2\nmaxAttempts: 3\ndelayMs: 1250\nreason: rate_limit\n`; + const instance = RetryPayload.fromYaml(yaml); + expect(instance).toBeDefined(); + expect(instance.operation).toEqual("llm"); + expect(instance.attempt).toEqual(2); + expect(instance.maxAttempts).toEqual(3); + expect(instance.delayMs).toEqual(1250); + expect(instance.reason).toEqual("rate_limit"); + }); + + it("should round-trip YAML - example 1", () => { + const yaml = `operation: llm\nattempt: 2\nmaxAttempts: 3\ndelayMs: 1250\nreason: rate_limit\n`; + const instance = RetryPayload.fromYaml(yaml); + const output = instance.toYaml(); + const reloaded = RetryPayload.fromYaml(output); + expect(reloaded.operation).toEqual(instance.operation); + expect(reloaded.attempt).toEqual(instance.attempt); + expect(reloaded.maxAttempts).toEqual(instance.maxAttempts); + expect(reloaded.delayMs).toEqual(instance.delayMs); + expect(reloaded.reason).toEqual(instance.reason); + }); + }); + + describe("load and save", () => { + it("should load from dictionary", () => { + const data: Record = {}; + const instance = RetryPayload.load(data); + expect(instance).toBeDefined(); + }); + + it("should save to dictionary", () => { + const instance = new RetryPayload(); + const data = instance.save(); + expect(data).toBeDefined(); + expect(typeof data).toBe("object"); + }); + }); +}); diff --git a/runtime/typescript/packages/core/tests/model/events/session-end-payload.test.ts b/runtime/typescript/packages/core/tests/model/events/session-end-payload.test.ts new file mode 100644 index 00000000..f0386c15 --- /dev/null +++ b/runtime/typescript/packages/core/tests/model/events/session-end-payload.test.ts @@ -0,0 +1,80 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. + +import { SessionEndPayload } from "../../../src/model/index"; + +describe("SessionEndPayload", () => { + describe("construction", () => { + it("should create a new instance with defaults", () => { + const instance = new SessionEndPayload(); + expect(instance).toBeDefined(); + }); + + it("should create a new instance with partial initialization", () => { + const instance = new SessionEndPayload({}); + expect(instance).toBeDefined(); + }); + }); + + describe("JSON serialization", () => { + it("should load from JSON - example 1", () => { + const json = `{\n "sessionId": "sess_abc123",\n "status": "success",\n "reason": "complete",\n "durationMs": 12500\n}`; + const instance = SessionEndPayload.fromJson(json); + expect(instance).toBeDefined(); + expect(instance.sessionId).toEqual("sess_abc123"); + expect(instance.status).toEqual("success"); + expect(instance.reason).toEqual("complete"); + expect(instance.durationMs).toEqual(12500); + }); + + it("should round-trip JSON - example 1", () => { + const json = `{\n "sessionId": "sess_abc123",\n "status": "success",\n "reason": "complete",\n "durationMs": 12500\n}`; + const instance = SessionEndPayload.fromJson(json); + const output = instance.toJson(); + const reloaded = SessionEndPayload.fromJson(output); + expect(reloaded.sessionId).toEqual(instance.sessionId); + expect(reloaded.status).toEqual(instance.status); + expect(reloaded.reason).toEqual(instance.reason); + expect(reloaded.durationMs).toEqual(instance.durationMs); + }); + }); + + describe("YAML serialization", () => { + it("should load from YAML - example 1", () => { + const yaml = `sessionId: sess_abc123\nstatus: success\nreason: complete\ndurationMs: 12500\n`; + const instance = SessionEndPayload.fromYaml(yaml); + expect(instance).toBeDefined(); + expect(instance.sessionId).toEqual("sess_abc123"); + expect(instance.status).toEqual("success"); + expect(instance.reason).toEqual("complete"); + expect(instance.durationMs).toEqual(12500); + }); + + it("should round-trip YAML - example 1", () => { + const yaml = `sessionId: sess_abc123\nstatus: success\nreason: complete\ndurationMs: 12500\n`; + const instance = SessionEndPayload.fromYaml(yaml); + const output = instance.toYaml(); + const reloaded = SessionEndPayload.fromYaml(output); + expect(reloaded.sessionId).toEqual(instance.sessionId); + expect(reloaded.status).toEqual(instance.status); + expect(reloaded.reason).toEqual(instance.reason); + expect(reloaded.durationMs).toEqual(instance.durationMs); + }); + }); + + describe("load and save", () => { + it("should load from dictionary", () => { + const data: Record = {}; + const instance = SessionEndPayload.load(data); + expect(instance).toBeDefined(); + }); + + it("should save to dictionary", () => { + const instance = new SessionEndPayload(); + const data = instance.save(); + expect(data).toBeDefined(); + expect(typeof data).toBe("object"); + }); + }); +}); diff --git a/runtime/typescript/packages/core/tests/model/events/session-event.test.ts b/runtime/typescript/packages/core/tests/model/events/session-event.test.ts new file mode 100644 index 00000000..2d619091 --- /dev/null +++ b/runtime/typescript/packages/core/tests/model/events/session-event.test.ts @@ -0,0 +1,88 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. + +import { SessionEvent } from "../../../src/model/index"; + +describe("SessionEvent", () => { + describe("construction", () => { + it("should create a new instance with defaults", () => { + const instance = new SessionEvent(); + expect(instance).toBeDefined(); + }); + + it("should create a new instance with partial initialization", () => { + const instance = new SessionEvent({}); + expect(instance).toBeDefined(); + }); + }); + + describe("JSON serialization", () => { + it("should load from JSON - example 1", () => { + const json = `{\n "id": "evt_abc123",\n "timestamp": "2026-06-09T20:00:00Z",\n "sessionId": "sess_abc123",\n "turnId": "turn_001",\n "parentId": "evt_parent",\n "spanId": "span_hook_001"\n}`; + const instance = SessionEvent.fromJson(json); + expect(instance).toBeDefined(); + expect(instance.id).toEqual("evt_abc123"); + expect(instance.timestamp).toEqual("2026-06-09T20:00:00Z"); + expect(instance.sessionId).toEqual("sess_abc123"); + expect(instance.turnId).toEqual("turn_001"); + expect(instance.parentId).toEqual("evt_parent"); + expect(instance.spanId).toEqual("span_hook_001"); + }); + + it("should round-trip JSON - example 1", () => { + const json = `{\n "id": "evt_abc123",\n "timestamp": "2026-06-09T20:00:00Z",\n "sessionId": "sess_abc123",\n "turnId": "turn_001",\n "parentId": "evt_parent",\n "spanId": "span_hook_001"\n}`; + const instance = SessionEvent.fromJson(json); + const output = instance.toJson(); + const reloaded = SessionEvent.fromJson(output); + expect(reloaded.id).toEqual(instance.id); + expect(reloaded.timestamp).toEqual(instance.timestamp); + expect(reloaded.sessionId).toEqual(instance.sessionId); + expect(reloaded.turnId).toEqual(instance.turnId); + expect(reloaded.parentId).toEqual(instance.parentId); + expect(reloaded.spanId).toEqual(instance.spanId); + }); + }); + + describe("YAML serialization", () => { + it("should load from YAML - example 1", () => { + const yaml = `id: evt_abc123\ntimestamp: "2026-06-09T20:00:00Z"\nsessionId: sess_abc123\nturnId: turn_001\nparentId: evt_parent\nspanId: span_hook_001\n`; + const instance = SessionEvent.fromYaml(yaml); + expect(instance).toBeDefined(); + expect(instance.id).toEqual("evt_abc123"); + expect(instance.timestamp).toEqual("2026-06-09T20:00:00Z"); + expect(instance.sessionId).toEqual("sess_abc123"); + expect(instance.turnId).toEqual("turn_001"); + expect(instance.parentId).toEqual("evt_parent"); + expect(instance.spanId).toEqual("span_hook_001"); + }); + + it("should round-trip YAML - example 1", () => { + const yaml = `id: evt_abc123\ntimestamp: "2026-06-09T20:00:00Z"\nsessionId: sess_abc123\nturnId: turn_001\nparentId: evt_parent\nspanId: span_hook_001\n`; + const instance = SessionEvent.fromYaml(yaml); + const output = instance.toYaml(); + const reloaded = SessionEvent.fromYaml(output); + expect(reloaded.id).toEqual(instance.id); + expect(reloaded.timestamp).toEqual(instance.timestamp); + expect(reloaded.sessionId).toEqual(instance.sessionId); + expect(reloaded.turnId).toEqual(instance.turnId); + expect(reloaded.parentId).toEqual(instance.parentId); + expect(reloaded.spanId).toEqual(instance.spanId); + }); + }); + + describe("load and save", () => { + it("should load from dictionary", () => { + const data: Record = {}; + const instance = SessionEvent.load(data); + expect(instance).toBeDefined(); + }); + + it("should save to dictionary", () => { + const instance = new SessionEvent(); + const data = instance.save(); + expect(data).toBeDefined(); + expect(typeof data).toBe("object"); + }); + }); +}); diff --git a/runtime/typescript/packages/core/tests/model/events/session-file-ref.test.ts b/runtime/typescript/packages/core/tests/model/events/session-file-ref.test.ts new file mode 100644 index 00000000..e215ea18 --- /dev/null +++ b/runtime/typescript/packages/core/tests/model/events/session-file-ref.test.ts @@ -0,0 +1,84 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. + +import { SessionFileRef } from "../../../src/model/index"; + +describe("SessionFileRef", () => { + describe("construction", () => { + it("should create a new instance with defaults", () => { + const instance = new SessionFileRef(); + expect(instance).toBeDefined(); + }); + + it("should create a new instance with partial initialization", () => { + const instance = new SessionFileRef({}); + expect(instance).toBeDefined(); + }); + }); + + describe("JSON serialization", () => { + it("should load from JSON - example 1", () => { + const json = `{\n "sessionId": "sess_abc123",\n "path": "src/index.ts",\n "toolName": "view",\n "turnIndex": 2,\n "firstSeenAt": "2026-06-09T20:00:00Z"\n}`; + const instance = SessionFileRef.fromJson(json); + expect(instance).toBeDefined(); + expect(instance.sessionId).toEqual("sess_abc123"); + expect(instance.path).toEqual("src/index.ts"); + expect(instance.toolName).toEqual("view"); + expect(instance.turnIndex).toEqual(2); + expect(instance.firstSeenAt).toEqual("2026-06-09T20:00:00Z"); + }); + + it("should round-trip JSON - example 1", () => { + const json = `{\n "sessionId": "sess_abc123",\n "path": "src/index.ts",\n "toolName": "view",\n "turnIndex": 2,\n "firstSeenAt": "2026-06-09T20:00:00Z"\n}`; + const instance = SessionFileRef.fromJson(json); + const output = instance.toJson(); + const reloaded = SessionFileRef.fromJson(output); + expect(reloaded.sessionId).toEqual(instance.sessionId); + expect(reloaded.path).toEqual(instance.path); + expect(reloaded.toolName).toEqual(instance.toolName); + expect(reloaded.turnIndex).toEqual(instance.turnIndex); + expect(reloaded.firstSeenAt).toEqual(instance.firstSeenAt); + }); + }); + + describe("YAML serialization", () => { + it("should load from YAML - example 1", () => { + const yaml = `sessionId: sess_abc123\npath: src/index.ts\ntoolName: view\nturnIndex: 2\nfirstSeenAt: "2026-06-09T20:00:00Z"\n`; + const instance = SessionFileRef.fromYaml(yaml); + expect(instance).toBeDefined(); + expect(instance.sessionId).toEqual("sess_abc123"); + expect(instance.path).toEqual("src/index.ts"); + expect(instance.toolName).toEqual("view"); + expect(instance.turnIndex).toEqual(2); + expect(instance.firstSeenAt).toEqual("2026-06-09T20:00:00Z"); + }); + + it("should round-trip YAML - example 1", () => { + const yaml = `sessionId: sess_abc123\npath: src/index.ts\ntoolName: view\nturnIndex: 2\nfirstSeenAt: "2026-06-09T20:00:00Z"\n`; + const instance = SessionFileRef.fromYaml(yaml); + const output = instance.toYaml(); + const reloaded = SessionFileRef.fromYaml(output); + expect(reloaded.sessionId).toEqual(instance.sessionId); + expect(reloaded.path).toEqual(instance.path); + expect(reloaded.toolName).toEqual(instance.toolName); + expect(reloaded.turnIndex).toEqual(instance.turnIndex); + expect(reloaded.firstSeenAt).toEqual(instance.firstSeenAt); + }); + }); + + describe("load and save", () => { + it("should load from dictionary", () => { + const data: Record = {}; + const instance = SessionFileRef.load(data); + expect(instance).toBeDefined(); + }); + + it("should save to dictionary", () => { + const instance = new SessionFileRef(); + const data = instance.save(); + expect(data).toBeDefined(); + expect(typeof data).toBe("object"); + }); + }); +}); diff --git a/runtime/typescript/packages/core/tests/model/events/session-ref.test.ts b/runtime/typescript/packages/core/tests/model/events/session-ref.test.ts new file mode 100644 index 00000000..0d3ac52d --- /dev/null +++ b/runtime/typescript/packages/core/tests/model/events/session-ref.test.ts @@ -0,0 +1,84 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. + +import { SessionRef } from "../../../src/model/index"; + +describe("SessionRef", () => { + describe("construction", () => { + it("should create a new instance with defaults", () => { + const instance = new SessionRef(); + expect(instance).toBeDefined(); + }); + + it("should create a new instance with partial initialization", () => { + const instance = new SessionRef({}); + expect(instance).toBeDefined(); + }); + }); + + describe("JSON serialization", () => { + it("should load from JSON - example 1", () => { + const json = `{\n "sessionId": "sess_abc123",\n "refType": "issue",\n "refValue": "owner/repo#123",\n "turnIndex": 2,\n "createdAt": "2026-06-09T20:00:00Z"\n}`; + const instance = SessionRef.fromJson(json); + expect(instance).toBeDefined(); + expect(instance.sessionId).toEqual("sess_abc123"); + expect(instance.refType).toEqual("issue"); + expect(instance.refValue).toEqual("owner/repo#123"); + expect(instance.turnIndex).toEqual(2); + expect(instance.createdAt).toEqual("2026-06-09T20:00:00Z"); + }); + + it("should round-trip JSON - example 1", () => { + const json = `{\n "sessionId": "sess_abc123",\n "refType": "issue",\n "refValue": "owner/repo#123",\n "turnIndex": 2,\n "createdAt": "2026-06-09T20:00:00Z"\n}`; + const instance = SessionRef.fromJson(json); + const output = instance.toJson(); + const reloaded = SessionRef.fromJson(output); + expect(reloaded.sessionId).toEqual(instance.sessionId); + expect(reloaded.refType).toEqual(instance.refType); + expect(reloaded.refValue).toEqual(instance.refValue); + expect(reloaded.turnIndex).toEqual(instance.turnIndex); + expect(reloaded.createdAt).toEqual(instance.createdAt); + }); + }); + + describe("YAML serialization", () => { + it("should load from YAML - example 1", () => { + const yaml = `sessionId: sess_abc123\nrefType: issue\nrefValue: "owner/repo#123"\nturnIndex: 2\ncreatedAt: "2026-06-09T20:00:00Z"\n`; + const instance = SessionRef.fromYaml(yaml); + expect(instance).toBeDefined(); + expect(instance.sessionId).toEqual("sess_abc123"); + expect(instance.refType).toEqual("issue"); + expect(instance.refValue).toEqual("owner/repo#123"); + expect(instance.turnIndex).toEqual(2); + expect(instance.createdAt).toEqual("2026-06-09T20:00:00Z"); + }); + + it("should round-trip YAML - example 1", () => { + const yaml = `sessionId: sess_abc123\nrefType: issue\nrefValue: "owner/repo#123"\nturnIndex: 2\ncreatedAt: "2026-06-09T20:00:00Z"\n`; + const instance = SessionRef.fromYaml(yaml); + const output = instance.toYaml(); + const reloaded = SessionRef.fromYaml(output); + expect(reloaded.sessionId).toEqual(instance.sessionId); + expect(reloaded.refType).toEqual(instance.refType); + expect(reloaded.refValue).toEqual(instance.refValue); + expect(reloaded.turnIndex).toEqual(instance.turnIndex); + expect(reloaded.createdAt).toEqual(instance.createdAt); + }); + }); + + describe("load and save", () => { + it("should load from dictionary", () => { + const data: Record = {}; + const instance = SessionRef.load(data); + expect(instance).toBeDefined(); + }); + + it("should save to dictionary", () => { + const instance = new SessionRef(); + const data = instance.save(); + expect(data).toBeDefined(); + expect(typeof data).toBe("object"); + }); + }); +}); diff --git a/runtime/typescript/packages/core/tests/model/events/session-start-payload.test.ts b/runtime/typescript/packages/core/tests/model/events/session-start-payload.test.ts new file mode 100644 index 00000000..bc804b7e --- /dev/null +++ b/runtime/typescript/packages/core/tests/model/events/session-start-payload.test.ts @@ -0,0 +1,96 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. + +import { SessionStartPayload } from "../../../src/model/index"; + +describe("SessionStartPayload", () => { + describe("construction", () => { + it("should create a new instance with defaults", () => { + const instance = new SessionStartPayload(); + expect(instance).toBeDefined(); + }); + + it("should create a new instance with partial initialization", () => { + const instance = new SessionStartPayload({}); + expect(instance).toBeDefined(); + }); + }); + + describe("JSON serialization", () => { + it("should load from JSON - example 1", () => { + const json = `{\n "sessionId": "sess_abc123",\n "schemaVersion": "1",\n "producer": "prompty-agent",\n "runtime": "typescript",\n "promptyVersion": "2.0.0",\n "startTime": "2026-06-09T20:00:00Z",\n "selectedModel": "gpt-4o-mini",\n "reasoningEffort": "medium"\n}`; + const instance = SessionStartPayload.fromJson(json); + expect(instance).toBeDefined(); + expect(instance.sessionId).toEqual("sess_abc123"); + expect(instance.schemaVersion).toEqual("1"); + expect(instance.producer).toEqual("prompty-agent"); + expect(instance.runtime).toEqual("typescript"); + expect(instance.promptyVersion).toEqual("2.0.0"); + expect(instance.startTime).toEqual("2026-06-09T20:00:00Z"); + expect(instance.selectedModel).toEqual("gpt-4o-mini"); + expect(instance.reasoningEffort).toEqual("medium"); + }); + + it("should round-trip JSON - example 1", () => { + const json = `{\n "sessionId": "sess_abc123",\n "schemaVersion": "1",\n "producer": "prompty-agent",\n "runtime": "typescript",\n "promptyVersion": "2.0.0",\n "startTime": "2026-06-09T20:00:00Z",\n "selectedModel": "gpt-4o-mini",\n "reasoningEffort": "medium"\n}`; + const instance = SessionStartPayload.fromJson(json); + const output = instance.toJson(); + const reloaded = SessionStartPayload.fromJson(output); + expect(reloaded.sessionId).toEqual(instance.sessionId); + expect(reloaded.schemaVersion).toEqual(instance.schemaVersion); + expect(reloaded.producer).toEqual(instance.producer); + expect(reloaded.runtime).toEqual(instance.runtime); + expect(reloaded.promptyVersion).toEqual(instance.promptyVersion); + expect(reloaded.startTime).toEqual(instance.startTime); + expect(reloaded.selectedModel).toEqual(instance.selectedModel); + expect(reloaded.reasoningEffort).toEqual(instance.reasoningEffort); + }); + }); + + describe("YAML serialization", () => { + it("should load from YAML - example 1", () => { + const yaml = `sessionId: sess_abc123\nschemaVersion: "1"\nproducer: prompty-agent\nruntime: typescript\npromptyVersion: 2.0.0\nstartTime: "2026-06-09T20:00:00Z"\nselectedModel: gpt-4o-mini\nreasoningEffort: medium\n`; + const instance = SessionStartPayload.fromYaml(yaml); + expect(instance).toBeDefined(); + expect(instance.sessionId).toEqual("sess_abc123"); + expect(instance.schemaVersion).toEqual("1"); + expect(instance.producer).toEqual("prompty-agent"); + expect(instance.runtime).toEqual("typescript"); + expect(instance.promptyVersion).toEqual("2.0.0"); + expect(instance.startTime).toEqual("2026-06-09T20:00:00Z"); + expect(instance.selectedModel).toEqual("gpt-4o-mini"); + expect(instance.reasoningEffort).toEqual("medium"); + }); + + it("should round-trip YAML - example 1", () => { + const yaml = `sessionId: sess_abc123\nschemaVersion: "1"\nproducer: prompty-agent\nruntime: typescript\npromptyVersion: 2.0.0\nstartTime: "2026-06-09T20:00:00Z"\nselectedModel: gpt-4o-mini\nreasoningEffort: medium\n`; + const instance = SessionStartPayload.fromYaml(yaml); + const output = instance.toYaml(); + const reloaded = SessionStartPayload.fromYaml(output); + expect(reloaded.sessionId).toEqual(instance.sessionId); + expect(reloaded.schemaVersion).toEqual(instance.schemaVersion); + expect(reloaded.producer).toEqual(instance.producer); + expect(reloaded.runtime).toEqual(instance.runtime); + expect(reloaded.promptyVersion).toEqual(instance.promptyVersion); + expect(reloaded.startTime).toEqual(instance.startTime); + expect(reloaded.selectedModel).toEqual(instance.selectedModel); + expect(reloaded.reasoningEffort).toEqual(instance.reasoningEffort); + }); + }); + + describe("load and save", () => { + it("should load from dictionary", () => { + const data: Record = {}; + const instance = SessionStartPayload.load(data); + expect(instance).toBeDefined(); + }); + + it("should save to dictionary", () => { + const instance = new SessionStartPayload(); + const data = instance.save(); + expect(data).toBeDefined(); + expect(typeof data).toBe("object"); + }); + }); +}); diff --git a/runtime/typescript/packages/core/tests/model/events/session-summary.test.ts b/runtime/typescript/packages/core/tests/model/events/session-summary.test.ts new file mode 100644 index 00000000..5d74c979 --- /dev/null +++ b/runtime/typescript/packages/core/tests/model/events/session-summary.test.ts @@ -0,0 +1,84 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. + +import { SessionSummary } from "../../../src/model/index"; + +describe("SessionSummary", () => { + describe("construction", () => { + it("should create a new instance with defaults", () => { + const instance = new SessionSummary(); + expect(instance).toBeDefined(); + }); + + it("should create a new instance with partial initialization", () => { + const instance = new SessionSummary({}); + expect(instance).toBeDefined(); + }); + }); + + describe("JSON serialization", () => { + it("should load from JSON - example 1", () => { + const json = `{\n "sessionId": "sess_abc123",\n "status": "success",\n "turns": 5,\n "checkpoints": 2,\n "durationMs": 12500\n}`; + const instance = SessionSummary.fromJson(json); + expect(instance).toBeDefined(); + expect(instance.sessionId).toEqual("sess_abc123"); + expect(instance.status).toEqual("success"); + expect(instance.turns).toEqual(5); + expect(instance.checkpoints).toEqual(2); + expect(instance.durationMs).toEqual(12500); + }); + + it("should round-trip JSON - example 1", () => { + const json = `{\n "sessionId": "sess_abc123",\n "status": "success",\n "turns": 5,\n "checkpoints": 2,\n "durationMs": 12500\n}`; + const instance = SessionSummary.fromJson(json); + const output = instance.toJson(); + const reloaded = SessionSummary.fromJson(output); + expect(reloaded.sessionId).toEqual(instance.sessionId); + expect(reloaded.status).toEqual(instance.status); + expect(reloaded.turns).toEqual(instance.turns); + expect(reloaded.checkpoints).toEqual(instance.checkpoints); + expect(reloaded.durationMs).toEqual(instance.durationMs); + }); + }); + + describe("YAML serialization", () => { + it("should load from YAML - example 1", () => { + const yaml = `sessionId: sess_abc123\nstatus: success\nturns: 5\ncheckpoints: 2\ndurationMs: 12500\n`; + const instance = SessionSummary.fromYaml(yaml); + expect(instance).toBeDefined(); + expect(instance.sessionId).toEqual("sess_abc123"); + expect(instance.status).toEqual("success"); + expect(instance.turns).toEqual(5); + expect(instance.checkpoints).toEqual(2); + expect(instance.durationMs).toEqual(12500); + }); + + it("should round-trip YAML - example 1", () => { + const yaml = `sessionId: sess_abc123\nstatus: success\nturns: 5\ncheckpoints: 2\ndurationMs: 12500\n`; + const instance = SessionSummary.fromYaml(yaml); + const output = instance.toYaml(); + const reloaded = SessionSummary.fromYaml(output); + expect(reloaded.sessionId).toEqual(instance.sessionId); + expect(reloaded.status).toEqual(instance.status); + expect(reloaded.turns).toEqual(instance.turns); + expect(reloaded.checkpoints).toEqual(instance.checkpoints); + expect(reloaded.durationMs).toEqual(instance.durationMs); + }); + }); + + describe("load and save", () => { + it("should load from dictionary", () => { + const data: Record = {}; + const instance = SessionSummary.load(data); + expect(instance).toBeDefined(); + }); + + it("should save to dictionary", () => { + const instance = new SessionSummary(); + const data = instance.save(); + expect(data).toBeDefined(); + expect(typeof data).toBe("object"); + }); + }); +}); diff --git a/runtime/typescript/packages/core/tests/model/events/session-trace.test.ts b/runtime/typescript/packages/core/tests/model/events/session-trace.test.ts new file mode 100644 index 00000000..1284e1ae --- /dev/null +++ b/runtime/typescript/packages/core/tests/model/events/session-trace.test.ts @@ -0,0 +1,80 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. + +import { SessionTrace } from "../../../src/model/index"; + +describe("SessionTrace", () => { + describe("construction", () => { + it("should create a new instance with defaults", () => { + const instance = new SessionTrace(); + expect(instance).toBeDefined(); + }); + + it("should create a new instance with partial initialization", () => { + const instance = new SessionTrace({}); + expect(instance).toBeDefined(); + }); + }); + + describe("JSON serialization", () => { + it("should load from JSON - example 1", () => { + const json = `{\n "version": "1",\n "runtime": "typescript",\n "promptyVersion": "2.0.0",\n "sessionId": "sess_abc123"\n}`; + const instance = SessionTrace.fromJson(json); + expect(instance).toBeDefined(); + expect(instance.version).toEqual("1"); + expect(instance.runtime).toEqual("typescript"); + expect(instance.promptyVersion).toEqual("2.0.0"); + expect(instance.sessionId).toEqual("sess_abc123"); + }); + + it("should round-trip JSON - example 1", () => { + const json = `{\n "version": "1",\n "runtime": "typescript",\n "promptyVersion": "2.0.0",\n "sessionId": "sess_abc123"\n}`; + const instance = SessionTrace.fromJson(json); + const output = instance.toJson(); + const reloaded = SessionTrace.fromJson(output); + expect(reloaded.version).toEqual(instance.version); + expect(reloaded.runtime).toEqual(instance.runtime); + expect(reloaded.promptyVersion).toEqual(instance.promptyVersion); + expect(reloaded.sessionId).toEqual(instance.sessionId); + }); + }); + + describe("YAML serialization", () => { + it("should load from YAML - example 1", () => { + const yaml = `version: "1"\nruntime: typescript\npromptyVersion: 2.0.0\nsessionId: sess_abc123\n`; + const instance = SessionTrace.fromYaml(yaml); + expect(instance).toBeDefined(); + expect(instance.version).toEqual("1"); + expect(instance.runtime).toEqual("typescript"); + expect(instance.promptyVersion).toEqual("2.0.0"); + expect(instance.sessionId).toEqual("sess_abc123"); + }); + + it("should round-trip YAML - example 1", () => { + const yaml = `version: "1"\nruntime: typescript\npromptyVersion: 2.0.0\nsessionId: sess_abc123\n`; + const instance = SessionTrace.fromYaml(yaml); + const output = instance.toYaml(); + const reloaded = SessionTrace.fromYaml(output); + expect(reloaded.version).toEqual(instance.version); + expect(reloaded.runtime).toEqual(instance.runtime); + expect(reloaded.promptyVersion).toEqual(instance.promptyVersion); + expect(reloaded.sessionId).toEqual(instance.sessionId); + }); + }); + + describe("load and save", () => { + it("should load from dictionary", () => { + const data: Record = {}; + const instance = SessionTrace.load(data); + expect(instance).toBeDefined(); + }); + + it("should save to dictionary", () => { + const instance = new SessionTrace(); + const data = instance.save(); + expect(data).toBeDefined(); + expect(typeof data).toBe("object"); + }); + }); +}); diff --git a/runtime/typescript/packages/core/tests/model/events/session-warning-payload.test.ts b/runtime/typescript/packages/core/tests/model/events/session-warning-payload.test.ts new file mode 100644 index 00000000..1b2ab319 --- /dev/null +++ b/runtime/typescript/packages/core/tests/model/events/session-warning-payload.test.ts @@ -0,0 +1,72 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. + +import { SessionWarningPayload } from "../../../src/model/index"; + +describe("SessionWarningPayload", () => { + describe("construction", () => { + it("should create a new instance with defaults", () => { + const instance = new SessionWarningPayload(); + expect(instance).toBeDefined(); + }); + + it("should create a new instance with partial initialization", () => { + const instance = new SessionWarningPayload({}); + expect(instance).toBeDefined(); + }); + }); + + describe("JSON serialization", () => { + it("should load from JSON - example 1", () => { + const json = `{\n "warningType": "remote",\n "message": "Remote session disabled"\n}`; + const instance = SessionWarningPayload.fromJson(json); + expect(instance).toBeDefined(); + expect(instance.warningType).toEqual("remote"); + expect(instance.message).toEqual("Remote session disabled"); + }); + + it("should round-trip JSON - example 1", () => { + const json = `{\n "warningType": "remote",\n "message": "Remote session disabled"\n}`; + const instance = SessionWarningPayload.fromJson(json); + const output = instance.toJson(); + const reloaded = SessionWarningPayload.fromJson(output); + expect(reloaded.warningType).toEqual(instance.warningType); + expect(reloaded.message).toEqual(instance.message); + }); + }); + + describe("YAML serialization", () => { + it("should load from YAML - example 1", () => { + const yaml = `warningType: remote\nmessage: Remote session disabled\n`; + const instance = SessionWarningPayload.fromYaml(yaml); + expect(instance).toBeDefined(); + expect(instance.warningType).toEqual("remote"); + expect(instance.message).toEqual("Remote session disabled"); + }); + + it("should round-trip YAML - example 1", () => { + const yaml = `warningType: remote\nmessage: Remote session disabled\n`; + const instance = SessionWarningPayload.fromYaml(yaml); + const output = instance.toYaml(); + const reloaded = SessionWarningPayload.fromYaml(output); + expect(reloaded.warningType).toEqual(instance.warningType); + expect(reloaded.message).toEqual(instance.message); + }); + }); + + describe("load and save", () => { + it("should load from dictionary", () => { + const data: Record = {}; + const instance = SessionWarningPayload.load(data); + expect(instance).toBeDefined(); + }); + + it("should save to dictionary", () => { + const instance = new SessionWarningPayload(); + const data = instance.save(); + expect(data).toBeDefined(); + expect(typeof data).toBe("object"); + }); + }); +}); diff --git a/runtime/typescript/packages/core/tests/model/events/status-event-payload.test.ts b/runtime/typescript/packages/core/tests/model/events/status-event-payload.test.ts index bf4f7059..fee9fb58 100644 --- a/runtime/typescript/packages/core/tests/model/events/status-event-payload.test.ts +++ b/runtime/typescript/packages/core/tests/model/events/status-event-payload.test.ts @@ -1,3 +1,4 @@ +// // Copyright (c) Microsoft. All rights reserved. // WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. diff --git a/runtime/typescript/packages/core/tests/model/events/stream-chunk.test.ts b/runtime/typescript/packages/core/tests/model/events/stream-chunk.test.ts index 347fbbae..186574a2 100644 --- a/runtime/typescript/packages/core/tests/model/events/stream-chunk.test.ts +++ b/runtime/typescript/packages/core/tests/model/events/stream-chunk.test.ts @@ -1,3 +1,4 @@ +// // Copyright (c) Microsoft. All rights reserved. // WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. diff --git a/runtime/typescript/packages/core/tests/model/events/text-chunk.test.ts b/runtime/typescript/packages/core/tests/model/events/text-chunk.test.ts index 54732d5a..7e7f5690 100644 --- a/runtime/typescript/packages/core/tests/model/events/text-chunk.test.ts +++ b/runtime/typescript/packages/core/tests/model/events/text-chunk.test.ts @@ -1,3 +1,4 @@ +// // Copyright (c) Microsoft. All rights reserved. // WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. diff --git a/runtime/typescript/packages/core/tests/model/events/thinking-chunk.test.ts b/runtime/typescript/packages/core/tests/model/events/thinking-chunk.test.ts index 3d18ceb1..49bfcf71 100644 --- a/runtime/typescript/packages/core/tests/model/events/thinking-chunk.test.ts +++ b/runtime/typescript/packages/core/tests/model/events/thinking-chunk.test.ts @@ -1,3 +1,4 @@ +// // Copyright (c) Microsoft. All rights reserved. // WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. diff --git a/runtime/typescript/packages/core/tests/model/events/thinking-event-payload.test.ts b/runtime/typescript/packages/core/tests/model/events/thinking-event-payload.test.ts index 0a79e6aa..b9f32d05 100644 --- a/runtime/typescript/packages/core/tests/model/events/thinking-event-payload.test.ts +++ b/runtime/typescript/packages/core/tests/model/events/thinking-event-payload.test.ts @@ -1,3 +1,4 @@ +// // Copyright (c) Microsoft. All rights reserved. // WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. diff --git a/runtime/typescript/packages/core/tests/model/events/token-event-payload.test.ts b/runtime/typescript/packages/core/tests/model/events/token-event-payload.test.ts index 596ed9b7..218d8e4c 100644 --- a/runtime/typescript/packages/core/tests/model/events/token-event-payload.test.ts +++ b/runtime/typescript/packages/core/tests/model/events/token-event-payload.test.ts @@ -1,3 +1,4 @@ +// // Copyright (c) Microsoft. All rights reserved. // WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. diff --git a/runtime/typescript/packages/core/tests/model/events/tool-call-complete-payload.test.ts b/runtime/typescript/packages/core/tests/model/events/tool-call-complete-payload.test.ts new file mode 100644 index 00000000..596b349e --- /dev/null +++ b/runtime/typescript/packages/core/tests/model/events/tool-call-complete-payload.test.ts @@ -0,0 +1,84 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. + +import { ToolCallCompletePayload } from "../../../src/model/index"; + +describe("ToolCallCompletePayload", () => { + describe("construction", () => { + it("should create a new instance with defaults", () => { + const instance = new ToolCallCompletePayload(); + expect(instance).toBeDefined(); + }); + + it("should create a new instance with partial initialization", () => { + const instance = new ToolCallCompletePayload({}); + expect(instance).toBeDefined(); + }); + }); + + describe("JSON serialization", () => { + it("should load from JSON - example 1", () => { + const json = `{\n "id": "call_abc123",\n "name": "get_weather",\n "success": true,\n "durationMs": 42,\n "errorKind": "timeout"\n}`; + const instance = ToolCallCompletePayload.fromJson(json); + expect(instance).toBeDefined(); + expect(instance.id).toEqual("call_abc123"); + expect(instance.name).toEqual("get_weather"); + expect(instance.success).toEqual(true); + expect(instance.durationMs).toEqual(42); + expect(instance.errorKind).toEqual("timeout"); + }); + + it("should round-trip JSON - example 1", () => { + const json = `{\n "id": "call_abc123",\n "name": "get_weather",\n "success": true,\n "durationMs": 42,\n "errorKind": "timeout"\n}`; + const instance = ToolCallCompletePayload.fromJson(json); + const output = instance.toJson(); + const reloaded = ToolCallCompletePayload.fromJson(output); + expect(reloaded.id).toEqual(instance.id); + expect(reloaded.name).toEqual(instance.name); + expect(reloaded.success).toEqual(instance.success); + expect(reloaded.durationMs).toEqual(instance.durationMs); + expect(reloaded.errorKind).toEqual(instance.errorKind); + }); + }); + + describe("YAML serialization", () => { + it("should load from YAML - example 1", () => { + const yaml = `id: call_abc123\nname: get_weather\nsuccess: true\ndurationMs: 42\nerrorKind: timeout\n`; + const instance = ToolCallCompletePayload.fromYaml(yaml); + expect(instance).toBeDefined(); + expect(instance.id).toEqual("call_abc123"); + expect(instance.name).toEqual("get_weather"); + expect(instance.success).toEqual(true); + expect(instance.durationMs).toEqual(42); + expect(instance.errorKind).toEqual("timeout"); + }); + + it("should round-trip YAML - example 1", () => { + const yaml = `id: call_abc123\nname: get_weather\nsuccess: true\ndurationMs: 42\nerrorKind: timeout\n`; + const instance = ToolCallCompletePayload.fromYaml(yaml); + const output = instance.toYaml(); + const reloaded = ToolCallCompletePayload.fromYaml(output); + expect(reloaded.id).toEqual(instance.id); + expect(reloaded.name).toEqual(instance.name); + expect(reloaded.success).toEqual(instance.success); + expect(reloaded.durationMs).toEqual(instance.durationMs); + expect(reloaded.errorKind).toEqual(instance.errorKind); + }); + }); + + describe("load and save", () => { + it("should load from dictionary", () => { + const data: Record = {}; + const instance = ToolCallCompletePayload.load(data); + expect(instance).toBeDefined(); + }); + + it("should save to dictionary", () => { + const instance = new ToolCallCompletePayload(); + const data = instance.save(); + expect(data).toBeDefined(); + expect(typeof data).toBe("object"); + }); + }); +}); diff --git a/runtime/typescript/packages/core/tests/model/events/tool-call-start-payload.test.ts b/runtime/typescript/packages/core/tests/model/events/tool-call-start-payload.test.ts index 144920d5..0c4f7fc2 100644 --- a/runtime/typescript/packages/core/tests/model/events/tool-call-start-payload.test.ts +++ b/runtime/typescript/packages/core/tests/model/events/tool-call-start-payload.test.ts @@ -1,3 +1,4 @@ +// // Copyright (c) Microsoft. All rights reserved. // WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. @@ -18,18 +19,20 @@ describe("ToolCallStartPayload", () => { describe("JSON serialization", () => { it("should load from JSON - example 1", () => { - const json = `{\n "name": "get_weather",\n "arguments": "{\\"city\\": \\"Paris\\"}"\n}`; + const json = `{\n "id": "call_abc123",\n "name": "get_weather",\n "arguments": "{\\"city\\": \\"Paris\\"}"\n}`; const instance = ToolCallStartPayload.fromJson(json); expect(instance).toBeDefined(); + expect(instance.id).toEqual("call_abc123"); expect(instance.name).toEqual("get_weather"); expect(instance.arguments).toEqual('{"city": "Paris"}'); }); it("should round-trip JSON - example 1", () => { - const json = `{\n "name": "get_weather",\n "arguments": "{\\"city\\": \\"Paris\\"}"\n}`; + const json = `{\n "id": "call_abc123",\n "name": "get_weather",\n "arguments": "{\\"city\\": \\"Paris\\"}"\n}`; const instance = ToolCallStartPayload.fromJson(json); const output = instance.toJson(); const reloaded = ToolCallStartPayload.fromJson(output); + expect(reloaded.id).toEqual(instance.id); expect(reloaded.name).toEqual(instance.name); expect(reloaded.arguments).toEqual(instance.arguments); }); @@ -37,18 +40,20 @@ describe("ToolCallStartPayload", () => { describe("YAML serialization", () => { it("should load from YAML - example 1", () => { - const yaml = `name: get_weather\narguments: "{\\"city\\": \\"Paris\\"}"\n`; + const yaml = `id: call_abc123\nname: get_weather\narguments: "{\\"city\\": \\"Paris\\"}"\n`; const instance = ToolCallStartPayload.fromYaml(yaml); expect(instance).toBeDefined(); + expect(instance.id).toEqual("call_abc123"); expect(instance.name).toEqual("get_weather"); expect(instance.arguments).toEqual('{"city": "Paris"}'); }); it("should round-trip YAML - example 1", () => { - const yaml = `name: get_weather\narguments: "{\\"city\\": \\"Paris\\"}"\n`; + const yaml = `id: call_abc123\nname: get_weather\narguments: "{\\"city\\": \\"Paris\\"}"\n`; const instance = ToolCallStartPayload.fromYaml(yaml); const output = instance.toYaml(); const reloaded = ToolCallStartPayload.fromYaml(output); + expect(reloaded.id).toEqual(instance.id); expect(reloaded.name).toEqual(instance.name); expect(reloaded.arguments).toEqual(instance.arguments); }); diff --git a/runtime/typescript/packages/core/tests/model/events/tool-chunk.test.ts b/runtime/typescript/packages/core/tests/model/events/tool-chunk.test.ts index c64c49b2..ec933714 100644 --- a/runtime/typescript/packages/core/tests/model/events/tool-chunk.test.ts +++ b/runtime/typescript/packages/core/tests/model/events/tool-chunk.test.ts @@ -1,3 +1,4 @@ +// // Copyright (c) Microsoft. All rights reserved. // WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. diff --git a/runtime/typescript/packages/core/tests/model/events/tool-execution-complete-payload.test.ts b/runtime/typescript/packages/core/tests/model/events/tool-execution-complete-payload.test.ts new file mode 100644 index 00000000..80e198ff --- /dev/null +++ b/runtime/typescript/packages/core/tests/model/events/tool-execution-complete-payload.test.ts @@ -0,0 +1,92 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. + +import { ToolExecutionCompletePayload } from "../../../src/model/index"; + +describe("ToolExecutionCompletePayload", () => { + describe("construction", () => { + it("should create a new instance with defaults", () => { + const instance = new ToolExecutionCompletePayload(); + expect(instance).toBeDefined(); + }); + + it("should create a new instance with partial initialization", () => { + const instance = new ToolExecutionCompletePayload({}); + expect(instance).toBeDefined(); + }); + }); + + describe("JSON serialization", () => { + it("should load from JSON - example 1", () => { + const json = `{\n "requestId": "exec_abc123",\n "toolCallId": "call_abc123",\n "toolName": "powershell",\n "success": true,\n "exitCode": 0,\n "durationMs": 250,\n "errorKind": "timeout"\n}`; + const instance = ToolExecutionCompletePayload.fromJson(json); + expect(instance).toBeDefined(); + expect(instance.requestId).toEqual("exec_abc123"); + expect(instance.toolCallId).toEqual("call_abc123"); + expect(instance.toolName).toEqual("powershell"); + expect(instance.success).toEqual(true); + expect(instance.exitCode).toEqual(0); + expect(instance.durationMs).toEqual(250); + expect(instance.errorKind).toEqual("timeout"); + }); + + it("should round-trip JSON - example 1", () => { + const json = `{\n "requestId": "exec_abc123",\n "toolCallId": "call_abc123",\n "toolName": "powershell",\n "success": true,\n "exitCode": 0,\n "durationMs": 250,\n "errorKind": "timeout"\n}`; + const instance = ToolExecutionCompletePayload.fromJson(json); + const output = instance.toJson(); + const reloaded = ToolExecutionCompletePayload.fromJson(output); + expect(reloaded.requestId).toEqual(instance.requestId); + expect(reloaded.toolCallId).toEqual(instance.toolCallId); + expect(reloaded.toolName).toEqual(instance.toolName); + expect(reloaded.success).toEqual(instance.success); + expect(reloaded.exitCode).toEqual(instance.exitCode); + expect(reloaded.durationMs).toEqual(instance.durationMs); + expect(reloaded.errorKind).toEqual(instance.errorKind); + }); + }); + + describe("YAML serialization", () => { + it("should load from YAML - example 1", () => { + const yaml = `requestId: exec_abc123\ntoolCallId: call_abc123\ntoolName: powershell\nsuccess: true\nexitCode: 0\ndurationMs: 250\nerrorKind: timeout\n`; + const instance = ToolExecutionCompletePayload.fromYaml(yaml); + expect(instance).toBeDefined(); + expect(instance.requestId).toEqual("exec_abc123"); + expect(instance.toolCallId).toEqual("call_abc123"); + expect(instance.toolName).toEqual("powershell"); + expect(instance.success).toEqual(true); + expect(instance.exitCode).toEqual(0); + expect(instance.durationMs).toEqual(250); + expect(instance.errorKind).toEqual("timeout"); + }); + + it("should round-trip YAML - example 1", () => { + const yaml = `requestId: exec_abc123\ntoolCallId: call_abc123\ntoolName: powershell\nsuccess: true\nexitCode: 0\ndurationMs: 250\nerrorKind: timeout\n`; + const instance = ToolExecutionCompletePayload.fromYaml(yaml); + const output = instance.toYaml(); + const reloaded = ToolExecutionCompletePayload.fromYaml(output); + expect(reloaded.requestId).toEqual(instance.requestId); + expect(reloaded.toolCallId).toEqual(instance.toolCallId); + expect(reloaded.toolName).toEqual(instance.toolName); + expect(reloaded.success).toEqual(instance.success); + expect(reloaded.exitCode).toEqual(instance.exitCode); + expect(reloaded.durationMs).toEqual(instance.durationMs); + expect(reloaded.errorKind).toEqual(instance.errorKind); + }); + }); + + describe("load and save", () => { + it("should load from dictionary", () => { + const data: Record = {}; + const instance = ToolExecutionCompletePayload.load(data); + expect(instance).toBeDefined(); + }); + + it("should save to dictionary", () => { + const instance = new ToolExecutionCompletePayload(); + const data = instance.save(); + expect(data).toBeDefined(); + expect(typeof data).toBe("object"); + }); + }); +}); diff --git a/runtime/typescript/packages/core/tests/model/events/tool-execution-start-payload.test.ts b/runtime/typescript/packages/core/tests/model/events/tool-execution-start-payload.test.ts new file mode 100644 index 00000000..fdee2615 --- /dev/null +++ b/runtime/typescript/packages/core/tests/model/events/tool-execution-start-payload.test.ts @@ -0,0 +1,80 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. + +import { ToolExecutionStartPayload } from "../../../src/model/index"; + +describe("ToolExecutionStartPayload", () => { + describe("construction", () => { + it("should create a new instance with defaults", () => { + const instance = new ToolExecutionStartPayload(); + expect(instance).toBeDefined(); + }); + + it("should create a new instance with partial initialization", () => { + const instance = new ToolExecutionStartPayload({}); + expect(instance).toBeDefined(); + }); + }); + + describe("JSON serialization", () => { + it("should load from JSON - example 1", () => { + const json = `{\n "requestId": "exec_abc123",\n "toolCallId": "call_abc123",\n "toolName": "powershell",\n "workingDirectory": "/workspace/project"\n}`; + const instance = ToolExecutionStartPayload.fromJson(json); + expect(instance).toBeDefined(); + expect(instance.requestId).toEqual("exec_abc123"); + expect(instance.toolCallId).toEqual("call_abc123"); + expect(instance.toolName).toEqual("powershell"); + expect(instance.workingDirectory).toEqual("/workspace/project"); + }); + + it("should round-trip JSON - example 1", () => { + const json = `{\n "requestId": "exec_abc123",\n "toolCallId": "call_abc123",\n "toolName": "powershell",\n "workingDirectory": "/workspace/project"\n}`; + const instance = ToolExecutionStartPayload.fromJson(json); + const output = instance.toJson(); + const reloaded = ToolExecutionStartPayload.fromJson(output); + expect(reloaded.requestId).toEqual(instance.requestId); + expect(reloaded.toolCallId).toEqual(instance.toolCallId); + expect(reloaded.toolName).toEqual(instance.toolName); + expect(reloaded.workingDirectory).toEqual(instance.workingDirectory); + }); + }); + + describe("YAML serialization", () => { + it("should load from YAML - example 1", () => { + const yaml = `requestId: exec_abc123\ntoolCallId: call_abc123\ntoolName: powershell\nworkingDirectory: /workspace/project\n`; + const instance = ToolExecutionStartPayload.fromYaml(yaml); + expect(instance).toBeDefined(); + expect(instance.requestId).toEqual("exec_abc123"); + expect(instance.toolCallId).toEqual("call_abc123"); + expect(instance.toolName).toEqual("powershell"); + expect(instance.workingDirectory).toEqual("/workspace/project"); + }); + + it("should round-trip YAML - example 1", () => { + const yaml = `requestId: exec_abc123\ntoolCallId: call_abc123\ntoolName: powershell\nworkingDirectory: /workspace/project\n`; + const instance = ToolExecutionStartPayload.fromYaml(yaml); + const output = instance.toYaml(); + const reloaded = ToolExecutionStartPayload.fromYaml(output); + expect(reloaded.requestId).toEqual(instance.requestId); + expect(reloaded.toolCallId).toEqual(instance.toolCallId); + expect(reloaded.toolName).toEqual(instance.toolName); + expect(reloaded.workingDirectory).toEqual(instance.workingDirectory); + }); + }); + + describe("load and save", () => { + it("should load from dictionary", () => { + const data: Record = {}; + const instance = ToolExecutionStartPayload.load(data); + expect(instance).toBeDefined(); + }); + + it("should save to dictionary", () => { + const instance = new ToolExecutionStartPayload(); + const data = instance.save(); + expect(data).toBeDefined(); + expect(typeof data).toBe("object"); + }); + }); +}); diff --git a/runtime/typescript/packages/core/tests/model/events/tool-result-payload.test.ts b/runtime/typescript/packages/core/tests/model/events/tool-result-payload.test.ts index 647804c7..e61f8778 100644 --- a/runtime/typescript/packages/core/tests/model/events/tool-result-payload.test.ts +++ b/runtime/typescript/packages/core/tests/model/events/tool-result-payload.test.ts @@ -1,3 +1,4 @@ +// // Copyright (c) Microsoft. All rights reserved. // WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. diff --git a/runtime/typescript/packages/core/tests/model/events/trajectory-event.test.ts b/runtime/typescript/packages/core/tests/model/events/trajectory-event.test.ts new file mode 100644 index 00000000..e53ae938 --- /dev/null +++ b/runtime/typescript/packages/core/tests/model/events/trajectory-event.test.ts @@ -0,0 +1,92 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. + +import { TrajectoryEvent } from "../../../src/model/index"; + +describe("TrajectoryEvent", () => { + describe("construction", () => { + it("should create a new instance with defaults", () => { + const instance = new TrajectoryEvent(); + expect(instance).toBeDefined(); + }); + + it("should create a new instance with partial initialization", () => { + const instance = new TrajectoryEvent({}); + expect(instance).toBeDefined(); + }); + }); + + describe("JSON serialization", () => { + it("should load from JSON - example 1", () => { + const json = `{\n "id": "traj_abc123",\n "sessionId": "sess_abc123",\n "turnId": "turn_001",\n "toolCallId": "call_abc123",\n "turnIndex": 4,\n "eventType": "command",\n "createdAt": "2026-06-09T20:00:00Z"\n}`; + const instance = TrajectoryEvent.fromJson(json); + expect(instance).toBeDefined(); + expect(instance.id).toEqual("traj_abc123"); + expect(instance.sessionId).toEqual("sess_abc123"); + expect(instance.turnId).toEqual("turn_001"); + expect(instance.toolCallId).toEqual("call_abc123"); + expect(instance.turnIndex).toEqual(4); + expect(instance.eventType).toEqual("command"); + expect(instance.createdAt).toEqual("2026-06-09T20:00:00Z"); + }); + + it("should round-trip JSON - example 1", () => { + const json = `{\n "id": "traj_abc123",\n "sessionId": "sess_abc123",\n "turnId": "turn_001",\n "toolCallId": "call_abc123",\n "turnIndex": 4,\n "eventType": "command",\n "createdAt": "2026-06-09T20:00:00Z"\n}`; + const instance = TrajectoryEvent.fromJson(json); + const output = instance.toJson(); + const reloaded = TrajectoryEvent.fromJson(output); + expect(reloaded.id).toEqual(instance.id); + expect(reloaded.sessionId).toEqual(instance.sessionId); + expect(reloaded.turnId).toEqual(instance.turnId); + expect(reloaded.toolCallId).toEqual(instance.toolCallId); + expect(reloaded.turnIndex).toEqual(instance.turnIndex); + expect(reloaded.eventType).toEqual(instance.eventType); + expect(reloaded.createdAt).toEqual(instance.createdAt); + }); + }); + + describe("YAML serialization", () => { + it("should load from YAML - example 1", () => { + const yaml = `id: traj_abc123\nsessionId: sess_abc123\nturnId: turn_001\ntoolCallId: call_abc123\nturnIndex: 4\neventType: command\ncreatedAt: "2026-06-09T20:00:00Z"\n`; + const instance = TrajectoryEvent.fromYaml(yaml); + expect(instance).toBeDefined(); + expect(instance.id).toEqual("traj_abc123"); + expect(instance.sessionId).toEqual("sess_abc123"); + expect(instance.turnId).toEqual("turn_001"); + expect(instance.toolCallId).toEqual("call_abc123"); + expect(instance.turnIndex).toEqual(4); + expect(instance.eventType).toEqual("command"); + expect(instance.createdAt).toEqual("2026-06-09T20:00:00Z"); + }); + + it("should round-trip YAML - example 1", () => { + const yaml = `id: traj_abc123\nsessionId: sess_abc123\nturnId: turn_001\ntoolCallId: call_abc123\nturnIndex: 4\neventType: command\ncreatedAt: "2026-06-09T20:00:00Z"\n`; + const instance = TrajectoryEvent.fromYaml(yaml); + const output = instance.toYaml(); + const reloaded = TrajectoryEvent.fromYaml(output); + expect(reloaded.id).toEqual(instance.id); + expect(reloaded.sessionId).toEqual(instance.sessionId); + expect(reloaded.turnId).toEqual(instance.turnId); + expect(reloaded.toolCallId).toEqual(instance.toolCallId); + expect(reloaded.turnIndex).toEqual(instance.turnIndex); + expect(reloaded.eventType).toEqual(instance.eventType); + expect(reloaded.createdAt).toEqual(instance.createdAt); + }); + }); + + describe("load and save", () => { + it("should load from dictionary", () => { + const data: Record = {}; + const instance = TrajectoryEvent.load(data); + expect(instance).toBeDefined(); + }); + + it("should save to dictionary", () => { + const instance = new TrajectoryEvent(); + const data = instance.save(); + expect(data).toBeDefined(); + expect(typeof data).toBe("object"); + }); + }); +}); diff --git a/runtime/typescript/packages/core/tests/model/events/turn-end-payload.test.ts b/runtime/typescript/packages/core/tests/model/events/turn-end-payload.test.ts new file mode 100644 index 00000000..4a7b8252 --- /dev/null +++ b/runtime/typescript/packages/core/tests/model/events/turn-end-payload.test.ts @@ -0,0 +1,72 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. + +import { TurnEndPayload } from "../../../src/model/index"; + +describe("TurnEndPayload", () => { + describe("construction", () => { + it("should create a new instance with defaults", () => { + const instance = new TurnEndPayload(); + expect(instance).toBeDefined(); + }); + + it("should create a new instance with partial initialization", () => { + const instance = new TurnEndPayload({}); + expect(instance).toBeDefined(); + }); + }); + + describe("JSON serialization", () => { + it("should load from JSON - example 1", () => { + const json = `{\n "iterations": 2,\n "durationMs": 1500\n}`; + const instance = TurnEndPayload.fromJson(json); + expect(instance).toBeDefined(); + expect(instance.iterations).toEqual(2); + expect(instance.durationMs).toEqual(1500); + }); + + it("should round-trip JSON - example 1", () => { + const json = `{\n "iterations": 2,\n "durationMs": 1500\n}`; + const instance = TurnEndPayload.fromJson(json); + const output = instance.toJson(); + const reloaded = TurnEndPayload.fromJson(output); + expect(reloaded.iterations).toEqual(instance.iterations); + expect(reloaded.durationMs).toEqual(instance.durationMs); + }); + }); + + describe("YAML serialization", () => { + it("should load from YAML - example 1", () => { + const yaml = `iterations: 2\ndurationMs: 1500\n`; + const instance = TurnEndPayload.fromYaml(yaml); + expect(instance).toBeDefined(); + expect(instance.iterations).toEqual(2); + expect(instance.durationMs).toEqual(1500); + }); + + it("should round-trip YAML - example 1", () => { + const yaml = `iterations: 2\ndurationMs: 1500\n`; + const instance = TurnEndPayload.fromYaml(yaml); + const output = instance.toYaml(); + const reloaded = TurnEndPayload.fromYaml(output); + expect(reloaded.iterations).toEqual(instance.iterations); + expect(reloaded.durationMs).toEqual(instance.durationMs); + }); + }); + + describe("load and save", () => { + it("should load from dictionary", () => { + const data: Record = {}; + const instance = TurnEndPayload.load(data); + expect(instance).toBeDefined(); + }); + + it("should save to dictionary", () => { + const instance = new TurnEndPayload(); + const data = instance.save(); + expect(data).toBeDefined(); + expect(typeof data).toBe("object"); + }); + }); +}); diff --git a/runtime/typescript/packages/core/tests/model/events/turn-event.test.ts b/runtime/typescript/packages/core/tests/model/events/turn-event.test.ts new file mode 100644 index 00000000..f432a24c --- /dev/null +++ b/runtime/typescript/packages/core/tests/model/events/turn-event.test.ts @@ -0,0 +1,88 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. + +import { TurnEvent } from "../../../src/model/index"; + +describe("TurnEvent", () => { + describe("construction", () => { + it("should create a new instance with defaults", () => { + const instance = new TurnEvent(); + expect(instance).toBeDefined(); + }); + + it("should create a new instance with partial initialization", () => { + const instance = new TurnEvent({}); + expect(instance).toBeDefined(); + }); + }); + + describe("JSON serialization", () => { + it("should load from JSON - example 1", () => { + const json = `{\n "id": "evt_abc123",\n "timestamp": "2026-06-09T20:00:00Z",\n "turnId": "turn_001",\n "iteration": 0,\n "parentId": "evt_parent",\n "spanId": "span_tool_001"\n}`; + const instance = TurnEvent.fromJson(json); + expect(instance).toBeDefined(); + expect(instance.id).toEqual("evt_abc123"); + expect(instance.timestamp).toEqual("2026-06-09T20:00:00Z"); + expect(instance.turnId).toEqual("turn_001"); + expect(instance.iteration).toEqual(0); + expect(instance.parentId).toEqual("evt_parent"); + expect(instance.spanId).toEqual("span_tool_001"); + }); + + it("should round-trip JSON - example 1", () => { + const json = `{\n "id": "evt_abc123",\n "timestamp": "2026-06-09T20:00:00Z",\n "turnId": "turn_001",\n "iteration": 0,\n "parentId": "evt_parent",\n "spanId": "span_tool_001"\n}`; + const instance = TurnEvent.fromJson(json); + const output = instance.toJson(); + const reloaded = TurnEvent.fromJson(output); + expect(reloaded.id).toEqual(instance.id); + expect(reloaded.timestamp).toEqual(instance.timestamp); + expect(reloaded.turnId).toEqual(instance.turnId); + expect(reloaded.iteration).toEqual(instance.iteration); + expect(reloaded.parentId).toEqual(instance.parentId); + expect(reloaded.spanId).toEqual(instance.spanId); + }); + }); + + describe("YAML serialization", () => { + it("should load from YAML - example 1", () => { + const yaml = `id: evt_abc123\ntimestamp: "2026-06-09T20:00:00Z"\nturnId: turn_001\niteration: 0\nparentId: evt_parent\nspanId: span_tool_001\n`; + const instance = TurnEvent.fromYaml(yaml); + expect(instance).toBeDefined(); + expect(instance.id).toEqual("evt_abc123"); + expect(instance.timestamp).toEqual("2026-06-09T20:00:00Z"); + expect(instance.turnId).toEqual("turn_001"); + expect(instance.iteration).toEqual(0); + expect(instance.parentId).toEqual("evt_parent"); + expect(instance.spanId).toEqual("span_tool_001"); + }); + + it("should round-trip YAML - example 1", () => { + const yaml = `id: evt_abc123\ntimestamp: "2026-06-09T20:00:00Z"\nturnId: turn_001\niteration: 0\nparentId: evt_parent\nspanId: span_tool_001\n`; + const instance = TurnEvent.fromYaml(yaml); + const output = instance.toYaml(); + const reloaded = TurnEvent.fromYaml(output); + expect(reloaded.id).toEqual(instance.id); + expect(reloaded.timestamp).toEqual(instance.timestamp); + expect(reloaded.turnId).toEqual(instance.turnId); + expect(reloaded.iteration).toEqual(instance.iteration); + expect(reloaded.parentId).toEqual(instance.parentId); + expect(reloaded.spanId).toEqual(instance.spanId); + }); + }); + + describe("load and save", () => { + it("should load from dictionary", () => { + const data: Record = {}; + const instance = TurnEvent.load(data); + expect(instance).toBeDefined(); + }); + + it("should save to dictionary", () => { + const instance = new TurnEvent(); + const data = instance.save(); + expect(data).toBeDefined(); + expect(typeof data).toBe("object"); + }); + }); +}); diff --git a/runtime/typescript/packages/core/tests/model/events/turn-start-payload.test.ts b/runtime/typescript/packages/core/tests/model/events/turn-start-payload.test.ts new file mode 100644 index 00000000..588712d8 --- /dev/null +++ b/runtime/typescript/packages/core/tests/model/events/turn-start-payload.test.ts @@ -0,0 +1,72 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. + +import { TurnStartPayload } from "../../../src/model/index"; + +describe("TurnStartPayload", () => { + describe("construction", () => { + it("should create a new instance with defaults", () => { + const instance = new TurnStartPayload(); + expect(instance).toBeDefined(); + }); + + it("should create a new instance with partial initialization", () => { + const instance = new TurnStartPayload({}); + expect(instance).toBeDefined(); + }); + }); + + describe("JSON serialization", () => { + it("should load from JSON - example 1", () => { + const json = `{\n "agent": "weather-agent",\n "maxIterations": 10\n}`; + const instance = TurnStartPayload.fromJson(json); + expect(instance).toBeDefined(); + expect(instance.agent).toEqual("weather-agent"); + expect(instance.maxIterations).toEqual(10); + }); + + it("should round-trip JSON - example 1", () => { + const json = `{\n "agent": "weather-agent",\n "maxIterations": 10\n}`; + const instance = TurnStartPayload.fromJson(json); + const output = instance.toJson(); + const reloaded = TurnStartPayload.fromJson(output); + expect(reloaded.agent).toEqual(instance.agent); + expect(reloaded.maxIterations).toEqual(instance.maxIterations); + }); + }); + + describe("YAML serialization", () => { + it("should load from YAML - example 1", () => { + const yaml = `agent: weather-agent\nmaxIterations: 10\n`; + const instance = TurnStartPayload.fromYaml(yaml); + expect(instance).toBeDefined(); + expect(instance.agent).toEqual("weather-agent"); + expect(instance.maxIterations).toEqual(10); + }); + + it("should round-trip YAML - example 1", () => { + const yaml = `agent: weather-agent\nmaxIterations: 10\n`; + const instance = TurnStartPayload.fromYaml(yaml); + const output = instance.toYaml(); + const reloaded = TurnStartPayload.fromYaml(output); + expect(reloaded.agent).toEqual(instance.agent); + expect(reloaded.maxIterations).toEqual(instance.maxIterations); + }); + }); + + describe("load and save", () => { + it("should load from dictionary", () => { + const data: Record = {}; + const instance = TurnStartPayload.load(data); + expect(instance).toBeDefined(); + }); + + it("should save to dictionary", () => { + const instance = new TurnStartPayload(); + const data = instance.save(); + expect(data).toBeDefined(); + expect(typeof data).toBe("object"); + }); + }); +}); diff --git a/runtime/typescript/packages/core/tests/model/events/turn-summary.test.ts b/runtime/typescript/packages/core/tests/model/events/turn-summary.test.ts new file mode 100644 index 00000000..43c4969c --- /dev/null +++ b/runtime/typescript/packages/core/tests/model/events/turn-summary.test.ts @@ -0,0 +1,92 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. + +import { TurnSummary } from "../../../src/model/index"; + +describe("TurnSummary", () => { + describe("construction", () => { + it("should create a new instance with defaults", () => { + const instance = new TurnSummary(); + expect(instance).toBeDefined(); + }); + + it("should create a new instance with partial initialization", () => { + const instance = new TurnSummary({}); + expect(instance).toBeDefined(); + }); + }); + + describe("JSON serialization", () => { + it("should load from JSON - example 1", () => { + const json = `{\n "turnId": "turn_001",\n "status": "success",\n "iterations": 2,\n "llmCalls": 3,\n "toolCalls": 2,\n "retries": 1,\n "durationMs": 2500\n}`; + const instance = TurnSummary.fromJson(json); + expect(instance).toBeDefined(); + expect(instance.turnId).toEqual("turn_001"); + expect(instance.status).toEqual("success"); + expect(instance.iterations).toEqual(2); + expect(instance.llmCalls).toEqual(3); + expect(instance.toolCalls).toEqual(2); + expect(instance.retries).toEqual(1); + expect(instance.durationMs).toEqual(2500); + }); + + it("should round-trip JSON - example 1", () => { + const json = `{\n "turnId": "turn_001",\n "status": "success",\n "iterations": 2,\n "llmCalls": 3,\n "toolCalls": 2,\n "retries": 1,\n "durationMs": 2500\n}`; + const instance = TurnSummary.fromJson(json); + const output = instance.toJson(); + const reloaded = TurnSummary.fromJson(output); + expect(reloaded.turnId).toEqual(instance.turnId); + expect(reloaded.status).toEqual(instance.status); + expect(reloaded.iterations).toEqual(instance.iterations); + expect(reloaded.llmCalls).toEqual(instance.llmCalls); + expect(reloaded.toolCalls).toEqual(instance.toolCalls); + expect(reloaded.retries).toEqual(instance.retries); + expect(reloaded.durationMs).toEqual(instance.durationMs); + }); + }); + + describe("YAML serialization", () => { + it("should load from YAML - example 1", () => { + const yaml = `turnId: turn_001\nstatus: success\niterations: 2\nllmCalls: 3\ntoolCalls: 2\nretries: 1\ndurationMs: 2500\n`; + const instance = TurnSummary.fromYaml(yaml); + expect(instance).toBeDefined(); + expect(instance.turnId).toEqual("turn_001"); + expect(instance.status).toEqual("success"); + expect(instance.iterations).toEqual(2); + expect(instance.llmCalls).toEqual(3); + expect(instance.toolCalls).toEqual(2); + expect(instance.retries).toEqual(1); + expect(instance.durationMs).toEqual(2500); + }); + + it("should round-trip YAML - example 1", () => { + const yaml = `turnId: turn_001\nstatus: success\niterations: 2\nllmCalls: 3\ntoolCalls: 2\nretries: 1\ndurationMs: 2500\n`; + const instance = TurnSummary.fromYaml(yaml); + const output = instance.toYaml(); + const reloaded = TurnSummary.fromYaml(output); + expect(reloaded.turnId).toEqual(instance.turnId); + expect(reloaded.status).toEqual(instance.status); + expect(reloaded.iterations).toEqual(instance.iterations); + expect(reloaded.llmCalls).toEqual(instance.llmCalls); + expect(reloaded.toolCalls).toEqual(instance.toolCalls); + expect(reloaded.retries).toEqual(instance.retries); + expect(reloaded.durationMs).toEqual(instance.durationMs); + }); + }); + + describe("load and save", () => { + it("should load from dictionary", () => { + const data: Record = {}; + const instance = TurnSummary.load(data); + expect(instance).toBeDefined(); + }); + + it("should save to dictionary", () => { + const instance = new TurnSummary(); + const data = instance.save(); + expect(data).toBeDefined(); + expect(typeof data).toBe("object"); + }); + }); +}); diff --git a/runtime/typescript/packages/core/tests/model/events/turn-trace.test.ts b/runtime/typescript/packages/core/tests/model/events/turn-trace.test.ts new file mode 100644 index 00000000..34a79a4e --- /dev/null +++ b/runtime/typescript/packages/core/tests/model/events/turn-trace.test.ts @@ -0,0 +1,76 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. + +import { TurnTrace } from "../../../src/model/index"; + +describe("TurnTrace", () => { + describe("construction", () => { + it("should create a new instance with defaults", () => { + const instance = new TurnTrace(); + expect(instance).toBeDefined(); + }); + + it("should create a new instance with partial initialization", () => { + const instance = new TurnTrace({}); + expect(instance).toBeDefined(); + }); + }); + + describe("JSON serialization", () => { + it("should load from JSON - example 1", () => { + const json = `{\n "version": "1",\n "runtime": "typescript",\n "promptyVersion": "2.0.0"\n}`; + const instance = TurnTrace.fromJson(json); + expect(instance).toBeDefined(); + expect(instance.version).toEqual("1"); + expect(instance.runtime).toEqual("typescript"); + expect(instance.promptyVersion).toEqual("2.0.0"); + }); + + it("should round-trip JSON - example 1", () => { + const json = `{\n "version": "1",\n "runtime": "typescript",\n "promptyVersion": "2.0.0"\n}`; + const instance = TurnTrace.fromJson(json); + const output = instance.toJson(); + const reloaded = TurnTrace.fromJson(output); + expect(reloaded.version).toEqual(instance.version); + expect(reloaded.runtime).toEqual(instance.runtime); + expect(reloaded.promptyVersion).toEqual(instance.promptyVersion); + }); + }); + + describe("YAML serialization", () => { + it("should load from YAML - example 1", () => { + const yaml = `version: "1"\nruntime: typescript\npromptyVersion: 2.0.0\n`; + const instance = TurnTrace.fromYaml(yaml); + expect(instance).toBeDefined(); + expect(instance.version).toEqual("1"); + expect(instance.runtime).toEqual("typescript"); + expect(instance.promptyVersion).toEqual("2.0.0"); + }); + + it("should round-trip YAML - example 1", () => { + const yaml = `version: "1"\nruntime: typescript\npromptyVersion: 2.0.0\n`; + const instance = TurnTrace.fromYaml(yaml); + const output = instance.toYaml(); + const reloaded = TurnTrace.fromYaml(output); + expect(reloaded.version).toEqual(instance.version); + expect(reloaded.runtime).toEqual(instance.runtime); + expect(reloaded.promptyVersion).toEqual(instance.promptyVersion); + }); + }); + + describe("load and save", () => { + it("should load from dictionary", () => { + const data: Record = {}; + const instance = TurnTrace.load(data); + expect(instance).toBeDefined(); + }); + + it("should save to dictionary", () => { + const instance = new TurnTrace(); + const data = instance.save(); + expect(data).toBeDefined(); + expect(typeof data).toBe("object"); + }); + }); +}); diff --git a/runtime/typescript/packages/core/tests/model/model/model-info.test.ts b/runtime/typescript/packages/core/tests/model/model/model-info.test.ts index 553d7ac4..e8a11a04 100644 --- a/runtime/typescript/packages/core/tests/model/model/model-info.test.ts +++ b/runtime/typescript/packages/core/tests/model/model/model-info.test.ts @@ -1,3 +1,4 @@ +// // Copyright (c) Microsoft. All rights reserved. // WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. diff --git a/runtime/typescript/packages/core/tests/model/model/model-options.test.ts b/runtime/typescript/packages/core/tests/model/model/model-options.test.ts index 6eadda6c..a96b7959 100644 --- a/runtime/typescript/packages/core/tests/model/model/model-options.test.ts +++ b/runtime/typescript/packages/core/tests/model/model/model-options.test.ts @@ -1,3 +1,4 @@ +// // Copyright (c) Microsoft. All rights reserved. // WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. diff --git a/runtime/typescript/packages/core/tests/model/model/model.test.ts b/runtime/typescript/packages/core/tests/model/model/model.test.ts index 6cae2cff..0d71d05b 100644 --- a/runtime/typescript/packages/core/tests/model/model/model.test.ts +++ b/runtime/typescript/packages/core/tests/model/model/model.test.ts @@ -1,3 +1,4 @@ +// // Copyright (c) Microsoft. All rights reserved. // WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. diff --git a/runtime/typescript/packages/core/tests/model/model/token-usage.test.ts b/runtime/typescript/packages/core/tests/model/model/token-usage.test.ts index a4ea1ba6..1da6e77c 100644 --- a/runtime/typescript/packages/core/tests/model/model/token-usage.test.ts +++ b/runtime/typescript/packages/core/tests/model/model/token-usage.test.ts @@ -1,3 +1,4 @@ +// // Copyright (c) Microsoft. All rights reserved. // WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. diff --git a/runtime/typescript/packages/core/tests/model/pipeline/compaction-config.test.ts b/runtime/typescript/packages/core/tests/model/pipeline/compaction-config.test.ts index d97b4f05..c055d5cd 100644 --- a/runtime/typescript/packages/core/tests/model/pipeline/compaction-config.test.ts +++ b/runtime/typescript/packages/core/tests/model/pipeline/compaction-config.test.ts @@ -1,3 +1,4 @@ +// // Copyright (c) Microsoft. All rights reserved. // WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. diff --git a/runtime/typescript/packages/core/tests/model/pipeline/replay-journal-record.test.ts b/runtime/typescript/packages/core/tests/model/pipeline/replay-journal-record.test.ts new file mode 100644 index 00000000..b10fb322 --- /dev/null +++ b/runtime/typescript/packages/core/tests/model/pipeline/replay-journal-record.test.ts @@ -0,0 +1,34 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. + +import { ReplayJournalRecord } from "../../../src/model/index"; + +describe("ReplayJournalRecord", () => { + describe("construction", () => { + it("should create a new instance with defaults", () => { + const instance = new ReplayJournalRecord(); + expect(instance).toBeDefined(); + }); + + it("should create a new instance with partial initialization", () => { + const instance = new ReplayJournalRecord({}); + expect(instance).toBeDefined(); + }); + }); + + describe("load and save", () => { + it("should load from dictionary", () => { + const data: Record = {}; + const instance = ReplayJournalRecord.load(data); + expect(instance).toBeDefined(); + }); + + it("should save to dictionary", () => { + const instance = new ReplayJournalRecord(); + const data = instance.save(); + expect(data).toBeDefined(); + expect(typeof data).toBe("object"); + }); + }); +}); diff --git a/runtime/typescript/packages/core/tests/model/pipeline/replay-mismatch.test.ts b/runtime/typescript/packages/core/tests/model/pipeline/replay-mismatch.test.ts new file mode 100644 index 00000000..9076fe15 --- /dev/null +++ b/runtime/typescript/packages/core/tests/model/pipeline/replay-mismatch.test.ts @@ -0,0 +1,34 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. + +import { ReplayMismatch } from "../../../src/model/index"; + +describe("ReplayMismatch", () => { + describe("construction", () => { + it("should create a new instance with defaults", () => { + const instance = new ReplayMismatch(); + expect(instance).toBeDefined(); + }); + + it("should create a new instance with partial initialization", () => { + const instance = new ReplayMismatch({}); + expect(instance).toBeDefined(); + }); + }); + + describe("load and save", () => { + it("should load from dictionary", () => { + const data: Record = {}; + const instance = ReplayMismatch.load(data); + expect(instance).toBeDefined(); + }); + + it("should save to dictionary", () => { + const instance = new ReplayMismatch(); + const data = instance.save(); + expect(data).toBeDefined(); + expect(typeof data).toBe("object"); + }); + }); +}); diff --git a/runtime/typescript/packages/core/tests/model/pipeline/replay-verification-request.test.ts b/runtime/typescript/packages/core/tests/model/pipeline/replay-verification-request.test.ts new file mode 100644 index 00000000..e5c215b4 --- /dev/null +++ b/runtime/typescript/packages/core/tests/model/pipeline/replay-verification-request.test.ts @@ -0,0 +1,34 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. + +import { ReplayVerificationRequest } from "../../../src/model/index"; + +describe("ReplayVerificationRequest", () => { + describe("construction", () => { + it("should create a new instance with defaults", () => { + const instance = new ReplayVerificationRequest(); + expect(instance).toBeDefined(); + }); + + it("should create a new instance with partial initialization", () => { + const instance = new ReplayVerificationRequest({}); + expect(instance).toBeDefined(); + }); + }); + + describe("load and save", () => { + it("should load from dictionary", () => { + const data: Record = {}; + const instance = ReplayVerificationRequest.load(data); + expect(instance).toBeDefined(); + }); + + it("should save to dictionary", () => { + const instance = new ReplayVerificationRequest(); + const data = instance.save(); + expect(data).toBeDefined(); + expect(typeof data).toBe("object"); + }); + }); +}); diff --git a/runtime/typescript/packages/core/tests/model/pipeline/replay-verification-result.test.ts b/runtime/typescript/packages/core/tests/model/pipeline/replay-verification-result.test.ts new file mode 100644 index 00000000..1669c046 --- /dev/null +++ b/runtime/typescript/packages/core/tests/model/pipeline/replay-verification-result.test.ts @@ -0,0 +1,34 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. + +import { ReplayVerificationResult } from "../../../src/model/index"; + +describe("ReplayVerificationResult", () => { + describe("construction", () => { + it("should create a new instance with defaults", () => { + const instance = new ReplayVerificationResult(); + expect(instance).toBeDefined(); + }); + + it("should create a new instance with partial initialization", () => { + const instance = new ReplayVerificationResult({}); + expect(instance).toBeDefined(); + }); + }); + + describe("load and save", () => { + it("should load from dictionary", () => { + const data: Record = {}; + const instance = ReplayVerificationResult.load(data); + expect(instance).toBeDefined(); + }); + + it("should save to dictionary", () => { + const instance = new ReplayVerificationResult(); + const data = instance.save(); + expect(data).toBeDefined(); + expect(typeof data).toBe("object"); + }); + }); +}); diff --git a/runtime/typescript/packages/core/tests/model/pipeline/run-turn-request.test.ts b/runtime/typescript/packages/core/tests/model/pipeline/run-turn-request.test.ts new file mode 100644 index 00000000..97138537 --- /dev/null +++ b/runtime/typescript/packages/core/tests/model/pipeline/run-turn-request.test.ts @@ -0,0 +1,72 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. + +import { RunTurnRequest } from "../../../src/model/index"; + +describe("RunTurnRequest", () => { + describe("construction", () => { + it("should create a new instance with defaults", () => { + const instance = new RunTurnRequest(); + expect(instance).toBeDefined(); + }); + + it("should create a new instance with partial initialization", () => { + const instance = new RunTurnRequest({}); + expect(instance).toBeDefined(); + }); + }); + + describe("JSON serialization", () => { + it("should load from JSON - example 1", () => { + const json = `{\n "sessionId": "sess_abc123",\n "turnId": "turn_abc123"\n}`; + const instance = RunTurnRequest.fromJson(json); + expect(instance).toBeDefined(); + expect(instance.sessionId).toEqual("sess_abc123"); + expect(instance.turnId).toEqual("turn_abc123"); + }); + + it("should round-trip JSON - example 1", () => { + const json = `{\n "sessionId": "sess_abc123",\n "turnId": "turn_abc123"\n}`; + const instance = RunTurnRequest.fromJson(json); + const output = instance.toJson(); + const reloaded = RunTurnRequest.fromJson(output); + expect(reloaded.sessionId).toEqual(instance.sessionId); + expect(reloaded.turnId).toEqual(instance.turnId); + }); + }); + + describe("YAML serialization", () => { + it("should load from YAML - example 1", () => { + const yaml = `sessionId: sess_abc123\nturnId: turn_abc123\n`; + const instance = RunTurnRequest.fromYaml(yaml); + expect(instance).toBeDefined(); + expect(instance.sessionId).toEqual("sess_abc123"); + expect(instance.turnId).toEqual("turn_abc123"); + }); + + it("should round-trip YAML - example 1", () => { + const yaml = `sessionId: sess_abc123\nturnId: turn_abc123\n`; + const instance = RunTurnRequest.fromYaml(yaml); + const output = instance.toYaml(); + const reloaded = RunTurnRequest.fromYaml(output); + expect(reloaded.sessionId).toEqual(instance.sessionId); + expect(reloaded.turnId).toEqual(instance.turnId); + }); + }); + + describe("load and save", () => { + it("should load from dictionary", () => { + const data: Record = {}; + const instance = RunTurnRequest.load(data); + expect(instance).toBeDefined(); + }); + + it("should save to dictionary", () => { + const instance = new RunTurnRequest(); + const data = instance.save(); + expect(data).toBeDefined(); + expect(typeof data).toBe("object"); + }); + }); +}); diff --git a/runtime/typescript/packages/core/tests/model/pipeline/run-turn-result.test.ts b/runtime/typescript/packages/core/tests/model/pipeline/run-turn-result.test.ts new file mode 100644 index 00000000..6fd0e11f --- /dev/null +++ b/runtime/typescript/packages/core/tests/model/pipeline/run-turn-result.test.ts @@ -0,0 +1,76 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. + +import { RunTurnResult } from "../../../src/model/index"; + +describe("RunTurnResult", () => { + describe("construction", () => { + it("should create a new instance with defaults", () => { + const instance = new RunTurnResult(); + expect(instance).toBeDefined(); + }); + + it("should create a new instance with partial initialization", () => { + const instance = new RunTurnResult({}); + expect(instance).toBeDefined(); + }); + }); + + describe("JSON serialization", () => { + it("should load from JSON - example 1", () => { + const json = `{\n "sessionId": "sess_abc123",\n "turnId": "turn_abc123",\n "iterations": 1\n}`; + const instance = RunTurnResult.fromJson(json); + expect(instance).toBeDefined(); + expect(instance.sessionId).toEqual("sess_abc123"); + expect(instance.turnId).toEqual("turn_abc123"); + expect(instance.iterations).toEqual(1); + }); + + it("should round-trip JSON - example 1", () => { + const json = `{\n "sessionId": "sess_abc123",\n "turnId": "turn_abc123",\n "iterations": 1\n}`; + const instance = RunTurnResult.fromJson(json); + const output = instance.toJson(); + const reloaded = RunTurnResult.fromJson(output); + expect(reloaded.sessionId).toEqual(instance.sessionId); + expect(reloaded.turnId).toEqual(instance.turnId); + expect(reloaded.iterations).toEqual(instance.iterations); + }); + }); + + describe("YAML serialization", () => { + it("should load from YAML - example 1", () => { + const yaml = `sessionId: sess_abc123\nturnId: turn_abc123\niterations: 1\n`; + const instance = RunTurnResult.fromYaml(yaml); + expect(instance).toBeDefined(); + expect(instance.sessionId).toEqual("sess_abc123"); + expect(instance.turnId).toEqual("turn_abc123"); + expect(instance.iterations).toEqual(1); + }); + + it("should round-trip YAML - example 1", () => { + const yaml = `sessionId: sess_abc123\nturnId: turn_abc123\niterations: 1\n`; + const instance = RunTurnResult.fromYaml(yaml); + const output = instance.toYaml(); + const reloaded = RunTurnResult.fromYaml(output); + expect(reloaded.sessionId).toEqual(instance.sessionId); + expect(reloaded.turnId).toEqual(instance.turnId); + expect(reloaded.iterations).toEqual(instance.iterations); + }); + }); + + describe("load and save", () => { + it("should load from dictionary", () => { + const data: Record = {}; + const instance = RunTurnResult.load(data); + expect(instance).toBeDefined(); + }); + + it("should save to dictionary", () => { + const instance = new RunTurnResult(); + const data = instance.save(); + expect(data).toBeDefined(); + expect(typeof data).toBe("object"); + }); + }); +}); diff --git a/runtime/typescript/packages/core/tests/model/pipeline/turn-model-request.test.ts b/runtime/typescript/packages/core/tests/model/pipeline/turn-model-request.test.ts new file mode 100644 index 00000000..5f2c8b8b --- /dev/null +++ b/runtime/typescript/packages/core/tests/model/pipeline/turn-model-request.test.ts @@ -0,0 +1,76 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. + +import { TurnModelRequest } from "../../../src/model/index"; + +describe("TurnModelRequest", () => { + describe("construction", () => { + it("should create a new instance with defaults", () => { + const instance = new TurnModelRequest(); + expect(instance).toBeDefined(); + }); + + it("should create a new instance with partial initialization", () => { + const instance = new TurnModelRequest({}); + expect(instance).toBeDefined(); + }); + }); + + describe("JSON serialization", () => { + it("should load from JSON - example 1", () => { + const json = `{\n "sessionId": "sess_abc123",\n "turnId": "turn_abc123",\n "iteration": 0\n}`; + const instance = TurnModelRequest.fromJson(json); + expect(instance).toBeDefined(); + expect(instance.sessionId).toEqual("sess_abc123"); + expect(instance.turnId).toEqual("turn_abc123"); + expect(instance.iteration).toEqual(0); + }); + + it("should round-trip JSON - example 1", () => { + const json = `{\n "sessionId": "sess_abc123",\n "turnId": "turn_abc123",\n "iteration": 0\n}`; + const instance = TurnModelRequest.fromJson(json); + const output = instance.toJson(); + const reloaded = TurnModelRequest.fromJson(output); + expect(reloaded.sessionId).toEqual(instance.sessionId); + expect(reloaded.turnId).toEqual(instance.turnId); + expect(reloaded.iteration).toEqual(instance.iteration); + }); + }); + + describe("YAML serialization", () => { + it("should load from YAML - example 1", () => { + const yaml = `sessionId: sess_abc123\nturnId: turn_abc123\niteration: 0\n`; + const instance = TurnModelRequest.fromYaml(yaml); + expect(instance).toBeDefined(); + expect(instance.sessionId).toEqual("sess_abc123"); + expect(instance.turnId).toEqual("turn_abc123"); + expect(instance.iteration).toEqual(0); + }); + + it("should round-trip YAML - example 1", () => { + const yaml = `sessionId: sess_abc123\nturnId: turn_abc123\niteration: 0\n`; + const instance = TurnModelRequest.fromYaml(yaml); + const output = instance.toYaml(); + const reloaded = TurnModelRequest.fromYaml(output); + expect(reloaded.sessionId).toEqual(instance.sessionId); + expect(reloaded.turnId).toEqual(instance.turnId); + expect(reloaded.iteration).toEqual(instance.iteration); + }); + }); + + describe("load and save", () => { + it("should load from dictionary", () => { + const data: Record = {}; + const instance = TurnModelRequest.load(data); + expect(instance).toBeDefined(); + }); + + it("should save to dictionary", () => { + const instance = new TurnModelRequest(); + const data = instance.save(); + expect(data).toBeDefined(); + expect(typeof data).toBe("object"); + }); + }); +}); diff --git a/runtime/typescript/packages/core/tests/model/pipeline/turn-model-response.test.ts b/runtime/typescript/packages/core/tests/model/pipeline/turn-model-response.test.ts new file mode 100644 index 00000000..7a9f54d7 --- /dev/null +++ b/runtime/typescript/packages/core/tests/model/pipeline/turn-model-response.test.ts @@ -0,0 +1,34 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. + +import { TurnModelResponse } from "../../../src/model/index"; + +describe("TurnModelResponse", () => { + describe("construction", () => { + it("should create a new instance with defaults", () => { + const instance = new TurnModelResponse(); + expect(instance).toBeDefined(); + }); + + it("should create a new instance with partial initialization", () => { + const instance = new TurnModelResponse({}); + expect(instance).toBeDefined(); + }); + }); + + describe("load and save", () => { + it("should load from dictionary", () => { + const data: Record = {}; + const instance = TurnModelResponse.load(data); + expect(instance).toBeDefined(); + }); + + it("should save to dictionary", () => { + const instance = new TurnModelResponse(); + const data = instance.save(); + expect(data).toBeDefined(); + expect(typeof data).toBe("object"); + }); + }); +}); diff --git a/runtime/typescript/packages/core/tests/model/pipeline/turn-options.test.ts b/runtime/typescript/packages/core/tests/model/pipeline/turn-options.test.ts index 7efdc80d..6248d328 100644 --- a/runtime/typescript/packages/core/tests/model/pipeline/turn-options.test.ts +++ b/runtime/typescript/packages/core/tests/model/pipeline/turn-options.test.ts @@ -1,3 +1,4 @@ +// // Copyright (c) Microsoft. All rights reserved. // WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. diff --git a/runtime/typescript/packages/core/tests/model/streaming/stream-options.test.ts b/runtime/typescript/packages/core/tests/model/streaming/stream-options.test.ts index ba97ce57..8bd71335 100644 --- a/runtime/typescript/packages/core/tests/model/streaming/stream-options.test.ts +++ b/runtime/typescript/packages/core/tests/model/streaming/stream-options.test.ts @@ -1,3 +1,4 @@ +// // Copyright (c) Microsoft. All rights reserved. // WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. diff --git a/runtime/typescript/packages/core/tests/model/template/format-config.test.ts b/runtime/typescript/packages/core/tests/model/template/format-config.test.ts index e27c452c..8d211d25 100644 --- a/runtime/typescript/packages/core/tests/model/template/format-config.test.ts +++ b/runtime/typescript/packages/core/tests/model/template/format-config.test.ts @@ -1,3 +1,4 @@ +// // Copyright (c) Microsoft. All rights reserved. // WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. diff --git a/runtime/typescript/packages/core/tests/model/template/parser-config.test.ts b/runtime/typescript/packages/core/tests/model/template/parser-config.test.ts index 4de1997f..bf783689 100644 --- a/runtime/typescript/packages/core/tests/model/template/parser-config.test.ts +++ b/runtime/typescript/packages/core/tests/model/template/parser-config.test.ts @@ -1,3 +1,4 @@ +// // Copyright (c) Microsoft. All rights reserved. // WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. diff --git a/runtime/typescript/packages/core/tests/model/template/template.test.ts b/runtime/typescript/packages/core/tests/model/template/template.test.ts index d04bd9d0..c82a0c52 100644 --- a/runtime/typescript/packages/core/tests/model/template/template.test.ts +++ b/runtime/typescript/packages/core/tests/model/template/template.test.ts @@ -1,3 +1,4 @@ +// // Copyright (c) Microsoft. All rights reserved. // WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. diff --git a/runtime/typescript/packages/core/tests/model/tools/binding.test.ts b/runtime/typescript/packages/core/tests/model/tools/binding.test.ts index dc7c6705..86fb579c 100644 --- a/runtime/typescript/packages/core/tests/model/tools/binding.test.ts +++ b/runtime/typescript/packages/core/tests/model/tools/binding.test.ts @@ -1,3 +1,4 @@ +// // Copyright (c) Microsoft. All rights reserved. // WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. diff --git a/runtime/typescript/packages/core/tests/model/tools/custom-tool.test.ts b/runtime/typescript/packages/core/tests/model/tools/custom-tool.test.ts index f0c7708d..950c3ba4 100644 --- a/runtime/typescript/packages/core/tests/model/tools/custom-tool.test.ts +++ b/runtime/typescript/packages/core/tests/model/tools/custom-tool.test.ts @@ -1,3 +1,4 @@ +// // Copyright (c) Microsoft. All rights reserved. // WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. diff --git a/runtime/typescript/packages/core/tests/model/tools/function-tool.test.ts b/runtime/typescript/packages/core/tests/model/tools/function-tool.test.ts index f43403d7..a9096d0a 100644 --- a/runtime/typescript/packages/core/tests/model/tools/function-tool.test.ts +++ b/runtime/typescript/packages/core/tests/model/tools/function-tool.test.ts @@ -1,3 +1,4 @@ +// // Copyright (c) Microsoft. All rights reserved. // WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. diff --git a/runtime/typescript/packages/core/tests/model/tools/mcp-approval-mode.test.ts b/runtime/typescript/packages/core/tests/model/tools/mcp-approval-mode.test.ts index 3e0d14d3..60d50872 100644 --- a/runtime/typescript/packages/core/tests/model/tools/mcp-approval-mode.test.ts +++ b/runtime/typescript/packages/core/tests/model/tools/mcp-approval-mode.test.ts @@ -1,3 +1,4 @@ +// // Copyright (c) Microsoft. All rights reserved. // WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. diff --git a/runtime/typescript/packages/core/tests/model/tools/mcp-tool.test.ts b/runtime/typescript/packages/core/tests/model/tools/mcp-tool.test.ts index eed2007e..53c93f41 100644 --- a/runtime/typescript/packages/core/tests/model/tools/mcp-tool.test.ts +++ b/runtime/typescript/packages/core/tests/model/tools/mcp-tool.test.ts @@ -1,3 +1,4 @@ +// // Copyright (c) Microsoft. All rights reserved. // WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. diff --git a/runtime/typescript/packages/core/tests/model/tools/open-api-tool.test.ts b/runtime/typescript/packages/core/tests/model/tools/open-api-tool.test.ts index 2f568db8..a2f10210 100644 --- a/runtime/typescript/packages/core/tests/model/tools/open-api-tool.test.ts +++ b/runtime/typescript/packages/core/tests/model/tools/open-api-tool.test.ts @@ -1,3 +1,4 @@ +// // Copyright (c) Microsoft. All rights reserved. // WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. diff --git a/runtime/typescript/packages/core/tests/model/tools/prompty-tool.test.ts b/runtime/typescript/packages/core/tests/model/tools/prompty-tool.test.ts index 3f61b608..d1f58846 100644 --- a/runtime/typescript/packages/core/tests/model/tools/prompty-tool.test.ts +++ b/runtime/typescript/packages/core/tests/model/tools/prompty-tool.test.ts @@ -1,3 +1,4 @@ +// // Copyright (c) Microsoft. All rights reserved. // WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. diff --git a/runtime/typescript/packages/core/tests/model/tools/tool-context.test.ts b/runtime/typescript/packages/core/tests/model/tools/tool-context.test.ts index 7d91c671..81c822f1 100644 --- a/runtime/typescript/packages/core/tests/model/tools/tool-context.test.ts +++ b/runtime/typescript/packages/core/tests/model/tools/tool-context.test.ts @@ -1,3 +1,4 @@ +// // Copyright (c) Microsoft. All rights reserved. // WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. diff --git a/runtime/typescript/packages/core/tests/model/tools/tool-dispatch-result.test.ts b/runtime/typescript/packages/core/tests/model/tools/tool-dispatch-result.test.ts index aef9bfe3..543f7dd6 100644 --- a/runtime/typescript/packages/core/tests/model/tools/tool-dispatch-result.test.ts +++ b/runtime/typescript/packages/core/tests/model/tools/tool-dispatch-result.test.ts @@ -1,3 +1,4 @@ +// // Copyright (c) Microsoft. All rights reserved. // WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. diff --git a/runtime/typescript/packages/core/tests/model/tools/tool.test.ts b/runtime/typescript/packages/core/tests/model/tools/tool.test.ts index 87db7245..683dfcd5 100644 --- a/runtime/typescript/packages/core/tests/model/tools/tool.test.ts +++ b/runtime/typescript/packages/core/tests/model/tools/tool.test.ts @@ -1,3 +1,4 @@ +// // Copyright (c) Microsoft. All rights reserved. // WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. diff --git a/runtime/typescript/packages/core/tests/model/tracing/trace-file.test.ts b/runtime/typescript/packages/core/tests/model/tracing/trace-file.test.ts index 9aaa2d26..ac1fa22c 100644 --- a/runtime/typescript/packages/core/tests/model/tracing/trace-file.test.ts +++ b/runtime/typescript/packages/core/tests/model/tracing/trace-file.test.ts @@ -1,3 +1,4 @@ +// // Copyright (c) Microsoft. All rights reserved. // WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. diff --git a/runtime/typescript/packages/core/tests/model/tracing/trace-span.test.ts b/runtime/typescript/packages/core/tests/model/tracing/trace-span.test.ts index 0a5c0fdb..572a778d 100644 --- a/runtime/typescript/packages/core/tests/model/tracing/trace-span.test.ts +++ b/runtime/typescript/packages/core/tests/model/tracing/trace-span.test.ts @@ -1,3 +1,4 @@ +// // Copyright (c) Microsoft. All rights reserved. // WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. diff --git a/runtime/typescript/packages/core/tests/model/tracing/trace-time.test.ts b/runtime/typescript/packages/core/tests/model/tracing/trace-time.test.ts index 2ba66ff9..12b14fd8 100644 --- a/runtime/typescript/packages/core/tests/model/tracing/trace-time.test.ts +++ b/runtime/typescript/packages/core/tests/model/tracing/trace-time.test.ts @@ -1,3 +1,4 @@ +// // Copyright (c) Microsoft. All rights reserved. // WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. diff --git a/runtime/typescript/packages/core/tests/model/wire/anthropic-image-block.test.ts b/runtime/typescript/packages/core/tests/model/wire/anthropic-image-block.test.ts index 22c37f56..55ae9668 100644 --- a/runtime/typescript/packages/core/tests/model/wire/anthropic-image-block.test.ts +++ b/runtime/typescript/packages/core/tests/model/wire/anthropic-image-block.test.ts @@ -1,3 +1,4 @@ +// // Copyright (c) Microsoft. All rights reserved. // WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. diff --git a/runtime/typescript/packages/core/tests/model/wire/anthropic-image-source.test.ts b/runtime/typescript/packages/core/tests/model/wire/anthropic-image-source.test.ts index c1a3f877..1e66ebdf 100644 --- a/runtime/typescript/packages/core/tests/model/wire/anthropic-image-source.test.ts +++ b/runtime/typescript/packages/core/tests/model/wire/anthropic-image-source.test.ts @@ -1,3 +1,4 @@ +// // Copyright (c) Microsoft. All rights reserved. // WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. diff --git a/runtime/typescript/packages/core/tests/model/wire/anthropic-messages-request.test.ts b/runtime/typescript/packages/core/tests/model/wire/anthropic-messages-request.test.ts index 1bd34776..79e8cf8e 100644 --- a/runtime/typescript/packages/core/tests/model/wire/anthropic-messages-request.test.ts +++ b/runtime/typescript/packages/core/tests/model/wire/anthropic-messages-request.test.ts @@ -1,3 +1,4 @@ +// // Copyright (c) Microsoft. All rights reserved. // WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. diff --git a/runtime/typescript/packages/core/tests/model/wire/anthropic-messages-response.test.ts b/runtime/typescript/packages/core/tests/model/wire/anthropic-messages-response.test.ts index 4a5522cd..dad0b6df 100644 --- a/runtime/typescript/packages/core/tests/model/wire/anthropic-messages-response.test.ts +++ b/runtime/typescript/packages/core/tests/model/wire/anthropic-messages-response.test.ts @@ -1,3 +1,4 @@ +// // Copyright (c) Microsoft. All rights reserved. // WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. diff --git a/runtime/typescript/packages/core/tests/model/wire/anthropic-text-block.test.ts b/runtime/typescript/packages/core/tests/model/wire/anthropic-text-block.test.ts index f15860ef..3f391cbc 100644 --- a/runtime/typescript/packages/core/tests/model/wire/anthropic-text-block.test.ts +++ b/runtime/typescript/packages/core/tests/model/wire/anthropic-text-block.test.ts @@ -1,3 +1,4 @@ +// // Copyright (c) Microsoft. All rights reserved. // WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. diff --git a/runtime/typescript/packages/core/tests/model/wire/anthropic-tool-definition.test.ts b/runtime/typescript/packages/core/tests/model/wire/anthropic-tool-definition.test.ts index b118e7c5..6b9de077 100644 --- a/runtime/typescript/packages/core/tests/model/wire/anthropic-tool-definition.test.ts +++ b/runtime/typescript/packages/core/tests/model/wire/anthropic-tool-definition.test.ts @@ -1,3 +1,4 @@ +// // Copyright (c) Microsoft. All rights reserved. // WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. diff --git a/runtime/typescript/packages/core/tests/model/wire/anthropic-tool-result-block.test.ts b/runtime/typescript/packages/core/tests/model/wire/anthropic-tool-result-block.test.ts index 9d3f0019..cd1c0100 100644 --- a/runtime/typescript/packages/core/tests/model/wire/anthropic-tool-result-block.test.ts +++ b/runtime/typescript/packages/core/tests/model/wire/anthropic-tool-result-block.test.ts @@ -1,3 +1,4 @@ +// // Copyright (c) Microsoft. All rights reserved. // WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. diff --git a/runtime/typescript/packages/core/tests/model/wire/anthropic-tool-use-block.test.ts b/runtime/typescript/packages/core/tests/model/wire/anthropic-tool-use-block.test.ts index c1afe495..cfc33b47 100644 --- a/runtime/typescript/packages/core/tests/model/wire/anthropic-tool-use-block.test.ts +++ b/runtime/typescript/packages/core/tests/model/wire/anthropic-tool-use-block.test.ts @@ -1,3 +1,4 @@ +// // Copyright (c) Microsoft. All rights reserved. // WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. diff --git a/runtime/typescript/packages/core/tests/model/wire/anthropic-usage.test.ts b/runtime/typescript/packages/core/tests/model/wire/anthropic-usage.test.ts index 1f66ac2f..139b7c41 100644 --- a/runtime/typescript/packages/core/tests/model/wire/anthropic-usage.test.ts +++ b/runtime/typescript/packages/core/tests/model/wire/anthropic-usage.test.ts @@ -1,3 +1,4 @@ +// // Copyright (c) Microsoft. All rights reserved. // WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. diff --git a/runtime/typescript/packages/core/tests/model/wire/anthropic-wire-message.test.ts b/runtime/typescript/packages/core/tests/model/wire/anthropic-wire-message.test.ts index 8b6afac9..41cf7807 100644 --- a/runtime/typescript/packages/core/tests/model/wire/anthropic-wire-message.test.ts +++ b/runtime/typescript/packages/core/tests/model/wire/anthropic-wire-message.test.ts @@ -1,3 +1,4 @@ +// // Copyright (c) Microsoft. All rights reserved. // WARNING: This is an auto-generated file. DO NOT EDIT THIS FILE DIRECTLY. diff --git a/schema/emitter/src/languages/csharp/emitter.ts b/schema/emitter/src/languages/csharp/emitter.ts index 54885174..8440dcce 100644 --- a/schema/emitter/src/languages/csharp/emitter.ts +++ b/schema/emitter/src/languages/csharp/emitter.ts @@ -158,7 +158,7 @@ export function emitCSharpEnum(enumDef: EnumDef, namespace: string): string { lines.push("{"); for (const value of enumDef.values) { - const memberName = value.charAt(0).toUpperCase() + value.slice(1); + const memberName = toPascalCase(value); // Add EnumMember attribute if the wire value doesn't match the member name exactly lines.push(` [JsonPropertyName("${value}")]`); lines.push(` ${memberName},`); @@ -226,14 +226,14 @@ function emitCSharpInterface(type: TypeDecl, namespace: string, lines: string[]) // Synchronous method if (method.optional) { // Return type already includes nullability — provide default body - lines.push(` ${ret} ${toPascalCase(method.name)}(${params}) => default;`); + lines.push(` ${ret} ${toPascalCase(method.name)}(${params}) => default!;`); } else { lines.push(` ${ret} ${toPascalCase(method.name)}(${params});`); } } else { // Async method if (method.optional) { - lines.push(` Task<${ret}> ${toPascalCase(method.name)}Async(${params}) => Task.FromResult<${ret}>(default);`); + lines.push(` Task<${ret}> ${toPascalCase(method.name)}Async(${params}) => Task.FromResult<${ret}>(default!);`); } else { lines.push(` Task<${ret}> ${toPascalCase(method.name)}Async(${params});`); } @@ -273,7 +273,7 @@ function emitXmlDocComment(description: string, indent: string, lines: string[]) lines.push(`${indent}/// ${descLines[i]}`); // Add empty /// line between paragraphs (if multiple lines and not the last) if (descLines.length > 1 && i < descLines.length - 1) { - lines.push(`${indent}/// `); + lines.push(`${indent}///`); } } lines.push(`${indent}/// `); @@ -873,7 +873,8 @@ function emitSaveAssignment(assign: SaveAssignment, lines: string[]): void { function getSaveExpression(assign: SaveAssignment, propName: string): string { // Named enum — serialize as string (skip open enums — they're already string) if (assign.enumName && !assign.isOpenEnum) { - return `result["${assign.targetName}"] = obj.${propName}.ToString().ToLowerInvariant();`; + const valueExpr = assign.isOptional ? `obj.${propName}.Value` : `obj.${propName}`; + return `result["${assign.targetName}"] = ${valueExpr}.ToString().ToLowerInvariant();`; } const cat = assign.category; switch (cat.kind) { diff --git a/schema/emitter/src/languages/python/test-emitter.ts b/schema/emitter/src/languages/python/test-emitter.ts index e8b6c944..e6a3c945 100644 --- a/schema/emitter/src/languages/python/test-emitter.ts +++ b/schema/emitter/src/languages/python/test-emitter.ts @@ -280,8 +280,8 @@ export function emitPythonTest(ctx: BaseTestContext & { classCtx: PythonClassCon for (let i = 0; i < examples.length; i++) { const sample = examples[i]; const suffix = i === 0 ? '' : `_${i}`; - const jsonBlock = sample.json.map(line => ` ${line}`).join('\n'); - const yamlBlock = sample.yaml.map(line => ` ${line}`).join('\n'); + const jsonBlock = sample.json.map(line => line.length > 0 ? ` ${line}` : '').join('\n'); + const yamlBlock = sample.yaml.map(line => line.length > 0 ? ` ${line}` : '').join('\n'); // test_load_json lines.push(`def test_load_json_${typeNameLower}${suffix}():`); diff --git a/schema/emitter/src/languages/rust/driver.ts b/schema/emitter/src/languages/rust/driver.ts index b0ade465..eeec574f 100644 --- a/schema/emitter/src/languages/rust/driver.ts +++ b/schema/emitter/src/languages/rust/driver.ts @@ -1,6 +1,6 @@ import { EmitContext, emitFile, resolvePath } from "@typespec/compiler"; import { execFileSync } from "child_process"; -import { existsSync, readdirSync, statSync, unlinkSync } from "fs"; +import { existsSync, readFileSync, readdirSync, statSync, unlinkSync, writeFileSync } from "fs"; import { resolve } from "path"; import { EmitTarget, PromptyEmitterOptions } from "../../lib.js"; import { @@ -212,12 +212,29 @@ function formatRustFiles(outputDir: string): void { stdio: 'pipe', encoding: 'utf-8' }); + normalizeRustFileEndings(resolve(outputDir, '..')); } catch (error) { console.warn(`Warning: cargo fmt failed. You may need to install Rust.`); } } } +function normalizeRustFileEndings(dir: string): void { + for (const entry of readdirSync(dir)) { + const fullPath = resolve(dir, entry); + const stat = statSync(fullPath); + if (stat.isDirectory()) { + normalizeRustFileEndings(fullPath); + continue; + } + if (!entry.endsWith(".rs")) { + continue; + } + const content = readFileSync(fullPath, "utf-8"); + writeFileSync(fullPath, `${content.trimEnd()}\n`, "utf-8"); + } +} + /** * Build context for rendering a test file. */ @@ -238,7 +255,7 @@ async function emitRustFile( const filePath = resolvePath(outputDir, filename); await emitFile(context.program, { path: filePath, - content, + content: `${content.trimEnd()}\n`, }); } @@ -435,7 +452,7 @@ function emitRustLib(rootModules: string[], groups: string[] = []): string { for (const group of groups) { out += `\npub mod ${group};\npub use ${group}::*;\n`; } - return out; + return `${out.trimEnd()}\n`; } /** diff --git a/schema/emitter/src/languages/rust/emitter.ts b/schema/emitter/src/languages/rust/emitter.ts index 4e5e9252..0db37eaf 100644 --- a/schema/emitter/src/languages/rust/emitter.ts +++ b/schema/emitter/src/languages/rust/emitter.ts @@ -62,6 +62,13 @@ function emitDocComment(description: string, indent: string, lines: string[]): v lines.push(`${indent}/// ${oneLine}`); } +function renderLines(lines: string[]): string { + while (lines.length > 0 && lines[lines.length - 1] === "") { + lines.pop(); + } + return `${lines.join("\n")}\n`; +} + /** * Convert a string literal value to a PascalCase Rust variant name. * e.g., "system" → "System", "tool" → "Tool", "text" → "Text" @@ -267,7 +274,7 @@ export function emitRustFile( if (baseType.isProtocol) { emitProtocolTrait(baseType, lines); lines.push(""); - return lines.join("\n"); + return renderLines(lines); } // Collect base field names for variant field extraction @@ -293,7 +300,7 @@ export function emitRustFile( } lines.push(""); - return lines.join("\n"); + return renderLines(lines); } // ============================================================================ diff --git a/schema/model/agent/agent.tsp b/schema/model/agent/agent.tsp index 4c226a97..0a7094e1 100644 --- a/schema/model/agent/agent.tsp +++ b/schema/model/agent/agent.tsp @@ -1,4 +1,4 @@ -import "prompty-emitter"; +import "@typra/emitter"; import "@typespec/json-schema"; import "../core/core.tsp"; import "../model/model.tsp"; diff --git a/schema/model/agent/guardrails.tsp b/schema/model/agent/guardrails.tsp index 650f34a0..e8a6c577 100644 --- a/schema/model/agent/guardrails.tsp +++ b/schema/model/agent/guardrails.tsp @@ -1,4 +1,4 @@ -import "prompty-emitter"; +import "@typra/emitter"; namespace Prompty; diff --git a/schema/model/connection/connection.tsp b/schema/model/connection/connection.tsp index 9358721e..e7489808 100644 --- a/schema/model/connection/connection.tsp +++ b/schema/model/connection/connection.tsp @@ -1,4 +1,4 @@ -import "prompty-emitter"; +import "@typra/emitter"; import "../core/core.tsp"; namespace Prompty; diff --git a/schema/model/connection/foundry.tsp b/schema/model/connection/foundry.tsp index 43b8fcba..f05c8800 100644 --- a/schema/model/connection/foundry.tsp +++ b/schema/model/connection/foundry.tsp @@ -1,4 +1,4 @@ -import "prompty-emitter"; +import "@typra/emitter"; import "./connection.tsp"; namespace Prompty; diff --git a/schema/model/connection/oauth.tsp b/schema/model/connection/oauth.tsp index 366381f4..5551eac2 100644 --- a/schema/model/connection/oauth.tsp +++ b/schema/model/connection/oauth.tsp @@ -1,4 +1,4 @@ -import "prompty-emitter"; +import "@typra/emitter"; import "./connection.tsp"; namespace Prompty; diff --git a/schema/model/conversation/content.tsp b/schema/model/conversation/content.tsp index c339efcb..fd0cb6d3 100644 --- a/schema/model/conversation/content.tsp +++ b/schema/model/conversation/content.tsp @@ -1,4 +1,4 @@ -import "prompty-emitter"; +import "@typra/emitter"; namespace Prompty; diff --git a/schema/model/conversation/message.tsp b/schema/model/conversation/message.tsp index 99cca2af..ae8fdf08 100644 --- a/schema/model/conversation/message.tsp +++ b/schema/model/conversation/message.tsp @@ -1,4 +1,4 @@ -import "prompty-emitter"; +import "@typra/emitter"; import "./content.tsp"; namespace Prompty; diff --git a/schema/model/conversation/thread.tsp b/schema/model/conversation/thread.tsp index 53b8bc67..f9f536e9 100644 --- a/schema/model/conversation/thread.tsp +++ b/schema/model/conversation/thread.tsp @@ -1,4 +1,4 @@ -import "prompty-emitter"; +import "@typra/emitter"; namespace Prompty; diff --git a/schema/model/conversation/tool-invocation.tsp b/schema/model/conversation/tool-invocation.tsp index fb586ef0..b8cad53d 100644 --- a/schema/model/conversation/tool-invocation.tsp +++ b/schema/model/conversation/tool-invocation.tsp @@ -1,8 +1,15 @@ -import "prompty-emitter"; +import "@typra/emitter"; import "./content.tsp"; namespace Prompty; +/** + * Normalized status for tool execution. The transport can complete while the + * tool itself fails, times out, or is cancelled; status captures the semantic + * outcome independently of callback delivery. + */ +alias ToolResultStatus = "success" | "error" | "cancelled" | "timeout"; + /** * A tool call requested by the LLM. Contains the function name and serialized * arguments that should be dispatched to the appropriate tool handler. @@ -38,4 +45,19 @@ model ToolResult { @doc("The content parts of the tool result") @sample(#{ parts: #[#{ kind: "text", value: "72°F and sunny" }] }) parts: ContentPart[]; + + @doc("Semantic execution status for the tool result") + status?: ToolResultStatus; + + @doc("Stable machine-readable error category when status is not success") + @sample(#{ errorKind: "missing_tool" }) + errorKind?: string; + + @doc("Human-readable error message when status is not success") + @sample(#{ errorMessage: "Tool 'get_weather' is not registered" }) + errorMessage?: string; + + @doc("Tool execution duration in milliseconds") + @sample(#{ durationMs: 42 }) + durationMs?: float64; } diff --git a/schema/model/core/core.tsp b/schema/model/core/core.tsp index e52bf1ec..a595ec16 100644 --- a/schema/model/core/core.tsp +++ b/schema/model/core/core.tsp @@ -1,4 +1,4 @@ -import "prompty-emitter"; +import "@typra/emitter"; namespace Prompty; diff --git a/schema/model/core/errors.tsp b/schema/model/core/errors.tsp index 9d8a552a..ef9bbbd3 100644 --- a/schema/model/core/errors.tsp +++ b/schema/model/core/errors.tsp @@ -1,4 +1,4 @@ -import "prompty-emitter"; +import "@typra/emitter"; namespace Prompty; diff --git a/schema/model/core/properties.tsp b/schema/model/core/properties.tsp index f15f084b..01664da3 100644 --- a/schema/model/core/properties.tsp +++ b/schema/model/core/properties.tsp @@ -1,4 +1,4 @@ -import "prompty-emitter"; +import "@typra/emitter"; import "./core.tsp"; namespace Prompty; diff --git a/schema/model/core/validation.tsp b/schema/model/core/validation.tsp index 1339bb30..8d89ee37 100644 --- a/schema/model/core/validation.tsp +++ b/schema/model/core/validation.tsp @@ -1,4 +1,4 @@ -import "prompty-emitter"; +import "@typra/emitter"; import "./errors.tsp"; namespace Prompty; diff --git a/schema/model/events/payloads.tsp b/schema/model/events/payloads.tsp index d044524f..ef113549 100644 --- a/schema/model/events/payloads.tsp +++ b/schema/model/events/payloads.tsp @@ -1,6 +1,7 @@ -import "prompty-emitter"; +import "@typra/emitter"; import "../conversation/message.tsp"; import "../conversation/tool-invocation.tsp"; +import "../model/usage.tsp"; namespace Prompty; @@ -9,11 +10,23 @@ namespace Prompty; * real-time UIs, logging, and coordination without coupling the loop * to any particular output mechanism. */ -alias AgentEventType = +alias 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" @@ -23,6 +36,287 @@ alias AgentEventType = | "compaction_complete" | "compaction_failed"; +/** + * Final semantic status for a turn. + */ +alias TurnStatus = "success" | "error" | "cancelled"; + +/** + * Scope where a host lifecycle hook is starting. + */ +alias HookStartScope = "turn" | "session"; + +/** + * Scope where a host lifecycle hook finished. + * + * This intentionally mirrors HookStartScope but remains distinct because some + * language generators emit enum aliases in each model file. + */ +alias HookEndScope = "turn" | "session"; + +/** + * A canonical event envelope emitted by the turn harness. The payload is kept + * JSON-shaped so runtimes can load all events even when newer payload types are + * added; event-specific typed payload models below define the canonical shapes. + */ +model TurnEvent { + @doc("Unique identifier for this event") + @sample(#{ id: "evt_abc123" }) + id: string; + + @doc("Event type discriminator") + type: TurnEventType; + + @doc("ISO 8601 UTC timestamp when the event was emitted") + @sample(#{ timestamp: "2026-06-09T20:00:00Z" }) + timestamp: string; + + @doc("Stable identifier for the outer turn") + @sample(#{ turnId: "turn_001" }) + turnId?: string; + + @doc("Zero-based agent-loop iteration associated with the event") + @sample(#{ iteration: 0 }) + iteration?: int32; + + @doc("Parent event or span identifier for reconstructing event hierarchy") + @sample(#{ parentId: "evt_parent" }) + parentId?: string; + + @doc("Trace span identifier associated with this event") + @sample(#{ spanId: "span_tool_001" }) + spanId?: string; + + @doc("Event-specific payload. Use the typed payload model matching 'type'.") + payload: Record = #{}; +} + +/** + * Payload for "turn_start" events — a turn is beginning. + */ +model TurnStartPayload { + @doc("Name of the loaded prompt/agent, when available") + @sample(#{ agent: "weather-agent" }) + agent?: string; + + @doc("Input values supplied to the turn after host-side sanitization") + inputs?: Record; + + @doc("Configured maximum tool-call iterations") + @sample(#{ maxIterations: 10 }) + maxIterations?: int32; +} + +/** + * Payload for "turn_end" events — a turn has completed. + */ +model TurnEndPayload { + @doc("Number of tool-call iterations performed") + @sample(#{ iterations: 2 }) + iterations?: int32; + + @doc("Final semantic status of the turn") + status?: TurnStatus; + + @doc("Final response after processing, if available") + response?: unknown; + + @doc("Total elapsed turn duration in milliseconds") + @sample(#{ durationMs: 1500 }) + durationMs?: float64; +} + +/** + * Payload for "llm_start" events — an LLM request is about to be sent. + */ +model LlmStartPayload { + @doc("Provider identifier used for the request") + @sample(#{ provider: "openai" }) + provider?: string; + + @doc("Model or deployment identifier used for the request") + @sample(#{ modelId: "gpt-4o-mini" }) + modelId?: string; + + @doc("Number of messages sent to the provider") + @sample(#{ messageCount: 4 }) + messageCount?: int32; + + @doc("Retry attempt number, zero for the initial attempt") + @sample(#{ attempt: 0 }) + attempt?: int32; +} + +/** + * Payload for "llm_complete" events — an LLM request completed. + */ +model LlmCompletePayload { + @doc("Provider request identifier, when supplied by the SDK/API") + @sample(#{ requestId: "req_abc123" }) + requestId?: string; + + @doc("Service request identifier, when supplied by the SDK/API") + @sample(#{ serviceRequestId: "srv_abc123" }) + serviceRequestId?: string; + + @doc("Token usage reported by the provider") + usage?: TokenUsage; + + @doc("LLM call duration in milliseconds") + @sample(#{ durationMs: 820 }) + durationMs?: float64; +} + +/** + * Payload for "retry" events — a transient operation will be retried. + */ +model RetryPayload { + @doc("Operation being retried") + @sample(#{ operation: "llm" }) + operation: string; + + @doc("Attempt number about to run") + @sample(#{ attempt: 2 }) + attempt: int32; + + @doc("Maximum configured attempts") + @sample(#{ maxAttempts: 3 }) + maxAttempts?: int32; + + @doc("Backoff delay before the next attempt in milliseconds") + @sample(#{ delayMs: 1250 }) + delayMs?: float64; + + @doc("Reason for the retry") + @sample(#{ reason: "rate_limit" }) + reason?: string; +} + +/** + * Payload for permission request events — a host is asked to approve an action. + */ +model PermissionRequestedPayload { + @doc("Stable permission request identifier") + @sample(#{ requestId: "perm_abc123" }) + requestId?: string; + + @doc("Associated tool call identifier, when the permission gates a tool call") + @sample(#{ toolCallId: "call_abc123" }) + toolCallId?: string; + + @doc("Permission/action name being requested") + @sample(#{ permission: "tool.execute" }) + permission: string; + + @doc("Resource or tool the permission applies to") + @sample(#{ target: "shell" }) + target?: string; + + @doc("Additional host-specific permission details") + details?: Record; + + @doc("Human-readable prompt or rationale that can be shown to an approval UI") + @sample(#{ promptRequest: "Allow shell to run tests?" }) + promptRequest?: string; + + @doc("Policy metadata used to evaluate or explain the permission request") + policy?: Record; + + @doc("Redaction state for sensitive request fields") + redaction?: RedactionMetadata; +} + +/** + * Payload for permission completion events — an approval decision was made. + */ +model PermissionCompletedPayload { + @doc("Stable permission request identifier") + @sample(#{ requestId: "perm_abc123" }) + requestId?: string; + + @doc("Associated tool call identifier, when the permission gated a tool call") + @sample(#{ toolCallId: "call_abc123" }) + toolCallId?: string; + + @doc("Permission/action name that was decided") + @sample(#{ permission: "tool.execute" }) + permission: string; + + @doc("Whether the requested permission was approved") + @sample(#{ approved: true }) + approved: boolean; + + @doc("Decision reason, if available") + @sample(#{ reason: "user_approved" }) + reason?: string; + + @doc("Host-specific decision result, such as a durable approval token or denial details") + result?: Record; + + @doc("Redaction state for sensitive decision fields") + redaction?: RedactionMetadata; +} + +/** + * Request passed to a permission resolver. This is the live protocol shape; the + * event payloads above can include trace-only metadata such as redaction state. + */ +model PermissionRequest { + @doc("Stable permission request identifier") + @sample(#{ requestId: "perm_abc123" }) + requestId?: string; + + @doc("Associated tool call identifier, when the permission gates a tool call") + @sample(#{ toolCallId: "call_abc123" }) + toolCallId?: string; + + @doc("Permission/action name being requested") + @sample(#{ permission: "tool.execute" }) + permission: string; + + @doc("Resource or tool the permission applies to") + @sample(#{ target: "shell" }) + target?: string; + + @doc("Additional host-specific permission details") + details?: Record; + + @doc("Human-readable prompt or rationale that can be shown to an approval UI") + @sample(#{ promptRequest: "Allow shell to run tests?" }) + promptRequest?: string; + + @doc("Policy metadata used to evaluate or explain the permission request") + policy?: Record; +} + +/** + * Decision returned by a permission resolver. + */ +model PermissionDecision { + @doc("Stable permission request identifier") + @sample(#{ requestId: "perm_abc123" }) + requestId?: string; + + @doc("Associated tool call identifier, when the permission gated a tool call") + @sample(#{ toolCallId: "call_abc123" }) + toolCallId?: string; + + @doc("Permission/action name that was decided") + @sample(#{ permission: "tool.execute" }) + permission: string; + + @doc("Whether the requested permission was approved") + @sample(#{ approved: true }) + approved: boolean; + + @doc("Decision reason, if available") + @sample(#{ reason: "user_approved" }) + reason?: string; + + @doc("Host-specific decision result, such as a durable approval token or denial details") + result?: Record; +} + /** * Payload for "token" events — a single text token streamed from the LLM. */ @@ -45,6 +339,10 @@ model ThinkingEventPayload { * Payload for "tool_call_start" events — the LLM has requested a tool call. */ model ToolCallStartPayload { + @doc("The unique identifier of the tool call") + @sample(#{ id: "call_abc123" }) + id?: string; + @doc("The name of the tool being called") @sample(#{ name: "get_weather" }) name: string; @@ -54,6 +352,225 @@ model ToolCallStartPayload { arguments: string; } +/** + * Payload for "tool_call_complete" events — a tool dispatch finished. + */ +model ToolCallCompletePayload { + @doc("The unique identifier of the tool call") + @sample(#{ id: "call_abc123" }) + id?: string; + + @doc("The name of the tool that completed") + @sample(#{ name: "get_weather" }) + name: string; + + @doc("Whether the tool dispatch succeeded semantically") + @sample(#{ success: true }) + success: boolean; + + @doc("Normalized tool result") + result?: ToolResult; + + @doc("Tool execution duration in milliseconds") + @sample(#{ durationMs: 42 }) + durationMs?: float64; + + @doc("Machine-readable error category when success is false") + @sample(#{ errorKind: "timeout" }) + errorKind?: string; +} + +/** + * Payload for "tool_execution_start" events — the host is about to execute a concrete tool request. + * + * This is distinct from "tool_call_start", which records the model requesting a tool. + * Tool execution events capture the harness-side action after policy and permission checks. + */ +model ToolExecutionStartPayload { + @doc("Stable host execution request identifier") + @sample(#{ requestId: "exec_abc123" }) + requestId?: string; + + @doc("Associated model tool call identifier, when available") + @sample(#{ toolCallId: "call_abc123" }) + toolCallId?: string; + + @doc("Name of the host tool being executed") + @sample(#{ toolName: "powershell" }) + toolName: string; + + @doc("Tool arguments after host-side sanitization") + arguments?: Record; + + @doc("Working directory or execution scope for the tool") + @sample(#{ workingDirectory: "/workspace/project" }) + workingDirectory?: string; + + @doc("Redaction state for sensitive argument fields") + redaction?: RedactionMetadata; +} + +/** + * Payload for "tool_execution_complete" events — a concrete host tool execution finished. + */ +model ToolExecutionCompletePayload { + @doc("Stable host execution request identifier") + @sample(#{ requestId: "exec_abc123" }) + requestId?: string; + + @doc("Associated model tool call identifier, when available") + @sample(#{ toolCallId: "call_abc123" }) + toolCallId?: string; + + @doc("Name of the host tool that executed") + @sample(#{ toolName: "powershell" }) + toolName: string; + + @doc("Whether the host execution completed successfully") + @sample(#{ success: true }) + success: boolean; + + @doc("Host-normalized execution result") + result?: unknown; + + @doc("Process or host exit code, when applicable") + @sample(#{ exitCode: 0 }) + exitCode?: int32; + + @doc("Tool execution duration in milliseconds") + @sample(#{ durationMs: 250 }) + durationMs?: float64; + + @doc("Machine-readable error category when success is false") + @sample(#{ errorKind: "timeout" }) + errorKind?: string; + + @doc("Host-specific telemetry for the execution") + telemetry?: Record; + + @doc("Redaction state for sensitive result fields") + redaction?: RedactionMetadata; +} + +/** + * Request passed to a host tool executor after policy and permission checks. + */ +model HostToolRequest { + @doc("Stable host execution request identifier") + @sample(#{ requestId: "exec_abc123" }) + requestId?: string; + + @doc("Associated model tool call identifier, when available") + @sample(#{ toolCallId: "call_abc123" }) + toolCallId?: string; + + @doc("Name of the host tool being executed") + @sample(#{ toolName: "powershell" }) + toolName: string; + + @doc("Tool arguments after host-side sanitization") + arguments?: Record; + + @doc("Working directory or execution scope for the tool") + @sample(#{ workingDirectory: "/workspace/project" }) + workingDirectory?: string; +} + +/** + * Result returned by a host tool executor. + */ +model HostToolResult { + @doc("Stable host execution request identifier") + @sample(#{ requestId: "exec_abc123" }) + requestId?: string; + + @doc("Associated model tool call identifier, when available") + @sample(#{ toolCallId: "call_abc123" }) + toolCallId?: string; + + @doc("Name of the host tool that executed") + @sample(#{ toolName: "powershell" }) + toolName: string; + + @doc("Whether the host execution completed successfully") + @sample(#{ success: true }) + success: boolean; + + @doc("Host-normalized execution result") + result?: unknown; + + @doc("Process or host exit code, when applicable") + @sample(#{ exitCode: 0 }) + exitCode?: int32; + + @doc("Tool execution duration in milliseconds") + @sample(#{ durationMs: 250 }) + durationMs?: float64; + + @doc("Machine-readable error category when success is false") + @sample(#{ errorKind: "timeout" }) + errorKind?: string; + + @doc("Host-specific telemetry for the execution") + telemetry?: Record; +} + +/** + * Payload for "hook_start" events — a host lifecycle hook is beginning. + */ +model HookStartPayload { + @doc("Stable hook invocation identifier") + @sample(#{ hookInvocationId: "hook_abc123" }) + hookInvocationId: string; + + @doc("Host-defined hook type") + @sample(#{ hookType: "preToolUse" }) + hookType: string; + + @doc("Whether the hook is scoped to a turn or the outer session") + scope?: HookStartScope; + + @doc("Hook input after host-side sanitization") + input?: Record; + + @doc("Redaction state for sensitive hook input fields") + redaction?: RedactionMetadata; +} + +/** + * Payload for "hook_end" events — a host lifecycle hook finished. + */ +model HookEndPayload { + @doc("Stable hook invocation identifier") + @sample(#{ hookInvocationId: "hook_abc123" }) + hookInvocationId: string; + + @doc("Host-defined hook type") + @sample(#{ hookType: "preToolUse" }) + hookType: string; + + @doc("Whether the hook is scoped to a turn or the outer session") + scope?: HookEndScope; + + @doc("Whether the hook completed successfully") + @sample(#{ success: true }) + success: boolean; + + @doc("Hook output after host-side sanitization") + output?: Record; + + @doc("Hook execution duration in milliseconds") + @sample(#{ durationMs: 12 }) + durationMs?: float64; + + @doc("Human-readable error when success is false") + @sample(#{ error: "hook failed" }) + error?: string; + + @doc("Redaction state for sensitive hook output fields") + redaction?: RedactionMetadata; +} + /** * Payload for "tool_result" events — a tool has returned its result. */ @@ -83,16 +600,26 @@ model StatusEventPayload { */ model MessagesUpdatedPayload { @doc("The current full message list after the update") - messages: Message[]; + messages?: Message[]; + + @doc("Why the message list changed") + @sample(#{ reason: "tool_results" }) + reason?: string; + + @doc("Messages appended by this update, when available") + appended?: Message[]; + + @doc("Number of messages removed by this update, when available") + @sample(#{ removed: 2 }) + removed?: int32; } /** * Payload for "done" events — the agent loop completed successfully. */ model DoneEventPayload { - @doc("The final text response from the LLM") - @sample(#{ response: "The weather in Paris is 72°F and sunny." }) - response: string; + @doc("The final response from the LLM after processing") + response: unknown; @doc("The final conversation state including all messages") messages: Message[]; @@ -105,6 +632,23 @@ model ErrorEventPayload { @doc("Human-readable error description") @sample(#{ message: "Rate limit exceeded" }) message: string; + + @doc("Stable machine-readable error category") + @sample(#{ errorKind: "rate_limit" }) + errorKind?: string; + + @doc("Operation or phase where the error occurred") + @sample(#{ phase: "llm" }) + phase?: string; +} + +/** + * Payload for "compaction_start" events — context compaction is beginning. + */ +model CompactionStartPayload { + @doc("Number of messages selected for compaction") + @sample(#{ droppedCount: 5 }) + droppedCount: int32; } /** @@ -118,6 +662,10 @@ model CompactionCompletePayload { @doc("Number of messages remaining after compaction") @sample(#{ remaining: 3 }) remaining: int32; + + @doc("Length of the generated summary, when a summarization strategy is used") + @sample(#{ summaryLength: 1200 }) + summaryLength?: int32; } /** @@ -128,3 +676,100 @@ model CompactionFailedPayload { @sample(#{ message: "Summarization prompt exceeded context window" }) message: string; } + +/** + * Summary statistics for a completed turn trace. + */ +model TurnSummary { + @doc("Stable identifier for the outer turn") + @sample(#{ turnId: "turn_001" }) + turnId: string; + + @doc("Final turn status: 'success', 'error', or 'cancelled'") + @sample(#{ status: "success" }) + status: string; + + @doc("Number of agent-loop iterations") + @sample(#{ iterations: 2 }) + iterations: int32; + + @doc("Number of LLM calls made during the turn") + @sample(#{ llmCalls: 3 }) + llmCalls?: int32; + + @doc("Number of tool calls dispatched during the turn") + @sample(#{ toolCalls: 2 }) + toolCalls?: int32; + + @doc("Number of retry events during the turn") + @sample(#{ retries: 1 }) + retries?: int32; + + @doc("Aggregated token usage for the turn") + usage?: TokenUsage; + + @doc("Total elapsed turn duration in milliseconds") + @sample(#{ durationMs: 2500 }) + durationMs?: float64; +} + +/** + * Portable JSONL/replay container for a recorded turn harness run. + */ +model TurnTrace { + @doc("Trace schema version") + @sample(#{ version: "1" }) + version: string = "1"; + + @doc("Runtime name that produced the trace") + @sample(#{ runtime: "typescript" }) + runtime?: string; + + @doc("Prompty library version that produced the trace") + @sample(#{ promptyVersion: "2.0.0" }) + promptyVersion?: string; + + @doc("Recorded turn events in emission order") + events: TurnEvent[]; + + @doc("Optional summary computed from the event stream") + summary?: TurnSummary; +} + +/** + * How a sensitive field was handled before persistence or emission. + */ +alias RedactionMode = "none" | "redacted" | "hashed" | "summary" | "reference"; + +/** + * Redaction handling for one JSON-shaped field path. + */ +model RedactedField { + @doc("JSONPath-like field path, relative to the containing payload") + @sample(#{ path: "$.arguments.apiKey" }) + path: string; + + @doc("How the field was represented") + @sample(#{ mode: "redacted" }) + mode: RedactionMode; + + @doc("Human-readable reason or policy that caused this handling") + @sample(#{ reason: "secret" }) + reason?: string; +} + +/** + * Metadata describing whether and how a payload was sanitized. + */ +model RedactionMetadata { + @doc("Whether the payload has been sanitized for persistence or external display") + @sample(#{ sanitized: true }) + sanitized?: boolean = false; + + @doc("Field-level redaction details") + fields?: RedactedField[]; + + @doc("Host policy or sanitizer version that produced this metadata") + @sample(#{ policy: "default-v1" }) + policy?: string; +} diff --git a/schema/model/events/session.tsp b/schema/model/events/session.tsp new file mode 100644 index 00000000..c502d968 --- /dev/null +++ b/schema/model/events/session.tsp @@ -0,0 +1,368 @@ +import "@typra/emitter"; +import "../model/usage.tsp"; +import "./payloads.tsp"; + +namespace Prompty; + +/** + * The type of event emitted by an outer agent harness session. Session events + * capture lifecycle and persistence concerns that sit below any particular UI + * but above a single model turn. + */ +alias SessionEventType = + | "session_start" + | "session_end" + | "session_warning" + | "session_hook_start" + | "session_hook_end" + | "checkpoint_created" + | "trajectory_event"; + +/** + * Final semantic status for an outer harness session end event. + */ +alias SessionEndStatus = "success" | "error" | "cancelled" | "interrupted"; + +/** + * Final semantic status for a summarized harness session. + * + * This intentionally mirrors SessionEndStatus but remains a distinct alias + * because some language generators emit enum aliases in each model file. + */ +alias SessionSummaryStatus = "success" | "error" | "cancelled" | "interrupted"; + +/** + * Execution context associated with a harness session. Host-specific + * environments can store detailed profiles in metadata without making the core + * contract depend on one source-control provider or workspace shape. + */ +model HarnessContext { + @doc("Current working directory for the harness") + @sample(#{ cwd: "/workspace/project" }) + cwd?: string; + + @doc("Git repository root, when known") + @sample(#{ gitRoot: "/workspace/project" }) + gitRoot?: string; + + @doc("Host-defined context metadata, such as source-control or sandbox details") + metadata?: Record; +} + +/** + * Payload for "session_start" events. + */ +model SessionStartPayload { + @doc("Stable session identifier") + @sample(#{ sessionId: "sess_abc123" }) + sessionId: string; + + @doc("Session event schema version") + @sample(#{ schemaVersion: "1" }) + schemaVersion?: string = "1"; + + @doc("Producer that started the session") + @sample(#{ producer: "prompty-agent" }) + producer?: string; + + @doc("Runtime that produced the session") + @sample(#{ runtime: "typescript" }) + runtime?: string; + + @doc("Prompty library version") + @sample(#{ promptyVersion: "2.0.0" }) + promptyVersion?: string; + + @doc("ISO 8601 UTC timestamp when the session started") + @sample(#{ startTime: "2026-06-09T20:00:00Z" }) + startTime?: string; + + @doc("Selected model identifier, when known") + @sample(#{ selectedModel: "gpt-4o-mini" }) + selectedModel?: string; + + @doc("Selected reasoning effort or equivalent model setting, when known") + @sample(#{ reasoningEffort: "medium" }) + reasoningEffort?: string; + + @doc("Repository and execution context") + context?: HarnessContext; +} + +/** + * Payload for "session_end" events. + */ +model SessionEndPayload { + @doc("Stable session identifier") + @sample(#{ sessionId: "sess_abc123" }) + sessionId?: string; + + @doc("Final session status") + @sample(#{ status: "success" }) + status?: SessionEndStatus; + + @doc("Host-specific reason the session ended") + @sample(#{ reason: "complete" }) + reason?: string; + + @doc("Total elapsed session duration in milliseconds") + @sample(#{ durationMs: 12500 }) + durationMs?: float64; +} + +/** + * Payload for "session_warning" events. + */ +model SessionWarningPayload { + @doc("Stable machine-readable warning category") + @sample(#{ warningType: "remote" }) + warningType: string; + + @doc("Human-readable warning message") + @sample(#{ message: "Remote session disabled" }) + message: string; + + @doc("Additional host-specific warning details") + details?: Record; +} + +/** + * A canonical event envelope emitted by an outer harness session. + */ +model SessionEvent { + @doc("Unique identifier for this event") + @sample(#{ id: "evt_abc123" }) + id: string; + + @doc("Event type discriminator") + type: SessionEventType; + + @doc("ISO 8601 UTC timestamp when the event was emitted") + @sample(#{ timestamp: "2026-06-09T20:00:00Z" }) + timestamp: string; + + @doc("Stable identifier for the outer session") + @sample(#{ sessionId: "sess_abc123" }) + sessionId?: string; + + @doc("Associated turn identifier, when this session event is linked to a turn") + @sample(#{ turnId: "turn_001" }) + turnId?: string; + + @doc("Parent event or span identifier for reconstructing event hierarchy") + @sample(#{ parentId: "evt_parent" }) + parentId?: string; + + @doc("Trace span identifier associated with this event") + @sample(#{ spanId: "span_hook_001" }) + spanId?: string; + + @doc("Event-specific payload. Use the typed payload model matching 'type'.") + payload: Record = #{}; + + @doc("Redaction state for sensitive payload fields") + redaction?: RedactionMetadata; +} + +/** + * A persisted handoff point for a harness session. + */ +model Checkpoint { + @doc("Stable checkpoint identifier") + @sample(#{ id: "chk_abc123" }) + id?: string; + + @doc("Stable session identifier") + @sample(#{ sessionId: "sess_abc123" }) + sessionId?: string; + + @doc("Associated turn identifier, when the checkpoint was created inside a turn") + @sample(#{ turnId: "turn_001" }) + turnId?: string; + + @doc("Monotonic checkpoint number within the session") + @sample(#{ checkpointNumber: 3 }) + checkpointNumber?: int32; + + @doc("Short checkpoint title") + @sample(#{ title: "Added harness contracts" }) + title: string; + + @doc("Short human-readable overview") + overview?: string; + + @doc("Portable checkpoint state needed to resume or hand off the session") + state?: Record; + + @doc("Optional host-authored summary or handoff note") + summary?: string; + + @doc("Host-defined checkpoint metadata") + metadata?: Record; + + @doc("ISO 8601 UTC timestamp when the checkpoint was created") + @sample(#{ createdAt: "2026-06-09T20:00:00Z" }) + createdAt?: string; + + @doc("Redaction state for sensitive checkpoint fields") + redaction?: RedactionMetadata; +} + +/** + * A compact, replay-oriented record of one harness-side action or observation. + */ +model TrajectoryEvent { + @doc("Stable trajectory event identifier") + @sample(#{ id: "traj_abc123" }) + id?: string; + + @doc("Stable session identifier") + @sample(#{ sessionId: "sess_abc123" }) + sessionId?: string; + + @doc("Associated turn identifier, when available") + @sample(#{ turnId: "turn_001" }) + turnId?: string; + + @doc("Associated tool call identifier, when available") + @sample(#{ toolCallId: "call_abc123" }) + toolCallId?: string; + + @doc("Zero-based turn index in the session") + @sample(#{ turnIndex: 4 }) + turnIndex?: int32; + + @doc("Host-defined trajectory event category") + @sample(#{ eventType: "command" }) + eventType: string; + + @doc("Sanitized event data") + data?: Record; + + @doc("ISO 8601 UTC timestamp when the trajectory event was recorded") + @sample(#{ createdAt: "2026-06-09T20:00:00Z" }) + createdAt?: string; + + @doc("Redaction state for sensitive trajectory fields") + redaction?: RedactionMetadata; +} + +/** + * A file observed or touched by a harness session. + */ +model SessionFileRef { + @doc("Stable session identifier") + @sample(#{ sessionId: "sess_abc123" }) + sessionId?: string; + + @doc("File path, relative to the harness workspace when possible") + @sample(#{ path: "src/index.ts" }) + path: string; + + @doc("Tool that first observed the file, when known") + @sample(#{ toolName: "view" }) + toolName?: string; + + @doc("Zero-based turn index where the file was first observed") + @sample(#{ turnIndex: 2 }) + turnIndex?: int32; + + @doc("ISO 8601 UTC timestamp when the file was first observed") + @sample(#{ firstSeenAt: "2026-06-09T20:00:00Z" }) + firstSeenAt?: string; +} + +/** + * A non-file reference observed by a harness session. + */ +model SessionRef { + @doc("Stable session identifier") + @sample(#{ sessionId: "sess_abc123" }) + sessionId?: string; + + @doc("Reference category") + @sample(#{ refType: "issue" }) + refType: string; + + @doc("Reference value") + @sample(#{ refValue: "owner/repo#123" }) + refValue: string; + + @doc("Zero-based turn index where the reference was first observed") + @sample(#{ turnIndex: 2 }) + turnIndex?: int32; + + @doc("ISO 8601 UTC timestamp when the reference was recorded") + @sample(#{ createdAt: "2026-06-09T20:00:00Z" }) + createdAt?: string; +} + +/** + * Summary statistics for a completed session trace. + */ +model SessionSummary { + @doc("Stable session identifier") + @sample(#{ sessionId: "sess_abc123" }) + sessionId: string; + + @doc("Final session status") + @sample(#{ status: "success" }) + status?: SessionSummaryStatus; + + @doc("Number of user/assistant turns in the session") + @sample(#{ turns: 5 }) + turns?: int32; + + @doc("Number of checkpoints created") + @sample(#{ checkpoints: 2 }) + checkpoints?: int32; + + @doc("Aggregated token usage for the session") + usage?: TokenUsage; + + @doc("Total elapsed session duration in milliseconds") + @sample(#{ durationMs: 12500 }) + durationMs?: float64; +} + +/** + * Portable replay container for an outer harness session. + */ +model SessionTrace { + @doc("Trace schema version") + @sample(#{ version: "1" }) + version: string = "1"; + + @doc("Runtime name that produced the trace") + @sample(#{ runtime: "typescript" }) + runtime?: string; + + @doc("Prompty library version that produced the trace") + @sample(#{ promptyVersion: "2.0.0" }) + promptyVersion?: string; + + @doc("Stable session identifier") + @sample(#{ sessionId: "sess_abc123" }) + sessionId?: string; + + @doc("Recorded session events in emission order") + events: SessionEvent[]; + + @doc("Recorded turn traces associated with the session") + turns?: TurnTrace[]; + + @doc("Checkpoints created during the session") + checkpoints?: Checkpoint[]; + + @doc("Compact trajectory records associated with the session") + trajectory?: TrajectoryEvent[]; + + @doc("Files observed or touched during the session") + files?: SessionFileRef[]; + + @doc("Non-file references observed during the session") + refs?: SessionRef[]; + + @doc("Optional summary computed from the event stream") + summary?: SessionSummary; +} diff --git a/schema/model/events/stream-chunks.tsp b/schema/model/events/stream-chunks.tsp index df018094..ab88b29c 100644 --- a/schema/model/events/stream-chunks.tsp +++ b/schema/model/events/stream-chunks.tsp @@ -1,4 +1,4 @@ -import "prompty-emitter"; +import "@typra/emitter"; import "../conversation/tool-invocation.tsp"; namespace Prompty; diff --git a/schema/model/main.tsp b/schema/model/main.tsp index a10c6ec5..ef2cfefa 100644 --- a/schema/model/main.tsp +++ b/schema/model/main.tsp @@ -24,10 +24,12 @@ import "./pipeline/renderer.tsp"; import "./pipeline/parser.tsp"; import "./pipeline/executor.tsp"; import "./pipeline/processor.tsp"; +import "./pipeline/harness.tsp"; // Events import "./events/payloads.tsp"; import "./events/stream-chunks.tsp"; +import "./events/session.tsp"; // Streaming import "./streaming/stream.tsp"; diff --git a/schema/model/model/discovery.tsp b/schema/model/model/discovery.tsp index e3533401..8fb623f5 100644 --- a/schema/model/model/discovery.tsp +++ b/schema/model/model/discovery.tsp @@ -1,4 +1,4 @@ -import "prompty-emitter"; +import "@typra/emitter"; namespace Prompty; diff --git a/schema/model/model/usage.tsp b/schema/model/model/usage.tsp index 80b73a8f..69d5eefa 100644 --- a/schema/model/model/usage.tsp +++ b/schema/model/model/usage.tsp @@ -1,4 +1,4 @@ -import "prompty-emitter"; +import "@typra/emitter"; namespace Prompty; diff --git a/schema/model/pipeline/executor.tsp b/schema/model/pipeline/executor.tsp index 555fb865..357a09cb 100644 --- a/schema/model/pipeline/executor.tsp +++ b/schema/model/pipeline/executor.tsp @@ -1,4 +1,4 @@ -import "prompty-emitter"; +import "@typra/emitter"; import "../agent/agent.tsp"; import "../conversation/message.tsp"; import "../conversation/tool-invocation.tsp"; diff --git a/schema/model/pipeline/harness.tsp b/schema/model/pipeline/harness.tsp new file mode 100644 index 00000000..6d9066ad --- /dev/null +++ b/schema/model/pipeline/harness.tsp @@ -0,0 +1,100 @@ +import "@typra/emitter"; +import "../events/payloads.tsp"; +import "../events/session.tsp"; + +namespace Prompty; + +@@protocol(EventSink); +@@method(EventSink, + "emitTurn", + "boolean", + "Emit a typed turn event to a host sink", + #{ turnEvent: "TurnEvent" }, + false, + true +); +@@method(EventSink, + "emitSession", + "boolean", + "Emit a typed session event to a host sink", + #{ sessionEvent: "SessionEvent" }, + false, + true +); + +/** Receives typed turn and session events from a harness. */ +model EventSink {} + +@@protocol(EventJournalWriter); +@@method(EventJournalWriter, + "appendTurn", + "boolean", + "Append a turn event to a durable replay journal", + #{ turnEvent: "TurnEvent" }, + false, + true +); +@@method(EventJournalWriter, + "appendSession", + "boolean", + "Append a session event to a durable replay journal", + #{ sessionEvent: "SessionEvent" }, + false, + true +); +@@method(EventJournalWriter, + "close", + "boolean", + "Finalize the journal with an optional session summary", + #{ summary: "SessionSummary?" }, + false, + true +); + +/** Persists typed events to a durable replay journal. */ +model EventJournalWriter {} + +@@protocol(PermissionResolver); +@@method(PermissionResolver, + "request", + "PermissionDecision", + "Resolve a host permission request", + #{ request: "PermissionRequest" } +); + +/** Resolves host permission requests for potentially sensitive actions. */ +model PermissionResolver {} + +@@protocol(CheckpointStore); +@@method(CheckpointStore, + "save", + "Checkpoint", + "Persist a session checkpoint and return the stored checkpoint", + #{ checkpoint: "Checkpoint" } +); +@@method(CheckpointStore, + "load", + "Checkpoint?", + "Load a checkpoint by session and checkpoint identifier", + #{ sessionId: "string", checkpointId: "string" } +); +@@method(CheckpointStore, + "listCheckpoints", + "Checkpoint[]", + "List checkpoints for a session", + #{ sessionId: "string" } +); + +/** Stores and retrieves resumable session checkpoints. */ +model CheckpointStore {} + +@@protocol(HostToolExecutor); +@@method(HostToolExecutor, + "execute", + "HostToolResult", + "Execute a concrete host tool request and return its completion payload", + #{ request: "HostToolRequest" } +); + +/** Executes host tools after policy and permission checks. */ +model HostToolExecutor {} diff --git a/schema/model/pipeline/parser.tsp b/schema/model/pipeline/parser.tsp index 566d03ce..ab9caed7 100644 --- a/schema/model/pipeline/parser.tsp +++ b/schema/model/pipeline/parser.tsp @@ -1,4 +1,4 @@ -import "prompty-emitter"; +import "@typra/emitter"; import "../agent/agent.tsp"; import "../conversation/message.tsp"; diff --git a/schema/model/pipeline/processor.tsp b/schema/model/pipeline/processor.tsp index 5ddc4d07..6a04b0fa 100644 --- a/schema/model/pipeline/processor.tsp +++ b/schema/model/pipeline/processor.tsp @@ -1,4 +1,4 @@ -import "prompty-emitter"; +import "@typra/emitter"; import "../agent/agent.tsp"; namespace Prompty; diff --git a/schema/model/pipeline/renderer.tsp b/schema/model/pipeline/renderer.tsp index 1b67bc86..2fbec10b 100644 --- a/schema/model/pipeline/renderer.tsp +++ b/schema/model/pipeline/renderer.tsp @@ -1,4 +1,4 @@ -import "prompty-emitter"; +import "@typra/emitter"; import "../agent/agent.tsp"; namespace Prompty; diff --git a/schema/model/pipeline/turn.tsp b/schema/model/pipeline/turn.tsp index 3eecbcf4..49f07083 100644 --- a/schema/model/pipeline/turn.tsp +++ b/schema/model/pipeline/turn.tsp @@ -1,4 +1,6 @@ -import "prompty-emitter"; +import "@typra/emitter"; +import "../events/payloads.tsp"; +import "../events/session.tsp"; namespace Prompty; @@ -58,3 +60,210 @@ model CompactionConfig { @sample(#{ options: #{ preserveSystemMessages: true } }) options?: Record = #{}; } + +/** + * Request passed by the reference turn runner to the injected model callback. + * + * The runner owns deterministic orchestration semantics; model/provider-specific + * execution stays behind this callback boundary. + */ +model TurnModelRequest { + @doc("Stable harness session identifier") + @sample(#{ sessionId: "sess_abc123" }) + sessionId: string; + + @doc("Stable turn identifier within the session") + @sample(#{ turnId: "turn_abc123" }) + turnId: string; + + @doc("Zero-based model loop iteration") + @sample(#{ iteration: 0 }) + iteration: int32; + + @doc("Inputs supplied to the deterministic single-turn run") + inputs?: Record = #{}; + + @doc("Canonical turn execution options") + options?: TurnOptions; + + @doc("Host tool results produced by the previous iteration") + toolResults?: HostToolResult[] = #[]; +} + +/** + * Response returned by the injected model callback to the reference turn runner. + */ +model TurnModelResponse { + @doc("Provider-neutral final model output for the turn when no more tools are requested") + output?: unknown; + + @doc("Host tool execution requests emitted by the model callback") + toolRequests?: HostToolRequest[] = #[]; + + @doc("Additional deterministic state to merge into the iteration checkpoint") + checkpointState?: Record = #{}; +} + +/** + * Request accepted by a reference turn runner implementation. + */ +model RunTurnRequest { + @doc("Stable harness session identifier") + @sample(#{ sessionId: "sess_abc123" }) + sessionId: string; + + @doc("Stable turn identifier within the session") + @sample(#{ turnId: "turn_abc123" }) + turnId: string; + + @doc("Inputs supplied to the deterministic single-turn run") + inputs?: Record = #{}; + + @doc("Canonical turn execution options") + options?: TurnOptions; +} + +/** + * Final semantic status for a reference turn runner result. + * + * This mirrors TurnStatus but remains distinct so generators can emit stable, + * model-local enum names without collisions. + */ +alias RunTurnStatus = "success" | "error" | "cancelled"; + +/** + * Result returned by a reference turn runner implementation. + */ +model RunTurnResult { + @doc("Stable harness session identifier") + @sample(#{ sessionId: "sess_abc123" }) + sessionId: string; + + @doc("Stable turn identifier within the session") + @sample(#{ turnId: "turn_abc123" }) + turnId: string; + + @doc("Final semantic status for the deterministic turn") + status: RunTurnStatus; + + @doc("Provider-neutral final output returned by the injected model callback") + output?: unknown; + + @doc("Number of model loop iterations executed") + @sample(#{ iterations: 1 }) + iterations: int32; + + @doc("Host tool results produced during the turn") + toolResults?: HostToolResult[] = #[]; + + @doc("Checkpoints created during the turn") + checkpoints?: Checkpoint[] = #[]; +} + +/** + * Stable, replay-comparable projection of a journal record. + * + * Runtime journal records may carry additional payload fields, durations, telemetry, + * or provider-specific data. Replay verification compares this normalized shape so + * deterministic orchestration semantics are mechanically shared across runtimes. + */ +model ReplayJournalRecord { + @doc("Journal record kind") + kind: ReplayRecordKind; + + @doc("Turn or session event type, when kind is not summary") + type?: string; + + @doc("Stable harness session identifier") + sessionId?: string; + + @doc("Stable turn identifier within the session") + turnId?: string; + + @doc("Zero-based model loop iteration for turn records") + iteration?: int32; + + @doc("Final semantic status for turn/session/summary records") + status?: ReplayRecordStatus; + + @doc("Permission request identifier for permission request records") + requestId?: string; + + @doc("Host tool name for tool execution/result records") + toolName?: string; + + @doc("Whether a permission or host tool operation succeeded") + success?: boolean; + + @doc("Stable error discriminator for failed records") + errorKind?: string; + + @doc("Number of turns represented by a summary record") + turns?: int32; + + @doc("Number of checkpoints represented by a summary record") + checkpoints?: int32; +} + +/** + * Durable journal record kind. + */ +alias ReplayRecordKind = "session" | "turn" | "summary"; + +/** + * Status values captured in normalized replay records. + * + * This mirrors RunTurnStatus but remains distinct so generators can emit stable, + * model-local enum names without collisions. + */ +alias ReplayRecordStatus = "success" | "error" | "cancelled"; + +/** + * Request accepted by a replay verifier implementation. + */ +model ReplayVerificationRequest { + @doc("Expected normalized replay records") + expected: ReplayJournalRecord[] = #[]; + + @doc("Actual normalized replay records") + actual: ReplayJournalRecord[] = #[]; +} + +/** + * Final semantic status for replay verification. + */ +alias ReplayVerificationStatus = "passed" | "failed"; + +/** + * A single mismatch produced by replay verification. + */ +model ReplayMismatch { + @doc("Zero-based record index where the mismatch was found") + index: int32; + + @doc("Expected record at this index, when present") + expected?: ReplayJournalRecord; + + @doc("Actual record at this index, when present") + actual?: ReplayJournalRecord; + + @doc("Human-readable mismatch explanation") + message: string; +} + +/** + * Result returned by a replay verifier implementation. + */ +model ReplayVerificationResult { + @doc("Replay verification status") + status: ReplayVerificationStatus; + + @doc("Record mismatches, empty when verification passed") + mismatches?: ReplayMismatch[] = #[]; + + @doc("Number of expected records") + expectedCount: int32; + + @doc("Number of actual records") + actualCount: int32; +} diff --git a/schema/model/streaming/stream.tsp b/schema/model/streaming/stream.tsp index 7e1222a1..aac8d264 100644 --- a/schema/model/streaming/stream.tsp +++ b/schema/model/streaming/stream.tsp @@ -1,4 +1,4 @@ -import "prompty-emitter"; +import "@typra/emitter"; namespace Prompty; diff --git a/schema/model/template/template.tsp b/schema/model/template/template.tsp index 0d6cf7c8..a5f4bf62 100644 --- a/schema/model/template/template.tsp +++ b/schema/model/template/template.tsp @@ -1,4 +1,4 @@ -import "prompty-emitter"; +import "@typra/emitter"; import "../core/core.tsp"; namespace Prompty; diff --git a/schema/model/tools/dispatch.tsp b/schema/model/tools/dispatch.tsp index e9b6a604..59135160 100644 --- a/schema/model/tools/dispatch.tsp +++ b/schema/model/tools/dispatch.tsp @@ -1,4 +1,4 @@ -import "prompty-emitter"; +import "@typra/emitter"; import "../conversation/message.tsp"; import "../conversation/tool-invocation.tsp"; diff --git a/schema/model/tools/tool.tsp b/schema/model/tools/tool.tsp index 018d9f12..5b324882 100644 --- a/schema/model/tools/tool.tsp +++ b/schema/model/tools/tool.tsp @@ -1,4 +1,4 @@ -import "prompty-emitter"; +import "@typra/emitter"; import "../core/core.tsp"; import "../core/properties.tsp"; import "../connection/connection.tsp"; diff --git a/schema/model/tracing/tracer.tsp b/schema/model/tracing/tracer.tsp index a1599592..dcd5fe93 100644 --- a/schema/model/tracing/tracer.tsp +++ b/schema/model/tracing/tracer.tsp @@ -1,4 +1,4 @@ -import "prompty-emitter"; +import "@typra/emitter"; import "../model/usage.tsp"; namespace Prompty; diff --git a/schema/model/wire/anthropic-types.tsp b/schema/model/wire/anthropic-types.tsp index 43f09c96..f497d331 100644 --- a/schema/model/wire/anthropic-types.tsp +++ b/schema/model/wire/anthropic-types.tsp @@ -1,4 +1,4 @@ -import "prompty-emitter"; +import "@typra/emitter"; namespace Prompty; diff --git a/schema/model/wire/anthropic.tsp b/schema/model/wire/anthropic.tsp index 60e020aa..973354d7 100644 --- a/schema/model/wire/anthropic.tsp +++ b/schema/model/wire/anthropic.tsp @@ -1,4 +1,4 @@ -import "prompty-emitter"; +import "@typra/emitter"; import "../model/model.tsp"; import "../model/usage.tsp"; diff --git a/schema/model/wire/openai.tsp b/schema/model/wire/openai.tsp index a36679ed..9046c6e1 100644 --- a/schema/model/wire/openai.tsp +++ b/schema/model/wire/openai.tsp @@ -1,4 +1,4 @@ -import "prompty-emitter"; +import "@typra/emitter"; import "../model/model.tsp"; import "../model/usage.tsp"; diff --git a/schema/package-lock.json b/schema/package-lock.json index 40e7f52d..195a2a27 100644 --- a/schema/package-lock.json +++ b/schema/package-lock.json @@ -6,43 +6,13 @@ "": { "name": "prompty-schema", "dependencies": { - "@typespec/compiler": "latest", - "@typespec/json-schema": "latest", - "prompty-emitter": "file:./emitter" - } - }, - "emitter": { - "name": "prompty-emitter", - "version": "0.2.0", - "license": "MIT", - "dependencies": { - "xml-formatter": "^3.6.7", - "yaml": "^2.8.1" - }, - "bin": { - "prompty-generate": "dist/src/cli.js" - }, - "devDependencies": { - "@types/node": "^24.7.0", - "@typescript-eslint/eslint-plugin": "^8.15.0", - "@typescript-eslint/parser": "^8.15.0", - "@typespec/compiler": "latest", - "@typespec/json-schema": "^1.8.0", - "eslint": "^9.15.0", - "markdownlint-cli": "^0.48.0", - "prettier": "^3.3.3", - "typescript": "^5.3.3", - "typescript-eslint": "^8.54.0" - }, - "peerDependencies": { - "@typespec/compiler": "latest", - "@typespec/json-schema": "latest" + "@typespec/compiler": "1.10.0", + "@typespec/json-schema": "1.10.0", + "@typra/emitter": "0.3.1" } }, "node_modules/@babel/code-frame": { "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", - "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", "license": "MIT", "dependencies": { "@babel/helper-validator-identifier": "^7.28.5", @@ -55,314 +25,33 @@ }, "node_modules/@babel/helper-validator-identifier": { "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", - "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", "license": "MIT", "engines": { "node": ">=6.9.0" } }, - "node_modules/@eslint-community/eslint-utils": { - "version": "4.9.1", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", - "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "eslint-visitor-keys": "^3.4.3" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - }, - "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" - } - }, - "node_modules/@eslint-community/regexpp": { - "version": "4.12.2", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", - "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.0.0 || ^14.0.0 || >=16.0.0" - } - }, - "node_modules/@eslint/config-array": { - "version": "0.21.2", - "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.2.tgz", - "integrity": "sha512-nJl2KGTlrf9GjLimgIru+V/mzgSK0ABCDQRvxw5BjURL7WfH5uoWmizbH7QB6MmnMBd8cIC9uceWnezL1VZWWw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@eslint/object-schema": "^2.1.7", - "debug": "^4.3.1", - "minimatch": "^3.1.5" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/@eslint/config-array/node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true, - "license": "MIT" - }, - "node_modules/@eslint/config-array/node_modules/brace-expansion": { - "version": "1.1.15", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.15.tgz", - "integrity": "sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/@eslint/config-array/node_modules/minimatch": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", - "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/@eslint/config-helpers": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz", - "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@eslint/core": "^0.17.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/@eslint/core": { - "version": "0.17.0", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz", - "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@types/json-schema": "^7.0.15" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/@eslint/eslintrc": { - "version": "3.3.5", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.5.tgz", - "integrity": "sha512-4IlJx0X0qftVsN5E+/vGujTRIFtwuLbNsVUe7TO6zYPDR1O6nFwvwhIKEKSrl6dZchmYBITazxKoUYOjdtjlRg==", - "dev": true, - "license": "MIT", - "dependencies": { - "ajv": "^6.14.0", - "debug": "^4.3.2", - "espree": "^10.0.1", - "globals": "^14.0.0", - "ignore": "^5.2.0", - "import-fresh": "^3.2.1", - "js-yaml": "^4.1.1", - "minimatch": "^3.1.5", - "strip-json-comments": "^3.1.1" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/@eslint/eslintrc/node_modules/ajv": { - "version": "6.14.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", - "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/@eslint/eslintrc/node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true, - "license": "MIT" - }, - "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { - "version": "1.1.15", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.15.tgz", - "integrity": "sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/@eslint/eslintrc/node_modules/ignore": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", - "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 4" - } - }, - "node_modules/@eslint/eslintrc/node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true, - "license": "MIT" - }, - "node_modules/@eslint/eslintrc/node_modules/minimatch": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", - "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/@eslint/js": { - "version": "9.39.4", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.4.tgz", - "integrity": "sha512-nE7DEIchvtiFTwBw4Lfbu59PG+kCofhjsKaCWzxTpt4lfRjRMqG6uMBzKXuEcyXhOHoUp9riAm7/aWYGhXZ9cw==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://eslint.org/donate" - } - }, - "node_modules/@eslint/object-schema": { - "version": "2.1.7", - "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz", - "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/@eslint/plugin-kit": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz", - "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@eslint/core": "^0.17.0", - "levn": "^0.4.1" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/@humanfs/core": { - "version": "0.19.1", - "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", - "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=18.18.0" - } - }, - "node_modules/@humanfs/node": { - "version": "0.16.7", - "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz", - "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@humanfs/core": "^0.19.1", - "@humanwhocodes/retry": "^0.4.0" - }, - "engines": { - "node": ">=18.18.0" - } - }, - "node_modules/@humanwhocodes/module-importer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", - "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=12.22" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" - } - }, - "node_modules/@humanwhocodes/retry": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", - "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=18.18" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" - } - }, "node_modules/@inquirer/ansi": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@inquirer/ansi/-/ansi-2.0.4.tgz", - "integrity": "sha512-DpcZrQObd7S0R/U3bFdkcT5ebRwbTTC4D3tCc1vsJizmgPLxNJBo+AAFmrZwe8zk30P2QzgzGWZ3Q9uJwWuhIg==", + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@inquirer/ansi/-/ansi-2.0.7.tgz", + "integrity": "sha512-3eTuUO1vH2cZm2ZKHeQxnOqlTi9EfZDGgIe3BL3I4u+rJHocr9Fz86M4fjYABPvFnQG/gGK551HqDiIcETwU6Q==", "license": "MIT", "engines": { - "node": ">=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0" + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" } }, "node_modules/@inquirer/checkbox": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/@inquirer/checkbox/-/checkbox-5.1.2.tgz", - "integrity": "sha512-PubpMPO2nJgMufkoB3P2wwxNXEMUXnBIKi/ACzDUYfaoPuM7gSTmuxJeMscoLVEsR4qqrCMf5p0SiYGWnVJ8kw==", + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/@inquirer/checkbox/-/checkbox-5.2.1.tgz", + "integrity": "sha512-b6xmA/VlTe0ZgDQHDui+Nav470u7u49nRd8/iuhOcQPO9Ch7lGuogydhi2VOmNlZ+zXcM8IcPuNSwQcdJaF/kw==", "license": "MIT", "dependencies": { - "@inquirer/ansi": "^2.0.4", - "@inquirer/core": "^11.1.7", - "@inquirer/figures": "^2.0.4", - "@inquirer/type": "^4.0.4" + "@inquirer/ansi": "^2.0.7", + "@inquirer/core": "^11.2.1", + "@inquirer/figures": "^2.0.7", + "@inquirer/type": "^4.0.7" }, "engines": { - "node": ">=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0" + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" }, "peerDependencies": { "@types/node": ">=18" @@ -374,16 +63,16 @@ } }, "node_modules/@inquirer/confirm": { - "version": "6.0.10", - "resolved": "https://registry.npmjs.org/@inquirer/confirm/-/confirm-6.0.10.tgz", - "integrity": "sha512-tiNyA73pgpQ0FQ7axqtoLUe4GDYjNCDcVsbgcA5anvwg2z6i+suEngLKKJrWKJolT//GFPZHwN30binDIHgSgQ==", + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/@inquirer/confirm/-/confirm-6.1.1.tgz", + "integrity": "sha512-eb8DBZcz/2qHWQda4rk2JiQk5h9QV/cVHi1yjt0f69WFZMRFn0sJTye3EAP8icut8UDMjQPsaH5KbcOogefrFQ==", "license": "MIT", "dependencies": { - "@inquirer/core": "^11.1.7", - "@inquirer/type": "^4.0.4" + "@inquirer/core": "^11.2.1", + "@inquirer/type": "^4.0.7" }, "engines": { - "node": ">=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0" + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" }, "peerDependencies": { "@types/node": ">=18" @@ -395,21 +84,21 @@ } }, "node_modules/@inquirer/core": { - "version": "11.1.7", - "resolved": "https://registry.npmjs.org/@inquirer/core/-/core-11.1.7.tgz", - "integrity": "sha512-1BiBNDk9btIwYIzNZpkikIHXWeNzNncJePPqwDyVMhXhD1ebqbpn1mKGctpoqAbzywZfdG0O4tvmsGIcOevAPQ==", + "version": "11.2.1", + "resolved": "https://registry.npmjs.org/@inquirer/core/-/core-11.2.1.tgz", + "integrity": "sha512-Qd6GJT1yVyrZZCfN8W2qKF5ApmqryXRhRKCuip8h01x2w/esJQ2XIYc6f9abMIHgKQdBfFTSOdbHRLAhuM09UA==", "license": "MIT", "dependencies": { - "@inquirer/ansi": "^2.0.4", - "@inquirer/figures": "^2.0.4", - "@inquirer/type": "^4.0.4", + "@inquirer/ansi": "^2.0.7", + "@inquirer/figures": "^2.0.7", + "@inquirer/type": "^4.0.7", "cli-width": "^4.1.0", "fast-wrap-ansi": "^0.2.0", "mute-stream": "^3.0.0", "signal-exit": "^4.1.0" }, "engines": { - "node": ">=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0" + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" }, "peerDependencies": { "@types/node": ">=18" @@ -421,17 +110,17 @@ } }, "node_modules/@inquirer/editor": { - "version": "5.0.10", - "resolved": "https://registry.npmjs.org/@inquirer/editor/-/editor-5.0.10.tgz", - "integrity": "sha512-VJx4XyaKea7t8hEApTw5dxeIyMtWXre2OiyJcICCRZI4hkoHsMoCnl/KbUnJJExLbH9csLLHMVR144ZhFE1CwA==", + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/@inquirer/editor/-/editor-5.2.2.tgz", + "integrity": "sha512-ZRVd/oD+sYsUd5zVm0NflqEzlqfYCyHNsqkHl2oWXEUHs12tCbcSFi+wVFEvD8+LGRaMUsVrE7qeo6lSG/S1Vg==", "license": "MIT", "dependencies": { - "@inquirer/core": "^11.1.7", - "@inquirer/external-editor": "^2.0.4", - "@inquirer/type": "^4.0.4" + "@inquirer/core": "^11.2.1", + "@inquirer/external-editor": "^3.0.3", + "@inquirer/type": "^4.0.7" }, "engines": { - "node": ">=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0" + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" }, "peerDependencies": { "@types/node": ">=18" @@ -443,16 +132,16 @@ } }, "node_modules/@inquirer/expand": { - "version": "5.0.10", - "resolved": "https://registry.npmjs.org/@inquirer/expand/-/expand-5.0.10.tgz", - "integrity": "sha512-fC0UHJPXsTRvY2fObiwuQYaAnHrp3aDqfwKUJSdfpgv18QUG054ezGbaRNStk/BKD5IPijeMKWej8VV8O5Q/eQ==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@inquirer/expand/-/expand-5.1.1.tgz", + "integrity": "sha512-YmQpenjbFSHAK3sOd44puHh3V1KXXr+JiNpUztoSQ4drLh2rTVzTap/YtlAVu/5xavifIlBfNEzJ/neZJ1a/1g==", "license": "MIT", "dependencies": { - "@inquirer/core": "^11.1.7", - "@inquirer/type": "^4.0.4" + "@inquirer/core": "^11.2.1", + "@inquirer/type": "^4.0.7" }, "engines": { - "node": ">=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0" + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" }, "peerDependencies": { "@types/node": ">=18" @@ -464,16 +153,16 @@ } }, "node_modules/@inquirer/external-editor": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@inquirer/external-editor/-/external-editor-2.0.4.tgz", - "integrity": "sha512-Prenuv9C1PHj2Itx0BcAOVBTonz02Hc2Nd2DbU67PdGUaqn0nPCnV34oDyyoaZHnmfRxkpuhh/u51ThkrO+RdA==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@inquirer/external-editor/-/external-editor-3.0.3.tgz", + "integrity": "sha512-6thf5I8q7lZwzGLAxPaaGEREEkZ3nyePPDQ1oyobblxmEE8mqTLguScP7pDjUTAibiyb4hfXl+qjUEJ+di/aNA==", "license": "MIT", "dependencies": { "chardet": "^2.1.1", "iconv-lite": "^0.7.2" }, "engines": { - "node": ">=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0" + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" }, "peerDependencies": { "@types/node": ">=18" @@ -485,25 +174,25 @@ } }, "node_modules/@inquirer/figures": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@inquirer/figures/-/figures-2.0.4.tgz", - "integrity": "sha512-eLBsjlS7rPS3WEhmOmh1znQ5IsQrxWzxWDxO51e4urv+iVrSnIHbq4zqJIOiyNdYLa+BVjwOtdetcQx1lWPpiQ==", + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@inquirer/figures/-/figures-2.0.7.tgz", + "integrity": "sha512-aJ8TBPOGB6f/2qziPfElISTCEd5XOYTFckA2SGjhNmiKzfK/u4ot3v0DUzGVdUnKjN10EqnnEPck36BkyfLnJw==", "license": "MIT", "engines": { - "node": ">=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0" + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" } }, "node_modules/@inquirer/input": { - "version": "5.0.10", - "resolved": "https://registry.npmjs.org/@inquirer/input/-/input-5.0.10.tgz", - "integrity": "sha512-nvZ6qEVeX/zVtZ1dY2hTGDQpVGD3R7MYPLODPgKO8Y+RAqxkrP3i/3NwF3fZpLdaMiNuK0z2NaYIx9tPwiSegQ==", + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/@inquirer/input/-/input-5.1.2.tgz", + "integrity": "sha512-9K/DDBSQpOyZSkt6sOVP9Vo0TR7atX2kuILsUu0x3wVcVbe97lJwIJKMLdMw25tDYuXl/qp6erT0Xs1rfmcfZg==", "license": "MIT", "dependencies": { - "@inquirer/core": "^11.1.7", - "@inquirer/type": "^4.0.4" + "@inquirer/core": "^11.2.1", + "@inquirer/type": "^4.0.7" }, "engines": { - "node": ">=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0" + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" }, "peerDependencies": { "@types/node": ">=18" @@ -515,16 +204,16 @@ } }, "node_modules/@inquirer/number": { - "version": "4.0.10", - "resolved": "https://registry.npmjs.org/@inquirer/number/-/number-4.0.10.tgz", - "integrity": "sha512-Ht8OQstxiS3APMGjHV0aYAjRAysidWdwurWEo2i8yI5xbhOBWqizT0+MU1S2GCcuhIBg+3SgWVjEoXgfhY+XaA==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@inquirer/number/-/number-4.1.1.tgz", + "integrity": "sha512-XF4IXAbPnGPgw0wsbC/i2tPcyfdZgDpUlhsqU0SfT4IRIGWha6Xm9VRgN5yYxJq+jnyXlfXI/nQ3ulfk0iEICA==", "license": "MIT", "dependencies": { - "@inquirer/core": "^11.1.7", - "@inquirer/type": "^4.0.4" + "@inquirer/core": "^11.2.1", + "@inquirer/type": "^4.0.7" }, "engines": { - "node": ">=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0" + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" }, "peerDependencies": { "@types/node": ">=18" @@ -536,17 +225,17 @@ } }, "node_modules/@inquirer/password": { - "version": "5.0.10", - "resolved": "https://registry.npmjs.org/@inquirer/password/-/password-5.0.10.tgz", - "integrity": "sha512-QbNyvIE8q2GTqKLYSsA8ATG+eETo+m31DSR0+AU7x3d2FhaTWzqQek80dj3JGTo743kQc6mhBR0erMjYw5jQ0A==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@inquirer/password/-/password-5.1.1.tgz", + "integrity": "sha512-3XBfF7DAsp5qeDsvN5Rd1HmbNokVvEQoUM0QLrRcybC9nX96w3Pbmu7qUsb3IT3J3jBvs2+mTXaKHOUsgHMLzg==", "license": "MIT", "dependencies": { - "@inquirer/ansi": "^2.0.4", - "@inquirer/core": "^11.1.7", - "@inquirer/type": "^4.0.4" + "@inquirer/ansi": "^2.0.7", + "@inquirer/core": "^11.2.1", + "@inquirer/type": "^4.0.7" }, "engines": { - "node": ">=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0" + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" }, "peerDependencies": { "@types/node": ">=18" @@ -558,24 +247,24 @@ } }, "node_modules/@inquirer/prompts": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/@inquirer/prompts/-/prompts-8.3.2.tgz", - "integrity": "sha512-yFroiSj2iiBFlm59amdTvAcQFvWS6ph5oKESls/uqPBect7rTU2GbjyZO2DqxMGuIwVA8z0P4K6ViPcd/cp+0w==", + "version": "8.5.2", + "resolved": "https://registry.npmjs.org/@inquirer/prompts/-/prompts-8.5.2.tgz", + "integrity": "sha512-IYR/3C/paEVVQYQvdDlFZVjRCJVYHHON0XXMH91KO9GSxs0TdKYWlUdvfQl2EfAHDxUaN3IBffkE/BDTh5nJ6g==", "license": "MIT", "dependencies": { - "@inquirer/checkbox": "^5.1.2", - "@inquirer/confirm": "^6.0.10", - "@inquirer/editor": "^5.0.10", - "@inquirer/expand": "^5.0.10", - "@inquirer/input": "^5.0.10", - "@inquirer/number": "^4.0.10", - "@inquirer/password": "^5.0.10", - "@inquirer/rawlist": "^5.2.6", - "@inquirer/search": "^4.1.6", - "@inquirer/select": "^5.1.2" + "@inquirer/checkbox": "^5.2.1", + "@inquirer/confirm": "^6.1.1", + "@inquirer/editor": "^5.2.2", + "@inquirer/expand": "^5.1.1", + "@inquirer/input": "^5.1.2", + "@inquirer/number": "^4.1.1", + "@inquirer/password": "^5.1.1", + "@inquirer/rawlist": "^5.3.1", + "@inquirer/search": "^4.2.1", + "@inquirer/select": "^5.2.1" }, "engines": { - "node": ">=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0" + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" }, "peerDependencies": { "@types/node": ">=18" @@ -587,16 +276,16 @@ } }, "node_modules/@inquirer/rawlist": { - "version": "5.2.6", - "resolved": "https://registry.npmjs.org/@inquirer/rawlist/-/rawlist-5.2.6.tgz", - "integrity": "sha512-jfw0MLJ5TilNsa9zlJ6nmRM0ZFVZhhTICt4/6CU2Dv1ndY7l3sqqo1gIYZyMMDw0LvE1u1nzJNisfHEhJIxq5w==", + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/@inquirer/rawlist/-/rawlist-5.3.1.tgz", + "integrity": "sha512-QqdTqQddL3qPX/PPrjobpsO25NZ4dWXgTLenrR445L2ptLEYE6Z+PD5c5CNDJNx4ugRgELAIpSIJxZaO2jJ2Og==", "license": "MIT", "dependencies": { - "@inquirer/core": "^11.1.7", - "@inquirer/type": "^4.0.4" + "@inquirer/core": "^11.2.1", + "@inquirer/type": "^4.0.7" }, "engines": { - "node": ">=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0" + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" }, "peerDependencies": { "@types/node": ">=18" @@ -608,17 +297,17 @@ } }, "node_modules/@inquirer/search": { - "version": "4.1.6", - "resolved": "https://registry.npmjs.org/@inquirer/search/-/search-4.1.6.tgz", - "integrity": "sha512-3/6kTRae98hhDevENScy7cdFEuURnSpM3JbBNg8yfXLw88HgTOl+neUuy/l9W0No5NzGsLVydhBzTIxZP7yChQ==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@inquirer/search/-/search-4.2.1.tgz", + "integrity": "sha512-xJj8QWKRSrfKoBIITLZK61dD3zwo0Rz11fgDImku30/Oe81zMdIdGgrLY2h6RkJ+KZ/GhNYIRMKnH/62qBTA5g==", "license": "MIT", "dependencies": { - "@inquirer/core": "^11.1.7", - "@inquirer/figures": "^2.0.4", - "@inquirer/type": "^4.0.4" + "@inquirer/core": "^11.2.1", + "@inquirer/figures": "^2.0.7", + "@inquirer/type": "^4.0.7" }, "engines": { - "node": ">=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0" + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" }, "peerDependencies": { "@types/node": ">=18" @@ -630,18 +319,18 @@ } }, "node_modules/@inquirer/select": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/@inquirer/select/-/select-5.1.2.tgz", - "integrity": "sha512-kTK8YIkHV+f02y7bWCh7E0u2/11lul5WepVTclr3UMBtBr05PgcZNWfMa7FY57ihpQFQH/spLMHTcr0rXy50tA==", + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/@inquirer/select/-/select-5.2.1.tgz", + "integrity": "sha512-FlDndEUww8m7BfukO2nJa25vhD+H5jxxCv4oGioKqzyWz3nPHhhw4LKdYRSlXuAx7DsdWia7iyaBPKKS95Evfw==", "license": "MIT", "dependencies": { - "@inquirer/ansi": "^2.0.4", - "@inquirer/core": "^11.1.7", - "@inquirer/figures": "^2.0.4", - "@inquirer/type": "^4.0.4" + "@inquirer/ansi": "^2.0.7", + "@inquirer/core": "^11.2.1", + "@inquirer/figures": "^2.0.7", + "@inquirer/type": "^4.0.7" }, "engines": { - "node": ">=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0" + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" }, "peerDependencies": { "@types/node": ">=18" @@ -653,12 +342,12 @@ } }, "node_modules/@inquirer/type": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-4.0.4.tgz", - "integrity": "sha512-PamArxO3cFJZoOzspzo6cxVlLeIftyBsZw/S9bKY5DzxqJVZgjoj1oP8d0rskKtp7sZxBycsoer1g6UeJV1BBA==", + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-4.0.7.tgz", + "integrity": "sha512-t28inv14nMQ1PhKpsJPY+kEs/c00qzeCOS2gTNRyTjG5d6qsVA2fItxW4hkvGZ5lvanGLdtCzVIx5dwdRpN1+g==", "license": "MIT", "engines": { - "node": ">=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0" + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" }, "peerDependencies": { "@types/node": ">=18" @@ -671,8 +360,6 @@ }, "node_modules/@isaacs/fs-minipass": { "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz", - "integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==", "license": "ISC", "dependencies": { "minipass": "^7.0.4" @@ -728,299 +415,8 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@types/debug": { - "version": "4.1.13", - "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.13.tgz", - "integrity": "sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/ms": "*" - } - }, - "node_modules/@types/estree": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", - "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/json-schema": { - "version": "7.0.15", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", - "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/katex": { - "version": "0.16.8", - "resolved": "https://registry.npmjs.org/@types/katex/-/katex-0.16.8.tgz", - "integrity": "sha512-trgaNyfU+Xh2Tc+ABIb44a5AYUpicB3uwirOioeOkNPPbmgRNtcWyDeeFRzjPZENO9Vq8gvVqfhaaXWLlevVwg==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/ms": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", - "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/node": { - "version": "24.12.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-24.12.0.tgz", - "integrity": "sha512-GYDxsZi3ChgmckRT9HPU0WEhKLP08ev/Yfcq2AstjrDASOYCSXeyjDsHg4v5t4jOj7cyDX3vmprafKlWIG9MXQ==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "undici-types": "~7.16.0" - } - }, - "node_modules/@types/unist": { - "version": "2.0.11", - "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz", - "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.57.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.57.2.tgz", - "integrity": "sha512-NZZgp0Fm2IkD+La5PR81sd+g+8oS6JwJje+aRWsDocxHkjyRw0J5L5ZTlN3LI1LlOcGL7ph3eaIUmTXMIjLk0w==", - "dev": true, - "license": "MIT", - "dependencies": { - "@eslint-community/regexpp": "^4.12.2", - "@typescript-eslint/scope-manager": "8.57.2", - "@typescript-eslint/type-utils": "8.57.2", - "@typescript-eslint/utils": "8.57.2", - "@typescript-eslint/visitor-keys": "8.57.2", - "ignore": "^7.0.5", - "natural-compare": "^1.4.0", - "ts-api-utils": "^2.4.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "@typescript-eslint/parser": "^8.57.2", - "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", - "typescript": ">=4.8.4 <6.0.0" - } - }, - "node_modules/@typescript-eslint/parser": { - "version": "8.57.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.57.2.tgz", - "integrity": "sha512-30ScMRHIAD33JJQkgfGW1t8CURZtjc2JpTrq5n2HFhOefbAhb7ucc7xJwdWcrEtqUIYJ73Nybpsggii6GtAHjA==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@typescript-eslint/scope-manager": "8.57.2", - "@typescript-eslint/types": "8.57.2", - "@typescript-eslint/typescript-estree": "8.57.2", - "@typescript-eslint/visitor-keys": "8.57.2", - "debug": "^4.4.3" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", - "typescript": ">=4.8.4 <6.0.0" - } - }, - "node_modules/@typescript-eslint/project-service": { - "version": "8.57.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.57.2.tgz", - "integrity": "sha512-FuH0wipFywXRTHf+bTTjNyuNQQsQC3qh/dYzaM4I4W0jrCqjCVuUh99+xd9KamUfmCGPvbO8NDngo/vsnNVqgw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.57.2", - "@typescript-eslint/types": "^8.57.2", - "debug": "^4.4.3" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "typescript": ">=4.8.4 <6.0.0" - } - }, - "node_modules/@typescript-eslint/scope-manager": { - "version": "8.57.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.57.2.tgz", - "integrity": "sha512-snZKH+W4WbWkrBqj4gUNRIGb/jipDW3qMqVJ4C9rzdFc+wLwruxk+2a5D+uoFcKPAqyqEnSb4l2ULuZf95eSkw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "8.57.2", - "@typescript-eslint/visitor-keys": "8.57.2" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.57.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.57.2.tgz", - "integrity": "sha512-3Lm5DSM+DCowsUOJC+YqHHnKEfFh5CoGkj5Z31NQSNF4l5wdOwqGn99wmwN/LImhfY3KJnmordBq/4+VDe2eKw==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "typescript": ">=4.8.4 <6.0.0" - } - }, - "node_modules/@typescript-eslint/type-utils": { - "version": "8.57.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.57.2.tgz", - "integrity": "sha512-Co6ZCShm6kIbAM/s+oYVpKFfW7LBc6FXoPXjTRQ449PPNBY8U0KZXuevz5IFuuUj2H9ss40atTaf9dlGLzbWZg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "8.57.2", - "@typescript-eslint/typescript-estree": "8.57.2", - "@typescript-eslint/utils": "8.57.2", - "debug": "^4.4.3", - "ts-api-utils": "^2.4.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", - "typescript": ">=4.8.4 <6.0.0" - } - }, - "node_modules/@typescript-eslint/types": { - "version": "8.57.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.57.2.tgz", - "integrity": "sha512-/iZM6FnM4tnx9csuTxspMW4BOSegshwX5oBDznJ7S4WggL7Vczz5d2W11ecc4vRrQMQHXRSxzrCsyG5EsPPTbA==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.57.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.57.2.tgz", - "integrity": "sha512-2MKM+I6g8tJxfSmFKOnHv2t8Sk3T6rF20A1Puk0svLK+uVapDZB/4pfAeB7nE83uAZrU6OxW+HmOd5wHVdXwXA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/project-service": "8.57.2", - "@typescript-eslint/tsconfig-utils": "8.57.2", - "@typescript-eslint/types": "8.57.2", - "@typescript-eslint/visitor-keys": "8.57.2", - "debug": "^4.4.3", - "minimatch": "^10.2.2", - "semver": "^7.7.3", - "tinyglobby": "^0.2.15", - "ts-api-utils": "^2.4.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "typescript": ">=4.8.4 <6.0.0" - } - }, - "node_modules/@typescript-eslint/utils": { - "version": "8.57.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.57.2.tgz", - "integrity": "sha512-krRIbvPK1ju1WBKIefiX+bngPs+odIQUtR7kymzPfo1POVw3jlF+nLkmexdSSd4UCbDcQn+wMBATOOmpBbqgKg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@eslint-community/eslint-utils": "^4.9.1", - "@typescript-eslint/scope-manager": "8.57.2", - "@typescript-eslint/types": "8.57.2", - "@typescript-eslint/typescript-estree": "8.57.2" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", - "typescript": ">=4.8.4 <6.0.0" - } - }, - "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.57.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.57.2.tgz", - "integrity": "sha512-zhahknjobV2FiD6Ee9iLbS7OV9zi10rG26odsQdfBO/hjSzUQbkIYgda+iNKK1zNiW2ey+Lf8MU5btN17V3dUw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "8.57.2", - "eslint-visitor-keys": "^5.0.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", - "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, "node_modules/@typespec/asset-emitter": { "version": "0.79.1", - "resolved": "https://registry.npmjs.org/@typespec/asset-emitter/-/asset-emitter-0.79.1.tgz", - "integrity": "sha512-53s3GLu5BwNkl7Itr/OizfhymTV2u7k5/cwjUOAt03AUDfiKlwbsp+iCIsq1vccJuoDOiXOceJOfL8rAf4/9LQ==", "license": "MIT", "engines": { "node": ">=20.0.0" @@ -1067,6 +463,7 @@ "resolved": "https://registry.npmjs.org/@typespec/json-schema/-/json-schema-1.10.0.tgz", "integrity": "sha512-FZTJvZoIqMbe/4qi17e8q3KF/2VDSyXiiF8QYwH4lar5dtEDGgwrw9ShLeLNiEZh8Ph3GjrQQV5qdpheDFJJvw==", "license": "MIT", + "peer": true, "dependencies": { "@typespec/asset-emitter": "^0.79.1", "yaml": "~2.8.2" @@ -1078,34 +475,27 @@ "@typespec/compiler": "^1.10.0" } }, - "node_modules/acorn": { - "version": "8.16.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", - "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", - "dev": true, + "node_modules/@typra/emitter": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@typra/emitter/-/emitter-0.3.1.tgz", + "integrity": "sha512-eZ3Wl+6NHPU3IL0W3oeqtTtTAo1oJsyKtjNTDksWwPKr9SIpvGBsl2Yzp6pBP2JlfEWJNHCTJwSJHTD7gg8Nww==", "license": "MIT", - "peer": true, + "dependencies": { + "xml-formatter": "^3.6.7", + "yaml": "^2.8.1" + }, "bin": { - "acorn": "bin/acorn" + "typra-consumer-smoke": "dist/src/consumer-smoke.js", + "typra-generate": "dist/src/cli.js", + "typra-verify": "dist/src/verify-cli.js" }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/acorn-jsx": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dev": true, - "license": "MIT", "peerDependencies": { - "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + "@typespec/compiler": "1.10.0", + "@typespec/json-schema": "1.10.0" } }, "node_modules/ajv": { "version": "8.18.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", - "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.3", @@ -1120,8 +510,6 @@ }, "node_modules/ansi-regex": { "version": "6.2.2", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", - "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", "license": "MIT", "engines": { "node": ">=12" @@ -1132,8 +520,6 @@ }, "node_modules/ansi-styles": { "version": "6.2.3", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", - "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", "license": "MIT", "engines": { "node": ">=12" @@ -1142,36 +528,6 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true, - "license": "Python-2.0" - }, - "node_modules/balanced-match": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", - "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", - "dev": true, - "license": "MIT", - "engines": { - "node": "18 || 20 || >=22" - } - }, - "node_modules/brace-expansion": { - "version": "5.0.6", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz", - "integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^4.0.2" - }, - "engines": { - "node": "18 || 20 || >=22" - } - }, "node_modules/braces": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", @@ -1184,98 +540,18 @@ "node": ">=8" } }, - "node_modules/callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/chalk/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, "node_modules/change-case": { "version": "5.4.4", - "resolved": "https://registry.npmjs.org/change-case/-/change-case-5.4.4.tgz", - "integrity": "sha512-HRQyTk2/YPEkt9TnUPbOpr64Uw3KOicFWPVBb+xiHvd6eBx/qPr9xqfBFDT8P2vWsvvz4jbEkfDe71W3VyNu2w==", "license": "MIT" }, - "node_modules/character-entities": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz", - "integrity": "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==", - "dev": true, - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/character-entities-legacy": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz", - "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==", - "dev": true, - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/character-reference-invalid": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-2.0.1.tgz", - "integrity": "sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==", - "dev": true, - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, "node_modules/chardet": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/chardet/-/chardet-2.1.1.tgz", - "integrity": "sha512-PsezH1rqdV9VvyNhxxOW32/d75r01NY7TQCmOqomRo15ZSOKbpTFVsfjghxo6JloQUCGnH4k1LGu0R4yCLlWQQ==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-2.2.0.tgz", + "integrity": "sha512-rddelWYNPRrXq6PtNEN2S3f6t9ILzvqaN5pVgi4kqt9jHQaXIial9PznB5iSPVlQSLNaaH22ItWz3EJtQ10+OA==", "license": "MIT" }, "node_modules/chownr": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", - "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==", "license": "BlueOak-1.0.0", "engines": { "node": ">=18" @@ -1292,8 +568,6 @@ }, "node_modules/cliui": { "version": "9.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-9.0.1.tgz", - "integrity": "sha512-k7ndgKhwoQveBL+/1tqGJYNz097I7WOvwbmmU2AR5+magtbjPWQTS1C5vzGkBC8Ym8UWRzfKUzUUqFLypY4Q+w==", "license": "ISC", "dependencies": { "string-width": "^7.2.0", @@ -1304,154 +578,12 @@ "node": ">=20" } }, - "node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, - "license": "MIT" - }, - "node_modules/commander": { - "version": "14.0.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.3.tgz", - "integrity": "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=20" - } - }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true, - "license": "MIT" - }, - "node_modules/cross-spawn": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", - "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", - "dev": true, - "license": "MIT", - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/decode-named-character-reference": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.3.0.tgz", - "integrity": "sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "character-entities": "^2.0.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/deep-extend": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", - "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/deep-is": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", - "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/dequal": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", - "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/devlop": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz", - "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==", - "dev": true, - "license": "MIT", - "dependencies": { - "dequal": "^2.0.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, "node_modules/emoji-regex": { "version": "10.6.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", - "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", "license": "MIT" }, - "node_modules/entities": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", - "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=0.12" - }, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, "node_modules/env-paths": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-4.0.0.tgz", - "integrity": "sha512-pxP8eL2SwwaTRi/KHYwLYXinDs7gL3jxFcBYmEdYfZmZXbaVDvdppd0XBU8qVz03rDfKZMXg1omHCbsJjZrMsw==", "license": "MIT", "dependencies": { "is-safe-filename": "^0.1.0" @@ -1465,289 +597,13 @@ }, "node_modules/escalade": { "version": "3.2.0", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", - "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", "license": "MIT", "engines": { "node": ">=6" } }, - "node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint": { - "version": "9.39.4", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.4.tgz", - "integrity": "sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@eslint-community/eslint-utils": "^4.8.0", - "@eslint-community/regexpp": "^4.12.1", - "@eslint/config-array": "^0.21.2", - "@eslint/config-helpers": "^0.4.2", - "@eslint/core": "^0.17.0", - "@eslint/eslintrc": "^3.3.5", - "@eslint/js": "9.39.4", - "@eslint/plugin-kit": "^0.4.1", - "@humanfs/node": "^0.16.6", - "@humanwhocodes/module-importer": "^1.0.1", - "@humanwhocodes/retry": "^0.4.2", - "@types/estree": "^1.0.6", - "ajv": "^6.14.0", - "chalk": "^4.0.0", - "cross-spawn": "^7.0.6", - "debug": "^4.3.2", - "escape-string-regexp": "^4.0.0", - "eslint-scope": "^8.4.0", - "eslint-visitor-keys": "^4.2.1", - "espree": "^10.4.0", - "esquery": "^1.5.0", - "esutils": "^2.0.2", - "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^8.0.0", - "find-up": "^5.0.0", - "glob-parent": "^6.0.2", - "ignore": "^5.2.0", - "imurmurhash": "^0.1.4", - "is-glob": "^4.0.0", - "json-stable-stringify-without-jsonify": "^1.0.1", - "lodash.merge": "^4.6.2", - "minimatch": "^3.1.5", - "natural-compare": "^1.4.0", - "optionator": "^0.9.3" - }, - "bin": { - "eslint": "bin/eslint.js" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://eslint.org/donate" - }, - "peerDependencies": { - "jiti": "*" - }, - "peerDependenciesMeta": { - "jiti": { - "optional": true - } - } - }, - "node_modules/eslint-scope": { - "version": "8.4.0", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", - "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^5.2.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint-visitor-keys": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", - "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint/node_modules/ajv": { - "version": "6.14.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", - "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/eslint/node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true, - "license": "MIT" - }, - "node_modules/eslint/node_modules/brace-expansion": { - "version": "1.1.15", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.15.tgz", - "integrity": "sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/eslint/node_modules/eslint-visitor-keys": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", - "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint/node_modules/glob-parent": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", - "dev": true, - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.3" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/eslint/node_modules/ignore": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", - "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 4" - } - }, - "node_modules/eslint/node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true, - "license": "MIT" - }, - "node_modules/eslint/node_modules/minimatch": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", - "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/espree": { - "version": "10.4.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", - "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "acorn": "^8.15.0", - "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^4.2.1" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/espree/node_modules/eslint-visitor-keys": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", - "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/esquery": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", - "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "estraverse": "^5.1.0" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/esrecurse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "estraverse": "^5.2.0" - }, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=4.0" - } - }, - "node_modules/esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/fast-deep-equal": { "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", "license": "MIT" }, "node_modules/fast-glob": { @@ -1766,20 +622,6 @@ "node": ">=8.6.0" } }, - "node_modules/fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true, - "license": "MIT" - }, - "node_modules/fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", - "dev": true, - "license": "MIT" - }, "node_modules/fast-string-truncated-width": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/fast-string-truncated-width/-/fast-string-truncated-width-3.0.3.tgz", @@ -1797,8 +639,6 @@ }, "node_modules/fast-uri": { "version": "3.1.2", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.2.tgz", - "integrity": "sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ==", "funding": [ { "type": "github", @@ -1812,9 +652,9 @@ "license": "BSD-3-Clause" }, "node_modules/fast-wrap-ansi": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/fast-wrap-ansi/-/fast-wrap-ansi-0.2.0.tgz", - "integrity": "sha512-rLV8JHxTyhVmFYhBJuMujcrHqOT2cnO5Zxj37qROj23CP39GXubJRBUFF0z8KFK77Uc0SukZUf7JZhsVEQ6n8w==", + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/fast-wrap-ansi/-/fast-wrap-ansi-0.2.2.tgz", + "integrity": "sha512-7F2Fl+TjRSenLqlU3UjSH0iyqopqoZIu7eZVpEirP2g1GtWa2G/ecEmBdgz31+Mxr+ELclgg6sokpSFIQiZ02Q==", "license": "MIT", "dependencies": { "fast-string-width": "^3.0.2" @@ -1829,19 +669,6 @@ "reusify": "^1.0.4" } }, - "node_modules/file-entry-cache": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", - "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "flat-cache": "^4.0.0" - }, - "engines": { - "node": ">=16.0.0" - } - }, "node_modules/fill-range": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", @@ -1854,57 +681,15 @@ "node": ">=8" } }, - "node_modules/find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "dev": true, - "license": "MIT", - "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "license": "ISC", "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/flat-cache": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", - "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", - "dev": true, - "license": "MIT", - "dependencies": { - "flatted": "^3.2.9", - "keyv": "^4.5.4" - }, - "engines": { - "node": ">=16" - } - }, - "node_modules/flatted": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", - "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", - "dev": true, - "license": "ISC" - }, - "node_modules/get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "license": "ISC", - "engines": { - "node": "6.* || 8.* || >= 10.*" + "node": "6.* || 8.* || >= 10.*" } }, "node_modules/get-east-asian-width": { "version": "1.5.0", - "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.5.0.tgz", - "integrity": "sha512-CQ+bEO+Tva/qlmw24dCejulK5pMzVnUOFOijVogd3KQs07HnRIgp8TGipvCCRT06xeYEbpbgwaCxglFyiuIcmA==", "license": "MIT", "engines": { "node": ">=18" @@ -1925,19 +710,6 @@ "node": ">= 6" } }, - "node_modules/globals": { - "version": "14.0.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", - "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/globby": { "version": "16.1.1", "resolved": "https://registry.npmjs.org/globby/-/globby-16.1.1.tgz", @@ -1958,16 +730,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/iconv-lite": { "version": "0.7.2", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz", @@ -1993,80 +755,6 @@ "node": ">= 4" } }, - "node_modules/import-fresh": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", - "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.8.19" - } - }, - "node_modules/ini": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/ini/-/ini-4.1.3.tgz", - "integrity": "sha512-X7rqawQBvfdjS10YU1y1YVreA3SsLrW9dX2CewP2EbBJM4ypVNLDkO5y04gejPwKIY9lR+7r9gn3rFPt/kmWFg==", - "dev": true, - "license": "ISC", - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/is-alphabetical": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-2.0.1.tgz", - "integrity": "sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==", - "dev": true, - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/is-alphanumerical": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-2.0.1.tgz", - "integrity": "sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-alphabetical": "^2.0.0", - "is-decimal": "^2.0.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/is-decimal": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-2.0.1.tgz", - "integrity": "sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==", - "dev": true, - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -2088,17 +776,6 @@ "node": ">=0.10.0" } }, - "node_modules/is-hexadecimal": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-2.0.1.tgz", - "integrity": "sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==", - "dev": true, - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, "node_modules/is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", @@ -2122,813 +799,41 @@ }, "node_modules/is-safe-filename": { "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-safe-filename/-/is-safe-filename-0.1.1.tgz", - "integrity": "sha512-4SrR7AdnY11LHfDKTZY1u6Ga3RuxZdl3YKWWShO5iyuG5h8QS4GD2tOb04peBJ5I7pXbR+CGBNEhTcwK+FzN3g==", "license": "MIT", "engines": { "node": ">=20" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-unicode-supported": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-2.1.0.tgz", - "integrity": "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true, - "license": "ISC" - }, - "node_modules/js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "license": "MIT" - }, - "node_modules/js-yaml": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", - "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", - "dev": true, - "license": "MIT", - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/json-buffer": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", - "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "license": "MIT" - }, - "node_modules/json-stable-stringify-without-jsonify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", - "dev": true, - "license": "MIT" - }, - "node_modules/jsonc-parser": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.3.1.tgz", - "integrity": "sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/jsonpointer": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-5.0.1.tgz", - "integrity": "sha512-p/nXbhSEcu3pZRdkW1OfJhpsVtW1gd4Wa1fnQc9YLiTfAjn0312eMKimbdIQzuZl9aa9xUGaRlP9T/CJE/ditQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/katex": { - "version": "0.16.45", - "resolved": "https://registry.npmjs.org/katex/-/katex-0.16.45.tgz", - "integrity": "sha512-pQpZbdBu7wCTmQUh7ufPmLr0pFoObnGUoL/yhtwJDgmmQpbkg/0HSVti25Fu4rmd1oCR6NGWe9vqTWuWv3GcNA==", - "dev": true, - "funding": [ - "https://opencollective.com/katex", - "https://github.com/sponsors/katex" - ], - "license": "MIT", - "dependencies": { - "commander": "^8.3.0" - }, - "bin": { - "katex": "cli.js" - } - }, - "node_modules/katex/node_modules/commander": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", - "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 12" - } - }, - "node_modules/keyv": { - "version": "4.5.4", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", - "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", - "dev": true, - "license": "MIT", - "dependencies": { - "json-buffer": "3.0.1" - } - }, - "node_modules/levn": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", - "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "prelude-ls": "^1.2.1", - "type-check": "~0.4.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/linkify-it": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz", - "integrity": "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "uc.micro": "^2.0.0" - } - }, - "node_modules/locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-locate": "^5.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/lodash.merge": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/markdown-it": { - "version": "14.1.1", - "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.1.tgz", - "integrity": "sha512-BuU2qnTti9YKgK5N+IeMubp14ZUKUUw7yeJbkjtosvHiP0AZ5c8IAgEMk79D0eC8F23r4Ac/q8cAIFdm2FtyoA==", - "dev": true, - "license": "MIT", - "dependencies": { - "argparse": "^2.0.1", - "entities": "^4.4.0", - "linkify-it": "^5.0.0", - "mdurl": "^2.0.0", - "punycode.js": "^2.3.1", - "uc.micro": "^2.1.0" - }, - "bin": { - "markdown-it": "bin/markdown-it.mjs" - } - }, - "node_modules/markdownlint": { - "version": "0.40.0", - "resolved": "https://registry.npmjs.org/markdownlint/-/markdownlint-0.40.0.tgz", - "integrity": "sha512-UKybllYNheWac61Ia7T6fzuQNDZimFIpCg2w6hHjgV1Qu0w1TV0LlSgryUGzM0bkKQCBhy2FDhEELB73Kb0kAg==", - "dev": true, - "license": "MIT", - "dependencies": { - "micromark": "4.0.2", - "micromark-core-commonmark": "2.0.3", - "micromark-extension-directive": "4.0.0", - "micromark-extension-gfm-autolink-literal": "2.1.0", - "micromark-extension-gfm-footnote": "2.1.0", - "micromark-extension-gfm-table": "2.1.1", - "micromark-extension-math": "3.1.0", - "micromark-util-types": "2.0.2", - "string-width": "8.1.0" - }, - "engines": { - "node": ">=20" - }, - "funding": { - "url": "https://github.com/sponsors/DavidAnson" - } - }, - "node_modules/markdownlint-cli": { - "version": "0.48.0", - "resolved": "https://registry.npmjs.org/markdownlint-cli/-/markdownlint-cli-0.48.0.tgz", - "integrity": "sha512-NkZQNu2E0Q5qLEEHwWj674eYISTLD4jMHkBzDobujXd1kv+yCxi8jOaD/rZoQNW1FBBMMGQpuW5So8B51N/e0A==", - "dev": true, - "license": "MIT", - "dependencies": { - "commander": "~14.0.3", - "deep-extend": "~0.6.0", - "ignore": "~7.0.5", - "js-yaml": "~4.1.1", - "jsonc-parser": "~3.3.1", - "jsonpointer": "~5.0.1", - "markdown-it": "~14.1.1", - "markdownlint": "~0.40.0", - "minimatch": "~10.2.4", - "run-con": "~1.3.2", - "smol-toml": "~1.6.0", - "tinyglobby": "~0.2.15" - }, - "bin": { - "markdownlint": "markdownlint.js" - }, - "engines": { - "node": ">=20" - } - }, - "node_modules/markdownlint/node_modules/string-width": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-8.1.0.tgz", - "integrity": "sha512-Kxl3KJGb/gxkaUMOjRsQ8IrXiGW75O4E3RPjFIINOVH8AMl2SQ/yWdTzWwF3FevIX9LcMAjJW+GRwAlAbTSXdg==", - "dev": true, - "license": "MIT", - "dependencies": { - "get-east-asian-width": "^1.3.0", - "strip-ansi": "^7.1.0" - }, - "engines": { - "node": ">=20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/mdurl": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz", - "integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==", - "dev": true, - "license": "MIT" - }, - "node_modules/merge2": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "license": "MIT", - "engines": { - "node": ">= 8" - } - }, - "node_modules/micromark": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.2.tgz", - "integrity": "sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==", - "dev": true, - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "@types/debug": "^4.0.0", - "debug": "^4.0.0", - "decode-named-character-reference": "^1.0.0", - "devlop": "^1.0.0", - "micromark-core-commonmark": "^2.0.0", - "micromark-factory-space": "^2.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-chunked": "^2.0.0", - "micromark-util-combine-extensions": "^2.0.0", - "micromark-util-decode-numeric-character-reference": "^2.0.0", - "micromark-util-encode": "^2.0.0", - "micromark-util-normalize-identifier": "^2.0.0", - "micromark-util-resolve-all": "^2.0.0", - "micromark-util-sanitize-uri": "^2.0.0", - "micromark-util-subtokenize": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-core-commonmark": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-2.0.3.tgz", - "integrity": "sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==", - "dev": true, - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "decode-named-character-reference": "^1.0.0", - "devlop": "^1.0.0", - "micromark-factory-destination": "^2.0.0", - "micromark-factory-label": "^2.0.0", - "micromark-factory-space": "^2.0.0", - "micromark-factory-title": "^2.0.0", - "micromark-factory-whitespace": "^2.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-chunked": "^2.0.0", - "micromark-util-classify-character": "^2.0.0", - "micromark-util-html-tag-name": "^2.0.0", - "micromark-util-normalize-identifier": "^2.0.0", - "micromark-util-resolve-all": "^2.0.0", - "micromark-util-subtokenize": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-extension-directive": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/micromark-extension-directive/-/micromark-extension-directive-4.0.0.tgz", - "integrity": "sha512-/C2nqVmXXmiseSSuCdItCMho7ybwwop6RrrRPk0KbOHW21JKoCldC+8rFOaundDoRBUWBnJJcxeA/Kvi34WQXg==", - "dev": true, - "license": "MIT", - "dependencies": { - "devlop": "^1.0.0", - "micromark-factory-space": "^2.0.0", - "micromark-factory-whitespace": "^2.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0", - "parse-entities": "^4.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/micromark-extension-gfm-autolink-literal": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm-autolink-literal/-/micromark-extension-gfm-autolink-literal-2.1.0.tgz", - "integrity": "sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==", - "dev": true, - "license": "MIT", - "dependencies": { - "micromark-util-character": "^2.0.0", - "micromark-util-sanitize-uri": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/micromark-extension-gfm-footnote": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm-footnote/-/micromark-extension-gfm-footnote-2.1.0.tgz", - "integrity": "sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw==", - "dev": true, - "license": "MIT", - "dependencies": { - "devlop": "^1.0.0", - "micromark-core-commonmark": "^2.0.0", - "micromark-factory-space": "^2.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-normalize-identifier": "^2.0.0", - "micromark-util-sanitize-uri": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/micromark-extension-gfm-table": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm-table/-/micromark-extension-gfm-table-2.1.1.tgz", - "integrity": "sha512-t2OU/dXXioARrC6yWfJ4hqB7rct14e8f7m0cbI5hUmDyyIlwv5vEtooptH8INkbLzOatzKuVbQmAYcbWoyz6Dg==", - "dev": true, - "license": "MIT", - "dependencies": { - "devlop": "^1.0.0", - "micromark-factory-space": "^2.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/micromark-extension-math": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/micromark-extension-math/-/micromark-extension-math-3.1.0.tgz", - "integrity": "sha512-lvEqd+fHjATVs+2v/8kg9i5Q0AP2k85H0WUOwpIVvUML8BapsMvh1XAogmQjOCsLpoKRCVQqEkQBB3NhVBcsOg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/katex": "^0.16.0", - "devlop": "^1.0.0", - "katex": "^0.16.0", - "micromark-factory-space": "^2.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/micromark-factory-destination": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-2.0.1.tgz", - "integrity": "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==", - "dev": true, - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-character": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-factory-label": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-2.0.1.tgz", - "integrity": "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==", - "dev": true, - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "devlop": "^1.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-factory-space": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", - "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", - "dev": true, - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-character": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-factory-title": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-2.0.1.tgz", - "integrity": "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==", - "dev": true, - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-factory-space": "^2.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-factory-whitespace": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-2.0.1.tgz", - "integrity": "sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==", - "dev": true, - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-factory-space": "^2.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-util-character": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", - "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", - "dev": true, - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-util-chunked": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-2.0.1.tgz", - "integrity": "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==", - "dev": true, - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-symbol": "^2.0.0" - } - }, - "node_modules/micromark-util-classify-character": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-2.0.1.tgz", - "integrity": "sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==", - "dev": true, - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-character": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-util-combine-extensions": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-2.0.1.tgz", - "integrity": "sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==", - "dev": true, - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-chunked": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-util-decode-numeric-character-reference": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-2.0.2.tgz", - "integrity": "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==", - "dev": true, - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-symbol": "^2.0.0" - } - }, - "node_modules/micromark-util-encode": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz", - "integrity": "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==", - "dev": true, - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT" - }, - "node_modules/micromark-util-html-tag-name": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-2.0.1.tgz", - "integrity": "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==", - "dev": true, - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT" - }, - "node_modules/micromark-util-normalize-identifier": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-2.0.1.tgz", - "integrity": "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==", - "dev": true, - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-symbol": "^2.0.0" - } - }, - "node_modules/micromark-util-resolve-all": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-2.0.1.tgz", - "integrity": "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==", - "dev": true, - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-util-sanitize-uri": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz", - "integrity": "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==", - "dev": true, - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-character": "^2.0.0", - "micromark-util-encode": "^2.0.0", - "micromark-util-symbol": "^2.0.0" - } - }, - "node_modules/micromark-util-subtokenize": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-2.1.0.tgz", - "integrity": "sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==", - "dev": true, - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-unicode-supported": { + "version": "2.1.0", "license": "MIT", - "dependencies": { - "devlop": "^1.0.0", - "micromark-util-chunked": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/micromark-util-symbol": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", - "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", - "dev": true, - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], + "node_modules/js-tokens": { + "version": "4.0.0", "license": "MIT" }, - "node_modules/micromark-util-types": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.2.tgz", - "integrity": "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==", - "dev": true, - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], + "node_modules/json-schema-traverse": { + "version": "1.0.0", "license": "MIT" }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, "node_modules/micromatch": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", @@ -2942,36 +847,8 @@ "node": ">=8.6" } }, - "node_modules/minimatch": { - "version": "10.2.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", - "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "brace-expansion": "^5.0.5" - }, - "engines": { - "node": "18 || 20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/minimist": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", - "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/minipass": { "version": "7.1.3", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", - "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", "license": "BlueOak-1.0.0", "engines": { "node": ">=16 || 14 >=14.17" @@ -2979,8 +856,6 @@ }, "node_modules/minizlib": { "version": "3.1.0", - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.1.0.tgz", - "integrity": "sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==", "license": "MIT", "dependencies": { "minipass": "^7.1.2" @@ -2989,17 +864,8 @@ "node": ">= 18" } }, - "node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, - "license": "MIT" - }, "node_modules/mustache": { "version": "4.2.0", - "resolved": "https://registry.npmjs.org/mustache/-/mustache-4.2.0.tgz", - "integrity": "sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ==", "license": "MIT", "bin": { "mustache": "bin/mustache" @@ -3014,120 +880,8 @@ "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", - "dev": true, - "license": "MIT" - }, - "node_modules/optionator": { - "version": "0.9.4", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", - "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", - "dev": true, - "license": "MIT", - "dependencies": { - "deep-is": "^0.1.3", - "fast-levenshtein": "^2.0.6", - "levn": "^0.4.1", - "prelude-ls": "^1.2.1", - "type-check": "^0.4.0", - "word-wrap": "^1.2.5" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "yocto-queue": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-limit": "^3.0.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/parent-module": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dev": true, - "license": "MIT", - "dependencies": { - "callsites": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/parse-entities": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-4.0.2.tgz", - "integrity": "sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/unist": "^2.0.0", - "character-entities-legacy": "^3.0.0", - "character-reference-invalid": "^2.0.0", - "decode-named-character-reference": "^1.0.0", - "is-alphanumerical": "^2.0.0", - "is-decimal": "^2.0.0", - "is-hexadecimal": "^2.0.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/picocolors": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", "license": "ISC" }, "node_modules/picomatch": { @@ -3142,20 +896,8 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/prelude-ls": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", - "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8.0" - } - }, "node_modules/prettier": { "version": "3.8.1", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.1.tgz", - "integrity": "sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==", "license": "MIT", "bin": { "prettier": "bin/prettier.cjs" @@ -3167,30 +909,6 @@ "url": "https://github.com/prettier/prettier?sponsor=1" } }, - "node_modules/prompty-emitter": { - "resolved": "emitter", - "link": true - }, - "node_modules/punycode": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", - "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/punycode.js": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz", - "integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -3213,23 +931,11 @@ }, "node_modules/require-from-string": { "version": "2.0.2", - "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", - "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", "license": "MIT", "engines": { "node": ">=0.10.0" } }, - "node_modules/resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, "node_modules/reusify": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", @@ -3240,22 +946,6 @@ "node": ">=0.10.0" } }, - "node_modules/run-con": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/run-con/-/run-con-1.3.2.tgz", - "integrity": "sha512-CcfE+mYiTcKEzg0IqS08+efdnH0oJ3zV0wSUFBNrMHMuxCtXvBCLzCJHatwuXDcu/RlhjTziTo/a1ruQik6/Yg==", - "dev": true, - "license": "(BSD-2-Clause OR MIT OR Apache-2.0)", - "dependencies": { - "deep-extend": "^0.6.0", - "ini": "~4.1.0", - "minimist": "^1.2.8", - "strip-json-comments": "~3.1.1" - }, - "bin": { - "run-con": "cli.js" - } - }, "node_modules/run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", @@ -3287,8 +977,6 @@ }, "node_modules/semver": { "version": "7.7.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", - "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -3297,29 +985,6 @@ "node": ">=10" } }, - "node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "license": "MIT", - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/signal-exit": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", @@ -3344,23 +1009,8 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/smol-toml": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/smol-toml/-/smol-toml-1.6.1.tgz", - "integrity": "sha512-dWUG8F5sIIARXih1DTaQAX4SsiTXhInKf1buxdY9DIg4ZYPZK5nGM1VRIYmEbDbsHt7USo99xSLFu5Q1IqTmsg==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">= 18" - }, - "funding": { - "url": "https://github.com/sponsors/cyyynthia" - } - }, "node_modules/string-width": { "version": "7.2.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", - "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", "license": "MIT", "dependencies": { "emoji-regex": "^10.3.0", @@ -3376,8 +1026,6 @@ }, "node_modules/strip-ansi": { "version": "7.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", - "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", "license": "MIT", "dependencies": { "ansi-regex": "^6.2.2" @@ -3389,36 +1037,8 @@ "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, - "node_modules/strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/tar": { "version": "7.5.13", - "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.13.tgz", - "integrity": "sha512-tOG/7GyXpFevhXVh8jOPJrmtRpOTsYqUIkVdVooZYJS/z8WhfQUX8RJILmeuJNinGAMSu1veBr4asSHFt5/hng==", "license": "BlueOak-1.0.0", "dependencies": { "@isaacs/fs-minipass": "^4.0.0", @@ -3433,8 +1053,6 @@ }, "node_modules/temporal-polyfill": { "version": "0.3.2", - "resolved": "https://registry.npmjs.org/temporal-polyfill/-/temporal-polyfill-0.3.2.tgz", - "integrity": "sha512-TzHthD/heRK947GNiSu3Y5gSPpeUDH34+LESnfsq8bqpFhsB79HFBX8+Z834IVX68P3EUyRPZK5bL/1fh437Eg==", "license": "MIT", "dependencies": { "temporal-spec": "0.3.1" @@ -3442,59 +1060,8 @@ }, "node_modules/temporal-spec": { "version": "0.3.1", - "resolved": "https://registry.npmjs.org/temporal-spec/-/temporal-spec-0.3.1.tgz", - "integrity": "sha512-B4TUhezh9knfSIMwt7RVggApDRJZo73uZdj8AacL2mZ8RP5KtLianh2MXxL06GN9ESYiIsiuoLQhgVfwe55Yhw==", "license": "ISC" }, - "node_modules/tinyglobby": { - "version": "0.2.15", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", - "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "fdir": "^6.5.0", - "picomatch": "^4.0.3" - }, - "engines": { - "node": ">=12.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/SuperchupuDev" - } - }, - "node_modules/tinyglobby/node_modules/fdir": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", - "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "picomatch": "^3 || ^4" - }, - "peerDependenciesMeta": { - "picomatch": { - "optional": true - } - } - }, - "node_modules/tinyglobby/node_modules/picomatch": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", - "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -3507,85 +1074,6 @@ "node": ">=8.0" } }, - "node_modules/ts-api-utils": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz", - "integrity": "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18.12" - }, - "peerDependencies": { - "typescript": ">=4.8.4" - } - }, - "node_modules/type-check": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", - "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", - "dev": true, - "license": "MIT", - "dependencies": { - "prelude-ls": "^1.2.1" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/typescript": { - "version": "5.9.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", - "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, - "node_modules/typescript-eslint": { - "version": "8.57.2", - "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.57.2.tgz", - "integrity": "sha512-VEPQ0iPgWO/sBaZOU1xo4nuNdODVOajPnTIbog2GKYr31nIlZ0fWPoCQgGfF3ETyBl1vn63F/p50Um9Z4J8O8A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/eslint-plugin": "8.57.2", - "@typescript-eslint/parser": "8.57.2", - "@typescript-eslint/typescript-estree": "8.57.2", - "@typescript-eslint/utils": "8.57.2" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", - "typescript": ">=4.8.4 <6.0.0" - } - }, - "node_modules/uc.micro": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz", - "integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==", - "dev": true, - "license": "MIT" - }, - "node_modules/undici-types": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", - "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", - "devOptional": true, - "license": "MIT" - }, "node_modules/unicorn-magic": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.4.0.tgz", @@ -3598,20 +1086,8 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "punycode": "^2.1.0" - } - }, "node_modules/vscode-jsonrpc": { "version": "8.2.0", - "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-8.2.0.tgz", - "integrity": "sha512-C+r0eKJUIfiDIfwJhria30+TYWPtuHJXHtI7J0YlOmKAo7ogxP20T0zxB7HZQIFhIyvoBPwWskjxrvAtfjyZfA==", "license": "MIT", "engines": { "node": ">=14.0.0" @@ -3619,8 +1095,6 @@ }, "node_modules/vscode-languageserver": { "version": "9.0.1", - "resolved": "https://registry.npmjs.org/vscode-languageserver/-/vscode-languageserver-9.0.1.tgz", - "integrity": "sha512-woByF3PDpkHFUreUa7Hos7+pUWdeWMXRd26+ZX2A8cFx6v/JPTtd4/uN0/jB6XQHYaOlHbio03NTHCqrgG5n7g==", "license": "MIT", "dependencies": { "vscode-languageserver-protocol": "3.17.5" @@ -3631,8 +1105,6 @@ }, "node_modules/vscode-languageserver-protocol": { "version": "3.17.5", - "resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.5.tgz", - "integrity": "sha512-mb1bvRJN8SVznADSGWM9u/b07H7Ecg0I3OgXDuLdn307rl/J3A9YD6/eYOssqhecL27hK1IPZAsaqh00i/Jljg==", "license": "MIT", "dependencies": { "vscode-jsonrpc": "8.2.0", @@ -3641,46 +1113,14 @@ }, "node_modules/vscode-languageserver-textdocument": { "version": "1.0.12", - "resolved": "https://registry.npmjs.org/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.12.tgz", - "integrity": "sha512-cxWNPesCnQCcMPeenjKKsOCKQZ/L6Tv19DTRIGuLWe32lyzWhihGVJ/rcckZXJxfdKCFvRLS3fpBIsV/ZGX4zA==", "license": "MIT" }, "node_modules/vscode-languageserver-types": { "version": "3.17.5", - "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.17.5.tgz", - "integrity": "sha512-Ld1VelNuX9pdF39h2Hgaeb5hEZM2Z3jUrrMgWQAu82jMtZp7p3vJT3BzToKtZI7NgQssZje5o0zryOrhQvzQAg==", "license": "MIT" }, - "node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "license": "ISC", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/word-wrap": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", - "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/wrap-ansi": { "version": "9.0.2", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz", - "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==", "license": "MIT", "dependencies": { "ansi-styles": "^6.2.1", @@ -3696,8 +1136,6 @@ }, "node_modules/xml-formatter": { "version": "3.7.0", - "resolved": "https://registry.npmjs.org/xml-formatter/-/xml-formatter-3.7.0.tgz", - "integrity": "sha512-+8qTc3zv2UcJ1v9IsSIce37Dl4MQG14Cp7tWrwmy202UaI1wqRukw5QMX1JHsV+DX64yw77EgGsj2s5wGvuMbQ==", "license": "MIT", "dependencies": { "xml-parser-xo": "^4.1.5" @@ -3708,8 +1146,6 @@ }, "node_modules/xml-parser-xo": { "version": "4.1.5", - "resolved": "https://registry.npmjs.org/xml-parser-xo/-/xml-parser-xo-4.1.5.tgz", - "integrity": "sha512-TxyRxk9sTOUg3glxSIY6f0nfuqRll2OEF8TspLgh5mZkLuBgheCn3zClcDSGJ58TvNmiwyCCuat4UajPud/5Og==", "license": "MIT", "engines": { "node": ">= 16" @@ -3717,8 +1153,6 @@ }, "node_modules/y18n": { "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", "license": "ISC", "engines": { "node": ">=10" @@ -3726,8 +1160,6 @@ }, "node_modules/yallist": { "version": "5.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", - "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==", "license": "BlueOak-1.0.0", "engines": { "node": ">=18" @@ -3735,8 +1167,6 @@ }, "node_modules/yaml": { "version": "2.8.3", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.3.tgz", - "integrity": "sha512-AvbaCLOO2Otw/lW5bmh9d/WEdcDFdQp2Z2ZUH3pX9U2ihyUY0nvLv7J6TrWowklRGPYbB/IuIMfYgxaCPg5Bpg==", "license": "ISC", "bin": { "yaml": "bin.mjs" @@ -3750,8 +1180,6 @@ }, "node_modules/yargs": { "version": "18.0.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-18.0.0.tgz", - "integrity": "sha512-4UEqdc2RYGHZc7Doyqkrqiln3p9X2DZVxaGbwhn2pi7MrRagKaOcIKe8L3OxYcbhXLgLFUS3zAYuQjKBQgmuNg==", "license": "MIT", "dependencies": { "cliui": "^9.0.1", @@ -3767,25 +1195,10 @@ }, "node_modules/yargs-parser": { "version": "22.0.0", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-22.0.0.tgz", - "integrity": "sha512-rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1/NRG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw==", "license": "ISC", "engines": { "node": "^20.19.0 || ^22.12.0 || >=23" } - }, - "node_modules/yocto-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } } } } diff --git a/schema/package.json b/schema/package.json index dbc49096..b133d2fa 100644 --- a/schema/package.json +++ b/schema/package.json @@ -3,16 +3,16 @@ "name": "prompty-schema", "description": "TypeSpec models and code generation for Prompty", "scripts": { - "build:emitter": "cd emitter && npm run build", "format:tsp": "npx tsp format \"model/**/*.tsp\"", "format:tsp:check": "npx tsp format \"model/**/*.tsp\" --check", "format:rust": "cargo fmt --all --manifest-path ../runtime/rust/prompty/Cargo.toml", - "generate": "npx tsp compile model/main.tsp --config tspconfig.yaml", - "build": "npm run build:emitter && npm run format:tsp && npm run generate && npm run format:rust" + "generate": "npx tsp compile model/main.tsp --config tspconfig.yaml && node scripts/normalize-typra-output.mjs", + "verify:typra": "node scripts/verify-typra.mjs", + "build": "npm run format:tsp && npm run generate && npm run format:rust" }, "dependencies": { - "prompty-emitter": "file:./emitter", - "@typespec/compiler": "latest", - "@typespec/json-schema": "latest" + "@typespec/compiler": "1.10.0", + "@typespec/json-schema": "1.10.0", + "@typra/emitter": "0.3.1" } } diff --git a/schema/scripts/normalize-typra-output.mjs b/schema/scripts/normalize-typra-output.mjs new file mode 100644 index 00000000..98544872 --- /dev/null +++ b/schema/scripts/normalize-typra-output.mjs @@ -0,0 +1,58 @@ +import { existsSync, readdirSync, readFileSync, statSync, writeFileSync } from "node:fs"; +import { join } from "node:path"; + +const metadataRoot = join("tsp-output", ".typra-generated"); +const manifestPath = join(metadataRoot, "manifest.json"); + +if (existsSync(manifestPath)) { + const manifest = JSON.parse(readFileSync(manifestPath, "utf8")); + manifest.generatedAt = ""; + writeFileSync(manifestPath, `${JSON.stringify(manifest, null, 2)}\n`); +} + +trimEmptyPythonGeneratedTests(join("..", "runtime", "python", "prompty", "tests", "model")); +trimTrailingWhitespace(join("..", "runtime", "go", "prompty", "model")); + +function trimEmptyPythonGeneratedTests(root) { + if (!existsSync(root)) { + return; + } + for (const entry of readdirSync(root)) { + const path = join(root, entry); + if (statSync(path).isDirectory()) { + trimEmptyPythonGeneratedTests(path); + continue; + } + if (!path.endsWith(".py")) { + continue; + } + const content = readFileSync(path, "utf8"); + if (content.trim() === "# ") { + writeFileSync(path, "# \n"); + } + } +} + +function trimTrailingWhitespace(root) { + if (!existsSync(root)) { + return; + } + for (const entry of readdirSync(root)) { + const path = join(root, entry); + if (statSync(path).isDirectory()) { + trimTrailingWhitespace(path); + continue; + } + if (!path.endsWith(".go")) { + continue; + } + const content = readFileSync(path, "utf8"); + const normalized = content + .split("\n") + .map((line) => line.replace(/[ \t]+$/u, "")) + .join("\n"); + if (normalized !== content) { + writeFileSync(path, normalized); + } + } +} diff --git a/schema/scripts/verify-typra.mjs b/schema/scripts/verify-typra.mjs new file mode 100644 index 00000000..e7b6d1d1 --- /dev/null +++ b/schema/scripts/verify-typra.mjs @@ -0,0 +1,59 @@ +import { execFileSync, spawnSync } from "node:child_process"; +import { existsSync, mkdirSync, rmSync, writeFileSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { dirname, join } from "node:path"; + +const outputRel = "schema/tsp-output"; +const verifierFiles = [ + ".typra-generated/export-surfaces.json", + ".typra-generated/manifest.json", + ".typra-generated/hydration-seams.json", + "json-ast/model.json", +]; +const repoRoot = execFileSync("git", ["rev-parse", "--show-toplevel"], { encoding: "utf8" }).trim(); +const currentRoot = join(repoRoot, outputRel); + +for (const file of verifierFiles) { + const currentPath = join(currentRoot, file); + if (!existsSync(currentPath)) { + throw new Error(`Missing current Typra verifier input: ${currentPath}. Run npm run generate first.`); + } +} + +const baselineRoot = join(tmpdir(), `prompty-typra-baseline-${process.pid}-${Date.now()}`); +mkdirSync(baselineRoot, { recursive: true }); + +let hasCommittedBaseline = true; +try { + for (const file of verifierFiles) { + const relPath = `${outputRel}/${file}`; + const content = execFileSync("git", ["show", `HEAD:${relPath}`], { + cwd: repoRoot, + encoding: "utf8", + stdio: ["ignore", "pipe", "pipe"], + }); + mkdirSync(dirname(join(baselineRoot, file)), { recursive: true }); + writeFileSync(join(baselineRoot, file), content); + } +} catch { + hasCommittedBaseline = false; +} + +if (!hasCommittedBaseline) { + console.warn("No committed Typra verifier baseline found at HEAD; verified current inputs exist."); + rmSync(baselineRoot, { recursive: true, force: true }); + process.exit(0); +} + +const typraVerify = + process.platform === "win32" + ? join(repoRoot, "schema", "node_modules", ".bin", "typra-verify.cmd") + : join(repoRoot, "schema", "node_modules", ".bin", "typra-verify"); +const result = spawnSync(typraVerify, ["--baseline", baselineRoot, "--current", currentRoot], { + cwd: repoRoot, + shell: process.platform === "win32", + stdio: "inherit", +}); + +rmSync(baselineRoot, { recursive: true, force: true }); +process.exit(result.status ?? 1); diff --git a/schema/tsp-output/.typra-generated/export-surfaces.json b/schema/tsp-output/.typra-generated/export-surfaces.json new file mode 100644 index 00000000..5539deb9 --- /dev/null +++ b/schema/tsp-output/.typra-generated/export-surfaces.json @@ -0,0 +1,9782 @@ +{ + "emitter": "typra-emitter", + "version": 1, + "toolchain": { + "packages": [ + { + "name": "@typespec/compiler", + "version": "1.10.0", + "supportedRange": "1.10.0", + "supported": true + }, + { + "name": "@typespec/json-schema", + "version": "1.10.0", + "supportedRange": "1.10.0", + "supported": true + }, + { + "name": "@typra/emitter", + "version": "0.3.1", + "supportedRange": "0.3.1", + "supported": true + } + ] + }, + "root": { + "object": "Prompty.Prompty", + "namespace": "Prompty", + "alias": "Prompty" + }, + "targets": [ + { + "target": "csharp", + "outputRoot": "../runtime/csharp/Prompty.Core/Model", + "namespace": "Prompty.Core", + "rootExports": [ + "AnonymousConnection", + "AnthropicImageBlock", + "AnthropicImageSource", + "AnthropicMessagesRequest", + "AnthropicMessagesResponse", + "AnthropicTextBlock", + "AnthropicToolDefinition", + "AnthropicToolResultBlock", + "AnthropicToolUseBlock", + "AnthropicUsage", + "AnthropicWireMessage", + "ApiKeyConnection", + "ArrayProperty", + "AudioPart", + "Binding", + "Checkpoint", + "CheckpointStore", + "CompactionCompletePayload", + "CompactionConfig", + "CompactionFailedPayload", + "CompactionStartPayload", + "Connection", + "ContentPart", + "CustomTool", + "DoneEventPayload", + "ErrorChunk", + "ErrorEventPayload", + "EventJournalWriter", + "EventSink", + "Executor", + "FileNotFoundError", + "FilePart", + "FormatConfig", + "FoundryConnection", + "FunctionTool", + "GuardrailResult", + "HarnessContext", + "HookEndPayload", + "HookStartPayload", + "HostToolExecutor", + "HostToolRequest", + "HostToolResult", + "ImagePart", + "InvokerError", + "LlmCompletePayload", + "LlmStartPayload", + "McpApprovalMode", + "McpTool", + "Message", + "MessagesUpdatedPayload", + "Model", + "ModelInfo", + "ModelOptions", + "OAuthConnection", + "ObjectProperty", + "OpenApiTool", + "Parser", + "ParserConfig", + "PermissionCompletedPayload", + "PermissionDecision", + "PermissionRequest", + "PermissionRequestedPayload", + "PermissionResolver", + "Processor", + "Prompty", + "PromptyTool", + "Property", + "RedactedField", + "RedactionMetadata", + "ReferenceConnection", + "RemoteConnection", + "Renderer", + "ReplayJournalRecord", + "ReplayMismatch", + "ReplayVerificationRequest", + "ReplayVerificationResult", + "RetryPayload", + "RunTurnRequest", + "RunTurnResult", + "SessionEndPayload", + "SessionEvent", + "SessionFileRef", + "SessionRef", + "SessionStartPayload", + "SessionSummary", + "SessionTrace", + "SessionWarningPayload", + "StatusEventPayload", + "StreamChunk", + "StreamOptions", + "Template", + "TextChunk", + "TextPart", + "ThinkingChunk", + "ThinkingEventPayload", + "ThreadMarker", + "TokenEventPayload", + "TokenUsage", + "Tool", + "ToolCall", + "ToolCallCompletePayload", + "ToolCallStartPayload", + "ToolChunk", + "ToolContext", + "ToolDispatchResult", + "ToolExecutionCompletePayload", + "ToolExecutionStartPayload", + "ToolResult", + "ToolResultPayload", + "TraceFile", + "TraceSpan", + "TraceTime", + "TrajectoryEvent", + "TurnEndPayload", + "TurnEvent", + "TurnModelRequest", + "TurnModelResponse", + "TurnOptions", + "TurnStartPayload", + "TurnSummary", + "TurnTrace", + "ValidationError", + "ValidationResult" + ], + "exports": [ + { + "name": "GuardrailResult", + "kind": "value", + "group": "agent", + "source": "agent/GuardrailResult.cs", + "protocol": false + }, + { + "name": "Prompty", + "kind": "value", + "group": "agent", + "source": "agent/Prompty.cs", + "protocol": false + }, + { + "name": "AnonymousConnection", + "kind": "value", + "group": "connection", + "source": "connection/Connection.cs", + "protocol": false + }, + { + "name": "ApiKeyConnection", + "kind": "value", + "group": "connection", + "source": "connection/Connection.cs", + "protocol": false + }, + { + "name": "Connection", + "kind": "value", + "group": "connection", + "source": "connection/Connection.cs", + "protocol": false + }, + { + "name": "FoundryConnection", + "kind": "value", + "group": "connection", + "source": "connection/Connection.cs", + "protocol": false + }, + { + "name": "OAuthConnection", + "kind": "value", + "group": "connection", + "source": "connection/Connection.cs", + "protocol": false + }, + { + "name": "ReferenceConnection", + "kind": "value", + "group": "connection", + "source": "connection/Connection.cs", + "protocol": false + }, + { + "name": "RemoteConnection", + "kind": "value", + "group": "connection", + "source": "connection/Connection.cs", + "protocol": false + }, + { + "name": "AudioPart", + "kind": "value", + "group": "conversation", + "source": "conversation/ContentPart.cs", + "protocol": false + }, + { + "name": "ContentPart", + "kind": "value", + "group": "conversation", + "source": "conversation/ContentPart.cs", + "protocol": false + }, + { + "name": "FilePart", + "kind": "value", + "group": "conversation", + "source": "conversation/ContentPart.cs", + "protocol": false + }, + { + "name": "ImagePart", + "kind": "value", + "group": "conversation", + "source": "conversation/ContentPart.cs", + "protocol": false + }, + { + "name": "Message", + "kind": "value", + "group": "conversation", + "source": "conversation/Message.cs", + "protocol": false + }, + { + "name": "TextPart", + "kind": "value", + "group": "conversation", + "source": "conversation/ContentPart.cs", + "protocol": false + }, + { + "name": "ThreadMarker", + "kind": "value", + "group": "conversation", + "source": "conversation/ThreadMarker.cs", + "protocol": false + }, + { + "name": "ToolCall", + "kind": "value", + "group": "conversation", + "source": "conversation/ToolCall.cs", + "protocol": false + }, + { + "name": "ToolResult", + "kind": "value", + "group": "conversation", + "source": "conversation/ToolResult.cs", + "protocol": false + }, + { + "name": "ArrayProperty", + "kind": "value", + "group": "core", + "source": "core/Property.cs", + "protocol": false + }, + { + "name": "FileNotFoundError", + "kind": "value", + "group": "core", + "source": "core/FileNotFoundError.cs", + "protocol": false + }, + { + "name": "InvokerError", + "kind": "value", + "group": "core", + "source": "core/InvokerError.cs", + "protocol": false + }, + { + "name": "ObjectProperty", + "kind": "value", + "group": "core", + "source": "core/Property.cs", + "protocol": false + }, + { + "name": "Property", + "kind": "value", + "group": "core", + "source": "core/Property.cs", + "protocol": false + }, + { + "name": "ValidationError", + "kind": "value", + "group": "core", + "source": "core/ValidationError.cs", + "protocol": false + }, + { + "name": "ValidationResult", + "kind": "value", + "group": "core", + "source": "core/ValidationResult.cs", + "protocol": false + }, + { + "name": "Checkpoint", + "kind": "value", + "group": "events", + "source": "events/Checkpoint.cs", + "protocol": false + }, + { + "name": "CompactionCompletePayload", + "kind": "value", + "group": "events", + "source": "events/CompactionCompletePayload.cs", + "protocol": false + }, + { + "name": "CompactionFailedPayload", + "kind": "value", + "group": "events", + "source": "events/CompactionFailedPayload.cs", + "protocol": false + }, + { + "name": "CompactionStartPayload", + "kind": "value", + "group": "events", + "source": "events/CompactionStartPayload.cs", + "protocol": false + }, + { + "name": "DoneEventPayload", + "kind": "value", + "group": "events", + "source": "events/DoneEventPayload.cs", + "protocol": false + }, + { + "name": "ErrorChunk", + "kind": "value", + "group": "events", + "source": "events/StreamChunk.cs", + "protocol": false + }, + { + "name": "ErrorEventPayload", + "kind": "value", + "group": "events", + "source": "events/ErrorEventPayload.cs", + "protocol": false + }, + { + "name": "HarnessContext", + "kind": "value", + "group": "events", + "source": "events/HarnessContext.cs", + "protocol": false + }, + { + "name": "HookEndPayload", + "kind": "value", + "group": "events", + "source": "events/HookEndPayload.cs", + "protocol": false + }, + { + "name": "HookStartPayload", + "kind": "value", + "group": "events", + "source": "events/HookStartPayload.cs", + "protocol": false + }, + { + "name": "HostToolRequest", + "kind": "value", + "group": "events", + "source": "events/HostToolRequest.cs", + "protocol": false + }, + { + "name": "HostToolResult", + "kind": "value", + "group": "events", + "source": "events/HostToolResult.cs", + "protocol": false + }, + { + "name": "LlmCompletePayload", + "kind": "value", + "group": "events", + "source": "events/LlmCompletePayload.cs", + "protocol": false + }, + { + "name": "LlmStartPayload", + "kind": "value", + "group": "events", + "source": "events/LlmStartPayload.cs", + "protocol": false + }, + { + "name": "MessagesUpdatedPayload", + "kind": "value", + "group": "events", + "source": "events/MessagesUpdatedPayload.cs", + "protocol": false + }, + { + "name": "PermissionCompletedPayload", + "kind": "value", + "group": "events", + "source": "events/PermissionCompletedPayload.cs", + "protocol": false + }, + { + "name": "PermissionDecision", + "kind": "value", + "group": "events", + "source": "events/PermissionDecision.cs", + "protocol": false + }, + { + "name": "PermissionRequest", + "kind": "value", + "group": "events", + "source": "events/PermissionRequest.cs", + "protocol": false + }, + { + "name": "PermissionRequestedPayload", + "kind": "value", + "group": "events", + "source": "events/PermissionRequestedPayload.cs", + "protocol": false + }, + { + "name": "RedactedField", + "kind": "value", + "group": "events", + "source": "events/RedactedField.cs", + "protocol": false + }, + { + "name": "RedactionMetadata", + "kind": "value", + "group": "events", + "source": "events/RedactionMetadata.cs", + "protocol": false + }, + { + "name": "RetryPayload", + "kind": "value", + "group": "events", + "source": "events/RetryPayload.cs", + "protocol": false + }, + { + "name": "SessionEndPayload", + "kind": "value", + "group": "events", + "source": "events/SessionEndPayload.cs", + "protocol": false + }, + { + "name": "SessionEvent", + "kind": "value", + "group": "events", + "source": "events/SessionEvent.cs", + "protocol": false + }, + { + "name": "SessionFileRef", + "kind": "value", + "group": "events", + "source": "events/SessionFileRef.cs", + "protocol": false + }, + { + "name": "SessionRef", + "kind": "value", + "group": "events", + "source": "events/SessionRef.cs", + "protocol": false + }, + { + "name": "SessionStartPayload", + "kind": "value", + "group": "events", + "source": "events/SessionStartPayload.cs", + "protocol": false + }, + { + "name": "SessionSummary", + "kind": "value", + "group": "events", + "source": "events/SessionSummary.cs", + "protocol": false + }, + { + "name": "SessionTrace", + "kind": "value", + "group": "events", + "source": "events/SessionTrace.cs", + "protocol": false + }, + { + "name": "SessionWarningPayload", + "kind": "value", + "group": "events", + "source": "events/SessionWarningPayload.cs", + "protocol": false + }, + { + "name": "StatusEventPayload", + "kind": "value", + "group": "events", + "source": "events/StatusEventPayload.cs", + "protocol": false + }, + { + "name": "StreamChunk", + "kind": "value", + "group": "events", + "source": "events/StreamChunk.cs", + "protocol": false + }, + { + "name": "TextChunk", + "kind": "value", + "group": "events", + "source": "events/StreamChunk.cs", + "protocol": false + }, + { + "name": "ThinkingChunk", + "kind": "value", + "group": "events", + "source": "events/StreamChunk.cs", + "protocol": false + }, + { + "name": "ThinkingEventPayload", + "kind": "value", + "group": "events", + "source": "events/ThinkingEventPayload.cs", + "protocol": false + }, + { + "name": "TokenEventPayload", + "kind": "value", + "group": "events", + "source": "events/TokenEventPayload.cs", + "protocol": false + }, + { + "name": "ToolCallCompletePayload", + "kind": "value", + "group": "events", + "source": "events/ToolCallCompletePayload.cs", + "protocol": false + }, + { + "name": "ToolCallStartPayload", + "kind": "value", + "group": "events", + "source": "events/ToolCallStartPayload.cs", + "protocol": false + }, + { + "name": "ToolChunk", + "kind": "value", + "group": "events", + "source": "events/StreamChunk.cs", + "protocol": false + }, + { + "name": "ToolExecutionCompletePayload", + "kind": "value", + "group": "events", + "source": "events/ToolExecutionCompletePayload.cs", + "protocol": false + }, + { + "name": "ToolExecutionStartPayload", + "kind": "value", + "group": "events", + "source": "events/ToolExecutionStartPayload.cs", + "protocol": false + }, + { + "name": "ToolResultPayload", + "kind": "value", + "group": "events", + "source": "events/ToolResultPayload.cs", + "protocol": false + }, + { + "name": "TrajectoryEvent", + "kind": "value", + "group": "events", + "source": "events/TrajectoryEvent.cs", + "protocol": false + }, + { + "name": "TurnEndPayload", + "kind": "value", + "group": "events", + "source": "events/TurnEndPayload.cs", + "protocol": false + }, + { + "name": "TurnEvent", + "kind": "value", + "group": "events", + "source": "events/TurnEvent.cs", + "protocol": false + }, + { + "name": "TurnStartPayload", + "kind": "value", + "group": "events", + "source": "events/TurnStartPayload.cs", + "protocol": false + }, + { + "name": "TurnSummary", + "kind": "value", + "group": "events", + "source": "events/TurnSummary.cs", + "protocol": false + }, + { + "name": "TurnTrace", + "kind": "value", + "group": "events", + "source": "events/TurnTrace.cs", + "protocol": false + }, + { + "name": "Model", + "kind": "value", + "group": "model", + "source": "model/Model.cs", + "protocol": false + }, + { + "name": "ModelInfo", + "kind": "value", + "group": "model", + "source": "model/ModelInfo.cs", + "protocol": false + }, + { + "name": "ModelOptions", + "kind": "value", + "group": "model", + "source": "model/ModelOptions.cs", + "protocol": false + }, + { + "name": "TokenUsage", + "kind": "value", + "group": "model", + "source": "model/TokenUsage.cs", + "protocol": false + }, + { + "name": "CheckpointStore", + "kind": "type", + "group": "pipeline", + "source": "pipeline/CheckpointStore.cs", + "protocol": true + }, + { + "name": "CompactionConfig", + "kind": "value", + "group": "pipeline", + "source": "pipeline/CompactionConfig.cs", + "protocol": false + }, + { + "name": "EventJournalWriter", + "kind": "type", + "group": "pipeline", + "source": "pipeline/EventJournalWriter.cs", + "protocol": true + }, + { + "name": "EventSink", + "kind": "type", + "group": "pipeline", + "source": "pipeline/EventSink.cs", + "protocol": true + }, + { + "name": "Executor", + "kind": "type", + "group": "pipeline", + "source": "pipeline/Executor.cs", + "protocol": true + }, + { + "name": "HostToolExecutor", + "kind": "type", + "group": "pipeline", + "source": "pipeline/HostToolExecutor.cs", + "protocol": true + }, + { + "name": "Parser", + "kind": "type", + "group": "pipeline", + "source": "pipeline/Parser.cs", + "protocol": true + }, + { + "name": "PermissionResolver", + "kind": "type", + "group": "pipeline", + "source": "pipeline/PermissionResolver.cs", + "protocol": true + }, + { + "name": "Processor", + "kind": "type", + "group": "pipeline", + "source": "pipeline/Processor.cs", + "protocol": true + }, + { + "name": "Renderer", + "kind": "type", + "group": "pipeline", + "source": "pipeline/Renderer.cs", + "protocol": true + }, + { + "name": "ReplayJournalRecord", + "kind": "value", + "group": "pipeline", + "source": "pipeline/ReplayJournalRecord.cs", + "protocol": false + }, + { + "name": "ReplayMismatch", + "kind": "value", + "group": "pipeline", + "source": "pipeline/ReplayMismatch.cs", + "protocol": false + }, + { + "name": "ReplayVerificationRequest", + "kind": "value", + "group": "pipeline", + "source": "pipeline/ReplayVerificationRequest.cs", + "protocol": false + }, + { + "name": "ReplayVerificationResult", + "kind": "value", + "group": "pipeline", + "source": "pipeline/ReplayVerificationResult.cs", + "protocol": false + }, + { + "name": "RunTurnRequest", + "kind": "value", + "group": "pipeline", + "source": "pipeline/RunTurnRequest.cs", + "protocol": false + }, + { + "name": "RunTurnResult", + "kind": "value", + "group": "pipeline", + "source": "pipeline/RunTurnResult.cs", + "protocol": false + }, + { + "name": "TurnModelRequest", + "kind": "value", + "group": "pipeline", + "source": "pipeline/TurnModelRequest.cs", + "protocol": false + }, + { + "name": "TurnModelResponse", + "kind": "value", + "group": "pipeline", + "source": "pipeline/TurnModelResponse.cs", + "protocol": false + }, + { + "name": "TurnOptions", + "kind": "value", + "group": "pipeline", + "source": "pipeline/TurnOptions.cs", + "protocol": false + }, + { + "name": "StreamOptions", + "kind": "value", + "group": "streaming", + "source": "streaming/StreamOptions.cs", + "protocol": false + }, + { + "name": "FormatConfig", + "kind": "value", + "group": "template", + "source": "template/FormatConfig.cs", + "protocol": false + }, + { + "name": "ParserConfig", + "kind": "value", + "group": "template", + "source": "template/ParserConfig.cs", + "protocol": false + }, + { + "name": "Template", + "kind": "value", + "group": "template", + "source": "template/Template.cs", + "protocol": false + }, + { + "name": "Binding", + "kind": "value", + "group": "tools", + "source": "tools/Binding.cs", + "protocol": false + }, + { + "name": "CustomTool", + "kind": "value", + "group": "tools", + "source": "tools/Tool.cs", + "protocol": false + }, + { + "name": "FunctionTool", + "kind": "value", + "group": "tools", + "source": "tools/Tool.cs", + "protocol": false + }, + { + "name": "McpApprovalMode", + "kind": "value", + "group": "tools", + "source": "tools/McpApprovalMode.cs", + "protocol": false + }, + { + "name": "McpTool", + "kind": "value", + "group": "tools", + "source": "tools/Tool.cs", + "protocol": false + }, + { + "name": "OpenApiTool", + "kind": "value", + "group": "tools", + "source": "tools/Tool.cs", + "protocol": false + }, + { + "name": "PromptyTool", + "kind": "value", + "group": "tools", + "source": "tools/Tool.cs", + "protocol": false + }, + { + "name": "Tool", + "kind": "value", + "group": "tools", + "source": "tools/Tool.cs", + "protocol": false + }, + { + "name": "ToolContext", + "kind": "value", + "group": "tools", + "source": "tools/ToolContext.cs", + "protocol": false + }, + { + "name": "ToolDispatchResult", + "kind": "value", + "group": "tools", + "source": "tools/ToolDispatchResult.cs", + "protocol": false + }, + { + "name": "TraceFile", + "kind": "value", + "group": "tracing", + "source": "tracing/TraceFile.cs", + "protocol": false + }, + { + "name": "TraceSpan", + "kind": "value", + "group": "tracing", + "source": "tracing/TraceSpan.cs", + "protocol": false + }, + { + "name": "TraceTime", + "kind": "value", + "group": "tracing", + "source": "tracing/TraceTime.cs", + "protocol": false + }, + { + "name": "AnthropicImageBlock", + "kind": "value", + "group": "wire", + "source": "wire/AnthropicImageBlock.cs", + "protocol": false + }, + { + "name": "AnthropicImageSource", + "kind": "value", + "group": "wire", + "source": "wire/AnthropicImageSource.cs", + "protocol": false + }, + { + "name": "AnthropicMessagesRequest", + "kind": "value", + "group": "wire", + "source": "wire/AnthropicMessagesRequest.cs", + "protocol": false + }, + { + "name": "AnthropicMessagesResponse", + "kind": "value", + "group": "wire", + "source": "wire/AnthropicMessagesResponse.cs", + "protocol": false + }, + { + "name": "AnthropicTextBlock", + "kind": "value", + "group": "wire", + "source": "wire/AnthropicTextBlock.cs", + "protocol": false + }, + { + "name": "AnthropicToolDefinition", + "kind": "value", + "group": "wire", + "source": "wire/AnthropicToolDefinition.cs", + "protocol": false + }, + { + "name": "AnthropicToolResultBlock", + "kind": "value", + "group": "wire", + "source": "wire/AnthropicToolResultBlock.cs", + "protocol": false + }, + { + "name": "AnthropicToolUseBlock", + "kind": "value", + "group": "wire", + "source": "wire/AnthropicToolUseBlock.cs", + "protocol": false + }, + { + "name": "AnthropicUsage", + "kind": "value", + "group": "wire", + "source": "wire/AnthropicUsage.cs", + "protocol": false + }, + { + "name": "AnthropicWireMessage", + "kind": "value", + "group": "wire", + "source": "wire/AnthropicWireMessage.cs", + "protocol": false + } + ], + "groups": [ + { + "name": "agent", + "exports": [ + "GuardrailResult", + "Prompty" + ], + "modules": [ + "GuardrailResult.cs", + "Prompty.cs" + ] + }, + { + "name": "connection", + "exports": [ + "AnonymousConnection", + "ApiKeyConnection", + "Connection", + "FoundryConnection", + "OAuthConnection", + "ReferenceConnection", + "RemoteConnection" + ], + "modules": [ + "Connection.cs" + ] + }, + { + "name": "conversation", + "exports": [ + "AudioPart", + "ContentPart", + "FilePart", + "ImagePart", + "Message", + "TextPart", + "ThreadMarker", + "ToolCall", + "ToolResult" + ], + "modules": [ + "ContentPart.cs", + "Message.cs", + "ThreadMarker.cs", + "ToolCall.cs", + "ToolResult.cs" + ] + }, + { + "name": "core", + "exports": [ + "ArrayProperty", + "FileNotFoundError", + "InvokerError", + "ObjectProperty", + "Property", + "ValidationError", + "ValidationResult" + ], + "modules": [ + "FileNotFoundError.cs", + "InvokerError.cs", + "Property.cs", + "ValidationError.cs", + "ValidationResult.cs" + ] + }, + { + "name": "events", + "exports": [ + "Checkpoint", + "CompactionCompletePayload", + "CompactionFailedPayload", + "CompactionStartPayload", + "DoneEventPayload", + "ErrorChunk", + "ErrorEventPayload", + "HarnessContext", + "HookEndPayload", + "HookStartPayload", + "HostToolRequest", + "HostToolResult", + "LlmCompletePayload", + "LlmStartPayload", + "MessagesUpdatedPayload", + "PermissionCompletedPayload", + "PermissionDecision", + "PermissionRequest", + "PermissionRequestedPayload", + "RedactedField", + "RedactionMetadata", + "RetryPayload", + "SessionEndPayload", + "SessionEvent", + "SessionFileRef", + "SessionRef", + "SessionStartPayload", + "SessionSummary", + "SessionTrace", + "SessionWarningPayload", + "StatusEventPayload", + "StreamChunk", + "TextChunk", + "ThinkingChunk", + "ThinkingEventPayload", + "TokenEventPayload", + "ToolCallCompletePayload", + "ToolCallStartPayload", + "ToolChunk", + "ToolExecutionCompletePayload", + "ToolExecutionStartPayload", + "ToolResultPayload", + "TrajectoryEvent", + "TurnEndPayload", + "TurnEvent", + "TurnStartPayload", + "TurnSummary", + "TurnTrace" + ], + "modules": [ + "Checkpoint.cs", + "CompactionCompletePayload.cs", + "CompactionFailedPayload.cs", + "CompactionStartPayload.cs", + "DoneEventPayload.cs", + "ErrorEventPayload.cs", + "HarnessContext.cs", + "HookEndPayload.cs", + "HookStartPayload.cs", + "HostToolRequest.cs", + "HostToolResult.cs", + "LlmCompletePayload.cs", + "LlmStartPayload.cs", + "MessagesUpdatedPayload.cs", + "PermissionCompletedPayload.cs", + "PermissionDecision.cs", + "PermissionRequest.cs", + "PermissionRequestedPayload.cs", + "RedactedField.cs", + "RedactionMetadata.cs", + "RetryPayload.cs", + "SessionEndPayload.cs", + "SessionEvent.cs", + "SessionFileRef.cs", + "SessionRef.cs", + "SessionStartPayload.cs", + "SessionSummary.cs", + "SessionTrace.cs", + "SessionWarningPayload.cs", + "StatusEventPayload.cs", + "StreamChunk.cs", + "ThinkingEventPayload.cs", + "TokenEventPayload.cs", + "ToolCallCompletePayload.cs", + "ToolCallStartPayload.cs", + "ToolExecutionCompletePayload.cs", + "ToolExecutionStartPayload.cs", + "ToolResultPayload.cs", + "TrajectoryEvent.cs", + "TurnEndPayload.cs", + "TurnEvent.cs", + "TurnStartPayload.cs", + "TurnSummary.cs", + "TurnTrace.cs" + ] + }, + { + "name": "model", + "exports": [ + "Model", + "ModelInfo", + "ModelOptions", + "TokenUsage" + ], + "modules": [ + "Model.cs", + "ModelInfo.cs", + "ModelOptions.cs", + "TokenUsage.cs" + ] + }, + { + "name": "pipeline", + "exports": [ + "CheckpointStore", + "CompactionConfig", + "EventJournalWriter", + "EventSink", + "Executor", + "HostToolExecutor", + "Parser", + "PermissionResolver", + "Processor", + "Renderer", + "ReplayJournalRecord", + "ReplayMismatch", + "ReplayVerificationRequest", + "ReplayVerificationResult", + "RunTurnRequest", + "RunTurnResult", + "TurnModelRequest", + "TurnModelResponse", + "TurnOptions" + ], + "modules": [ + "CheckpointStore.cs", + "CompactionConfig.cs", + "EventJournalWriter.cs", + "EventSink.cs", + "Executor.cs", + "HostToolExecutor.cs", + "Parser.cs", + "PermissionResolver.cs", + "Processor.cs", + "Renderer.cs", + "ReplayJournalRecord.cs", + "ReplayMismatch.cs", + "ReplayVerificationRequest.cs", + "ReplayVerificationResult.cs", + "RunTurnRequest.cs", + "RunTurnResult.cs", + "TurnModelRequest.cs", + "TurnModelResponse.cs", + "TurnOptions.cs" + ] + }, + { + "name": "streaming", + "exports": [ + "StreamOptions" + ], + "modules": [ + "StreamOptions.cs" + ] + }, + { + "name": "template", + "exports": [ + "FormatConfig", + "ParserConfig", + "Template" + ], + "modules": [ + "FormatConfig.cs", + "ParserConfig.cs", + "Template.cs" + ] + }, + { + "name": "tools", + "exports": [ + "Binding", + "CustomTool", + "FunctionTool", + "McpApprovalMode", + "McpTool", + "OpenApiTool", + "PromptyTool", + "Tool", + "ToolContext", + "ToolDispatchResult" + ], + "modules": [ + "Binding.cs", + "McpApprovalMode.cs", + "Tool.cs", + "ToolContext.cs", + "ToolDispatchResult.cs" + ] + }, + { + "name": "tracing", + "exports": [ + "TraceFile", + "TraceSpan", + "TraceTime" + ], + "modules": [ + "TraceFile.cs", + "TraceSpan.cs", + "TraceTime.cs" + ] + }, + { + "name": "wire", + "exports": [ + "AnthropicImageBlock", + "AnthropicImageSource", + "AnthropicMessagesRequest", + "AnthropicMessagesResponse", + "AnthropicTextBlock", + "AnthropicToolDefinition", + "AnthropicToolResultBlock", + "AnthropicToolUseBlock", + "AnthropicUsage", + "AnthropicWireMessage" + ], + "modules": [ + "AnthropicImageBlock.cs", + "AnthropicImageSource.cs", + "AnthropicMessagesRequest.cs", + "AnthropicMessagesResponse.cs", + "AnthropicTextBlock.cs", + "AnthropicToolDefinition.cs", + "AnthropicToolResultBlock.cs", + "AnthropicToolUseBlock.cs", + "AnthropicUsage.cs", + "AnthropicWireMessage.cs" + ] + } + ], + "protocols": [ + { + "name": "CheckpointStore", + "group": "pipeline", + "symbol": "CheckpointStore", + "source": "pipeline/CheckpointStore.cs", + "methods": [ + { + "name": "listCheckpoints", + "returns": "Checkpoint[]", + "params": { + "sessionId": "string" + }, + "optional": false, + "sync": false + }, + { + "name": "load", + "returns": "Checkpoint?", + "params": { + "checkpointId": "string", + "sessionId": "string" + }, + "optional": false, + "sync": false + }, + { + "name": "save", + "returns": "Checkpoint", + "params": { + "checkpoint": "Checkpoint" + }, + "optional": false, + "sync": false + } + ] + }, + { + "name": "EventJournalWriter", + "group": "pipeline", + "symbol": "EventJournalWriter", + "source": "pipeline/EventJournalWriter.cs", + "methods": [ + { + "name": "appendSession", + "returns": "boolean", + "params": { + "sessionEvent": "SessionEvent" + }, + "optional": false, + "sync": true + }, + { + "name": "appendTurn", + "returns": "boolean", + "params": { + "turnEvent": "TurnEvent" + }, + "optional": false, + "sync": true + }, + { + "name": "close", + "returns": "boolean", + "params": { + "summary": "SessionSummary?" + }, + "optional": false, + "sync": true + } + ] + }, + { + "name": "EventSink", + "group": "pipeline", + "symbol": "EventSink", + "source": "pipeline/EventSink.cs", + "methods": [ + { + "name": "emitSession", + "returns": "boolean", + "params": { + "sessionEvent": "SessionEvent" + }, + "optional": false, + "sync": true + }, + { + "name": "emitTurn", + "returns": "boolean", + "params": { + "turnEvent": "TurnEvent" + }, + "optional": false, + "sync": true + } + ] + }, + { + "name": "Executor", + "group": "pipeline", + "symbol": "Executor", + "source": "pipeline/Executor.cs", + "methods": [ + { + "name": "execute", + "returns": "unknown", + "params": { + "agent": "Prompty", + "messages": "Message[]" + }, + "optional": false, + "sync": false + }, + { + "name": "executeStream", + "returns": "unknown", + "params": { + "agent": "Prompty", + "messages": "Message[]" + }, + "optional": true, + "sync": false + }, + { + "name": "formatToolMessages", + "returns": "Message[]", + "params": { + "rawResponse": "unknown", + "textContent": "string?", + "toolCalls": "ToolCall[]", + "toolResults": "string[]" + }, + "optional": false, + "sync": true + } + ] + }, + { + "name": "HostToolExecutor", + "group": "pipeline", + "symbol": "HostToolExecutor", + "source": "pipeline/HostToolExecutor.cs", + "methods": [ + { + "name": "execute", + "returns": "HostToolResult", + "params": { + "request": "HostToolRequest" + }, + "optional": false, + "sync": false + } + ] + }, + { + "name": "Parser", + "group": "pipeline", + "symbol": "Parser", + "source": "pipeline/Parser.cs", + "methods": [ + { + "name": "parse", + "returns": "Message[]", + "params": { + "agent": "Prompty", + "context": "Record?", + "rendered": "string" + }, + "optional": false, + "sync": false + }, + { + "name": "preRender", + "returns": "unknown?", + "params": { + "template": "string" + }, + "optional": true, + "sync": true + } + ] + }, + { + "name": "PermissionResolver", + "group": "pipeline", + "symbol": "PermissionResolver", + "source": "pipeline/PermissionResolver.cs", + "methods": [ + { + "name": "request", + "returns": "PermissionDecision", + "params": { + "request": "PermissionRequest" + }, + "optional": false, + "sync": false + } + ] + }, + { + "name": "Processor", + "group": "pipeline", + "symbol": "Processor", + "source": "pipeline/Processor.cs", + "methods": [ + { + "name": "process", + "returns": "unknown", + "params": { + "agent": "Prompty", + "response": "unknown" + }, + "optional": false, + "sync": false + }, + { + "name": "processStream", + "returns": "unknown", + "params": { + "stream": "unknown" + }, + "optional": true, + "sync": false + } + ] + }, + { + "name": "Renderer", + "group": "pipeline", + "symbol": "Renderer", + "source": "pipeline/Renderer.cs", + "methods": [ + { + "name": "render", + "returns": "string", + "params": { + "agent": "Prompty", + "inputs": "Record", + "template": "string" + }, + "optional": false, + "sync": false + } + ] + } + ], + "modules": [ + "agent/GuardrailResult.cs", + "agent/Prompty.cs", + "connection/Connection.cs", + "conversation/ContentPart.cs", + "conversation/Message.cs", + "conversation/ThreadMarker.cs", + "conversation/ToolCall.cs", + "conversation/ToolResult.cs", + "core/FileNotFoundError.cs", + "core/InvokerError.cs", + "core/Property.cs", + "core/ValidationError.cs", + "core/ValidationResult.cs", + "events/Checkpoint.cs", + "events/CompactionCompletePayload.cs", + "events/CompactionFailedPayload.cs", + "events/CompactionStartPayload.cs", + "events/DoneEventPayload.cs", + "events/ErrorEventPayload.cs", + "events/HarnessContext.cs", + "events/HookEndPayload.cs", + "events/HookStartPayload.cs", + "events/HostToolRequest.cs", + "events/HostToolResult.cs", + "events/LlmCompletePayload.cs", + "events/LlmStartPayload.cs", + "events/MessagesUpdatedPayload.cs", + "events/PermissionCompletedPayload.cs", + "events/PermissionDecision.cs", + "events/PermissionRequest.cs", + "events/PermissionRequestedPayload.cs", + "events/RedactedField.cs", + "events/RedactionMetadata.cs", + "events/RetryPayload.cs", + "events/SessionEndPayload.cs", + "events/SessionEvent.cs", + "events/SessionFileRef.cs", + "events/SessionRef.cs", + "events/SessionStartPayload.cs", + "events/SessionSummary.cs", + "events/SessionTrace.cs", + "events/SessionWarningPayload.cs", + "events/StatusEventPayload.cs", + "events/StreamChunk.cs", + "events/ThinkingEventPayload.cs", + "events/TokenEventPayload.cs", + "events/ToolCallCompletePayload.cs", + "events/ToolCallStartPayload.cs", + "events/ToolExecutionCompletePayload.cs", + "events/ToolExecutionStartPayload.cs", + "events/ToolResultPayload.cs", + "events/TrajectoryEvent.cs", + "events/TurnEndPayload.cs", + "events/TurnEvent.cs", + "events/TurnStartPayload.cs", + "events/TurnSummary.cs", + "events/TurnTrace.cs", + "model/Model.cs", + "model/ModelInfo.cs", + "model/ModelOptions.cs", + "model/TokenUsage.cs", + "pipeline/CheckpointStore.cs", + "pipeline/CompactionConfig.cs", + "pipeline/EventJournalWriter.cs", + "pipeline/EventSink.cs", + "pipeline/Executor.cs", + "pipeline/HostToolExecutor.cs", + "pipeline/Parser.cs", + "pipeline/PermissionResolver.cs", + "pipeline/Processor.cs", + "pipeline/Renderer.cs", + "pipeline/ReplayJournalRecord.cs", + "pipeline/ReplayMismatch.cs", + "pipeline/ReplayVerificationRequest.cs", + "pipeline/ReplayVerificationResult.cs", + "pipeline/RunTurnRequest.cs", + "pipeline/RunTurnResult.cs", + "pipeline/TurnModelRequest.cs", + "pipeline/TurnModelResponse.cs", + "pipeline/TurnOptions.cs", + "streaming/StreamOptions.cs", + "template/FormatConfig.cs", + "template/ParserConfig.cs", + "template/Template.cs", + "tools/Binding.cs", + "tools/McpApprovalMode.cs", + "tools/Tool.cs", + "tools/ToolContext.cs", + "tools/ToolDispatchResult.cs", + "tracing/TraceFile.cs", + "tracing/TraceSpan.cs", + "tracing/TraceTime.cs", + "wire/AnthropicImageBlock.cs", + "wire/AnthropicImageSource.cs", + "wire/AnthropicMessagesRequest.cs", + "wire/AnthropicMessagesResponse.cs", + "wire/AnthropicTextBlock.cs", + "wire/AnthropicToolDefinition.cs", + "wire/AnthropicToolResultBlock.cs", + "wire/AnthropicToolUseBlock.cs", + "wire/AnthropicUsage.cs", + "wire/AnthropicWireMessage.cs" + ] + }, + { + "target": "go", + "outputRoot": "../runtime/go/prompty/model", + "packageName": "prompty", + "rootExports": [ + "AnonymousConnection", + "AnthropicImageBlock", + "AnthropicImageSource", + "AnthropicMessagesRequest", + "AnthropicMessagesResponse", + "AnthropicTextBlock", + "AnthropicToolDefinition", + "AnthropicToolResultBlock", + "AnthropicToolUseBlock", + "AnthropicUsage", + "AnthropicWireMessage", + "ApiKeyConnection", + "ArrayProperty", + "AudioPart", + "Binding", + "Checkpoint", + "CheckpointStore", + "CompactionCompletePayload", + "CompactionConfig", + "CompactionFailedPayload", + "CompactionStartPayload", + "Connection", + "ContentPart", + "CustomTool", + "DoneEventPayload", + "ErrorChunk", + "ErrorEventPayload", + "EventJournalWriter", + "EventSink", + "Executor", + "FileNotFoundError", + "FilePart", + "FormatConfig", + "FoundryConnection", + "FunctionTool", + "GuardrailResult", + "HarnessContext", + "HookEndPayload", + "HookStartPayload", + "HostToolExecutor", + "HostToolRequest", + "HostToolResult", + "ImagePart", + "InvokerError", + "LlmCompletePayload", + "LlmStartPayload", + "McpApprovalMode", + "McpTool", + "Message", + "MessagesUpdatedPayload", + "Model", + "ModelInfo", + "ModelOptions", + "OAuthConnection", + "ObjectProperty", + "OpenApiTool", + "Parser", + "ParserConfig", + "PermissionCompletedPayload", + "PermissionDecision", + "PermissionRequest", + "PermissionRequestedPayload", + "PermissionResolver", + "Processor", + "Prompty", + "PromptyTool", + "Property", + "RedactedField", + "RedactionMetadata", + "ReferenceConnection", + "RemoteConnection", + "Renderer", + "ReplayJournalRecord", + "ReplayMismatch", + "ReplayVerificationRequest", + "ReplayVerificationResult", + "RetryPayload", + "RunTurnRequest", + "RunTurnResult", + "SessionEndPayload", + "SessionEvent", + "SessionFileRef", + "SessionRef", + "SessionStartPayload", + "SessionSummary", + "SessionTrace", + "SessionWarningPayload", + "StatusEventPayload", + "StreamChunk", + "StreamOptions", + "Template", + "TextChunk", + "TextPart", + "ThinkingChunk", + "ThinkingEventPayload", + "ThreadMarker", + "TokenEventPayload", + "TokenUsage", + "Tool", + "ToolCall", + "ToolCallCompletePayload", + "ToolCallStartPayload", + "ToolChunk", + "ToolContext", + "ToolDispatchResult", + "ToolExecutionCompletePayload", + "ToolExecutionStartPayload", + "ToolResult", + "ToolResultPayload", + "TraceFile", + "TraceSpan", + "TraceTime", + "TrajectoryEvent", + "TurnEndPayload", + "TurnEvent", + "TurnModelRequest", + "TurnModelResponse", + "TurnOptions", + "TurnStartPayload", + "TurnSummary", + "TurnTrace", + "ValidationError", + "ValidationResult" + ], + "exports": [ + { + "name": "GuardrailResult", + "kind": "value", + "group": "agent", + "source": "guardrail_result.go", + "protocol": false + }, + { + "name": "Prompty", + "kind": "value", + "group": "agent", + "source": "prompty.go", + "protocol": false + }, + { + "name": "AnonymousConnection", + "kind": "value", + "group": "connection", + "source": "connection.go", + "protocol": false + }, + { + "name": "ApiKeyConnection", + "kind": "value", + "group": "connection", + "source": "connection.go", + "protocol": false + }, + { + "name": "Connection", + "kind": "value", + "group": "connection", + "source": "connection.go", + "protocol": false + }, + { + "name": "FoundryConnection", + "kind": "value", + "group": "connection", + "source": "connection.go", + "protocol": false + }, + { + "name": "OAuthConnection", + "kind": "value", + "group": "connection", + "source": "connection.go", + "protocol": false + }, + { + "name": "ReferenceConnection", + "kind": "value", + "group": "connection", + "source": "connection.go", + "protocol": false + }, + { + "name": "RemoteConnection", + "kind": "value", + "group": "connection", + "source": "connection.go", + "protocol": false + }, + { + "name": "AudioPart", + "kind": "value", + "group": "conversation", + "source": "content_part.go", + "protocol": false + }, + { + "name": "ContentPart", + "kind": "value", + "group": "conversation", + "source": "content_part.go", + "protocol": false + }, + { + "name": "FilePart", + "kind": "value", + "group": "conversation", + "source": "content_part.go", + "protocol": false + }, + { + "name": "ImagePart", + "kind": "value", + "group": "conversation", + "source": "content_part.go", + "protocol": false + }, + { + "name": "Message", + "kind": "value", + "group": "conversation", + "source": "message.go", + "protocol": false + }, + { + "name": "TextPart", + "kind": "value", + "group": "conversation", + "source": "content_part.go", + "protocol": false + }, + { + "name": "ThreadMarker", + "kind": "value", + "group": "conversation", + "source": "thread_marker.go", + "protocol": false + }, + { + "name": "ToolCall", + "kind": "value", + "group": "conversation", + "source": "tool_call.go", + "protocol": false + }, + { + "name": "ToolResult", + "kind": "value", + "group": "conversation", + "source": "tool_result.go", + "protocol": false + }, + { + "name": "ArrayProperty", + "kind": "value", + "group": "core", + "source": "property.go", + "protocol": false + }, + { + "name": "FileNotFoundError", + "kind": "value", + "group": "core", + "source": "file_not_found_error.go", + "protocol": false + }, + { + "name": "InvokerError", + "kind": "value", + "group": "core", + "source": "invoker_error.go", + "protocol": false + }, + { + "name": "ObjectProperty", + "kind": "value", + "group": "core", + "source": "property.go", + "protocol": false + }, + { + "name": "Property", + "kind": "value", + "group": "core", + "source": "property.go", + "protocol": false + }, + { + "name": "ValidationError", + "kind": "value", + "group": "core", + "source": "validation_error.go", + "protocol": false + }, + { + "name": "ValidationResult", + "kind": "value", + "group": "core", + "source": "validation_result.go", + "protocol": false + }, + { + "name": "Checkpoint", + "kind": "value", + "group": "events", + "source": "checkpoint.go", + "protocol": false + }, + { + "name": "CompactionCompletePayload", + "kind": "value", + "group": "events", + "source": "compaction_complete_payload.go", + "protocol": false + }, + { + "name": "CompactionFailedPayload", + "kind": "value", + "group": "events", + "source": "compaction_failed_payload.go", + "protocol": false + }, + { + "name": "CompactionStartPayload", + "kind": "value", + "group": "events", + "source": "compaction_start_payload.go", + "protocol": false + }, + { + "name": "DoneEventPayload", + "kind": "value", + "group": "events", + "source": "done_event_payload.go", + "protocol": false + }, + { + "name": "ErrorChunk", + "kind": "value", + "group": "events", + "source": "stream_chunk.go", + "protocol": false + }, + { + "name": "ErrorEventPayload", + "kind": "value", + "group": "events", + "source": "error_event_payload.go", + "protocol": false + }, + { + "name": "HarnessContext", + "kind": "value", + "group": "events", + "source": "harness_context.go", + "protocol": false + }, + { + "name": "HookEndPayload", + "kind": "value", + "group": "events", + "source": "hook_end_payload.go", + "protocol": false + }, + { + "name": "HookStartPayload", + "kind": "value", + "group": "events", + "source": "hook_start_payload.go", + "protocol": false + }, + { + "name": "HostToolRequest", + "kind": "value", + "group": "events", + "source": "host_tool_request.go", + "protocol": false + }, + { + "name": "HostToolResult", + "kind": "value", + "group": "events", + "source": "host_tool_result.go", + "protocol": false + }, + { + "name": "LlmCompletePayload", + "kind": "value", + "group": "events", + "source": "llm_complete_payload.go", + "protocol": false + }, + { + "name": "LlmStartPayload", + "kind": "value", + "group": "events", + "source": "llm_start_payload.go", + "protocol": false + }, + { + "name": "MessagesUpdatedPayload", + "kind": "value", + "group": "events", + "source": "messages_updated_payload.go", + "protocol": false + }, + { + "name": "PermissionCompletedPayload", + "kind": "value", + "group": "events", + "source": "permission_completed_payload.go", + "protocol": false + }, + { + "name": "PermissionDecision", + "kind": "value", + "group": "events", + "source": "permission_decision.go", + "protocol": false + }, + { + "name": "PermissionRequest", + "kind": "value", + "group": "events", + "source": "permission_request.go", + "protocol": false + }, + { + "name": "PermissionRequestedPayload", + "kind": "value", + "group": "events", + "source": "permission_requested_payload.go", + "protocol": false + }, + { + "name": "RedactedField", + "kind": "value", + "group": "events", + "source": "redacted_field.go", + "protocol": false + }, + { + "name": "RedactionMetadata", + "kind": "value", + "group": "events", + "source": "redaction_metadata.go", + "protocol": false + }, + { + "name": "RetryPayload", + "kind": "value", + "group": "events", + "source": "retry_payload.go", + "protocol": false + }, + { + "name": "SessionEndPayload", + "kind": "value", + "group": "events", + "source": "session_end_payload.go", + "protocol": false + }, + { + "name": "SessionEvent", + "kind": "value", + "group": "events", + "source": "session_event.go", + "protocol": false + }, + { + "name": "SessionFileRef", + "kind": "value", + "group": "events", + "source": "session_file_ref.go", + "protocol": false + }, + { + "name": "SessionRef", + "kind": "value", + "group": "events", + "source": "session_ref.go", + "protocol": false + }, + { + "name": "SessionStartPayload", + "kind": "value", + "group": "events", + "source": "session_start_payload.go", + "protocol": false + }, + { + "name": "SessionSummary", + "kind": "value", + "group": "events", + "source": "session_summary.go", + "protocol": false + }, + { + "name": "SessionTrace", + "kind": "value", + "group": "events", + "source": "session_trace.go", + "protocol": false + }, + { + "name": "SessionWarningPayload", + "kind": "value", + "group": "events", + "source": "session_warning_payload.go", + "protocol": false + }, + { + "name": "StatusEventPayload", + "kind": "value", + "group": "events", + "source": "status_event_payload.go", + "protocol": false + }, + { + "name": "StreamChunk", + "kind": "value", + "group": "events", + "source": "stream_chunk.go", + "protocol": false + }, + { + "name": "TextChunk", + "kind": "value", + "group": "events", + "source": "stream_chunk.go", + "protocol": false + }, + { + "name": "ThinkingChunk", + "kind": "value", + "group": "events", + "source": "stream_chunk.go", + "protocol": false + }, + { + "name": "ThinkingEventPayload", + "kind": "value", + "group": "events", + "source": "thinking_event_payload.go", + "protocol": false + }, + { + "name": "TokenEventPayload", + "kind": "value", + "group": "events", + "source": "token_event_payload.go", + "protocol": false + }, + { + "name": "ToolCallCompletePayload", + "kind": "value", + "group": "events", + "source": "tool_call_complete_payload.go", + "protocol": false + }, + { + "name": "ToolCallStartPayload", + "kind": "value", + "group": "events", + "source": "tool_call_start_payload.go", + "protocol": false + }, + { + "name": "ToolChunk", + "kind": "value", + "group": "events", + "source": "stream_chunk.go", + "protocol": false + }, + { + "name": "ToolExecutionCompletePayload", + "kind": "value", + "group": "events", + "source": "tool_execution_complete_payload.go", + "protocol": false + }, + { + "name": "ToolExecutionStartPayload", + "kind": "value", + "group": "events", + "source": "tool_execution_start_payload.go", + "protocol": false + }, + { + "name": "ToolResultPayload", + "kind": "value", + "group": "events", + "source": "tool_result_payload.go", + "protocol": false + }, + { + "name": "TrajectoryEvent", + "kind": "value", + "group": "events", + "source": "trajectory_event.go", + "protocol": false + }, + { + "name": "TurnEndPayload", + "kind": "value", + "group": "events", + "source": "turn_end_payload.go", + "protocol": false + }, + { + "name": "TurnEvent", + "kind": "value", + "group": "events", + "source": "turn_event.go", + "protocol": false + }, + { + "name": "TurnStartPayload", + "kind": "value", + "group": "events", + "source": "turn_start_payload.go", + "protocol": false + }, + { + "name": "TurnSummary", + "kind": "value", + "group": "events", + "source": "turn_summary.go", + "protocol": false + }, + { + "name": "TurnTrace", + "kind": "value", + "group": "events", + "source": "turn_trace.go", + "protocol": false + }, + { + "name": "Model", + "kind": "value", + "group": "model", + "source": "model.go", + "protocol": false + }, + { + "name": "ModelInfo", + "kind": "value", + "group": "model", + "source": "model_info.go", + "protocol": false + }, + { + "name": "ModelOptions", + "kind": "value", + "group": "model", + "source": "model_options.go", + "protocol": false + }, + { + "name": "TokenUsage", + "kind": "value", + "group": "model", + "source": "token_usage.go", + "protocol": false + }, + { + "name": "CheckpointStore", + "kind": "type", + "group": "pipeline", + "source": "checkpoint_store.go", + "protocol": true + }, + { + "name": "CompactionConfig", + "kind": "value", + "group": "pipeline", + "source": "compaction_config.go", + "protocol": false + }, + { + "name": "EventJournalWriter", + "kind": "type", + "group": "pipeline", + "source": "event_journal_writer.go", + "protocol": true + }, + { + "name": "EventSink", + "kind": "type", + "group": "pipeline", + "source": "event_sink.go", + "protocol": true + }, + { + "name": "Executor", + "kind": "type", + "group": "pipeline", + "source": "executor.go", + "protocol": true + }, + { + "name": "HostToolExecutor", + "kind": "type", + "group": "pipeline", + "source": "host_tool_executor.go", + "protocol": true + }, + { + "name": "Parser", + "kind": "type", + "group": "pipeline", + "source": "parser.go", + "protocol": true + }, + { + "name": "PermissionResolver", + "kind": "type", + "group": "pipeline", + "source": "permission_resolver.go", + "protocol": true + }, + { + "name": "Processor", + "kind": "type", + "group": "pipeline", + "source": "processor.go", + "protocol": true + }, + { + "name": "Renderer", + "kind": "type", + "group": "pipeline", + "source": "renderer.go", + "protocol": true + }, + { + "name": "ReplayJournalRecord", + "kind": "value", + "group": "pipeline", + "source": "replay_journal_record.go", + "protocol": false + }, + { + "name": "ReplayMismatch", + "kind": "value", + "group": "pipeline", + "source": "replay_mismatch.go", + "protocol": false + }, + { + "name": "ReplayVerificationRequest", + "kind": "value", + "group": "pipeline", + "source": "replay_verification_request.go", + "protocol": false + }, + { + "name": "ReplayVerificationResult", + "kind": "value", + "group": "pipeline", + "source": "replay_verification_result.go", + "protocol": false + }, + { + "name": "RunTurnRequest", + "kind": "value", + "group": "pipeline", + "source": "run_turn_request.go", + "protocol": false + }, + { + "name": "RunTurnResult", + "kind": "value", + "group": "pipeline", + "source": "run_turn_result.go", + "protocol": false + }, + { + "name": "TurnModelRequest", + "kind": "value", + "group": "pipeline", + "source": "turn_model_request.go", + "protocol": false + }, + { + "name": "TurnModelResponse", + "kind": "value", + "group": "pipeline", + "source": "turn_model_response.go", + "protocol": false + }, + { + "name": "TurnOptions", + "kind": "value", + "group": "pipeline", + "source": "turn_options.go", + "protocol": false + }, + { + "name": "StreamOptions", + "kind": "value", + "group": "streaming", + "source": "stream_options.go", + "protocol": false + }, + { + "name": "FormatConfig", + "kind": "value", + "group": "template", + "source": "format_config.go", + "protocol": false + }, + { + "name": "ParserConfig", + "kind": "value", + "group": "template", + "source": "parser_config.go", + "protocol": false + }, + { + "name": "Template", + "kind": "value", + "group": "template", + "source": "template.go", + "protocol": false + }, + { + "name": "Binding", + "kind": "value", + "group": "tools", + "source": "binding.go", + "protocol": false + }, + { + "name": "CustomTool", + "kind": "value", + "group": "tools", + "source": "tool.go", + "protocol": false + }, + { + "name": "FunctionTool", + "kind": "value", + "group": "tools", + "source": "tool.go", + "protocol": false + }, + { + "name": "McpApprovalMode", + "kind": "value", + "group": "tools", + "source": "mcp_approval_mode.go", + "protocol": false + }, + { + "name": "McpTool", + "kind": "value", + "group": "tools", + "source": "tool.go", + "protocol": false + }, + { + "name": "OpenApiTool", + "kind": "value", + "group": "tools", + "source": "tool.go", + "protocol": false + }, + { + "name": "PromptyTool", + "kind": "value", + "group": "tools", + "source": "tool.go", + "protocol": false + }, + { + "name": "Tool", + "kind": "value", + "group": "tools", + "source": "tool.go", + "protocol": false + }, + { + "name": "ToolContext", + "kind": "value", + "group": "tools", + "source": "tool_context.go", + "protocol": false + }, + { + "name": "ToolDispatchResult", + "kind": "value", + "group": "tools", + "source": "tool_dispatch_result.go", + "protocol": false + }, + { + "name": "TraceFile", + "kind": "value", + "group": "tracing", + "source": "trace_file.go", + "protocol": false + }, + { + "name": "TraceSpan", + "kind": "value", + "group": "tracing", + "source": "trace_span.go", + "protocol": false + }, + { + "name": "TraceTime", + "kind": "value", + "group": "tracing", + "source": "trace_time.go", + "protocol": false + }, + { + "name": "AnthropicImageBlock", + "kind": "value", + "group": "wire", + "source": "anthropic_image_block.go", + "protocol": false + }, + { + "name": "AnthropicImageSource", + "kind": "value", + "group": "wire", + "source": "anthropic_image_source.go", + "protocol": false + }, + { + "name": "AnthropicMessagesRequest", + "kind": "value", + "group": "wire", + "source": "anthropic_messages_request.go", + "protocol": false + }, + { + "name": "AnthropicMessagesResponse", + "kind": "value", + "group": "wire", + "source": "anthropic_messages_response.go", + "protocol": false + }, + { + "name": "AnthropicTextBlock", + "kind": "value", + "group": "wire", + "source": "anthropic_text_block.go", + "protocol": false + }, + { + "name": "AnthropicToolDefinition", + "kind": "value", + "group": "wire", + "source": "anthropic_tool_definition.go", + "protocol": false + }, + { + "name": "AnthropicToolResultBlock", + "kind": "value", + "group": "wire", + "source": "anthropic_tool_result_block.go", + "protocol": false + }, + { + "name": "AnthropicToolUseBlock", + "kind": "value", + "group": "wire", + "source": "anthropic_tool_use_block.go", + "protocol": false + }, + { + "name": "AnthropicUsage", + "kind": "value", + "group": "wire", + "source": "anthropic_usage.go", + "protocol": false + }, + { + "name": "AnthropicWireMessage", + "kind": "value", + "group": "wire", + "source": "anthropic_wire_message.go", + "protocol": false + } + ], + "groups": [ + { + "name": "agent", + "exports": [ + "GuardrailResult", + "Prompty" + ], + "modules": [ + "guardrail_result", + "prompty" + ] + }, + { + "name": "connection", + "exports": [ + "AnonymousConnection", + "ApiKeyConnection", + "Connection", + "FoundryConnection", + "OAuthConnection", + "ReferenceConnection", + "RemoteConnection" + ], + "modules": [ + "connection" + ] + }, + { + "name": "conversation", + "exports": [ + "AudioPart", + "ContentPart", + "FilePart", + "ImagePart", + "Message", + "TextPart", + "ThreadMarker", + "ToolCall", + "ToolResult" + ], + "modules": [ + "content_part", + "message", + "thread_marker", + "tool_call", + "tool_result" + ] + }, + { + "name": "core", + "exports": [ + "ArrayProperty", + "FileNotFoundError", + "InvokerError", + "ObjectProperty", + "Property", + "ValidationError", + "ValidationResult" + ], + "modules": [ + "file_not_found_error", + "invoker_error", + "property", + "validation_error", + "validation_result" + ] + }, + { + "name": "events", + "exports": [ + "Checkpoint", + "CompactionCompletePayload", + "CompactionFailedPayload", + "CompactionStartPayload", + "DoneEventPayload", + "ErrorChunk", + "ErrorEventPayload", + "HarnessContext", + "HookEndPayload", + "HookStartPayload", + "HostToolRequest", + "HostToolResult", + "LlmCompletePayload", + "LlmStartPayload", + "MessagesUpdatedPayload", + "PermissionCompletedPayload", + "PermissionDecision", + "PermissionRequest", + "PermissionRequestedPayload", + "RedactedField", + "RedactionMetadata", + "RetryPayload", + "SessionEndPayload", + "SessionEvent", + "SessionFileRef", + "SessionRef", + "SessionStartPayload", + "SessionSummary", + "SessionTrace", + "SessionWarningPayload", + "StatusEventPayload", + "StreamChunk", + "TextChunk", + "ThinkingChunk", + "ThinkingEventPayload", + "TokenEventPayload", + "ToolCallCompletePayload", + "ToolCallStartPayload", + "ToolChunk", + "ToolExecutionCompletePayload", + "ToolExecutionStartPayload", + "ToolResultPayload", + "TrajectoryEvent", + "TurnEndPayload", + "TurnEvent", + "TurnStartPayload", + "TurnSummary", + "TurnTrace" + ], + "modules": [ + "checkpoint", + "compaction_complete_payload", + "compaction_failed_payload", + "compaction_start_payload", + "done_event_payload", + "error_event_payload", + "harness_context", + "hook_end_payload", + "hook_start_payload", + "host_tool_request", + "host_tool_result", + "llm_complete_payload", + "llm_start_payload", + "messages_updated_payload", + "permission_completed_payload", + "permission_decision", + "permission_request", + "permission_requested_payload", + "redacted_field", + "redaction_metadata", + "retry_payload", + "session_end_payload", + "session_event", + "session_file_ref", + "session_ref", + "session_start_payload", + "session_summary", + "session_trace", + "session_warning_payload", + "status_event_payload", + "stream_chunk", + "thinking_event_payload", + "token_event_payload", + "tool_call_complete_payload", + "tool_call_start_payload", + "tool_execution_complete_payload", + "tool_execution_start_payload", + "tool_result_payload", + "trajectory_event", + "turn_end_payload", + "turn_event", + "turn_start_payload", + "turn_summary", + "turn_trace" + ] + }, + { + "name": "model", + "exports": [ + "Model", + "ModelInfo", + "ModelOptions", + "TokenUsage" + ], + "modules": [ + "model", + "model_info", + "model_options", + "token_usage" + ] + }, + { + "name": "pipeline", + "exports": [ + "CheckpointStore", + "CompactionConfig", + "EventJournalWriter", + "EventSink", + "Executor", + "HostToolExecutor", + "Parser", + "PermissionResolver", + "Processor", + "Renderer", + "ReplayJournalRecord", + "ReplayMismatch", + "ReplayVerificationRequest", + "ReplayVerificationResult", + "RunTurnRequest", + "RunTurnResult", + "TurnModelRequest", + "TurnModelResponse", + "TurnOptions" + ], + "modules": [ + "checkpoint_store", + "compaction_config", + "event_journal_writer", + "event_sink", + "executor", + "host_tool_executor", + "parser", + "permission_resolver", + "processor", + "renderer", + "replay_journal_record", + "replay_mismatch", + "replay_verification_request", + "replay_verification_result", + "run_turn_request", + "run_turn_result", + "turn_model_request", + "turn_model_response", + "turn_options" + ] + }, + { + "name": "streaming", + "exports": [ + "StreamOptions" + ], + "modules": [ + "stream_options" + ] + }, + { + "name": "template", + "exports": [ + "FormatConfig", + "ParserConfig", + "Template" + ], + "modules": [ + "format_config", + "parser_config", + "template" + ] + }, + { + "name": "tools", + "exports": [ + "Binding", + "CustomTool", + "FunctionTool", + "McpApprovalMode", + "McpTool", + "OpenApiTool", + "PromptyTool", + "Tool", + "ToolContext", + "ToolDispatchResult" + ], + "modules": [ + "binding", + "mcp_approval_mode", + "tool", + "tool_context", + "tool_dispatch_result" + ] + }, + { + "name": "tracing", + "exports": [ + "TraceFile", + "TraceSpan", + "TraceTime" + ], + "modules": [ + "trace_file", + "trace_span", + "trace_time" + ] + }, + { + "name": "wire", + "exports": [ + "AnthropicImageBlock", + "AnthropicImageSource", + "AnthropicMessagesRequest", + "AnthropicMessagesResponse", + "AnthropicTextBlock", + "AnthropicToolDefinition", + "AnthropicToolResultBlock", + "AnthropicToolUseBlock", + "AnthropicUsage", + "AnthropicWireMessage" + ], + "modules": [ + "anthropic_image_block", + "anthropic_image_source", + "anthropic_messages_request", + "anthropic_messages_response", + "anthropic_text_block", + "anthropic_tool_definition", + "anthropic_tool_result_block", + "anthropic_tool_use_block", + "anthropic_usage", + "anthropic_wire_message" + ] + } + ], + "protocols": [ + { + "name": "CheckpointStore", + "group": "pipeline", + "symbol": "CheckpointStore", + "source": "checkpoint_store.go", + "methods": [ + { + "name": "listCheckpoints", + "returns": "Checkpoint[]", + "params": { + "sessionId": "string" + }, + "optional": false, + "sync": false + }, + { + "name": "load", + "returns": "Checkpoint?", + "params": { + "checkpointId": "string", + "sessionId": "string" + }, + "optional": false, + "sync": false + }, + { + "name": "save", + "returns": "Checkpoint", + "params": { + "checkpoint": "Checkpoint" + }, + "optional": false, + "sync": false + } + ] + }, + { + "name": "EventJournalWriter", + "group": "pipeline", + "symbol": "EventJournalWriter", + "source": "event_journal_writer.go", + "methods": [ + { + "name": "appendSession", + "returns": "boolean", + "params": { + "sessionEvent": "SessionEvent" + }, + "optional": false, + "sync": true + }, + { + "name": "appendTurn", + "returns": "boolean", + "params": { + "turnEvent": "TurnEvent" + }, + "optional": false, + "sync": true + }, + { + "name": "close", + "returns": "boolean", + "params": { + "summary": "SessionSummary?" + }, + "optional": false, + "sync": true + } + ] + }, + { + "name": "EventSink", + "group": "pipeline", + "symbol": "EventSink", + "source": "event_sink.go", + "methods": [ + { + "name": "emitSession", + "returns": "boolean", + "params": { + "sessionEvent": "SessionEvent" + }, + "optional": false, + "sync": true + }, + { + "name": "emitTurn", + "returns": "boolean", + "params": { + "turnEvent": "TurnEvent" + }, + "optional": false, + "sync": true + } + ] + }, + { + "name": "Executor", + "group": "pipeline", + "symbol": "Executor", + "source": "executor.go", + "methods": [ + { + "name": "execute", + "returns": "unknown", + "params": { + "agent": "Prompty", + "messages": "Message[]" + }, + "optional": false, + "sync": false + }, + { + "name": "executeStream", + "returns": "unknown", + "params": { + "agent": "Prompty", + "messages": "Message[]" + }, + "optional": true, + "sync": false + }, + { + "name": "formatToolMessages", + "returns": "Message[]", + "params": { + "rawResponse": "unknown", + "textContent": "string?", + "toolCalls": "ToolCall[]", + "toolResults": "string[]" + }, + "optional": false, + "sync": true + } + ] + }, + { + "name": "HostToolExecutor", + "group": "pipeline", + "symbol": "HostToolExecutor", + "source": "host_tool_executor.go", + "methods": [ + { + "name": "execute", + "returns": "HostToolResult", + "params": { + "request": "HostToolRequest" + }, + "optional": false, + "sync": false + } + ] + }, + { + "name": "Parser", + "group": "pipeline", + "symbol": "Parser", + "source": "parser.go", + "methods": [ + { + "name": "parse", + "returns": "Message[]", + "params": { + "agent": "Prompty", + "context": "Record?", + "rendered": "string" + }, + "optional": false, + "sync": false + }, + { + "name": "preRender", + "returns": "unknown?", + "params": { + "template": "string" + }, + "optional": true, + "sync": true + } + ] + }, + { + "name": "PermissionResolver", + "group": "pipeline", + "symbol": "PermissionResolver", + "source": "permission_resolver.go", + "methods": [ + { + "name": "request", + "returns": "PermissionDecision", + "params": { + "request": "PermissionRequest" + }, + "optional": false, + "sync": false + } + ] + }, + { + "name": "Processor", + "group": "pipeline", + "symbol": "Processor", + "source": "processor.go", + "methods": [ + { + "name": "process", + "returns": "unknown", + "params": { + "agent": "Prompty", + "response": "unknown" + }, + "optional": false, + "sync": false + }, + { + "name": "processStream", + "returns": "unknown", + "params": { + "stream": "unknown" + }, + "optional": true, + "sync": false + } + ] + }, + { + "name": "Renderer", + "group": "pipeline", + "symbol": "Renderer", + "source": "renderer.go", + "methods": [ + { + "name": "render", + "returns": "string", + "params": { + "agent": "Prompty", + "inputs": "Record", + "template": "string" + }, + "optional": false, + "sync": false + } + ] + } + ], + "modules": [ + "anthropic_image_block.go", + "anthropic_image_source.go", + "anthropic_messages_request.go", + "anthropic_messages_response.go", + "anthropic_text_block.go", + "anthropic_tool_definition.go", + "anthropic_tool_result_block.go", + "anthropic_tool_use_block.go", + "anthropic_usage.go", + "anthropic_wire_message.go", + "binding.go", + "checkpoint.go", + "checkpoint_store.go", + "compaction_complete_payload.go", + "compaction_config.go", + "compaction_failed_payload.go", + "compaction_start_payload.go", + "connection.go", + "content_part.go", + "done_event_payload.go", + "error_event_payload.go", + "event_journal_writer.go", + "event_sink.go", + "executor.go", + "file_not_found_error.go", + "format_config.go", + "guardrail_result.go", + "harness_context.go", + "hook_end_payload.go", + "hook_start_payload.go", + "host_tool_executor.go", + "host_tool_request.go", + "host_tool_result.go", + "invoker_error.go", + "llm_complete_payload.go", + "llm_start_payload.go", + "mcp_approval_mode.go", + "message.go", + "messages_updated_payload.go", + "model.go", + "model_info.go", + "model_options.go", + "parser.go", + "parser_config.go", + "permission_completed_payload.go", + "permission_decision.go", + "permission_request.go", + "permission_requested_payload.go", + "permission_resolver.go", + "processor.go", + "prompty.go", + "property.go", + "redacted_field.go", + "redaction_metadata.go", + "renderer.go", + "replay_journal_record.go", + "replay_mismatch.go", + "replay_verification_request.go", + "replay_verification_result.go", + "retry_payload.go", + "run_turn_request.go", + "run_turn_result.go", + "session_end_payload.go", + "session_event.go", + "session_file_ref.go", + "session_ref.go", + "session_start_payload.go", + "session_summary.go", + "session_trace.go", + "session_warning_payload.go", + "status_event_payload.go", + "stream_chunk.go", + "stream_options.go", + "template.go", + "thinking_event_payload.go", + "thread_marker.go", + "token_event_payload.go", + "token_usage.go", + "tool.go", + "tool_call.go", + "tool_call_complete_payload.go", + "tool_call_start_payload.go", + "tool_context.go", + "tool_dispatch_result.go", + "tool_execution_complete_payload.go", + "tool_execution_start_payload.go", + "tool_result.go", + "tool_result_payload.go", + "trace_file.go", + "trace_span.go", + "trace_time.go", + "trajectory_event.go", + "turn_end_payload.go", + "turn_event.go", + "turn_model_request.go", + "turn_model_response.go", + "turn_options.go", + "turn_start_payload.go", + "turn_summary.go", + "turn_trace.go", + "validation_error.go", + "validation_result.go" + ] + }, + { + "target": "markdown", + "outputRoot": "../web/src/content/docs/reference", + "rootExports": [ + "AnonymousConnection", + "AnthropicImageBlock", + "AnthropicImageSource", + "AnthropicMessagesRequest", + "AnthropicMessagesResponse", + "AnthropicTextBlock", + "AnthropicToolDefinition", + "AnthropicToolResultBlock", + "AnthropicToolUseBlock", + "AnthropicUsage", + "AnthropicWireMessage", + "ApiKeyConnection", + "ArrayProperty", + "AudioPart", + "Binding", + "Checkpoint", + "CheckpointStore", + "CompactionCompletePayload", + "CompactionConfig", + "CompactionFailedPayload", + "CompactionStartPayload", + "Connection", + "ContentPart", + "CustomTool", + "DoneEventPayload", + "ErrorChunk", + "ErrorEventPayload", + "EventJournalWriter", + "EventSink", + "Executor", + "FileNotFoundError", + "FilePart", + "FormatConfig", + "FoundryConnection", + "FunctionTool", + "GuardrailResult", + "HarnessContext", + "HookEndPayload", + "HookStartPayload", + "HostToolExecutor", + "HostToolRequest", + "HostToolResult", + "ImagePart", + "InvokerError", + "LlmCompletePayload", + "LlmStartPayload", + "McpApprovalMode", + "McpTool", + "Message", + "MessagesUpdatedPayload", + "Model", + "ModelInfo", + "ModelOptions", + "OAuthConnection", + "ObjectProperty", + "OpenApiTool", + "Parser", + "ParserConfig", + "PermissionCompletedPayload", + "PermissionDecision", + "PermissionRequest", + "PermissionRequestedPayload", + "PermissionResolver", + "Processor", + "Prompty", + "PromptyTool", + "Property", + "RedactedField", + "RedactionMetadata", + "ReferenceConnection", + "RemoteConnection", + "Renderer", + "ReplayJournalRecord", + "ReplayMismatch", + "ReplayVerificationRequest", + "ReplayVerificationResult", + "RetryPayload", + "RunTurnRequest", + "RunTurnResult", + "SessionEndPayload", + "SessionEvent", + "SessionFileRef", + "SessionRef", + "SessionStartPayload", + "SessionSummary", + "SessionTrace", + "SessionWarningPayload", + "StatusEventPayload", + "StreamChunk", + "StreamOptions", + "Template", + "TextChunk", + "TextPart", + "ThinkingChunk", + "ThinkingEventPayload", + "ThreadMarker", + "TokenEventPayload", + "TokenUsage", + "Tool", + "ToolCall", + "ToolCallCompletePayload", + "ToolCallStartPayload", + "ToolChunk", + "ToolContext", + "ToolDispatchResult", + "ToolExecutionCompletePayload", + "ToolExecutionStartPayload", + "ToolResult", + "ToolResultPayload", + "TraceFile", + "TraceSpan", + "TraceTime", + "TrajectoryEvent", + "TurnEndPayload", + "TurnEvent", + "TurnModelRequest", + "TurnModelResponse", + "TurnOptions", + "TurnStartPayload", + "TurnSummary", + "TurnTrace", + "ValidationError", + "ValidationResult" + ], + "exports": [ + { + "name": "GuardrailResult", + "kind": "value", + "group": "agent", + "source": "GuardrailResult", + "protocol": false + }, + { + "name": "Prompty", + "kind": "value", + "group": "agent", + "source": "Prompty", + "protocol": false + }, + { + "name": "AnonymousConnection", + "kind": "value", + "group": "connection", + "source": "Connection", + "protocol": false + }, + { + "name": "ApiKeyConnection", + "kind": "value", + "group": "connection", + "source": "Connection", + "protocol": false + }, + { + "name": "Connection", + "kind": "value", + "group": "connection", + "source": "Connection", + "protocol": false + }, + { + "name": "FoundryConnection", + "kind": "value", + "group": "connection", + "source": "Connection", + "protocol": false + }, + { + "name": "OAuthConnection", + "kind": "value", + "group": "connection", + "source": "Connection", + "protocol": false + }, + { + "name": "ReferenceConnection", + "kind": "value", + "group": "connection", + "source": "Connection", + "protocol": false + }, + { + "name": "RemoteConnection", + "kind": "value", + "group": "connection", + "source": "Connection", + "protocol": false + }, + { + "name": "AudioPart", + "kind": "value", + "group": "conversation", + "source": "ContentPart", + "protocol": false + }, + { + "name": "ContentPart", + "kind": "value", + "group": "conversation", + "source": "ContentPart", + "protocol": false + }, + { + "name": "FilePart", + "kind": "value", + "group": "conversation", + "source": "ContentPart", + "protocol": false + }, + { + "name": "ImagePart", + "kind": "value", + "group": "conversation", + "source": "ContentPart", + "protocol": false + }, + { + "name": "Message", + "kind": "value", + "group": "conversation", + "source": "Message", + "protocol": false + }, + { + "name": "TextPart", + "kind": "value", + "group": "conversation", + "source": "ContentPart", + "protocol": false + }, + { + "name": "ThreadMarker", + "kind": "value", + "group": "conversation", + "source": "ThreadMarker", + "protocol": false + }, + { + "name": "ToolCall", + "kind": "value", + "group": "conversation", + "source": "ToolCall", + "protocol": false + }, + { + "name": "ToolResult", + "kind": "value", + "group": "conversation", + "source": "ToolResult", + "protocol": false + }, + { + "name": "ArrayProperty", + "kind": "value", + "group": "core", + "source": "Property", + "protocol": false + }, + { + "name": "FileNotFoundError", + "kind": "value", + "group": "core", + "source": "FileNotFoundError", + "protocol": false + }, + { + "name": "InvokerError", + "kind": "value", + "group": "core", + "source": "InvokerError", + "protocol": false + }, + { + "name": "ObjectProperty", + "kind": "value", + "group": "core", + "source": "Property", + "protocol": false + }, + { + "name": "Property", + "kind": "value", + "group": "core", + "source": "Property", + "protocol": false + }, + { + "name": "ValidationError", + "kind": "value", + "group": "core", + "source": "ValidationError", + "protocol": false + }, + { + "name": "ValidationResult", + "kind": "value", + "group": "core", + "source": "ValidationResult", + "protocol": false + }, + { + "name": "Checkpoint", + "kind": "value", + "group": "events", + "source": "Checkpoint", + "protocol": false + }, + { + "name": "CompactionCompletePayload", + "kind": "value", + "group": "events", + "source": "CompactionCompletePayload", + "protocol": false + }, + { + "name": "CompactionFailedPayload", + "kind": "value", + "group": "events", + "source": "CompactionFailedPayload", + "protocol": false + }, + { + "name": "CompactionStartPayload", + "kind": "value", + "group": "events", + "source": "CompactionStartPayload", + "protocol": false + }, + { + "name": "DoneEventPayload", + "kind": "value", + "group": "events", + "source": "DoneEventPayload", + "protocol": false + }, + { + "name": "ErrorChunk", + "kind": "value", + "group": "events", + "source": "StreamChunk", + "protocol": false + }, + { + "name": "ErrorEventPayload", + "kind": "value", + "group": "events", + "source": "ErrorEventPayload", + "protocol": false + }, + { + "name": "HarnessContext", + "kind": "value", + "group": "events", + "source": "HarnessContext", + "protocol": false + }, + { + "name": "HookEndPayload", + "kind": "value", + "group": "events", + "source": "HookEndPayload", + "protocol": false + }, + { + "name": "HookStartPayload", + "kind": "value", + "group": "events", + "source": "HookStartPayload", + "protocol": false + }, + { + "name": "HostToolRequest", + "kind": "value", + "group": "events", + "source": "HostToolRequest", + "protocol": false + }, + { + "name": "HostToolResult", + "kind": "value", + "group": "events", + "source": "HostToolResult", + "protocol": false + }, + { + "name": "LlmCompletePayload", + "kind": "value", + "group": "events", + "source": "LlmCompletePayload", + "protocol": false + }, + { + "name": "LlmStartPayload", + "kind": "value", + "group": "events", + "source": "LlmStartPayload", + "protocol": false + }, + { + "name": "MessagesUpdatedPayload", + "kind": "value", + "group": "events", + "source": "MessagesUpdatedPayload", + "protocol": false + }, + { + "name": "PermissionCompletedPayload", + "kind": "value", + "group": "events", + "source": "PermissionCompletedPayload", + "protocol": false + }, + { + "name": "PermissionDecision", + "kind": "value", + "group": "events", + "source": "PermissionDecision", + "protocol": false + }, + { + "name": "PermissionRequest", + "kind": "value", + "group": "events", + "source": "PermissionRequest", + "protocol": false + }, + { + "name": "PermissionRequestedPayload", + "kind": "value", + "group": "events", + "source": "PermissionRequestedPayload", + "protocol": false + }, + { + "name": "RedactedField", + "kind": "value", + "group": "events", + "source": "RedactedField", + "protocol": false + }, + { + "name": "RedactionMetadata", + "kind": "value", + "group": "events", + "source": "RedactionMetadata", + "protocol": false + }, + { + "name": "RetryPayload", + "kind": "value", + "group": "events", + "source": "RetryPayload", + "protocol": false + }, + { + "name": "SessionEndPayload", + "kind": "value", + "group": "events", + "source": "SessionEndPayload", + "protocol": false + }, + { + "name": "SessionEvent", + "kind": "value", + "group": "events", + "source": "SessionEvent", + "protocol": false + }, + { + "name": "SessionFileRef", + "kind": "value", + "group": "events", + "source": "SessionFileRef", + "protocol": false + }, + { + "name": "SessionRef", + "kind": "value", + "group": "events", + "source": "SessionRef", + "protocol": false + }, + { + "name": "SessionStartPayload", + "kind": "value", + "group": "events", + "source": "SessionStartPayload", + "protocol": false + }, + { + "name": "SessionSummary", + "kind": "value", + "group": "events", + "source": "SessionSummary", + "protocol": false + }, + { + "name": "SessionTrace", + "kind": "value", + "group": "events", + "source": "SessionTrace", + "protocol": false + }, + { + "name": "SessionWarningPayload", + "kind": "value", + "group": "events", + "source": "SessionWarningPayload", + "protocol": false + }, + { + "name": "StatusEventPayload", + "kind": "value", + "group": "events", + "source": "StatusEventPayload", + "protocol": false + }, + { + "name": "StreamChunk", + "kind": "value", + "group": "events", + "source": "StreamChunk", + "protocol": false + }, + { + "name": "TextChunk", + "kind": "value", + "group": "events", + "source": "StreamChunk", + "protocol": false + }, + { + "name": "ThinkingChunk", + "kind": "value", + "group": "events", + "source": "StreamChunk", + "protocol": false + }, + { + "name": "ThinkingEventPayload", + "kind": "value", + "group": "events", + "source": "ThinkingEventPayload", + "protocol": false + }, + { + "name": "TokenEventPayload", + "kind": "value", + "group": "events", + "source": "TokenEventPayload", + "protocol": false + }, + { + "name": "ToolCallCompletePayload", + "kind": "value", + "group": "events", + "source": "ToolCallCompletePayload", + "protocol": false + }, + { + "name": "ToolCallStartPayload", + "kind": "value", + "group": "events", + "source": "ToolCallStartPayload", + "protocol": false + }, + { + "name": "ToolChunk", + "kind": "value", + "group": "events", + "source": "StreamChunk", + "protocol": false + }, + { + "name": "ToolExecutionCompletePayload", + "kind": "value", + "group": "events", + "source": "ToolExecutionCompletePayload", + "protocol": false + }, + { + "name": "ToolExecutionStartPayload", + "kind": "value", + "group": "events", + "source": "ToolExecutionStartPayload", + "protocol": false + }, + { + "name": "ToolResultPayload", + "kind": "value", + "group": "events", + "source": "ToolResultPayload", + "protocol": false + }, + { + "name": "TrajectoryEvent", + "kind": "value", + "group": "events", + "source": "TrajectoryEvent", + "protocol": false + }, + { + "name": "TurnEndPayload", + "kind": "value", + "group": "events", + "source": "TurnEndPayload", + "protocol": false + }, + { + "name": "TurnEvent", + "kind": "value", + "group": "events", + "source": "TurnEvent", + "protocol": false + }, + { + "name": "TurnStartPayload", + "kind": "value", + "group": "events", + "source": "TurnStartPayload", + "protocol": false + }, + { + "name": "TurnSummary", + "kind": "value", + "group": "events", + "source": "TurnSummary", + "protocol": false + }, + { + "name": "TurnTrace", + "kind": "value", + "group": "events", + "source": "TurnTrace", + "protocol": false + }, + { + "name": "Model", + "kind": "value", + "group": "model", + "source": "Model", + "protocol": false + }, + { + "name": "ModelInfo", + "kind": "value", + "group": "model", + "source": "ModelInfo", + "protocol": false + }, + { + "name": "ModelOptions", + "kind": "value", + "group": "model", + "source": "ModelOptions", + "protocol": false + }, + { + "name": "TokenUsage", + "kind": "value", + "group": "model", + "source": "TokenUsage", + "protocol": false + }, + { + "name": "CheckpointStore", + "kind": "type", + "group": "pipeline", + "source": "CheckpointStore", + "protocol": true + }, + { + "name": "CompactionConfig", + "kind": "value", + "group": "pipeline", + "source": "CompactionConfig", + "protocol": false + }, + { + "name": "EventJournalWriter", + "kind": "type", + "group": "pipeline", + "source": "EventJournalWriter", + "protocol": true + }, + { + "name": "EventSink", + "kind": "type", + "group": "pipeline", + "source": "EventSink", + "protocol": true + }, + { + "name": "Executor", + "kind": "type", + "group": "pipeline", + "source": "Executor", + "protocol": true + }, + { + "name": "HostToolExecutor", + "kind": "type", + "group": "pipeline", + "source": "HostToolExecutor", + "protocol": true + }, + { + "name": "Parser", + "kind": "type", + "group": "pipeline", + "source": "Parser", + "protocol": true + }, + { + "name": "PermissionResolver", + "kind": "type", + "group": "pipeline", + "source": "PermissionResolver", + "protocol": true + }, + { + "name": "Processor", + "kind": "type", + "group": "pipeline", + "source": "Processor", + "protocol": true + }, + { + "name": "Renderer", + "kind": "type", + "group": "pipeline", + "source": "Renderer", + "protocol": true + }, + { + "name": "ReplayJournalRecord", + "kind": "value", + "group": "pipeline", + "source": "ReplayJournalRecord", + "protocol": false + }, + { + "name": "ReplayMismatch", + "kind": "value", + "group": "pipeline", + "source": "ReplayMismatch", + "protocol": false + }, + { + "name": "ReplayVerificationRequest", + "kind": "value", + "group": "pipeline", + "source": "ReplayVerificationRequest", + "protocol": false + }, + { + "name": "ReplayVerificationResult", + "kind": "value", + "group": "pipeline", + "source": "ReplayVerificationResult", + "protocol": false + }, + { + "name": "RunTurnRequest", + "kind": "value", + "group": "pipeline", + "source": "RunTurnRequest", + "protocol": false + }, + { + "name": "RunTurnResult", + "kind": "value", + "group": "pipeline", + "source": "RunTurnResult", + "protocol": false + }, + { + "name": "TurnModelRequest", + "kind": "value", + "group": "pipeline", + "source": "TurnModelRequest", + "protocol": false + }, + { + "name": "TurnModelResponse", + "kind": "value", + "group": "pipeline", + "source": "TurnModelResponse", + "protocol": false + }, + { + "name": "TurnOptions", + "kind": "value", + "group": "pipeline", + "source": "TurnOptions", + "protocol": false + }, + { + "name": "StreamOptions", + "kind": "value", + "group": "streaming", + "source": "StreamOptions", + "protocol": false + }, + { + "name": "FormatConfig", + "kind": "value", + "group": "template", + "source": "FormatConfig", + "protocol": false + }, + { + "name": "ParserConfig", + "kind": "value", + "group": "template", + "source": "ParserConfig", + "protocol": false + }, + { + "name": "Template", + "kind": "value", + "group": "template", + "source": "Template", + "protocol": false + }, + { + "name": "Binding", + "kind": "value", + "group": "tools", + "source": "Binding", + "protocol": false + }, + { + "name": "CustomTool", + "kind": "value", + "group": "tools", + "source": "Tool", + "protocol": false + }, + { + "name": "FunctionTool", + "kind": "value", + "group": "tools", + "source": "Tool", + "protocol": false + }, + { + "name": "McpApprovalMode", + "kind": "value", + "group": "tools", + "source": "McpApprovalMode", + "protocol": false + }, + { + "name": "McpTool", + "kind": "value", + "group": "tools", + "source": "Tool", + "protocol": false + }, + { + "name": "OpenApiTool", + "kind": "value", + "group": "tools", + "source": "Tool", + "protocol": false + }, + { + "name": "PromptyTool", + "kind": "value", + "group": "tools", + "source": "Tool", + "protocol": false + }, + { + "name": "Tool", + "kind": "value", + "group": "tools", + "source": "Tool", + "protocol": false + }, + { + "name": "ToolContext", + "kind": "value", + "group": "tools", + "source": "ToolContext", + "protocol": false + }, + { + "name": "ToolDispatchResult", + "kind": "value", + "group": "tools", + "source": "ToolDispatchResult", + "protocol": false + }, + { + "name": "TraceFile", + "kind": "value", + "group": "tracing", + "source": "TraceFile", + "protocol": false + }, + { + "name": "TraceSpan", + "kind": "value", + "group": "tracing", + "source": "TraceSpan", + "protocol": false + }, + { + "name": "TraceTime", + "kind": "value", + "group": "tracing", + "source": "TraceTime", + "protocol": false + }, + { + "name": "AnthropicImageBlock", + "kind": "value", + "group": "wire", + "source": "AnthropicImageBlock", + "protocol": false + }, + { + "name": "AnthropicImageSource", + "kind": "value", + "group": "wire", + "source": "AnthropicImageSource", + "protocol": false + }, + { + "name": "AnthropicMessagesRequest", + "kind": "value", + "group": "wire", + "source": "AnthropicMessagesRequest", + "protocol": false + }, + { + "name": "AnthropicMessagesResponse", + "kind": "value", + "group": "wire", + "source": "AnthropicMessagesResponse", + "protocol": false + }, + { + "name": "AnthropicTextBlock", + "kind": "value", + "group": "wire", + "source": "AnthropicTextBlock", + "protocol": false + }, + { + "name": "AnthropicToolDefinition", + "kind": "value", + "group": "wire", + "source": "AnthropicToolDefinition", + "protocol": false + }, + { + "name": "AnthropicToolResultBlock", + "kind": "value", + "group": "wire", + "source": "AnthropicToolResultBlock", + "protocol": false + }, + { + "name": "AnthropicToolUseBlock", + "kind": "value", + "group": "wire", + "source": "AnthropicToolUseBlock", + "protocol": false + }, + { + "name": "AnthropicUsage", + "kind": "value", + "group": "wire", + "source": "AnthropicUsage", + "protocol": false + }, + { + "name": "AnthropicWireMessage", + "kind": "value", + "group": "wire", + "source": "AnthropicWireMessage", + "protocol": false + } + ], + "groups": [ + { + "name": "agent", + "exports": [ + "GuardrailResult", + "Prompty" + ], + "modules": [ + "guardrail_result", + "prompty" + ] + }, + { + "name": "connection", + "exports": [ + "AnonymousConnection", + "ApiKeyConnection", + "Connection", + "FoundryConnection", + "OAuthConnection", + "ReferenceConnection", + "RemoteConnection" + ], + "modules": [ + "connection" + ] + }, + { + "name": "conversation", + "exports": [ + "AudioPart", + "ContentPart", + "FilePart", + "ImagePart", + "Message", + "TextPart", + "ThreadMarker", + "ToolCall", + "ToolResult" + ], + "modules": [ + "content_part", + "message", + "thread_marker", + "tool_call", + "tool_result" + ] + }, + { + "name": "core", + "exports": [ + "ArrayProperty", + "FileNotFoundError", + "InvokerError", + "ObjectProperty", + "Property", + "ValidationError", + "ValidationResult" + ], + "modules": [ + "file_not_found_error", + "invoker_error", + "property", + "validation_error", + "validation_result" + ] + }, + { + "name": "events", + "exports": [ + "Checkpoint", + "CompactionCompletePayload", + "CompactionFailedPayload", + "CompactionStartPayload", + "DoneEventPayload", + "ErrorChunk", + "ErrorEventPayload", + "HarnessContext", + "HookEndPayload", + "HookStartPayload", + "HostToolRequest", + "HostToolResult", + "LlmCompletePayload", + "LlmStartPayload", + "MessagesUpdatedPayload", + "PermissionCompletedPayload", + "PermissionDecision", + "PermissionRequest", + "PermissionRequestedPayload", + "RedactedField", + "RedactionMetadata", + "RetryPayload", + "SessionEndPayload", + "SessionEvent", + "SessionFileRef", + "SessionRef", + "SessionStartPayload", + "SessionSummary", + "SessionTrace", + "SessionWarningPayload", + "StatusEventPayload", + "StreamChunk", + "TextChunk", + "ThinkingChunk", + "ThinkingEventPayload", + "TokenEventPayload", + "ToolCallCompletePayload", + "ToolCallStartPayload", + "ToolChunk", + "ToolExecutionCompletePayload", + "ToolExecutionStartPayload", + "ToolResultPayload", + "TrajectoryEvent", + "TurnEndPayload", + "TurnEvent", + "TurnStartPayload", + "TurnSummary", + "TurnTrace" + ], + "modules": [ + "checkpoint", + "compaction_complete_payload", + "compaction_failed_payload", + "compaction_start_payload", + "done_event_payload", + "error_event_payload", + "harness_context", + "hook_end_payload", + "hook_start_payload", + "host_tool_request", + "host_tool_result", + "llm_complete_payload", + "llm_start_payload", + "messages_updated_payload", + "permission_completed_payload", + "permission_decision", + "permission_request", + "permission_requested_payload", + "redacted_field", + "redaction_metadata", + "retry_payload", + "session_end_payload", + "session_event", + "session_file_ref", + "session_ref", + "session_start_payload", + "session_summary", + "session_trace", + "session_warning_payload", + "status_event_payload", + "stream_chunk", + "thinking_event_payload", + "token_event_payload", + "tool_call_complete_payload", + "tool_call_start_payload", + "tool_execution_complete_payload", + "tool_execution_start_payload", + "tool_result_payload", + "trajectory_event", + "turn_end_payload", + "turn_event", + "turn_start_payload", + "turn_summary", + "turn_trace" + ] + }, + { + "name": "model", + "exports": [ + "Model", + "ModelInfo", + "ModelOptions", + "TokenUsage" + ], + "modules": [ + "model", + "model_info", + "model_options", + "token_usage" + ] + }, + { + "name": "pipeline", + "exports": [ + "CheckpointStore", + "CompactionConfig", + "EventJournalWriter", + "EventSink", + "Executor", + "HostToolExecutor", + "Parser", + "PermissionResolver", + "Processor", + "Renderer", + "ReplayJournalRecord", + "ReplayMismatch", + "ReplayVerificationRequest", + "ReplayVerificationResult", + "RunTurnRequest", + "RunTurnResult", + "TurnModelRequest", + "TurnModelResponse", + "TurnOptions" + ], + "modules": [ + "checkpoint_store", + "compaction_config", + "event_journal_writer", + "event_sink", + "executor", + "host_tool_executor", + "parser", + "permission_resolver", + "processor", + "renderer", + "replay_journal_record", + "replay_mismatch", + "replay_verification_request", + "replay_verification_result", + "run_turn_request", + "run_turn_result", + "turn_model_request", + "turn_model_response", + "turn_options" + ] + }, + { + "name": "streaming", + "exports": [ + "StreamOptions" + ], + "modules": [ + "stream_options" + ] + }, + { + "name": "template", + "exports": [ + "FormatConfig", + "ParserConfig", + "Template" + ], + "modules": [ + "format_config", + "parser_config", + "template" + ] + }, + { + "name": "tools", + "exports": [ + "Binding", + "CustomTool", + "FunctionTool", + "McpApprovalMode", + "McpTool", + "OpenApiTool", + "PromptyTool", + "Tool", + "ToolContext", + "ToolDispatchResult" + ], + "modules": [ + "binding", + "mcp_approval_mode", + "tool", + "tool_context", + "tool_dispatch_result" + ] + }, + { + "name": "tracing", + "exports": [ + "TraceFile", + "TraceSpan", + "TraceTime" + ], + "modules": [ + "trace_file", + "trace_span", + "trace_time" + ] + }, + { + "name": "wire", + "exports": [ + "AnthropicImageBlock", + "AnthropicImageSource", + "AnthropicMessagesRequest", + "AnthropicMessagesResponse", + "AnthropicTextBlock", + "AnthropicToolDefinition", + "AnthropicToolResultBlock", + "AnthropicToolUseBlock", + "AnthropicUsage", + "AnthropicWireMessage" + ], + "modules": [ + "anthropic_image_block", + "anthropic_image_source", + "anthropic_messages_request", + "anthropic_messages_response", + "anthropic_text_block", + "anthropic_tool_definition", + "anthropic_tool_result_block", + "anthropic_tool_use_block", + "anthropic_usage", + "anthropic_wire_message" + ] + } + ], + "protocols": [ + { + "name": "CheckpointStore", + "group": "pipeline", + "symbol": "CheckpointStore", + "source": "CheckpointStore", + "methods": [ + { + "name": "listCheckpoints", + "returns": "Checkpoint[]", + "params": { + "sessionId": "string" + }, + "optional": false, + "sync": false + }, + { + "name": "load", + "returns": "Checkpoint?", + "params": { + "checkpointId": "string", + "sessionId": "string" + }, + "optional": false, + "sync": false + }, + { + "name": "save", + "returns": "Checkpoint", + "params": { + "checkpoint": "Checkpoint" + }, + "optional": false, + "sync": false + } + ] + }, + { + "name": "EventJournalWriter", + "group": "pipeline", + "symbol": "EventJournalWriter", + "source": "EventJournalWriter", + "methods": [ + { + "name": "appendSession", + "returns": "boolean", + "params": { + "sessionEvent": "SessionEvent" + }, + "optional": false, + "sync": true + }, + { + "name": "appendTurn", + "returns": "boolean", + "params": { + "turnEvent": "TurnEvent" + }, + "optional": false, + "sync": true + }, + { + "name": "close", + "returns": "boolean", + "params": { + "summary": "SessionSummary?" + }, + "optional": false, + "sync": true + } + ] + }, + { + "name": "EventSink", + "group": "pipeline", + "symbol": "EventSink", + "source": "EventSink", + "methods": [ + { + "name": "emitSession", + "returns": "boolean", + "params": { + "sessionEvent": "SessionEvent" + }, + "optional": false, + "sync": true + }, + { + "name": "emitTurn", + "returns": "boolean", + "params": { + "turnEvent": "TurnEvent" + }, + "optional": false, + "sync": true + } + ] + }, + { + "name": "Executor", + "group": "pipeline", + "symbol": "Executor", + "source": "Executor", + "methods": [ + { + "name": "execute", + "returns": "unknown", + "params": { + "agent": "Prompty", + "messages": "Message[]" + }, + "optional": false, + "sync": false + }, + { + "name": "executeStream", + "returns": "unknown", + "params": { + "agent": "Prompty", + "messages": "Message[]" + }, + "optional": true, + "sync": false + }, + { + "name": "formatToolMessages", + "returns": "Message[]", + "params": { + "rawResponse": "unknown", + "textContent": "string?", + "toolCalls": "ToolCall[]", + "toolResults": "string[]" + }, + "optional": false, + "sync": true + } + ] + }, + { + "name": "HostToolExecutor", + "group": "pipeline", + "symbol": "HostToolExecutor", + "source": "HostToolExecutor", + "methods": [ + { + "name": "execute", + "returns": "HostToolResult", + "params": { + "request": "HostToolRequest" + }, + "optional": false, + "sync": false + } + ] + }, + { + "name": "Parser", + "group": "pipeline", + "symbol": "Parser", + "source": "Parser", + "methods": [ + { + "name": "parse", + "returns": "Message[]", + "params": { + "agent": "Prompty", + "context": "Record?", + "rendered": "string" + }, + "optional": false, + "sync": false + }, + { + "name": "preRender", + "returns": "unknown?", + "params": { + "template": "string" + }, + "optional": true, + "sync": true + } + ] + }, + { + "name": "PermissionResolver", + "group": "pipeline", + "symbol": "PermissionResolver", + "source": "PermissionResolver", + "methods": [ + { + "name": "request", + "returns": "PermissionDecision", + "params": { + "request": "PermissionRequest" + }, + "optional": false, + "sync": false + } + ] + }, + { + "name": "Processor", + "group": "pipeline", + "symbol": "Processor", + "source": "Processor", + "methods": [ + { + "name": "process", + "returns": "unknown", + "params": { + "agent": "Prompty", + "response": "unknown" + }, + "optional": false, + "sync": false + }, + { + "name": "processStream", + "returns": "unknown", + "params": { + "stream": "unknown" + }, + "optional": true, + "sync": false + } + ] + }, + { + "name": "Renderer", + "group": "pipeline", + "symbol": "Renderer", + "source": "Renderer", + "methods": [ + { + "name": "render", + "returns": "string", + "params": { + "agent": "Prompty", + "inputs": "Record", + "template": "string" + }, + "optional": false, + "sync": false + } + ] + } + ], + "modules": [ + "AnthropicImageBlock", + "AnthropicImageSource", + "AnthropicMessagesRequest", + "AnthropicMessagesResponse", + "AnthropicTextBlock", + "AnthropicToolDefinition", + "AnthropicToolResultBlock", + "AnthropicToolUseBlock", + "AnthropicUsage", + "AnthropicWireMessage", + "Binding", + "Checkpoint", + "CheckpointStore", + "CompactionCompletePayload", + "CompactionConfig", + "CompactionFailedPayload", + "CompactionStartPayload", + "Connection", + "ContentPart", + "DoneEventPayload", + "ErrorEventPayload", + "EventJournalWriter", + "EventSink", + "Executor", + "FileNotFoundError", + "FormatConfig", + "GuardrailResult", + "HarnessContext", + "HookEndPayload", + "HookStartPayload", + "HostToolExecutor", + "HostToolRequest", + "HostToolResult", + "InvokerError", + "LlmCompletePayload", + "LlmStartPayload", + "McpApprovalMode", + "Message", + "MessagesUpdatedPayload", + "Model", + "ModelInfo", + "ModelOptions", + "Parser", + "ParserConfig", + "PermissionCompletedPayload", + "PermissionDecision", + "PermissionRequest", + "PermissionRequestedPayload", + "PermissionResolver", + "Processor", + "Prompty", + "Property", + "RedactedField", + "RedactionMetadata", + "Renderer", + "ReplayJournalRecord", + "ReplayMismatch", + "ReplayVerificationRequest", + "ReplayVerificationResult", + "RetryPayload", + "RunTurnRequest", + "RunTurnResult", + "SessionEndPayload", + "SessionEvent", + "SessionFileRef", + "SessionRef", + "SessionStartPayload", + "SessionSummary", + "SessionTrace", + "SessionWarningPayload", + "StatusEventPayload", + "StreamChunk", + "StreamOptions", + "Template", + "ThinkingEventPayload", + "ThreadMarker", + "TokenEventPayload", + "TokenUsage", + "Tool", + "ToolCall", + "ToolCallCompletePayload", + "ToolCallStartPayload", + "ToolContext", + "ToolDispatchResult", + "ToolExecutionCompletePayload", + "ToolExecutionStartPayload", + "ToolResult", + "ToolResultPayload", + "TraceFile", + "TraceSpan", + "TraceTime", + "TrajectoryEvent", + "TurnEndPayload", + "TurnEvent", + "TurnModelRequest", + "TurnModelResponse", + "TurnOptions", + "TurnStartPayload", + "TurnSummary", + "TurnTrace", + "ValidationError", + "ValidationResult" + ] + }, + { + "target": "python", + "outputRoot": "../runtime/python/prompty/prompty/model", + "packageName": "prompty", + "rootExports": [ + "AnonymousConnection", + "AnthropicImageBlock", + "AnthropicImageSource", + "AnthropicMessagesRequest", + "AnthropicMessagesResponse", + "AnthropicTextBlock", + "AnthropicToolDefinition", + "AnthropicToolResultBlock", + "AnthropicToolUseBlock", + "AnthropicUsage", + "AnthropicWireMessage", + "ApiKeyConnection", + "ArrayProperty", + "AudioPart", + "Binding", + "Checkpoint", + "CheckpointStore", + "CompactionCompletePayload", + "CompactionConfig", + "CompactionFailedPayload", + "CompactionStartPayload", + "Connection", + "ContentPart", + "CustomTool", + "DoneEventPayload", + "ErrorChunk", + "ErrorEventPayload", + "EventJournalWriter", + "EventSink", + "Executor", + "FileNotFoundError", + "FilePart", + "FormatConfig", + "FoundryConnection", + "FunctionTool", + "GuardrailResult", + "HarnessContext", + "HookEndPayload", + "HookStartPayload", + "HostToolExecutor", + "HostToolRequest", + "HostToolResult", + "ImagePart", + "InvokerError", + "LlmCompletePayload", + "LlmStartPayload", + "McpApprovalMode", + "McpTool", + "Message", + "MessagesUpdatedPayload", + "Model", + "ModelInfo", + "ModelOptions", + "OAuthConnection", + "ObjectProperty", + "OpenApiTool", + "Parser", + "ParserConfig", + "PermissionCompletedPayload", + "PermissionDecision", + "PermissionRequest", + "PermissionRequestedPayload", + "PermissionResolver", + "Processor", + "Prompty", + "PromptyTool", + "Property", + "RedactedField", + "RedactionMetadata", + "ReferenceConnection", + "RemoteConnection", + "Renderer", + "ReplayJournalRecord", + "ReplayMismatch", + "ReplayVerificationRequest", + "ReplayVerificationResult", + "RetryPayload", + "RunTurnRequest", + "RunTurnResult", + "SessionEndPayload", + "SessionEvent", + "SessionFileRef", + "SessionRef", + "SessionStartPayload", + "SessionSummary", + "SessionTrace", + "SessionWarningPayload", + "StatusEventPayload", + "StreamChunk", + "StreamOptions", + "Template", + "TextChunk", + "TextPart", + "ThinkingChunk", + "ThinkingEventPayload", + "ThreadMarker", + "TokenEventPayload", + "TokenUsage", + "Tool", + "ToolCall", + "ToolCallCompletePayload", + "ToolCallStartPayload", + "ToolChunk", + "ToolContext", + "ToolDispatchResult", + "ToolExecutionCompletePayload", + "ToolExecutionStartPayload", + "ToolResult", + "ToolResultPayload", + "TraceFile", + "TraceSpan", + "TraceTime", + "TrajectoryEvent", + "TurnEndPayload", + "TurnEvent", + "TurnModelRequest", + "TurnModelResponse", + "TurnOptions", + "TurnStartPayload", + "TurnSummary", + "TurnTrace", + "ValidationError", + "ValidationResult" + ], + "exports": [ + { + "name": "GuardrailResult", + "kind": "value", + "group": "agent", + "source": ".agent", + "protocol": false + }, + { + "name": "Prompty", + "kind": "value", + "group": "agent", + "source": ".agent", + "protocol": false + }, + { + "name": "AnonymousConnection", + "kind": "value", + "group": "connection", + "source": ".connection", + "protocol": false + }, + { + "name": "ApiKeyConnection", + "kind": "value", + "group": "connection", + "source": ".connection", + "protocol": false + }, + { + "name": "Connection", + "kind": "value", + "group": "connection", + "source": ".connection", + "protocol": false + }, + { + "name": "FoundryConnection", + "kind": "value", + "group": "connection", + "source": ".connection", + "protocol": false + }, + { + "name": "OAuthConnection", + "kind": "value", + "group": "connection", + "source": ".connection", + "protocol": false + }, + { + "name": "ReferenceConnection", + "kind": "value", + "group": "connection", + "source": ".connection", + "protocol": false + }, + { + "name": "RemoteConnection", + "kind": "value", + "group": "connection", + "source": ".connection", + "protocol": false + }, + { + "name": "AudioPart", + "kind": "value", + "group": "conversation", + "source": ".conversation", + "protocol": false + }, + { + "name": "ContentPart", + "kind": "value", + "group": "conversation", + "source": ".conversation", + "protocol": false + }, + { + "name": "FilePart", + "kind": "value", + "group": "conversation", + "source": ".conversation", + "protocol": false + }, + { + "name": "ImagePart", + "kind": "value", + "group": "conversation", + "source": ".conversation", + "protocol": false + }, + { + "name": "Message", + "kind": "value", + "group": "conversation", + "source": ".conversation", + "protocol": false + }, + { + "name": "TextPart", + "kind": "value", + "group": "conversation", + "source": ".conversation", + "protocol": false + }, + { + "name": "ThreadMarker", + "kind": "value", + "group": "conversation", + "source": ".conversation", + "protocol": false + }, + { + "name": "ToolCall", + "kind": "value", + "group": "conversation", + "source": ".conversation", + "protocol": false + }, + { + "name": "ToolResult", + "kind": "value", + "group": "conversation", + "source": ".conversation", + "protocol": false + }, + { + "name": "ArrayProperty", + "kind": "value", + "group": "core", + "source": ".core", + "protocol": false + }, + { + "name": "FileNotFoundError", + "kind": "value", + "group": "core", + "source": ".core", + "protocol": false + }, + { + "name": "InvokerError", + "kind": "value", + "group": "core", + "source": ".core", + "protocol": false + }, + { + "name": "ObjectProperty", + "kind": "value", + "group": "core", + "source": ".core", + "protocol": false + }, + { + "name": "Property", + "kind": "value", + "group": "core", + "source": ".core", + "protocol": false + }, + { + "name": "ValidationError", + "kind": "value", + "group": "core", + "source": ".core", + "protocol": false + }, + { + "name": "ValidationResult", + "kind": "value", + "group": "core", + "source": ".core", + "protocol": false + }, + { + "name": "Checkpoint", + "kind": "value", + "group": "events", + "source": ".events", + "protocol": false + }, + { + "name": "CompactionCompletePayload", + "kind": "value", + "group": "events", + "source": ".events", + "protocol": false + }, + { + "name": "CompactionFailedPayload", + "kind": "value", + "group": "events", + "source": ".events", + "protocol": false + }, + { + "name": "CompactionStartPayload", + "kind": "value", + "group": "events", + "source": ".events", + "protocol": false + }, + { + "name": "DoneEventPayload", + "kind": "value", + "group": "events", + "source": ".events", + "protocol": false + }, + { + "name": "ErrorChunk", + "kind": "value", + "group": "events", + "source": ".events", + "protocol": false + }, + { + "name": "ErrorEventPayload", + "kind": "value", + "group": "events", + "source": ".events", + "protocol": false + }, + { + "name": "HarnessContext", + "kind": "value", + "group": "events", + "source": ".events", + "protocol": false + }, + { + "name": "HookEndPayload", + "kind": "value", + "group": "events", + "source": ".events", + "protocol": false + }, + { + "name": "HookStartPayload", + "kind": "value", + "group": "events", + "source": ".events", + "protocol": false + }, + { + "name": "HostToolRequest", + "kind": "value", + "group": "events", + "source": ".events", + "protocol": false + }, + { + "name": "HostToolResult", + "kind": "value", + "group": "events", + "source": ".events", + "protocol": false + }, + { + "name": "LlmCompletePayload", + "kind": "value", + "group": "events", + "source": ".events", + "protocol": false + }, + { + "name": "LlmStartPayload", + "kind": "value", + "group": "events", + "source": ".events", + "protocol": false + }, + { + "name": "MessagesUpdatedPayload", + "kind": "value", + "group": "events", + "source": ".events", + "protocol": false + }, + { + "name": "PermissionCompletedPayload", + "kind": "value", + "group": "events", + "source": ".events", + "protocol": false + }, + { + "name": "PermissionDecision", + "kind": "value", + "group": "events", + "source": ".events", + "protocol": false + }, + { + "name": "PermissionRequest", + "kind": "value", + "group": "events", + "source": ".events", + "protocol": false + }, + { + "name": "PermissionRequestedPayload", + "kind": "value", + "group": "events", + "source": ".events", + "protocol": false + }, + { + "name": "RedactedField", + "kind": "value", + "group": "events", + "source": ".events", + "protocol": false + }, + { + "name": "RedactionMetadata", + "kind": "value", + "group": "events", + "source": ".events", + "protocol": false + }, + { + "name": "RetryPayload", + "kind": "value", + "group": "events", + "source": ".events", + "protocol": false + }, + { + "name": "SessionEndPayload", + "kind": "value", + "group": "events", + "source": ".events", + "protocol": false + }, + { + "name": "SessionEvent", + "kind": "value", + "group": "events", + "source": ".events", + "protocol": false + }, + { + "name": "SessionFileRef", + "kind": "value", + "group": "events", + "source": ".events", + "protocol": false + }, + { + "name": "SessionRef", + "kind": "value", + "group": "events", + "source": ".events", + "protocol": false + }, + { + "name": "SessionStartPayload", + "kind": "value", + "group": "events", + "source": ".events", + "protocol": false + }, + { + "name": "SessionSummary", + "kind": "value", + "group": "events", + "source": ".events", + "protocol": false + }, + { + "name": "SessionTrace", + "kind": "value", + "group": "events", + "source": ".events", + "protocol": false + }, + { + "name": "SessionWarningPayload", + "kind": "value", + "group": "events", + "source": ".events", + "protocol": false + }, + { + "name": "StatusEventPayload", + "kind": "value", + "group": "events", + "source": ".events", + "protocol": false + }, + { + "name": "StreamChunk", + "kind": "value", + "group": "events", + "source": ".events", + "protocol": false + }, + { + "name": "TextChunk", + "kind": "value", + "group": "events", + "source": ".events", + "protocol": false + }, + { + "name": "ThinkingChunk", + "kind": "value", + "group": "events", + "source": ".events", + "protocol": false + }, + { + "name": "ThinkingEventPayload", + "kind": "value", + "group": "events", + "source": ".events", + "protocol": false + }, + { + "name": "TokenEventPayload", + "kind": "value", + "group": "events", + "source": ".events", + "protocol": false + }, + { + "name": "ToolCallCompletePayload", + "kind": "value", + "group": "events", + "source": ".events", + "protocol": false + }, + { + "name": "ToolCallStartPayload", + "kind": "value", + "group": "events", + "source": ".events", + "protocol": false + }, + { + "name": "ToolChunk", + "kind": "value", + "group": "events", + "source": ".events", + "protocol": false + }, + { + "name": "ToolExecutionCompletePayload", + "kind": "value", + "group": "events", + "source": ".events", + "protocol": false + }, + { + "name": "ToolExecutionStartPayload", + "kind": "value", + "group": "events", + "source": ".events", + "protocol": false + }, + { + "name": "ToolResultPayload", + "kind": "value", + "group": "events", + "source": ".events", + "protocol": false + }, + { + "name": "TrajectoryEvent", + "kind": "value", + "group": "events", + "source": ".events", + "protocol": false + }, + { + "name": "TurnEndPayload", + "kind": "value", + "group": "events", + "source": ".events", + "protocol": false + }, + { + "name": "TurnEvent", + "kind": "value", + "group": "events", + "source": ".events", + "protocol": false + }, + { + "name": "TurnStartPayload", + "kind": "value", + "group": "events", + "source": ".events", + "protocol": false + }, + { + "name": "TurnSummary", + "kind": "value", + "group": "events", + "source": ".events", + "protocol": false + }, + { + "name": "TurnTrace", + "kind": "value", + "group": "events", + "source": ".events", + "protocol": false + }, + { + "name": "Model", + "kind": "value", + "group": "model", + "source": ".model", + "protocol": false + }, + { + "name": "ModelInfo", + "kind": "value", + "group": "model", + "source": ".model", + "protocol": false + }, + { + "name": "ModelOptions", + "kind": "value", + "group": "model", + "source": ".model", + "protocol": false + }, + { + "name": "TokenUsage", + "kind": "value", + "group": "model", + "source": ".model", + "protocol": false + }, + { + "name": "CheckpointStore", + "kind": "type", + "group": "pipeline", + "source": ".pipeline", + "protocol": true + }, + { + "name": "CompactionConfig", + "kind": "value", + "group": "pipeline", + "source": ".pipeline", + "protocol": false + }, + { + "name": "EventJournalWriter", + "kind": "type", + "group": "pipeline", + "source": ".pipeline", + "protocol": true + }, + { + "name": "EventSink", + "kind": "type", + "group": "pipeline", + "source": ".pipeline", + "protocol": true + }, + { + "name": "Executor", + "kind": "type", + "group": "pipeline", + "source": ".pipeline", + "protocol": true + }, + { + "name": "HostToolExecutor", + "kind": "type", + "group": "pipeline", + "source": ".pipeline", + "protocol": true + }, + { + "name": "Parser", + "kind": "type", + "group": "pipeline", + "source": ".pipeline", + "protocol": true + }, + { + "name": "PermissionResolver", + "kind": "type", + "group": "pipeline", + "source": ".pipeline", + "protocol": true + }, + { + "name": "Processor", + "kind": "type", + "group": "pipeline", + "source": ".pipeline", + "protocol": true + }, + { + "name": "Renderer", + "kind": "type", + "group": "pipeline", + "source": ".pipeline", + "protocol": true + }, + { + "name": "ReplayJournalRecord", + "kind": "value", + "group": "pipeline", + "source": ".pipeline", + "protocol": false + }, + { + "name": "ReplayMismatch", + "kind": "value", + "group": "pipeline", + "source": ".pipeline", + "protocol": false + }, + { + "name": "ReplayVerificationRequest", + "kind": "value", + "group": "pipeline", + "source": ".pipeline", + "protocol": false + }, + { + "name": "ReplayVerificationResult", + "kind": "value", + "group": "pipeline", + "source": ".pipeline", + "protocol": false + }, + { + "name": "RunTurnRequest", + "kind": "value", + "group": "pipeline", + "source": ".pipeline", + "protocol": false + }, + { + "name": "RunTurnResult", + "kind": "value", + "group": "pipeline", + "source": ".pipeline", + "protocol": false + }, + { + "name": "TurnModelRequest", + "kind": "value", + "group": "pipeline", + "source": ".pipeline", + "protocol": false + }, + { + "name": "TurnModelResponse", + "kind": "value", + "group": "pipeline", + "source": ".pipeline", + "protocol": false + }, + { + "name": "TurnOptions", + "kind": "value", + "group": "pipeline", + "source": ".pipeline", + "protocol": false + }, + { + "name": "StreamOptions", + "kind": "value", + "group": "streaming", + "source": ".streaming", + "protocol": false + }, + { + "name": "FormatConfig", + "kind": "value", + "group": "template", + "source": ".template", + "protocol": false + }, + { + "name": "ParserConfig", + "kind": "value", + "group": "template", + "source": ".template", + "protocol": false + }, + { + "name": "Template", + "kind": "value", + "group": "template", + "source": ".template", + "protocol": false + }, + { + "name": "Binding", + "kind": "value", + "group": "tools", + "source": ".tools", + "protocol": false + }, + { + "name": "CustomTool", + "kind": "value", + "group": "tools", + "source": ".tools", + "protocol": false + }, + { + "name": "FunctionTool", + "kind": "value", + "group": "tools", + "source": ".tools", + "protocol": false + }, + { + "name": "McpApprovalMode", + "kind": "value", + "group": "tools", + "source": ".tools", + "protocol": false + }, + { + "name": "McpTool", + "kind": "value", + "group": "tools", + "source": ".tools", + "protocol": false + }, + { + "name": "OpenApiTool", + "kind": "value", + "group": "tools", + "source": ".tools", + "protocol": false + }, + { + "name": "PromptyTool", + "kind": "value", + "group": "tools", + "source": ".tools", + "protocol": false + }, + { + "name": "Tool", + "kind": "value", + "group": "tools", + "source": ".tools", + "protocol": false + }, + { + "name": "ToolContext", + "kind": "value", + "group": "tools", + "source": ".tools", + "protocol": false + }, + { + "name": "ToolDispatchResult", + "kind": "value", + "group": "tools", + "source": ".tools", + "protocol": false + }, + { + "name": "TraceFile", + "kind": "value", + "group": "tracing", + "source": ".tracing", + "protocol": false + }, + { + "name": "TraceSpan", + "kind": "value", + "group": "tracing", + "source": ".tracing", + "protocol": false + }, + { + "name": "TraceTime", + "kind": "value", + "group": "tracing", + "source": ".tracing", + "protocol": false + }, + { + "name": "AnthropicImageBlock", + "kind": "value", + "group": "wire", + "source": ".wire", + "protocol": false + }, + { + "name": "AnthropicImageSource", + "kind": "value", + "group": "wire", + "source": ".wire", + "protocol": false + }, + { + "name": "AnthropicMessagesRequest", + "kind": "value", + "group": "wire", + "source": ".wire", + "protocol": false + }, + { + "name": "AnthropicMessagesResponse", + "kind": "value", + "group": "wire", + "source": ".wire", + "protocol": false + }, + { + "name": "AnthropicTextBlock", + "kind": "value", + "group": "wire", + "source": ".wire", + "protocol": false + }, + { + "name": "AnthropicToolDefinition", + "kind": "value", + "group": "wire", + "source": ".wire", + "protocol": false + }, + { + "name": "AnthropicToolResultBlock", + "kind": "value", + "group": "wire", + "source": ".wire", + "protocol": false + }, + { + "name": "AnthropicToolUseBlock", + "kind": "value", + "group": "wire", + "source": ".wire", + "protocol": false + }, + { + "name": "AnthropicUsage", + "kind": "value", + "group": "wire", + "source": ".wire", + "protocol": false + }, + { + "name": "AnthropicWireMessage", + "kind": "value", + "group": "wire", + "source": ".wire", + "protocol": false + } + ], + "groups": [ + { + "name": "agent", + "exports": [ + "GuardrailResult", + "Prompty" + ], + "modules": [ + "_GuardrailResult", + "_Prompty" + ] + }, + { + "name": "connection", + "exports": [ + "AnonymousConnection", + "ApiKeyConnection", + "Connection", + "FoundryConnection", + "OAuthConnection", + "ReferenceConnection", + "RemoteConnection" + ], + "modules": [ + "_Connection" + ] + }, + { + "name": "conversation", + "exports": [ + "AudioPart", + "ContentPart", + "FilePart", + "ImagePart", + "Message", + "TextPart", + "ThreadMarker", + "ToolCall", + "ToolResult" + ], + "modules": [ + "_ContentPart", + "_Message", + "_ThreadMarker", + "_ToolCall", + "_ToolResult" + ] + }, + { + "name": "core", + "exports": [ + "ArrayProperty", + "FileNotFoundError", + "InvokerError", + "ObjectProperty", + "Property", + "ValidationError", + "ValidationResult" + ], + "modules": [ + "_FileNotFoundError", + "_InvokerError", + "_Property", + "_ValidationError", + "_ValidationResult" + ] + }, + { + "name": "events", + "exports": [ + "Checkpoint", + "CompactionCompletePayload", + "CompactionFailedPayload", + "CompactionStartPayload", + "DoneEventPayload", + "ErrorChunk", + "ErrorEventPayload", + "HarnessContext", + "HookEndPayload", + "HookStartPayload", + "HostToolRequest", + "HostToolResult", + "LlmCompletePayload", + "LlmStartPayload", + "MessagesUpdatedPayload", + "PermissionCompletedPayload", + "PermissionDecision", + "PermissionRequest", + "PermissionRequestedPayload", + "RedactedField", + "RedactionMetadata", + "RetryPayload", + "SessionEndPayload", + "SessionEvent", + "SessionFileRef", + "SessionRef", + "SessionStartPayload", + "SessionSummary", + "SessionTrace", + "SessionWarningPayload", + "StatusEventPayload", + "StreamChunk", + "TextChunk", + "ThinkingChunk", + "ThinkingEventPayload", + "TokenEventPayload", + "ToolCallCompletePayload", + "ToolCallStartPayload", + "ToolChunk", + "ToolExecutionCompletePayload", + "ToolExecutionStartPayload", + "ToolResultPayload", + "TrajectoryEvent", + "TurnEndPayload", + "TurnEvent", + "TurnStartPayload", + "TurnSummary", + "TurnTrace" + ], + "modules": [ + "_Checkpoint", + "_CompactionCompletePayload", + "_CompactionFailedPayload", + "_CompactionStartPayload", + "_DoneEventPayload", + "_ErrorEventPayload", + "_HarnessContext", + "_HookEndPayload", + "_HookStartPayload", + "_HostToolRequest", + "_HostToolResult", + "_LlmCompletePayload", + "_LlmStartPayload", + "_MessagesUpdatedPayload", + "_PermissionCompletedPayload", + "_PermissionDecision", + "_PermissionRequest", + "_PermissionRequestedPayload", + "_RedactedField", + "_RedactionMetadata", + "_RetryPayload", + "_SessionEndPayload", + "_SessionEvent", + "_SessionFileRef", + "_SessionRef", + "_SessionStartPayload", + "_SessionSummary", + "_SessionTrace", + "_SessionWarningPayload", + "_StatusEventPayload", + "_StreamChunk", + "_ThinkingEventPayload", + "_TokenEventPayload", + "_ToolCallCompletePayload", + "_ToolCallStartPayload", + "_ToolExecutionCompletePayload", + "_ToolExecutionStartPayload", + "_ToolResultPayload", + "_TrajectoryEvent", + "_TurnEndPayload", + "_TurnEvent", + "_TurnStartPayload", + "_TurnSummary", + "_TurnTrace" + ] + }, + { + "name": "model", + "exports": [ + "Model", + "ModelInfo", + "ModelOptions", + "TokenUsage" + ], + "modules": [ + "_Model", + "_ModelInfo", + "_ModelOptions", + "_TokenUsage" + ] + }, + { + "name": "pipeline", + "exports": [ + "CheckpointStore", + "CompactionConfig", + "EventJournalWriter", + "EventSink", + "Executor", + "HostToolExecutor", + "Parser", + "PermissionResolver", + "Processor", + "Renderer", + "ReplayJournalRecord", + "ReplayMismatch", + "ReplayVerificationRequest", + "ReplayVerificationResult", + "RunTurnRequest", + "RunTurnResult", + "TurnModelRequest", + "TurnModelResponse", + "TurnOptions" + ], + "modules": [ + "_CheckpointStore", + "_CompactionConfig", + "_EventJournalWriter", + "_EventSink", + "_Executor", + "_HostToolExecutor", + "_Parser", + "_PermissionResolver", + "_Processor", + "_Renderer", + "_ReplayJournalRecord", + "_ReplayMismatch", + "_ReplayVerificationRequest", + "_ReplayVerificationResult", + "_RunTurnRequest", + "_RunTurnResult", + "_TurnModelRequest", + "_TurnModelResponse", + "_TurnOptions" + ] + }, + { + "name": "streaming", + "exports": [ + "StreamOptions" + ], + "modules": [ + "_StreamOptions" + ] + }, + { + "name": "template", + "exports": [ + "FormatConfig", + "ParserConfig", + "Template" + ], + "modules": [ + "_FormatConfig", + "_ParserConfig", + "_Template" + ] + }, + { + "name": "tools", + "exports": [ + "Binding", + "CustomTool", + "FunctionTool", + "McpApprovalMode", + "McpTool", + "OpenApiTool", + "PromptyTool", + "Tool", + "ToolContext", + "ToolDispatchResult" + ], + "modules": [ + "_Binding", + "_McpApprovalMode", + "_Tool", + "_ToolContext", + "_ToolDispatchResult" + ] + }, + { + "name": "tracing", + "exports": [ + "TraceFile", + "TraceSpan", + "TraceTime" + ], + "modules": [ + "_TraceFile", + "_TraceSpan", + "_TraceTime" + ] + }, + { + "name": "wire", + "exports": [ + "AnthropicImageBlock", + "AnthropicImageSource", + "AnthropicMessagesRequest", + "AnthropicMessagesResponse", + "AnthropicTextBlock", + "AnthropicToolDefinition", + "AnthropicToolResultBlock", + "AnthropicToolUseBlock", + "AnthropicUsage", + "AnthropicWireMessage" + ], + "modules": [ + "_AnthropicImageBlock", + "_AnthropicImageSource", + "_AnthropicMessagesRequest", + "_AnthropicMessagesResponse", + "_AnthropicTextBlock", + "_AnthropicToolDefinition", + "_AnthropicToolResultBlock", + "_AnthropicToolUseBlock", + "_AnthropicUsage", + "_AnthropicWireMessage" + ] + } + ], + "protocols": [ + { + "name": "CheckpointStore", + "group": "pipeline", + "symbol": "CheckpointStore", + "source": ".pipeline", + "methods": [ + { + "name": "listCheckpoints", + "returns": "Checkpoint[]", + "params": { + "sessionId": "string" + }, + "optional": false, + "sync": false + }, + { + "name": "load", + "returns": "Checkpoint?", + "params": { + "checkpointId": "string", + "sessionId": "string" + }, + "optional": false, + "sync": false + }, + { + "name": "save", + "returns": "Checkpoint", + "params": { + "checkpoint": "Checkpoint" + }, + "optional": false, + "sync": false + } + ] + }, + { + "name": "EventJournalWriter", + "group": "pipeline", + "symbol": "EventJournalWriter", + "source": ".pipeline", + "methods": [ + { + "name": "appendSession", + "returns": "boolean", + "params": { + "sessionEvent": "SessionEvent" + }, + "optional": false, + "sync": true + }, + { + "name": "appendTurn", + "returns": "boolean", + "params": { + "turnEvent": "TurnEvent" + }, + "optional": false, + "sync": true + }, + { + "name": "close", + "returns": "boolean", + "params": { + "summary": "SessionSummary?" + }, + "optional": false, + "sync": true + } + ] + }, + { + "name": "EventSink", + "group": "pipeline", + "symbol": "EventSink", + "source": ".pipeline", + "methods": [ + { + "name": "emitSession", + "returns": "boolean", + "params": { + "sessionEvent": "SessionEvent" + }, + "optional": false, + "sync": true + }, + { + "name": "emitTurn", + "returns": "boolean", + "params": { + "turnEvent": "TurnEvent" + }, + "optional": false, + "sync": true + } + ] + }, + { + "name": "Executor", + "group": "pipeline", + "symbol": "Executor", + "source": ".pipeline", + "methods": [ + { + "name": "execute", + "returns": "unknown", + "params": { + "agent": "Prompty", + "messages": "Message[]" + }, + "optional": false, + "sync": false + }, + { + "name": "executeStream", + "returns": "unknown", + "params": { + "agent": "Prompty", + "messages": "Message[]" + }, + "optional": true, + "sync": false + }, + { + "name": "formatToolMessages", + "returns": "Message[]", + "params": { + "rawResponse": "unknown", + "textContent": "string?", + "toolCalls": "ToolCall[]", + "toolResults": "string[]" + }, + "optional": false, + "sync": true + } + ] + }, + { + "name": "HostToolExecutor", + "group": "pipeline", + "symbol": "HostToolExecutor", + "source": ".pipeline", + "methods": [ + { + "name": "execute", + "returns": "HostToolResult", + "params": { + "request": "HostToolRequest" + }, + "optional": false, + "sync": false + } + ] + }, + { + "name": "Parser", + "group": "pipeline", + "symbol": "Parser", + "source": ".pipeline", + "methods": [ + { + "name": "parse", + "returns": "Message[]", + "params": { + "agent": "Prompty", + "context": "Record?", + "rendered": "string" + }, + "optional": false, + "sync": false + }, + { + "name": "preRender", + "returns": "unknown?", + "params": { + "template": "string" + }, + "optional": true, + "sync": true + } + ] + }, + { + "name": "PermissionResolver", + "group": "pipeline", + "symbol": "PermissionResolver", + "source": ".pipeline", + "methods": [ + { + "name": "request", + "returns": "PermissionDecision", + "params": { + "request": "PermissionRequest" + }, + "optional": false, + "sync": false + } + ] + }, + { + "name": "Processor", + "group": "pipeline", + "symbol": "Processor", + "source": ".pipeline", + "methods": [ + { + "name": "process", + "returns": "unknown", + "params": { + "agent": "Prompty", + "response": "unknown" + }, + "optional": false, + "sync": false + }, + { + "name": "processStream", + "returns": "unknown", + "params": { + "stream": "unknown" + }, + "optional": true, + "sync": false + } + ] + }, + { + "name": "Renderer", + "group": "pipeline", + "symbol": "Renderer", + "source": ".pipeline", + "methods": [ + { + "name": "render", + "returns": "string", + "params": { + "agent": "Prompty", + "inputs": "Record", + "template": "string" + }, + "optional": false, + "sync": false + } + ] + } + ], + "modules": [ + ".agent", + ".connection", + ".conversation", + ".core", + ".events", + ".model", + ".pipeline", + ".streaming", + ".template", + ".tools", + ".tracing", + ".wire" + ] + }, + { + "target": "rust", + "outputRoot": "../runtime/rust/prompty/src/model", + "rootExports": [ + "AnonymousConnection", + "AnthropicImageBlock", + "AnthropicImageSource", + "AnthropicMessagesRequest", + "AnthropicMessagesResponse", + "AnthropicTextBlock", + "AnthropicToolDefinition", + "AnthropicToolResultBlock", + "AnthropicToolUseBlock", + "AnthropicUsage", + "AnthropicWireMessage", + "ApiKeyConnection", + "ArrayProperty", + "AudioPart", + "Binding", + "Checkpoint", + "CheckpointStore", + "CompactionCompletePayload", + "CompactionConfig", + "CompactionFailedPayload", + "CompactionStartPayload", + "Connection", + "ContentPart", + "CustomTool", + "DoneEventPayload", + "ErrorChunk", + "ErrorEventPayload", + "EventJournalWriter", + "EventSink", + "Executor", + "FileNotFoundError", + "FilePart", + "FormatConfig", + "FoundryConnection", + "FunctionTool", + "GuardrailResult", + "HarnessContext", + "HookEndPayload", + "HookStartPayload", + "HostToolExecutor", + "HostToolRequest", + "HostToolResult", + "ImagePart", + "InvokerError", + "LlmCompletePayload", + "LlmStartPayload", + "McpApprovalMode", + "McpTool", + "Message", + "MessagesUpdatedPayload", + "Model", + "ModelInfo", + "ModelOptions", + "OAuthConnection", + "ObjectProperty", + "OpenApiTool", + "Parser", + "ParserConfig", + "PermissionCompletedPayload", + "PermissionDecision", + "PermissionRequest", + "PermissionRequestedPayload", + "PermissionResolver", + "Processor", + "Prompty", + "PromptyTool", + "Property", + "RedactedField", + "RedactionMetadata", + "ReferenceConnection", + "RemoteConnection", + "Renderer", + "ReplayJournalRecord", + "ReplayMismatch", + "ReplayVerificationRequest", + "ReplayVerificationResult", + "RetryPayload", + "RunTurnRequest", + "RunTurnResult", + "SessionEndPayload", + "SessionEvent", + "SessionFileRef", + "SessionRef", + "SessionStartPayload", + "SessionSummary", + "SessionTrace", + "SessionWarningPayload", + "StatusEventPayload", + "StreamChunk", + "StreamOptions", + "Template", + "TextChunk", + "TextPart", + "ThinkingChunk", + "ThinkingEventPayload", + "ThreadMarker", + "TokenEventPayload", + "TokenUsage", + "Tool", + "ToolCall", + "ToolCallCompletePayload", + "ToolCallStartPayload", + "ToolChunk", + "ToolContext", + "ToolDispatchResult", + "ToolExecutionCompletePayload", + "ToolExecutionStartPayload", + "ToolResult", + "ToolResultPayload", + "TraceFile", + "TraceSpan", + "TraceTime", + "TrajectoryEvent", + "TurnEndPayload", + "TurnEvent", + "TurnModelRequest", + "TurnModelResponse", + "TurnOptions", + "TurnStartPayload", + "TurnSummary", + "TurnTrace", + "ValidationError", + "ValidationResult" + ], + "exports": [ + { + "name": "GuardrailResult", + "kind": "value", + "group": "agent", + "source": "agent::guardrail_result", + "protocol": false + }, + { + "name": "Prompty", + "kind": "value", + "group": "agent", + "source": "agent::prompty", + "protocol": false + }, + { + "name": "AnonymousConnection", + "kind": "value", + "group": "connection", + "source": "connection::connection", + "protocol": false + }, + { + "name": "ApiKeyConnection", + "kind": "value", + "group": "connection", + "source": "connection::connection", + "protocol": false + }, + { + "name": "Connection", + "kind": "value", + "group": "connection", + "source": "connection::connection", + "protocol": false + }, + { + "name": "FoundryConnection", + "kind": "value", + "group": "connection", + "source": "connection::connection", + "protocol": false + }, + { + "name": "OAuthConnection", + "kind": "value", + "group": "connection", + "source": "connection::connection", + "protocol": false + }, + { + "name": "ReferenceConnection", + "kind": "value", + "group": "connection", + "source": "connection::connection", + "protocol": false + }, + { + "name": "RemoteConnection", + "kind": "value", + "group": "connection", + "source": "connection::connection", + "protocol": false + }, + { + "name": "AudioPart", + "kind": "value", + "group": "conversation", + "source": "conversation::content_part", + "protocol": false + }, + { + "name": "ContentPart", + "kind": "value", + "group": "conversation", + "source": "conversation::content_part", + "protocol": false + }, + { + "name": "FilePart", + "kind": "value", + "group": "conversation", + "source": "conversation::content_part", + "protocol": false + }, + { + "name": "ImagePart", + "kind": "value", + "group": "conversation", + "source": "conversation::content_part", + "protocol": false + }, + { + "name": "Message", + "kind": "value", + "group": "conversation", + "source": "conversation::message", + "protocol": false + }, + { + "name": "TextPart", + "kind": "value", + "group": "conversation", + "source": "conversation::content_part", + "protocol": false + }, + { + "name": "ThreadMarker", + "kind": "value", + "group": "conversation", + "source": "conversation::thread_marker", + "protocol": false + }, + { + "name": "ToolCall", + "kind": "value", + "group": "conversation", + "source": "conversation::tool_call", + "protocol": false + }, + { + "name": "ToolResult", + "kind": "value", + "group": "conversation", + "source": "conversation::tool_result", + "protocol": false + }, + { + "name": "ArrayProperty", + "kind": "value", + "group": "core", + "source": "core::property", + "protocol": false + }, + { + "name": "FileNotFoundError", + "kind": "value", + "group": "core", + "source": "core::file_not_found_error", + "protocol": false + }, + { + "name": "InvokerError", + "kind": "value", + "group": "core", + "source": "core::invoker_error", + "protocol": false + }, + { + "name": "ObjectProperty", + "kind": "value", + "group": "core", + "source": "core::property", + "protocol": false + }, + { + "name": "Property", + "kind": "value", + "group": "core", + "source": "core::property", + "protocol": false + }, + { + "name": "ValidationError", + "kind": "value", + "group": "core", + "source": "core::validation_error", + "protocol": false + }, + { + "name": "ValidationResult", + "kind": "value", + "group": "core", + "source": "core::validation_result", + "protocol": false + }, + { + "name": "Checkpoint", + "kind": "value", + "group": "events", + "source": "events::checkpoint", + "protocol": false + }, + { + "name": "CompactionCompletePayload", + "kind": "value", + "group": "events", + "source": "events::compaction_complete_payload", + "protocol": false + }, + { + "name": "CompactionFailedPayload", + "kind": "value", + "group": "events", + "source": "events::compaction_failed_payload", + "protocol": false + }, + { + "name": "CompactionStartPayload", + "kind": "value", + "group": "events", + "source": "events::compaction_start_payload", + "protocol": false + }, + { + "name": "DoneEventPayload", + "kind": "value", + "group": "events", + "source": "events::done_event_payload", + "protocol": false + }, + { + "name": "ErrorChunk", + "kind": "value", + "group": "events", + "source": "events::stream_chunk", + "protocol": false + }, + { + "name": "ErrorEventPayload", + "kind": "value", + "group": "events", + "source": "events::error_event_payload", + "protocol": false + }, + { + "name": "HarnessContext", + "kind": "value", + "group": "events", + "source": "events::harness_context", + "protocol": false + }, + { + "name": "HookEndPayload", + "kind": "value", + "group": "events", + "source": "events::hook_end_payload", + "protocol": false + }, + { + "name": "HookStartPayload", + "kind": "value", + "group": "events", + "source": "events::hook_start_payload", + "protocol": false + }, + { + "name": "HostToolRequest", + "kind": "value", + "group": "events", + "source": "events::host_tool_request", + "protocol": false + }, + { + "name": "HostToolResult", + "kind": "value", + "group": "events", + "source": "events::host_tool_result", + "protocol": false + }, + { + "name": "LlmCompletePayload", + "kind": "value", + "group": "events", + "source": "events::llm_complete_payload", + "protocol": false + }, + { + "name": "LlmStartPayload", + "kind": "value", + "group": "events", + "source": "events::llm_start_payload", + "protocol": false + }, + { + "name": "MessagesUpdatedPayload", + "kind": "value", + "group": "events", + "source": "events::messages_updated_payload", + "protocol": false + }, + { + "name": "PermissionCompletedPayload", + "kind": "value", + "group": "events", + "source": "events::permission_completed_payload", + "protocol": false + }, + { + "name": "PermissionDecision", + "kind": "value", + "group": "events", + "source": "events::permission_decision", + "protocol": false + }, + { + "name": "PermissionRequest", + "kind": "value", + "group": "events", + "source": "events::permission_request", + "protocol": false + }, + { + "name": "PermissionRequestedPayload", + "kind": "value", + "group": "events", + "source": "events::permission_requested_payload", + "protocol": false + }, + { + "name": "RedactedField", + "kind": "value", + "group": "events", + "source": "events::redacted_field", + "protocol": false + }, + { + "name": "RedactionMetadata", + "kind": "value", + "group": "events", + "source": "events::redaction_metadata", + "protocol": false + }, + { + "name": "RetryPayload", + "kind": "value", + "group": "events", + "source": "events::retry_payload", + "protocol": false + }, + { + "name": "SessionEndPayload", + "kind": "value", + "group": "events", + "source": "events::session_end_payload", + "protocol": false + }, + { + "name": "SessionEvent", + "kind": "value", + "group": "events", + "source": "events::session_event", + "protocol": false + }, + { + "name": "SessionFileRef", + "kind": "value", + "group": "events", + "source": "events::session_file_ref", + "protocol": false + }, + { + "name": "SessionRef", + "kind": "value", + "group": "events", + "source": "events::session_ref", + "protocol": false + }, + { + "name": "SessionStartPayload", + "kind": "value", + "group": "events", + "source": "events::session_start_payload", + "protocol": false + }, + { + "name": "SessionSummary", + "kind": "value", + "group": "events", + "source": "events::session_summary", + "protocol": false + }, + { + "name": "SessionTrace", + "kind": "value", + "group": "events", + "source": "events::session_trace", + "protocol": false + }, + { + "name": "SessionWarningPayload", + "kind": "value", + "group": "events", + "source": "events::session_warning_payload", + "protocol": false + }, + { + "name": "StatusEventPayload", + "kind": "value", + "group": "events", + "source": "events::status_event_payload", + "protocol": false + }, + { + "name": "StreamChunk", + "kind": "value", + "group": "events", + "source": "events::stream_chunk", + "protocol": false + }, + { + "name": "TextChunk", + "kind": "value", + "group": "events", + "source": "events::stream_chunk", + "protocol": false + }, + { + "name": "ThinkingChunk", + "kind": "value", + "group": "events", + "source": "events::stream_chunk", + "protocol": false + }, + { + "name": "ThinkingEventPayload", + "kind": "value", + "group": "events", + "source": "events::thinking_event_payload", + "protocol": false + }, + { + "name": "TokenEventPayload", + "kind": "value", + "group": "events", + "source": "events::token_event_payload", + "protocol": false + }, + { + "name": "ToolCallCompletePayload", + "kind": "value", + "group": "events", + "source": "events::tool_call_complete_payload", + "protocol": false + }, + { + "name": "ToolCallStartPayload", + "kind": "value", + "group": "events", + "source": "events::tool_call_start_payload", + "protocol": false + }, + { + "name": "ToolChunk", + "kind": "value", + "group": "events", + "source": "events::stream_chunk", + "protocol": false + }, + { + "name": "ToolExecutionCompletePayload", + "kind": "value", + "group": "events", + "source": "events::tool_execution_complete_payload", + "protocol": false + }, + { + "name": "ToolExecutionStartPayload", + "kind": "value", + "group": "events", + "source": "events::tool_execution_start_payload", + "protocol": false + }, + { + "name": "ToolResultPayload", + "kind": "value", + "group": "events", + "source": "events::tool_result_payload", + "protocol": false + }, + { + "name": "TrajectoryEvent", + "kind": "value", + "group": "events", + "source": "events::trajectory_event", + "protocol": false + }, + { + "name": "TurnEndPayload", + "kind": "value", + "group": "events", + "source": "events::turn_end_payload", + "protocol": false + }, + { + "name": "TurnEvent", + "kind": "value", + "group": "events", + "source": "events::turn_event", + "protocol": false + }, + { + "name": "TurnStartPayload", + "kind": "value", + "group": "events", + "source": "events::turn_start_payload", + "protocol": false + }, + { + "name": "TurnSummary", + "kind": "value", + "group": "events", + "source": "events::turn_summary", + "protocol": false + }, + { + "name": "TurnTrace", + "kind": "value", + "group": "events", + "source": "events::turn_trace", + "protocol": false + }, + { + "name": "Model", + "kind": "value", + "group": "model", + "source": "model::model", + "protocol": false + }, + { + "name": "ModelInfo", + "kind": "value", + "group": "model", + "source": "model::model_info", + "protocol": false + }, + { + "name": "ModelOptions", + "kind": "value", + "group": "model", + "source": "model::model_options", + "protocol": false + }, + { + "name": "TokenUsage", + "kind": "value", + "group": "model", + "source": "model::token_usage", + "protocol": false + }, + { + "name": "CheckpointStore", + "kind": "type", + "group": "pipeline", + "source": "pipeline::checkpoint_store", + "protocol": true + }, + { + "name": "CompactionConfig", + "kind": "value", + "group": "pipeline", + "source": "pipeline::compaction_config", + "protocol": false + }, + { + "name": "EventJournalWriter", + "kind": "type", + "group": "pipeline", + "source": "pipeline::event_journal_writer", + "protocol": true + }, + { + "name": "EventSink", + "kind": "type", + "group": "pipeline", + "source": "pipeline::event_sink", + "protocol": true + }, + { + "name": "Executor", + "kind": "type", + "group": "pipeline", + "source": "pipeline::executor", + "protocol": true + }, + { + "name": "HostToolExecutor", + "kind": "type", + "group": "pipeline", + "source": "pipeline::host_tool_executor", + "protocol": true + }, + { + "name": "Parser", + "kind": "type", + "group": "pipeline", + "source": "pipeline::parser", + "protocol": true + }, + { + "name": "PermissionResolver", + "kind": "type", + "group": "pipeline", + "source": "pipeline::permission_resolver", + "protocol": true + }, + { + "name": "Processor", + "kind": "type", + "group": "pipeline", + "source": "pipeline::processor", + "protocol": true + }, + { + "name": "Renderer", + "kind": "type", + "group": "pipeline", + "source": "pipeline::renderer", + "protocol": true + }, + { + "name": "ReplayJournalRecord", + "kind": "value", + "group": "pipeline", + "source": "pipeline::replay_journal_record", + "protocol": false + }, + { + "name": "ReplayMismatch", + "kind": "value", + "group": "pipeline", + "source": "pipeline::replay_mismatch", + "protocol": false + }, + { + "name": "ReplayVerificationRequest", + "kind": "value", + "group": "pipeline", + "source": "pipeline::replay_verification_request", + "protocol": false + }, + { + "name": "ReplayVerificationResult", + "kind": "value", + "group": "pipeline", + "source": "pipeline::replay_verification_result", + "protocol": false + }, + { + "name": "RunTurnRequest", + "kind": "value", + "group": "pipeline", + "source": "pipeline::run_turn_request", + "protocol": false + }, + { + "name": "RunTurnResult", + "kind": "value", + "group": "pipeline", + "source": "pipeline::run_turn_result", + "protocol": false + }, + { + "name": "TurnModelRequest", + "kind": "value", + "group": "pipeline", + "source": "pipeline::turn_model_request", + "protocol": false + }, + { + "name": "TurnModelResponse", + "kind": "value", + "group": "pipeline", + "source": "pipeline::turn_model_response", + "protocol": false + }, + { + "name": "TurnOptions", + "kind": "value", + "group": "pipeline", + "source": "pipeline::turn_options", + "protocol": false + }, + { + "name": "StreamOptions", + "kind": "value", + "group": "streaming", + "source": "streaming::stream_options", + "protocol": false + }, + { + "name": "FormatConfig", + "kind": "value", + "group": "template", + "source": "template::format_config", + "protocol": false + }, + { + "name": "ParserConfig", + "kind": "value", + "group": "template", + "source": "template::parser_config", + "protocol": false + }, + { + "name": "Template", + "kind": "value", + "group": "template", + "source": "template::template", + "protocol": false + }, + { + "name": "Binding", + "kind": "value", + "group": "tools", + "source": "tools::binding", + "protocol": false + }, + { + "name": "CustomTool", + "kind": "value", + "group": "tools", + "source": "tools::tool", + "protocol": false + }, + { + "name": "FunctionTool", + "kind": "value", + "group": "tools", + "source": "tools::tool", + "protocol": false + }, + { + "name": "McpApprovalMode", + "kind": "value", + "group": "tools", + "source": "tools::mcp_approval_mode", + "protocol": false + }, + { + "name": "McpTool", + "kind": "value", + "group": "tools", + "source": "tools::tool", + "protocol": false + }, + { + "name": "OpenApiTool", + "kind": "value", + "group": "tools", + "source": "tools::tool", + "protocol": false + }, + { + "name": "PromptyTool", + "kind": "value", + "group": "tools", + "source": "tools::tool", + "protocol": false + }, + { + "name": "Tool", + "kind": "value", + "group": "tools", + "source": "tools::tool", + "protocol": false + }, + { + "name": "ToolContext", + "kind": "value", + "group": "tools", + "source": "tools::tool_context", + "protocol": false + }, + { + "name": "ToolDispatchResult", + "kind": "value", + "group": "tools", + "source": "tools::tool_dispatch_result", + "protocol": false + }, + { + "name": "TraceFile", + "kind": "value", + "group": "tracing", + "source": "tracing::trace_file", + "protocol": false + }, + { + "name": "TraceSpan", + "kind": "value", + "group": "tracing", + "source": "tracing::trace_span", + "protocol": false + }, + { + "name": "TraceTime", + "kind": "value", + "group": "tracing", + "source": "tracing::trace_time", + "protocol": false + }, + { + "name": "AnthropicImageBlock", + "kind": "value", + "group": "wire", + "source": "wire::anthropic_image_block", + "protocol": false + }, + { + "name": "AnthropicImageSource", + "kind": "value", + "group": "wire", + "source": "wire::anthropic_image_source", + "protocol": false + }, + { + "name": "AnthropicMessagesRequest", + "kind": "value", + "group": "wire", + "source": "wire::anthropic_messages_request", + "protocol": false + }, + { + "name": "AnthropicMessagesResponse", + "kind": "value", + "group": "wire", + "source": "wire::anthropic_messages_response", + "protocol": false + }, + { + "name": "AnthropicTextBlock", + "kind": "value", + "group": "wire", + "source": "wire::anthropic_text_block", + "protocol": false + }, + { + "name": "AnthropicToolDefinition", + "kind": "value", + "group": "wire", + "source": "wire::anthropic_tool_definition", + "protocol": false + }, + { + "name": "AnthropicToolResultBlock", + "kind": "value", + "group": "wire", + "source": "wire::anthropic_tool_result_block", + "protocol": false + }, + { + "name": "AnthropicToolUseBlock", + "kind": "value", + "group": "wire", + "source": "wire::anthropic_tool_use_block", + "protocol": false + }, + { + "name": "AnthropicUsage", + "kind": "value", + "group": "wire", + "source": "wire::anthropic_usage", + "protocol": false + }, + { + "name": "AnthropicWireMessage", + "kind": "value", + "group": "wire", + "source": "wire::anthropic_wire_message", + "protocol": false + } + ], + "groups": [ + { + "name": "agent", + "exports": [ + "GuardrailResult", + "Prompty" + ], + "modules": [ + "guardrail_result", + "prompty" + ] + }, + { + "name": "connection", + "exports": [ + "AnonymousConnection", + "ApiKeyConnection", + "Connection", + "FoundryConnection", + "OAuthConnection", + "ReferenceConnection", + "RemoteConnection" + ], + "modules": [ + "connection" + ] + }, + { + "name": "conversation", + "exports": [ + "AudioPart", + "ContentPart", + "FilePart", + "ImagePart", + "Message", + "TextPart", + "ThreadMarker", + "ToolCall", + "ToolResult" + ], + "modules": [ + "content_part", + "message", + "thread_marker", + "tool_call", + "tool_result" + ] + }, + { + "name": "core", + "exports": [ + "ArrayProperty", + "FileNotFoundError", + "InvokerError", + "ObjectProperty", + "Property", + "ValidationError", + "ValidationResult" + ], + "modules": [ + "file_not_found_error", + "invoker_error", + "property", + "validation_error", + "validation_result" + ] + }, + { + "name": "events", + "exports": [ + "Checkpoint", + "CompactionCompletePayload", + "CompactionFailedPayload", + "CompactionStartPayload", + "DoneEventPayload", + "ErrorChunk", + "ErrorEventPayload", + "HarnessContext", + "HookEndPayload", + "HookStartPayload", + "HostToolRequest", + "HostToolResult", + "LlmCompletePayload", + "LlmStartPayload", + "MessagesUpdatedPayload", + "PermissionCompletedPayload", + "PermissionDecision", + "PermissionRequest", + "PermissionRequestedPayload", + "RedactedField", + "RedactionMetadata", + "RetryPayload", + "SessionEndPayload", + "SessionEvent", + "SessionFileRef", + "SessionRef", + "SessionStartPayload", + "SessionSummary", + "SessionTrace", + "SessionWarningPayload", + "StatusEventPayload", + "StreamChunk", + "TextChunk", + "ThinkingChunk", + "ThinkingEventPayload", + "TokenEventPayload", + "ToolCallCompletePayload", + "ToolCallStartPayload", + "ToolChunk", + "ToolExecutionCompletePayload", + "ToolExecutionStartPayload", + "ToolResultPayload", + "TrajectoryEvent", + "TurnEndPayload", + "TurnEvent", + "TurnStartPayload", + "TurnSummary", + "TurnTrace" + ], + "modules": [ + "checkpoint", + "compaction_complete_payload", + "compaction_failed_payload", + "compaction_start_payload", + "done_event_payload", + "error_event_payload", + "harness_context", + "hook_end_payload", + "hook_start_payload", + "host_tool_request", + "host_tool_result", + "llm_complete_payload", + "llm_start_payload", + "messages_updated_payload", + "permission_completed_payload", + "permission_decision", + "permission_request", + "permission_requested_payload", + "redacted_field", + "redaction_metadata", + "retry_payload", + "session_end_payload", + "session_event", + "session_file_ref", + "session_ref", + "session_start_payload", + "session_summary", + "session_trace", + "session_warning_payload", + "status_event_payload", + "stream_chunk", + "thinking_event_payload", + "token_event_payload", + "tool_call_complete_payload", + "tool_call_start_payload", + "tool_execution_complete_payload", + "tool_execution_start_payload", + "tool_result_payload", + "trajectory_event", + "turn_end_payload", + "turn_event", + "turn_start_payload", + "turn_summary", + "turn_trace" + ] + }, + { + "name": "model", + "exports": [ + "Model", + "ModelInfo", + "ModelOptions", + "TokenUsage" + ], + "modules": [ + "model", + "model_info", + "model_options", + "token_usage" + ] + }, + { + "name": "pipeline", + "exports": [ + "CheckpointStore", + "CompactionConfig", + "EventJournalWriter", + "EventSink", + "Executor", + "HostToolExecutor", + "Parser", + "PermissionResolver", + "Processor", + "Renderer", + "ReplayJournalRecord", + "ReplayMismatch", + "ReplayVerificationRequest", + "ReplayVerificationResult", + "RunTurnRequest", + "RunTurnResult", + "TurnModelRequest", + "TurnModelResponse", + "TurnOptions" + ], + "modules": [ + "checkpoint_store", + "compaction_config", + "event_journal_writer", + "event_sink", + "executor", + "host_tool_executor", + "parser", + "permission_resolver", + "processor", + "renderer", + "replay_journal_record", + "replay_mismatch", + "replay_verification_request", + "replay_verification_result", + "run_turn_request", + "run_turn_result", + "turn_model_request", + "turn_model_response", + "turn_options" + ] + }, + { + "name": "streaming", + "exports": [ + "StreamOptions" + ], + "modules": [ + "stream_options" + ] + }, + { + "name": "template", + "exports": [ + "FormatConfig", + "ParserConfig", + "Template" + ], + "modules": [ + "format_config", + "parser_config", + "template" + ] + }, + { + "name": "tools", + "exports": [ + "Binding", + "CustomTool", + "FunctionTool", + "McpApprovalMode", + "McpTool", + "OpenApiTool", + "PromptyTool", + "Tool", + "ToolContext", + "ToolDispatchResult" + ], + "modules": [ + "binding", + "mcp_approval_mode", + "tool", + "tool_context", + "tool_dispatch_result" + ] + }, + { + "name": "tracing", + "exports": [ + "TraceFile", + "TraceSpan", + "TraceTime" + ], + "modules": [ + "trace_file", + "trace_span", + "trace_time" + ] + }, + { + "name": "wire", + "exports": [ + "AnthropicImageBlock", + "AnthropicImageSource", + "AnthropicMessagesRequest", + "AnthropicMessagesResponse", + "AnthropicTextBlock", + "AnthropicToolDefinition", + "AnthropicToolResultBlock", + "AnthropicToolUseBlock", + "AnthropicUsage", + "AnthropicWireMessage" + ], + "modules": [ + "anthropic_image_block", + "anthropic_image_source", + "anthropic_messages_request", + "anthropic_messages_response", + "anthropic_text_block", + "anthropic_tool_definition", + "anthropic_tool_result_block", + "anthropic_tool_use_block", + "anthropic_usage", + "anthropic_wire_message" + ] + } + ], + "protocols": [ + { + "name": "CheckpointStore", + "group": "pipeline", + "symbol": "CheckpointStore", + "source": "pipeline::checkpoint_store", + "methods": [ + { + "name": "listCheckpoints", + "returns": "Checkpoint[]", + "params": { + "sessionId": "string" + }, + "optional": false, + "sync": false + }, + { + "name": "load", + "returns": "Checkpoint?", + "params": { + "checkpointId": "string", + "sessionId": "string" + }, + "optional": false, + "sync": false + }, + { + "name": "save", + "returns": "Checkpoint", + "params": { + "checkpoint": "Checkpoint" + }, + "optional": false, + "sync": false + } + ] + }, + { + "name": "EventJournalWriter", + "group": "pipeline", + "symbol": "EventJournalWriter", + "source": "pipeline::event_journal_writer", + "methods": [ + { + "name": "appendSession", + "returns": "boolean", + "params": { + "sessionEvent": "SessionEvent" + }, + "optional": false, + "sync": true + }, + { + "name": "appendTurn", + "returns": "boolean", + "params": { + "turnEvent": "TurnEvent" + }, + "optional": false, + "sync": true + }, + { + "name": "close", + "returns": "boolean", + "params": { + "summary": "SessionSummary?" + }, + "optional": false, + "sync": true + } + ] + }, + { + "name": "EventSink", + "group": "pipeline", + "symbol": "EventSink", + "source": "pipeline::event_sink", + "methods": [ + { + "name": "emitSession", + "returns": "boolean", + "params": { + "sessionEvent": "SessionEvent" + }, + "optional": false, + "sync": true + }, + { + "name": "emitTurn", + "returns": "boolean", + "params": { + "turnEvent": "TurnEvent" + }, + "optional": false, + "sync": true + } + ] + }, + { + "name": "Executor", + "group": "pipeline", + "symbol": "Executor", + "source": "pipeline::executor", + "methods": [ + { + "name": "execute", + "returns": "unknown", + "params": { + "agent": "Prompty", + "messages": "Message[]" + }, + "optional": false, + "sync": false + }, + { + "name": "executeStream", + "returns": "unknown", + "params": { + "agent": "Prompty", + "messages": "Message[]" + }, + "optional": true, + "sync": false + }, + { + "name": "formatToolMessages", + "returns": "Message[]", + "params": { + "rawResponse": "unknown", + "textContent": "string?", + "toolCalls": "ToolCall[]", + "toolResults": "string[]" + }, + "optional": false, + "sync": true + } + ] + }, + { + "name": "HostToolExecutor", + "group": "pipeline", + "symbol": "HostToolExecutor", + "source": "pipeline::host_tool_executor", + "methods": [ + { + "name": "execute", + "returns": "HostToolResult", + "params": { + "request": "HostToolRequest" + }, + "optional": false, + "sync": false + } + ] + }, + { + "name": "Parser", + "group": "pipeline", + "symbol": "Parser", + "source": "pipeline::parser", + "methods": [ + { + "name": "parse", + "returns": "Message[]", + "params": { + "agent": "Prompty", + "context": "Record?", + "rendered": "string" + }, + "optional": false, + "sync": false + }, + { + "name": "preRender", + "returns": "unknown?", + "params": { + "template": "string" + }, + "optional": true, + "sync": true + } + ] + }, + { + "name": "PermissionResolver", + "group": "pipeline", + "symbol": "PermissionResolver", + "source": "pipeline::permission_resolver", + "methods": [ + { + "name": "request", + "returns": "PermissionDecision", + "params": { + "request": "PermissionRequest" + }, + "optional": false, + "sync": false + } + ] + }, + { + "name": "Processor", + "group": "pipeline", + "symbol": "Processor", + "source": "pipeline::processor", + "methods": [ + { + "name": "process", + "returns": "unknown", + "params": { + "agent": "Prompty", + "response": "unknown" + }, + "optional": false, + "sync": false + }, + { + "name": "processStream", + "returns": "unknown", + "params": { + "stream": "unknown" + }, + "optional": true, + "sync": false + } + ] + }, + { + "name": "Renderer", + "group": "pipeline", + "symbol": "Renderer", + "source": "pipeline::renderer", + "methods": [ + { + "name": "render", + "returns": "string", + "params": { + "agent": "Prompty", + "inputs": "Record", + "template": "string" + }, + "optional": false, + "sync": false + } + ] + } + ], + "modules": [ + "agent", + "connection", + "context", + "conversation", + "core", + "events", + "model", + "pipeline", + "streaming", + "template", + "tools", + "tracing", + "wire" + ] + }, + { + "target": "typescript", + "outputRoot": "../runtime/typescript/packages/core/src/model", + "namespace": "Prompty", + "rootExports": [ + "AnonymousConnection", + "AnthropicImageBlock", + "AnthropicImageSource", + "AnthropicMessagesRequest", + "AnthropicMessagesResponse", + "AnthropicTextBlock", + "AnthropicToolDefinition", + "AnthropicToolResultBlock", + "AnthropicToolUseBlock", + "AnthropicUsage", + "AnthropicWireMessage", + "ApiKeyConnection", + "ArrayProperty", + "AudioPart", + "Binding", + "Checkpoint", + "CheckpointStore", + "CompactionCompletePayload", + "CompactionConfig", + "CompactionFailedPayload", + "CompactionStartPayload", + "Connection", + "ContentPart", + "CustomTool", + "DoneEventPayload", + "ErrorChunk", + "ErrorEventPayload", + "EventJournalWriter", + "EventSink", + "Executor", + "FileNotFoundError", + "FilePart", + "FormatConfig", + "FoundryConnection", + "FunctionTool", + "GuardrailResult", + "HarnessContext", + "HookEndPayload", + "HookStartPayload", + "HostToolExecutor", + "HostToolRequest", + "HostToolResult", + "ImagePart", + "InvokerError", + "LlmCompletePayload", + "LlmStartPayload", + "McpApprovalMode", + "McpTool", + "Message", + "MessagesUpdatedPayload", + "Model", + "ModelInfo", + "ModelOptions", + "OAuthConnection", + "ObjectProperty", + "OpenApiTool", + "Parser", + "ParserConfig", + "PermissionCompletedPayload", + "PermissionDecision", + "PermissionRequest", + "PermissionRequestedPayload", + "PermissionResolver", + "Processor", + "Prompty", + "PromptyTool", + "Property", + "RedactedField", + "RedactionMetadata", + "ReferenceConnection", + "RemoteConnection", + "Renderer", + "ReplayJournalRecord", + "ReplayMismatch", + "ReplayVerificationRequest", + "ReplayVerificationResult", + "RetryPayload", + "RunTurnRequest", + "RunTurnResult", + "SessionEndPayload", + "SessionEvent", + "SessionFileRef", + "SessionRef", + "SessionStartPayload", + "SessionSummary", + "SessionTrace", + "SessionWarningPayload", + "StatusEventPayload", + "StreamChunk", + "StreamOptions", + "Template", + "TextChunk", + "TextPart", + "ThinkingChunk", + "ThinkingEventPayload", + "ThreadMarker", + "TokenEventPayload", + "TokenUsage", + "Tool", + "ToolCall", + "ToolCallCompletePayload", + "ToolCallStartPayload", + "ToolChunk", + "ToolContext", + "ToolDispatchResult", + "ToolExecutionCompletePayload", + "ToolExecutionStartPayload", + "ToolResult", + "ToolResultPayload", + "TraceFile", + "TraceSpan", + "TraceTime", + "TrajectoryEvent", + "TurnEndPayload", + "TurnEvent", + "TurnModelRequest", + "TurnModelResponse", + "TurnOptions", + "TurnStartPayload", + "TurnSummary", + "TurnTrace", + "ValidationError", + "ValidationResult" + ], + "exports": [ + { + "name": "GuardrailResult", + "kind": "value", + "group": "agent", + "source": "./agent/guardrail-result", + "protocol": false + }, + { + "name": "Prompty", + "kind": "value", + "group": "agent", + "source": "./agent/prompty", + "protocol": false + }, + { + "name": "AnonymousConnection", + "kind": "value", + "group": "connection", + "source": "./connection/connection", + "protocol": false + }, + { + "name": "ApiKeyConnection", + "kind": "value", + "group": "connection", + "source": "./connection/connection", + "protocol": false + }, + { + "name": "Connection", + "kind": "value", + "group": "connection", + "source": "./connection/connection", + "protocol": false + }, + { + "name": "FoundryConnection", + "kind": "value", + "group": "connection", + "source": "./connection/connection", + "protocol": false + }, + { + "name": "OAuthConnection", + "kind": "value", + "group": "connection", + "source": "./connection/connection", + "protocol": false + }, + { + "name": "ReferenceConnection", + "kind": "value", + "group": "connection", + "source": "./connection/connection", + "protocol": false + }, + { + "name": "RemoteConnection", + "kind": "value", + "group": "connection", + "source": "./connection/connection", + "protocol": false + }, + { + "name": "AudioPart", + "kind": "value", + "group": "conversation", + "source": "./conversation/content-part", + "protocol": false + }, + { + "name": "ContentPart", + "kind": "value", + "group": "conversation", + "source": "./conversation/content-part", + "protocol": false + }, + { + "name": "FilePart", + "kind": "value", + "group": "conversation", + "source": "./conversation/content-part", + "protocol": false + }, + { + "name": "ImagePart", + "kind": "value", + "group": "conversation", + "source": "./conversation/content-part", + "protocol": false + }, + { + "name": "Message", + "kind": "value", + "group": "conversation", + "source": "./conversation/message", + "protocol": false + }, + { + "name": "TextPart", + "kind": "value", + "group": "conversation", + "source": "./conversation/content-part", + "protocol": false + }, + { + "name": "ThreadMarker", + "kind": "value", + "group": "conversation", + "source": "./conversation/thread-marker", + "protocol": false + }, + { + "name": "ToolCall", + "kind": "value", + "group": "conversation", + "source": "./conversation/tool-call", + "protocol": false + }, + { + "name": "ToolResult", + "kind": "value", + "group": "conversation", + "source": "./conversation/tool-result", + "protocol": false + }, + { + "name": "ArrayProperty", + "kind": "value", + "group": "core", + "source": "./core/property", + "protocol": false + }, + { + "name": "FileNotFoundError", + "kind": "value", + "group": "core", + "source": "./core/file-not-found-error", + "protocol": false + }, + { + "name": "InvokerError", + "kind": "value", + "group": "core", + "source": "./core/invoker-error", + "protocol": false + }, + { + "name": "ObjectProperty", + "kind": "value", + "group": "core", + "source": "./core/property", + "protocol": false + }, + { + "name": "Property", + "kind": "value", + "group": "core", + "source": "./core/property", + "protocol": false + }, + { + "name": "ValidationError", + "kind": "value", + "group": "core", + "source": "./core/validation-error", + "protocol": false + }, + { + "name": "ValidationResult", + "kind": "value", + "group": "core", + "source": "./core/validation-result", + "protocol": false + }, + { + "name": "Checkpoint", + "kind": "value", + "group": "events", + "source": "./events/checkpoint", + "protocol": false + }, + { + "name": "CompactionCompletePayload", + "kind": "value", + "group": "events", + "source": "./events/compaction-complete-payload", + "protocol": false + }, + { + "name": "CompactionFailedPayload", + "kind": "value", + "group": "events", + "source": "./events/compaction-failed-payload", + "protocol": false + }, + { + "name": "CompactionStartPayload", + "kind": "value", + "group": "events", + "source": "./events/compaction-start-payload", + "protocol": false + }, + { + "name": "DoneEventPayload", + "kind": "value", + "group": "events", + "source": "./events/done-event-payload", + "protocol": false + }, + { + "name": "ErrorChunk", + "kind": "value", + "group": "events", + "source": "./events/stream-chunk", + "protocol": false + }, + { + "name": "ErrorEventPayload", + "kind": "value", + "group": "events", + "source": "./events/error-event-payload", + "protocol": false + }, + { + "name": "HarnessContext", + "kind": "value", + "group": "events", + "source": "./events/harness-context", + "protocol": false + }, + { + "name": "HookEndPayload", + "kind": "value", + "group": "events", + "source": "./events/hook-end-payload", + "protocol": false + }, + { + "name": "HookStartPayload", + "kind": "value", + "group": "events", + "source": "./events/hook-start-payload", + "protocol": false + }, + { + "name": "HostToolRequest", + "kind": "value", + "group": "events", + "source": "./events/host-tool-request", + "protocol": false + }, + { + "name": "HostToolResult", + "kind": "value", + "group": "events", + "source": "./events/host-tool-result", + "protocol": false + }, + { + "name": "LlmCompletePayload", + "kind": "value", + "group": "events", + "source": "./events/llm-complete-payload", + "protocol": false + }, + { + "name": "LlmStartPayload", + "kind": "value", + "group": "events", + "source": "./events/llm-start-payload", + "protocol": false + }, + { + "name": "MessagesUpdatedPayload", + "kind": "value", + "group": "events", + "source": "./events/messages-updated-payload", + "protocol": false + }, + { + "name": "PermissionCompletedPayload", + "kind": "value", + "group": "events", + "source": "./events/permission-completed-payload", + "protocol": false + }, + { + "name": "PermissionDecision", + "kind": "value", + "group": "events", + "source": "./events/permission-decision", + "protocol": false + }, + { + "name": "PermissionRequest", + "kind": "value", + "group": "events", + "source": "./events/permission-request", + "protocol": false + }, + { + "name": "PermissionRequestedPayload", + "kind": "value", + "group": "events", + "source": "./events/permission-requested-payload", + "protocol": false + }, + { + "name": "RedactedField", + "kind": "value", + "group": "events", + "source": "./events/redacted-field", + "protocol": false + }, + { + "name": "RedactionMetadata", + "kind": "value", + "group": "events", + "source": "./events/redaction-metadata", + "protocol": false + }, + { + "name": "RetryPayload", + "kind": "value", + "group": "events", + "source": "./events/retry-payload", + "protocol": false + }, + { + "name": "SessionEndPayload", + "kind": "value", + "group": "events", + "source": "./events/session-end-payload", + "protocol": false + }, + { + "name": "SessionEvent", + "kind": "value", + "group": "events", + "source": "./events/session-event", + "protocol": false + }, + { + "name": "SessionFileRef", + "kind": "value", + "group": "events", + "source": "./events/session-file-ref", + "protocol": false + }, + { + "name": "SessionRef", + "kind": "value", + "group": "events", + "source": "./events/session-ref", + "protocol": false + }, + { + "name": "SessionStartPayload", + "kind": "value", + "group": "events", + "source": "./events/session-start-payload", + "protocol": false + }, + { + "name": "SessionSummary", + "kind": "value", + "group": "events", + "source": "./events/session-summary", + "protocol": false + }, + { + "name": "SessionTrace", + "kind": "value", + "group": "events", + "source": "./events/session-trace", + "protocol": false + }, + { + "name": "SessionWarningPayload", + "kind": "value", + "group": "events", + "source": "./events/session-warning-payload", + "protocol": false + }, + { + "name": "StatusEventPayload", + "kind": "value", + "group": "events", + "source": "./events/status-event-payload", + "protocol": false + }, + { + "name": "StreamChunk", + "kind": "value", + "group": "events", + "source": "./events/stream-chunk", + "protocol": false + }, + { + "name": "TextChunk", + "kind": "value", + "group": "events", + "source": "./events/stream-chunk", + "protocol": false + }, + { + "name": "ThinkingChunk", + "kind": "value", + "group": "events", + "source": "./events/stream-chunk", + "protocol": false + }, + { + "name": "ThinkingEventPayload", + "kind": "value", + "group": "events", + "source": "./events/thinking-event-payload", + "protocol": false + }, + { + "name": "TokenEventPayload", + "kind": "value", + "group": "events", + "source": "./events/token-event-payload", + "protocol": false + }, + { + "name": "ToolCallCompletePayload", + "kind": "value", + "group": "events", + "source": "./events/tool-call-complete-payload", + "protocol": false + }, + { + "name": "ToolCallStartPayload", + "kind": "value", + "group": "events", + "source": "./events/tool-call-start-payload", + "protocol": false + }, + { + "name": "ToolChunk", + "kind": "value", + "group": "events", + "source": "./events/stream-chunk", + "protocol": false + }, + { + "name": "ToolExecutionCompletePayload", + "kind": "value", + "group": "events", + "source": "./events/tool-execution-complete-payload", + "protocol": false + }, + { + "name": "ToolExecutionStartPayload", + "kind": "value", + "group": "events", + "source": "./events/tool-execution-start-payload", + "protocol": false + }, + { + "name": "ToolResultPayload", + "kind": "value", + "group": "events", + "source": "./events/tool-result-payload", + "protocol": false + }, + { + "name": "TrajectoryEvent", + "kind": "value", + "group": "events", + "source": "./events/trajectory-event", + "protocol": false + }, + { + "name": "TurnEndPayload", + "kind": "value", + "group": "events", + "source": "./events/turn-end-payload", + "protocol": false + }, + { + "name": "TurnEvent", + "kind": "value", + "group": "events", + "source": "./events/turn-event", + "protocol": false + }, + { + "name": "TurnStartPayload", + "kind": "value", + "group": "events", + "source": "./events/turn-start-payload", + "protocol": false + }, + { + "name": "TurnSummary", + "kind": "value", + "group": "events", + "source": "./events/turn-summary", + "protocol": false + }, + { + "name": "TurnTrace", + "kind": "value", + "group": "events", + "source": "./events/turn-trace", + "protocol": false + }, + { + "name": "Model", + "kind": "value", + "group": "model", + "source": "./model/model", + "protocol": false + }, + { + "name": "ModelInfo", + "kind": "value", + "group": "model", + "source": "./model/model-info", + "protocol": false + }, + { + "name": "ModelOptions", + "kind": "value", + "group": "model", + "source": "./model/model-options", + "protocol": false + }, + { + "name": "TokenUsage", + "kind": "value", + "group": "model", + "source": "./model/token-usage", + "protocol": false + }, + { + "name": "CheckpointStore", + "kind": "type", + "group": "pipeline", + "source": "./pipeline/checkpoint-store", + "protocol": true + }, + { + "name": "CompactionConfig", + "kind": "value", + "group": "pipeline", + "source": "./pipeline/compaction-config", + "protocol": false + }, + { + "name": "EventJournalWriter", + "kind": "type", + "group": "pipeline", + "source": "./pipeline/event-journal-writer", + "protocol": true + }, + { + "name": "EventSink", + "kind": "type", + "group": "pipeline", + "source": "./pipeline/event-sink", + "protocol": true + }, + { + "name": "Executor", + "kind": "type", + "group": "pipeline", + "source": "./pipeline/executor", + "protocol": true + }, + { + "name": "HostToolExecutor", + "kind": "type", + "group": "pipeline", + "source": "./pipeline/host-tool-executor", + "protocol": true + }, + { + "name": "Parser", + "kind": "type", + "group": "pipeline", + "source": "./pipeline/parser", + "protocol": true + }, + { + "name": "PermissionResolver", + "kind": "type", + "group": "pipeline", + "source": "./pipeline/permission-resolver", + "protocol": true + }, + { + "name": "Processor", + "kind": "type", + "group": "pipeline", + "source": "./pipeline/processor", + "protocol": true + }, + { + "name": "Renderer", + "kind": "type", + "group": "pipeline", + "source": "./pipeline/renderer", + "protocol": true + }, + { + "name": "ReplayJournalRecord", + "kind": "value", + "group": "pipeline", + "source": "./pipeline/replay-journal-record", + "protocol": false + }, + { + "name": "ReplayMismatch", + "kind": "value", + "group": "pipeline", + "source": "./pipeline/replay-mismatch", + "protocol": false + }, + { + "name": "ReplayVerificationRequest", + "kind": "value", + "group": "pipeline", + "source": "./pipeline/replay-verification-request", + "protocol": false + }, + { + "name": "ReplayVerificationResult", + "kind": "value", + "group": "pipeline", + "source": "./pipeline/replay-verification-result", + "protocol": false + }, + { + "name": "RunTurnRequest", + "kind": "value", + "group": "pipeline", + "source": "./pipeline/run-turn-request", + "protocol": false + }, + { + "name": "RunTurnResult", + "kind": "value", + "group": "pipeline", + "source": "./pipeline/run-turn-result", + "protocol": false + }, + { + "name": "TurnModelRequest", + "kind": "value", + "group": "pipeline", + "source": "./pipeline/turn-model-request", + "protocol": false + }, + { + "name": "TurnModelResponse", + "kind": "value", + "group": "pipeline", + "source": "./pipeline/turn-model-response", + "protocol": false + }, + { + "name": "TurnOptions", + "kind": "value", + "group": "pipeline", + "source": "./pipeline/turn-options", + "protocol": false + }, + { + "name": "StreamOptions", + "kind": "value", + "group": "streaming", + "source": "./streaming/stream-options", + "protocol": false + }, + { + "name": "FormatConfig", + "kind": "value", + "group": "template", + "source": "./template/format-config", + "protocol": false + }, + { + "name": "ParserConfig", + "kind": "value", + "group": "template", + "source": "./template/parser-config", + "protocol": false + }, + { + "name": "Template", + "kind": "value", + "group": "template", + "source": "./template/template", + "protocol": false + }, + { + "name": "Binding", + "kind": "value", + "group": "tools", + "source": "./tools/binding", + "protocol": false + }, + { + "name": "CustomTool", + "kind": "value", + "group": "tools", + "source": "./tools/tool", + "protocol": false + }, + { + "name": "FunctionTool", + "kind": "value", + "group": "tools", + "source": "./tools/tool", + "protocol": false + }, + { + "name": "McpApprovalMode", + "kind": "value", + "group": "tools", + "source": "./tools/mcp-approval-mode", + "protocol": false + }, + { + "name": "McpTool", + "kind": "value", + "group": "tools", + "source": "./tools/tool", + "protocol": false + }, + { + "name": "OpenApiTool", + "kind": "value", + "group": "tools", + "source": "./tools/tool", + "protocol": false + }, + { + "name": "PromptyTool", + "kind": "value", + "group": "tools", + "source": "./tools/tool", + "protocol": false + }, + { + "name": "Tool", + "kind": "value", + "group": "tools", + "source": "./tools/tool", + "protocol": false + }, + { + "name": "ToolContext", + "kind": "value", + "group": "tools", + "source": "./tools/tool-context", + "protocol": false + }, + { + "name": "ToolDispatchResult", + "kind": "value", + "group": "tools", + "source": "./tools/tool-dispatch-result", + "protocol": false + }, + { + "name": "TraceFile", + "kind": "value", + "group": "tracing", + "source": "./tracing/trace-file", + "protocol": false + }, + { + "name": "TraceSpan", + "kind": "value", + "group": "tracing", + "source": "./tracing/trace-span", + "protocol": false + }, + { + "name": "TraceTime", + "kind": "value", + "group": "tracing", + "source": "./tracing/trace-time", + "protocol": false + }, + { + "name": "AnthropicImageBlock", + "kind": "value", + "group": "wire", + "source": "./wire/anthropic-image-block", + "protocol": false + }, + { + "name": "AnthropicImageSource", + "kind": "value", + "group": "wire", + "source": "./wire/anthropic-image-source", + "protocol": false + }, + { + "name": "AnthropicMessagesRequest", + "kind": "value", + "group": "wire", + "source": "./wire/anthropic-messages-request", + "protocol": false + }, + { + "name": "AnthropicMessagesResponse", + "kind": "value", + "group": "wire", + "source": "./wire/anthropic-messages-response", + "protocol": false + }, + { + "name": "AnthropicTextBlock", + "kind": "value", + "group": "wire", + "source": "./wire/anthropic-text-block", + "protocol": false + }, + { + "name": "AnthropicToolDefinition", + "kind": "value", + "group": "wire", + "source": "./wire/anthropic-tool-definition", + "protocol": false + }, + { + "name": "AnthropicToolResultBlock", + "kind": "value", + "group": "wire", + "source": "./wire/anthropic-tool-result-block", + "protocol": false + }, + { + "name": "AnthropicToolUseBlock", + "kind": "value", + "group": "wire", + "source": "./wire/anthropic-tool-use-block", + "protocol": false + }, + { + "name": "AnthropicUsage", + "kind": "value", + "group": "wire", + "source": "./wire/anthropic-usage", + "protocol": false + }, + { + "name": "AnthropicWireMessage", + "kind": "value", + "group": "wire", + "source": "./wire/anthropic-wire-message", + "protocol": false + } + ], + "groups": [ + { + "name": "agent", + "exports": [ + "GuardrailResult", + "Prompty" + ], + "modules": [ + "guardrail-result", + "prompty" + ] + }, + { + "name": "connection", + "exports": [ + "AnonymousConnection", + "ApiKeyConnection", + "Connection", + "FoundryConnection", + "OAuthConnection", + "ReferenceConnection", + "RemoteConnection" + ], + "modules": [ + "connection" + ] + }, + { + "name": "conversation", + "exports": [ + "AudioPart", + "ContentPart", + "FilePart", + "ImagePart", + "Message", + "TextPart", + "ThreadMarker", + "ToolCall", + "ToolResult" + ], + "modules": [ + "content-part", + "message", + "thread-marker", + "tool-call", + "tool-result" + ] + }, + { + "name": "core", + "exports": [ + "ArrayProperty", + "FileNotFoundError", + "InvokerError", + "ObjectProperty", + "Property", + "ValidationError", + "ValidationResult" + ], + "modules": [ + "file-not-found-error", + "invoker-error", + "property", + "validation-error", + "validation-result" + ] + }, + { + "name": "events", + "exports": [ + "Checkpoint", + "CompactionCompletePayload", + "CompactionFailedPayload", + "CompactionStartPayload", + "DoneEventPayload", + "ErrorChunk", + "ErrorEventPayload", + "HarnessContext", + "HookEndPayload", + "HookStartPayload", + "HostToolRequest", + "HostToolResult", + "LlmCompletePayload", + "LlmStartPayload", + "MessagesUpdatedPayload", + "PermissionCompletedPayload", + "PermissionDecision", + "PermissionRequest", + "PermissionRequestedPayload", + "RedactedField", + "RedactionMetadata", + "RetryPayload", + "SessionEndPayload", + "SessionEvent", + "SessionFileRef", + "SessionRef", + "SessionStartPayload", + "SessionSummary", + "SessionTrace", + "SessionWarningPayload", + "StatusEventPayload", + "StreamChunk", + "TextChunk", + "ThinkingChunk", + "ThinkingEventPayload", + "TokenEventPayload", + "ToolCallCompletePayload", + "ToolCallStartPayload", + "ToolChunk", + "ToolExecutionCompletePayload", + "ToolExecutionStartPayload", + "ToolResultPayload", + "TrajectoryEvent", + "TurnEndPayload", + "TurnEvent", + "TurnStartPayload", + "TurnSummary", + "TurnTrace" + ], + "modules": [ + "checkpoint", + "compaction-complete-payload", + "compaction-failed-payload", + "compaction-start-payload", + "done-event-payload", + "error-event-payload", + "harness-context", + "hook-end-payload", + "hook-start-payload", + "host-tool-request", + "host-tool-result", + "llm-complete-payload", + "llm-start-payload", + "messages-updated-payload", + "permission-completed-payload", + "permission-decision", + "permission-request", + "permission-requested-payload", + "redacted-field", + "redaction-metadata", + "retry-payload", + "session-end-payload", + "session-event", + "session-file-ref", + "session-ref", + "session-start-payload", + "session-summary", + "session-trace", + "session-warning-payload", + "status-event-payload", + "stream-chunk", + "thinking-event-payload", + "token-event-payload", + "tool-call-complete-payload", + "tool-call-start-payload", + "tool-execution-complete-payload", + "tool-execution-start-payload", + "tool-result-payload", + "trajectory-event", + "turn-end-payload", + "turn-event", + "turn-start-payload", + "turn-summary", + "turn-trace" + ] + }, + { + "name": "model", + "exports": [ + "Model", + "ModelInfo", + "ModelOptions", + "TokenUsage" + ], + "modules": [ + "model", + "model-info", + "model-options", + "token-usage" + ] + }, + { + "name": "pipeline", + "exports": [ + "CheckpointStore", + "CompactionConfig", + "EventJournalWriter", + "EventSink", + "Executor", + "HostToolExecutor", + "Parser", + "PermissionResolver", + "Processor", + "Renderer", + "ReplayJournalRecord", + "ReplayMismatch", + "ReplayVerificationRequest", + "ReplayVerificationResult", + "RunTurnRequest", + "RunTurnResult", + "TurnModelRequest", + "TurnModelResponse", + "TurnOptions" + ], + "modules": [ + "checkpoint-store", + "compaction-config", + "event-journal-writer", + "event-sink", + "executor", + "host-tool-executor", + "parser", + "permission-resolver", + "processor", + "renderer", + "replay-journal-record", + "replay-mismatch", + "replay-verification-request", + "replay-verification-result", + "run-turn-request", + "run-turn-result", + "turn-model-request", + "turn-model-response", + "turn-options" + ] + }, + { + "name": "streaming", + "exports": [ + "StreamOptions" + ], + "modules": [ + "stream-options" + ] + }, + { + "name": "template", + "exports": [ + "FormatConfig", + "ParserConfig", + "Template" + ], + "modules": [ + "format-config", + "parser-config", + "template" + ] + }, + { + "name": "tools", + "exports": [ + "Binding", + "CustomTool", + "FunctionTool", + "McpApprovalMode", + "McpTool", + "OpenApiTool", + "PromptyTool", + "Tool", + "ToolContext", + "ToolDispatchResult" + ], + "modules": [ + "binding", + "mcp-approval-mode", + "tool", + "tool-context", + "tool-dispatch-result" + ] + }, + { + "name": "tracing", + "exports": [ + "TraceFile", + "TraceSpan", + "TraceTime" + ], + "modules": [ + "trace-file", + "trace-span", + "trace-time" + ] + }, + { + "name": "wire", + "exports": [ + "AnthropicImageBlock", + "AnthropicImageSource", + "AnthropicMessagesRequest", + "AnthropicMessagesResponse", + "AnthropicTextBlock", + "AnthropicToolDefinition", + "AnthropicToolResultBlock", + "AnthropicToolUseBlock", + "AnthropicUsage", + "AnthropicWireMessage" + ], + "modules": [ + "anthropic-image-block", + "anthropic-image-source", + "anthropic-messages-request", + "anthropic-messages-response", + "anthropic-text-block", + "anthropic-tool-definition", + "anthropic-tool-result-block", + "anthropic-tool-use-block", + "anthropic-usage", + "anthropic-wire-message" + ] + } + ], + "protocols": [ + { + "name": "CheckpointStore", + "group": "pipeline", + "symbol": "CheckpointStore", + "source": "./pipeline/checkpoint-store", + "methods": [ + { + "name": "listCheckpoints", + "returns": "Checkpoint[]", + "params": { + "sessionId": "string" + }, + "optional": false, + "sync": false + }, + { + "name": "load", + "returns": "Checkpoint?", + "params": { + "checkpointId": "string", + "sessionId": "string" + }, + "optional": false, + "sync": false + }, + { + "name": "save", + "returns": "Checkpoint", + "params": { + "checkpoint": "Checkpoint" + }, + "optional": false, + "sync": false + } + ] + }, + { + "name": "EventJournalWriter", + "group": "pipeline", + "symbol": "EventJournalWriter", + "source": "./pipeline/event-journal-writer", + "methods": [ + { + "name": "appendSession", + "returns": "boolean", + "params": { + "sessionEvent": "SessionEvent" + }, + "optional": false, + "sync": true + }, + { + "name": "appendTurn", + "returns": "boolean", + "params": { + "turnEvent": "TurnEvent" + }, + "optional": false, + "sync": true + }, + { + "name": "close", + "returns": "boolean", + "params": { + "summary": "SessionSummary?" + }, + "optional": false, + "sync": true + } + ] + }, + { + "name": "EventSink", + "group": "pipeline", + "symbol": "EventSink", + "source": "./pipeline/event-sink", + "methods": [ + { + "name": "emitSession", + "returns": "boolean", + "params": { + "sessionEvent": "SessionEvent" + }, + "optional": false, + "sync": true + }, + { + "name": "emitTurn", + "returns": "boolean", + "params": { + "turnEvent": "TurnEvent" + }, + "optional": false, + "sync": true + } + ] + }, + { + "name": "Executor", + "group": "pipeline", + "symbol": "Executor", + "source": "./pipeline/executor", + "methods": [ + { + "name": "execute", + "returns": "unknown", + "params": { + "agent": "Prompty", + "messages": "Message[]" + }, + "optional": false, + "sync": false + }, + { + "name": "executeStream", + "returns": "unknown", + "params": { + "agent": "Prompty", + "messages": "Message[]" + }, + "optional": true, + "sync": false + }, + { + "name": "formatToolMessages", + "returns": "Message[]", + "params": { + "rawResponse": "unknown", + "textContent": "string?", + "toolCalls": "ToolCall[]", + "toolResults": "string[]" + }, + "optional": false, + "sync": true + } + ] + }, + { + "name": "HostToolExecutor", + "group": "pipeline", + "symbol": "HostToolExecutor", + "source": "./pipeline/host-tool-executor", + "methods": [ + { + "name": "execute", + "returns": "HostToolResult", + "params": { + "request": "HostToolRequest" + }, + "optional": false, + "sync": false + } + ] + }, + { + "name": "Parser", + "group": "pipeline", + "symbol": "Parser", + "source": "./pipeline/parser", + "methods": [ + { + "name": "parse", + "returns": "Message[]", + "params": { + "agent": "Prompty", + "context": "Record?", + "rendered": "string" + }, + "optional": false, + "sync": false + }, + { + "name": "preRender", + "returns": "unknown?", + "params": { + "template": "string" + }, + "optional": true, + "sync": true + } + ] + }, + { + "name": "PermissionResolver", + "group": "pipeline", + "symbol": "PermissionResolver", + "source": "./pipeline/permission-resolver", + "methods": [ + { + "name": "request", + "returns": "PermissionDecision", + "params": { + "request": "PermissionRequest" + }, + "optional": false, + "sync": false + } + ] + }, + { + "name": "Processor", + "group": "pipeline", + "symbol": "Processor", + "source": "./pipeline/processor", + "methods": [ + { + "name": "process", + "returns": "unknown", + "params": { + "agent": "Prompty", + "response": "unknown" + }, + "optional": false, + "sync": false + }, + { + "name": "processStream", + "returns": "unknown", + "params": { + "stream": "unknown" + }, + "optional": true, + "sync": false + } + ] + }, + { + "name": "Renderer", + "group": "pipeline", + "symbol": "Renderer", + "source": "./pipeline/renderer", + "methods": [ + { + "name": "render", + "returns": "string", + "params": { + "agent": "Prompty", + "inputs": "Record", + "template": "string" + }, + "optional": false, + "sync": false + } + ] + } + ], + "modules": [ + "./agent/guardrail-result", + "./agent/prompty", + "./connection/connection", + "./conversation/content-part", + "./conversation/message", + "./conversation/thread-marker", + "./conversation/tool-call", + "./conversation/tool-result", + "./core/file-not-found-error", + "./core/invoker-error", + "./core/property", + "./core/validation-error", + "./core/validation-result", + "./events/checkpoint", + "./events/compaction-complete-payload", + "./events/compaction-failed-payload", + "./events/compaction-start-payload", + "./events/done-event-payload", + "./events/error-event-payload", + "./events/harness-context", + "./events/hook-end-payload", + "./events/hook-start-payload", + "./events/host-tool-request", + "./events/host-tool-result", + "./events/llm-complete-payload", + "./events/llm-start-payload", + "./events/messages-updated-payload", + "./events/permission-completed-payload", + "./events/permission-decision", + "./events/permission-request", + "./events/permission-requested-payload", + "./events/redacted-field", + "./events/redaction-metadata", + "./events/retry-payload", + "./events/session-end-payload", + "./events/session-event", + "./events/session-file-ref", + "./events/session-ref", + "./events/session-start-payload", + "./events/session-summary", + "./events/session-trace", + "./events/session-warning-payload", + "./events/status-event-payload", + "./events/stream-chunk", + "./events/thinking-event-payload", + "./events/token-event-payload", + "./events/tool-call-complete-payload", + "./events/tool-call-start-payload", + "./events/tool-execution-complete-payload", + "./events/tool-execution-start-payload", + "./events/tool-result-payload", + "./events/trajectory-event", + "./events/turn-end-payload", + "./events/turn-event", + "./events/turn-start-payload", + "./events/turn-summary", + "./events/turn-trace", + "./model/model", + "./model/model-info", + "./model/model-options", + "./model/token-usage", + "./pipeline/checkpoint-store", + "./pipeline/compaction-config", + "./pipeline/event-journal-writer", + "./pipeline/event-sink", + "./pipeline/executor", + "./pipeline/host-tool-executor", + "./pipeline/parser", + "./pipeline/permission-resolver", + "./pipeline/processor", + "./pipeline/renderer", + "./pipeline/replay-journal-record", + "./pipeline/replay-mismatch", + "./pipeline/replay-verification-request", + "./pipeline/replay-verification-result", + "./pipeline/run-turn-request", + "./pipeline/run-turn-result", + "./pipeline/turn-model-request", + "./pipeline/turn-model-response", + "./pipeline/turn-options", + "./streaming/stream-options", + "./template/format-config", + "./template/parser-config", + "./template/template", + "./tools/binding", + "./tools/mcp-approval-mode", + "./tools/tool", + "./tools/tool-context", + "./tools/tool-dispatch-result", + "./tracing/trace-file", + "./tracing/trace-span", + "./tracing/trace-time", + "./wire/anthropic-image-block", + "./wire/anthropic-image-source", + "./wire/anthropic-messages-request", + "./wire/anthropic-messages-response", + "./wire/anthropic-text-block", + "./wire/anthropic-tool-definition", + "./wire/anthropic-tool-result-block", + "./wire/anthropic-tool-use-block", + "./wire/anthropic-usage", + "./wire/anthropic-wire-message" + ] + } + ] +} diff --git a/schema/tsp-output/.typra-generated/hydration-seams.json b/schema/tsp-output/.typra-generated/hydration-seams.json new file mode 100644 index 00000000..d4bb1884 --- /dev/null +++ b/schema/tsp-output/.typra-generated/hydration-seams.json @@ -0,0 +1,440 @@ +{ + "emitter": "typra-emitter", + "version": 1, + "protectedPaths": [], + "hydrationZones": [], + "seams": [ + { + "contract": "CheckpointStore", + "target": "csharp", + "group": "pipeline", + "symbol": "CheckpointStore", + "generatedSource": "pipeline/CheckpointStore.cs", + "seamKind": "protocol-adapter" + }, + { + "contract": "EventJournalWriter", + "target": "csharp", + "group": "pipeline", + "symbol": "EventJournalWriter", + "generatedSource": "pipeline/EventJournalWriter.cs", + "seamKind": "protocol-adapter" + }, + { + "contract": "EventSink", + "target": "csharp", + "group": "pipeline", + "symbol": "EventSink", + "generatedSource": "pipeline/EventSink.cs", + "seamKind": "protocol-adapter" + }, + { + "contract": "Executor", + "target": "csharp", + "group": "pipeline", + "symbol": "Executor", + "generatedSource": "pipeline/Executor.cs", + "seamKind": "protocol-adapter" + }, + { + "contract": "HostToolExecutor", + "target": "csharp", + "group": "pipeline", + "symbol": "HostToolExecutor", + "generatedSource": "pipeline/HostToolExecutor.cs", + "seamKind": "protocol-adapter" + }, + { + "contract": "Parser", + "target": "csharp", + "group": "pipeline", + "symbol": "Parser", + "generatedSource": "pipeline/Parser.cs", + "seamKind": "protocol-adapter" + }, + { + "contract": "PermissionResolver", + "target": "csharp", + "group": "pipeline", + "symbol": "PermissionResolver", + "generatedSource": "pipeline/PermissionResolver.cs", + "seamKind": "protocol-adapter" + }, + { + "contract": "Processor", + "target": "csharp", + "group": "pipeline", + "symbol": "Processor", + "generatedSource": "pipeline/Processor.cs", + "seamKind": "protocol-adapter" + }, + { + "contract": "Renderer", + "target": "csharp", + "group": "pipeline", + "symbol": "Renderer", + "generatedSource": "pipeline/Renderer.cs", + "seamKind": "protocol-adapter" + }, + { + "contract": "CheckpointStore", + "target": "go", + "group": "pipeline", + "symbol": "CheckpointStore", + "generatedSource": "checkpoint_store.go", + "seamKind": "protocol-adapter" + }, + { + "contract": "EventJournalWriter", + "target": "go", + "group": "pipeline", + "symbol": "EventJournalWriter", + "generatedSource": "event_journal_writer.go", + "seamKind": "protocol-adapter" + }, + { + "contract": "EventSink", + "target": "go", + "group": "pipeline", + "symbol": "EventSink", + "generatedSource": "event_sink.go", + "seamKind": "protocol-adapter" + }, + { + "contract": "Executor", + "target": "go", + "group": "pipeline", + "symbol": "Executor", + "generatedSource": "executor.go", + "seamKind": "protocol-adapter" + }, + { + "contract": "HostToolExecutor", + "target": "go", + "group": "pipeline", + "symbol": "HostToolExecutor", + "generatedSource": "host_tool_executor.go", + "seamKind": "protocol-adapter" + }, + { + "contract": "Parser", + "target": "go", + "group": "pipeline", + "symbol": "Parser", + "generatedSource": "parser.go", + "seamKind": "protocol-adapter" + }, + { + "contract": "PermissionResolver", + "target": "go", + "group": "pipeline", + "symbol": "PermissionResolver", + "generatedSource": "permission_resolver.go", + "seamKind": "protocol-adapter" + }, + { + "contract": "Processor", + "target": "go", + "group": "pipeline", + "symbol": "Processor", + "generatedSource": "processor.go", + "seamKind": "protocol-adapter" + }, + { + "contract": "Renderer", + "target": "go", + "group": "pipeline", + "symbol": "Renderer", + "generatedSource": "renderer.go", + "seamKind": "protocol-adapter" + }, + { + "contract": "CheckpointStore", + "target": "markdown", + "group": "pipeline", + "symbol": "CheckpointStore", + "generatedSource": "CheckpointStore", + "seamKind": "protocol-adapter" + }, + { + "contract": "EventJournalWriter", + "target": "markdown", + "group": "pipeline", + "symbol": "EventJournalWriter", + "generatedSource": "EventJournalWriter", + "seamKind": "protocol-adapter" + }, + { + "contract": "EventSink", + "target": "markdown", + "group": "pipeline", + "symbol": "EventSink", + "generatedSource": "EventSink", + "seamKind": "protocol-adapter" + }, + { + "contract": "Executor", + "target": "markdown", + "group": "pipeline", + "symbol": "Executor", + "generatedSource": "Executor", + "seamKind": "protocol-adapter" + }, + { + "contract": "HostToolExecutor", + "target": "markdown", + "group": "pipeline", + "symbol": "HostToolExecutor", + "generatedSource": "HostToolExecutor", + "seamKind": "protocol-adapter" + }, + { + "contract": "Parser", + "target": "markdown", + "group": "pipeline", + "symbol": "Parser", + "generatedSource": "Parser", + "seamKind": "protocol-adapter" + }, + { + "contract": "PermissionResolver", + "target": "markdown", + "group": "pipeline", + "symbol": "PermissionResolver", + "generatedSource": "PermissionResolver", + "seamKind": "protocol-adapter" + }, + { + "contract": "Processor", + "target": "markdown", + "group": "pipeline", + "symbol": "Processor", + "generatedSource": "Processor", + "seamKind": "protocol-adapter" + }, + { + "contract": "Renderer", + "target": "markdown", + "group": "pipeline", + "symbol": "Renderer", + "generatedSource": "Renderer", + "seamKind": "protocol-adapter" + }, + { + "contract": "CheckpointStore", + "target": "python", + "group": "pipeline", + "symbol": "CheckpointStore", + "generatedSource": ".pipeline", + "seamKind": "protocol-adapter" + }, + { + "contract": "EventJournalWriter", + "target": "python", + "group": "pipeline", + "symbol": "EventJournalWriter", + "generatedSource": ".pipeline", + "seamKind": "protocol-adapter" + }, + { + "contract": "EventSink", + "target": "python", + "group": "pipeline", + "symbol": "EventSink", + "generatedSource": ".pipeline", + "seamKind": "protocol-adapter" + }, + { + "contract": "Executor", + "target": "python", + "group": "pipeline", + "symbol": "Executor", + "generatedSource": ".pipeline", + "seamKind": "protocol-adapter" + }, + { + "contract": "HostToolExecutor", + "target": "python", + "group": "pipeline", + "symbol": "HostToolExecutor", + "generatedSource": ".pipeline", + "seamKind": "protocol-adapter" + }, + { + "contract": "Parser", + "target": "python", + "group": "pipeline", + "symbol": "Parser", + "generatedSource": ".pipeline", + "seamKind": "protocol-adapter" + }, + { + "contract": "PermissionResolver", + "target": "python", + "group": "pipeline", + "symbol": "PermissionResolver", + "generatedSource": ".pipeline", + "seamKind": "protocol-adapter" + }, + { + "contract": "Processor", + "target": "python", + "group": "pipeline", + "symbol": "Processor", + "generatedSource": ".pipeline", + "seamKind": "protocol-adapter" + }, + { + "contract": "Renderer", + "target": "python", + "group": "pipeline", + "symbol": "Renderer", + "generatedSource": ".pipeline", + "seamKind": "protocol-adapter" + }, + { + "contract": "CheckpointStore", + "target": "rust", + "group": "pipeline", + "symbol": "CheckpointStore", + "generatedSource": "pipeline::checkpoint_store", + "seamKind": "protocol-adapter" + }, + { + "contract": "EventJournalWriter", + "target": "rust", + "group": "pipeline", + "symbol": "EventJournalWriter", + "generatedSource": "pipeline::event_journal_writer", + "seamKind": "protocol-adapter" + }, + { + "contract": "EventSink", + "target": "rust", + "group": "pipeline", + "symbol": "EventSink", + "generatedSource": "pipeline::event_sink", + "seamKind": "protocol-adapter" + }, + { + "contract": "Executor", + "target": "rust", + "group": "pipeline", + "symbol": "Executor", + "generatedSource": "pipeline::executor", + "seamKind": "protocol-adapter" + }, + { + "contract": "HostToolExecutor", + "target": "rust", + "group": "pipeline", + "symbol": "HostToolExecutor", + "generatedSource": "pipeline::host_tool_executor", + "seamKind": "protocol-adapter" + }, + { + "contract": "Parser", + "target": "rust", + "group": "pipeline", + "symbol": "Parser", + "generatedSource": "pipeline::parser", + "seamKind": "protocol-adapter" + }, + { + "contract": "PermissionResolver", + "target": "rust", + "group": "pipeline", + "symbol": "PermissionResolver", + "generatedSource": "pipeline::permission_resolver", + "seamKind": "protocol-adapter" + }, + { + "contract": "Processor", + "target": "rust", + "group": "pipeline", + "symbol": "Processor", + "generatedSource": "pipeline::processor", + "seamKind": "protocol-adapter" + }, + { + "contract": "Renderer", + "target": "rust", + "group": "pipeline", + "symbol": "Renderer", + "generatedSource": "pipeline::renderer", + "seamKind": "protocol-adapter" + }, + { + "contract": "CheckpointStore", + "target": "typescript", + "group": "pipeline", + "symbol": "CheckpointStore", + "generatedSource": "./pipeline/checkpoint-store", + "seamKind": "protocol-adapter" + }, + { + "contract": "EventJournalWriter", + "target": "typescript", + "group": "pipeline", + "symbol": "EventJournalWriter", + "generatedSource": "./pipeline/event-journal-writer", + "seamKind": "protocol-adapter" + }, + { + "contract": "EventSink", + "target": "typescript", + "group": "pipeline", + "symbol": "EventSink", + "generatedSource": "./pipeline/event-sink", + "seamKind": "protocol-adapter" + }, + { + "contract": "Executor", + "target": "typescript", + "group": "pipeline", + "symbol": "Executor", + "generatedSource": "./pipeline/executor", + "seamKind": "protocol-adapter" + }, + { + "contract": "HostToolExecutor", + "target": "typescript", + "group": "pipeline", + "symbol": "HostToolExecutor", + "generatedSource": "./pipeline/host-tool-executor", + "seamKind": "protocol-adapter" + }, + { + "contract": "Parser", + "target": "typescript", + "group": "pipeline", + "symbol": "Parser", + "generatedSource": "./pipeline/parser", + "seamKind": "protocol-adapter" + }, + { + "contract": "PermissionResolver", + "target": "typescript", + "group": "pipeline", + "symbol": "PermissionResolver", + "generatedSource": "./pipeline/permission-resolver", + "seamKind": "protocol-adapter" + }, + { + "contract": "Processor", + "target": "typescript", + "group": "pipeline", + "symbol": "Processor", + "generatedSource": "./pipeline/processor", + "seamKind": "protocol-adapter" + }, + { + "contract": "Renderer", + "target": "typescript", + "group": "pipeline", + "symbol": "Renderer", + "generatedSource": "./pipeline/renderer", + "seamKind": "protocol-adapter" + } + ] +} diff --git a/schema/tsp-output/.typra-generated/manifest.json b/schema/tsp-output/.typra-generated/manifest.json new file mode 100644 index 00000000..3e5ab1b1 --- /dev/null +++ b/schema/tsp-output/.typra-generated/manifest.json @@ -0,0 +1,6417 @@ +{ + "emitter": "typra-emitter", + "version": 1, + "generatedAt": "", + "files": [ + { + "outputRoot": "../runtime/csharp/Prompty.Core.Tests/Model", + "path": "../runtime/csharp/Prompty.Core.Tests/Model/agent/GuardrailResultConversionTests.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core.Tests/Model", + "path": "../runtime/csharp/Prompty.Core.Tests/Model/agent/PromptyConversionTests.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core.Tests/Model", + "path": "../runtime/csharp/Prompty.Core.Tests/Model/connection/AnonymousConnectionConversionTests.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core.Tests/Model", + "path": "../runtime/csharp/Prompty.Core.Tests/Model/connection/ApiKeyConnectionConversionTests.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core.Tests/Model", + "path": "../runtime/csharp/Prompty.Core.Tests/Model/connection/ConnectionConversionTests.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core.Tests/Model", + "path": "../runtime/csharp/Prompty.Core.Tests/Model/connection/FoundryConnectionConversionTests.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core.Tests/Model", + "path": "../runtime/csharp/Prompty.Core.Tests/Model/connection/OAuthConnectionConversionTests.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core.Tests/Model", + "path": "../runtime/csharp/Prompty.Core.Tests/Model/connection/ReferenceConnectionConversionTests.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core.Tests/Model", + "path": "../runtime/csharp/Prompty.Core.Tests/Model/connection/RemoteConnectionConversionTests.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core.Tests/Model", + "path": "../runtime/csharp/Prompty.Core.Tests/Model/conversation/AudioPartConversionTests.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core.Tests/Model", + "path": "../runtime/csharp/Prompty.Core.Tests/Model/conversation/ContentPartConversionTests.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core.Tests/Model", + "path": "../runtime/csharp/Prompty.Core.Tests/Model/conversation/FilePartConversionTests.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core.Tests/Model", + "path": "../runtime/csharp/Prompty.Core.Tests/Model/conversation/ImagePartConversionTests.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core.Tests/Model", + "path": "../runtime/csharp/Prompty.Core.Tests/Model/conversation/MessageConversionTests.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core.Tests/Model", + "path": "../runtime/csharp/Prompty.Core.Tests/Model/conversation/TextPartConversionTests.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core.Tests/Model", + "path": "../runtime/csharp/Prompty.Core.Tests/Model/conversation/ThreadMarkerConversionTests.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core.Tests/Model", + "path": "../runtime/csharp/Prompty.Core.Tests/Model/conversation/ToolCallConversionTests.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core.Tests/Model", + "path": "../runtime/csharp/Prompty.Core.Tests/Model/conversation/ToolResultConversionTests.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core.Tests/Model", + "path": "../runtime/csharp/Prompty.Core.Tests/Model/core/ArrayPropertyConversionTests.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core.Tests/Model", + "path": "../runtime/csharp/Prompty.Core.Tests/Model/core/FileNotFoundErrorConversionTests.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core.Tests/Model", + "path": "../runtime/csharp/Prompty.Core.Tests/Model/core/InvokerErrorConversionTests.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core.Tests/Model", + "path": "../runtime/csharp/Prompty.Core.Tests/Model/core/ObjectPropertyConversionTests.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core.Tests/Model", + "path": "../runtime/csharp/Prompty.Core.Tests/Model/core/PropertyConversionTests.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core.Tests/Model", + "path": "../runtime/csharp/Prompty.Core.Tests/Model/core/ValidationErrorConversionTests.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core.Tests/Model", + "path": "../runtime/csharp/Prompty.Core.Tests/Model/core/ValidationResultConversionTests.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core.Tests/Model", + "path": "../runtime/csharp/Prompty.Core.Tests/Model/events/CheckpointConversionTests.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core.Tests/Model", + "path": "../runtime/csharp/Prompty.Core.Tests/Model/events/CompactionCompletePayloadConversionTests.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core.Tests/Model", + "path": "../runtime/csharp/Prompty.Core.Tests/Model/events/CompactionFailedPayloadConversionTests.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core.Tests/Model", + "path": "../runtime/csharp/Prompty.Core.Tests/Model/events/CompactionStartPayloadConversionTests.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core.Tests/Model", + "path": "../runtime/csharp/Prompty.Core.Tests/Model/events/DoneEventPayloadConversionTests.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core.Tests/Model", + "path": "../runtime/csharp/Prompty.Core.Tests/Model/events/ErrorChunkConversionTests.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core.Tests/Model", + "path": "../runtime/csharp/Prompty.Core.Tests/Model/events/ErrorEventPayloadConversionTests.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core.Tests/Model", + "path": "../runtime/csharp/Prompty.Core.Tests/Model/events/HarnessContextConversionTests.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core.Tests/Model", + "path": "../runtime/csharp/Prompty.Core.Tests/Model/events/HookEndPayloadConversionTests.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core.Tests/Model", + "path": "../runtime/csharp/Prompty.Core.Tests/Model/events/HookStartPayloadConversionTests.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core.Tests/Model", + "path": "../runtime/csharp/Prompty.Core.Tests/Model/events/HostToolRequestConversionTests.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core.Tests/Model", + "path": "../runtime/csharp/Prompty.Core.Tests/Model/events/HostToolResultConversionTests.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core.Tests/Model", + "path": "../runtime/csharp/Prompty.Core.Tests/Model/events/LlmCompletePayloadConversionTests.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core.Tests/Model", + "path": "../runtime/csharp/Prompty.Core.Tests/Model/events/LlmStartPayloadConversionTests.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core.Tests/Model", + "path": "../runtime/csharp/Prompty.Core.Tests/Model/events/MessagesUpdatedPayloadConversionTests.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core.Tests/Model", + "path": "../runtime/csharp/Prompty.Core.Tests/Model/events/PermissionCompletedPayloadConversionTests.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core.Tests/Model", + "path": "../runtime/csharp/Prompty.Core.Tests/Model/events/PermissionDecisionConversionTests.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core.Tests/Model", + "path": "../runtime/csharp/Prompty.Core.Tests/Model/events/PermissionRequestConversionTests.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core.Tests/Model", + "path": "../runtime/csharp/Prompty.Core.Tests/Model/events/PermissionRequestedPayloadConversionTests.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core.Tests/Model", + "path": "../runtime/csharp/Prompty.Core.Tests/Model/events/RedactedFieldConversionTests.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core.Tests/Model", + "path": "../runtime/csharp/Prompty.Core.Tests/Model/events/RedactionMetadataConversionTests.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core.Tests/Model", + "path": "../runtime/csharp/Prompty.Core.Tests/Model/events/RetryPayloadConversionTests.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core.Tests/Model", + "path": "../runtime/csharp/Prompty.Core.Tests/Model/events/SessionEndPayloadConversionTests.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core.Tests/Model", + "path": "../runtime/csharp/Prompty.Core.Tests/Model/events/SessionEventConversionTests.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core.Tests/Model", + "path": "../runtime/csharp/Prompty.Core.Tests/Model/events/SessionFileRefConversionTests.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core.Tests/Model", + "path": "../runtime/csharp/Prompty.Core.Tests/Model/events/SessionRefConversionTests.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core.Tests/Model", + "path": "../runtime/csharp/Prompty.Core.Tests/Model/events/SessionStartPayloadConversionTests.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core.Tests/Model", + "path": "../runtime/csharp/Prompty.Core.Tests/Model/events/SessionSummaryConversionTests.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core.Tests/Model", + "path": "../runtime/csharp/Prompty.Core.Tests/Model/events/SessionTraceConversionTests.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core.Tests/Model", + "path": "../runtime/csharp/Prompty.Core.Tests/Model/events/SessionWarningPayloadConversionTests.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core.Tests/Model", + "path": "../runtime/csharp/Prompty.Core.Tests/Model/events/StatusEventPayloadConversionTests.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core.Tests/Model", + "path": "../runtime/csharp/Prompty.Core.Tests/Model/events/StreamChunkConversionTests.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core.Tests/Model", + "path": "../runtime/csharp/Prompty.Core.Tests/Model/events/TextChunkConversionTests.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core.Tests/Model", + "path": "../runtime/csharp/Prompty.Core.Tests/Model/events/ThinkingChunkConversionTests.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core.Tests/Model", + "path": "../runtime/csharp/Prompty.Core.Tests/Model/events/ThinkingEventPayloadConversionTests.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core.Tests/Model", + "path": "../runtime/csharp/Prompty.Core.Tests/Model/events/TokenEventPayloadConversionTests.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core.Tests/Model", + "path": "../runtime/csharp/Prompty.Core.Tests/Model/events/ToolCallCompletePayloadConversionTests.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core.Tests/Model", + "path": "../runtime/csharp/Prompty.Core.Tests/Model/events/ToolCallStartPayloadConversionTests.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core.Tests/Model", + "path": "../runtime/csharp/Prompty.Core.Tests/Model/events/ToolChunkConversionTests.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core.Tests/Model", + "path": "../runtime/csharp/Prompty.Core.Tests/Model/events/ToolExecutionCompletePayloadConversionTests.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core.Tests/Model", + "path": "../runtime/csharp/Prompty.Core.Tests/Model/events/ToolExecutionStartPayloadConversionTests.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core.Tests/Model", + "path": "../runtime/csharp/Prompty.Core.Tests/Model/events/ToolResultPayloadConversionTests.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core.Tests/Model", + "path": "../runtime/csharp/Prompty.Core.Tests/Model/events/TrajectoryEventConversionTests.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core.Tests/Model", + "path": "../runtime/csharp/Prompty.Core.Tests/Model/events/TurnEndPayloadConversionTests.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core.Tests/Model", + "path": "../runtime/csharp/Prompty.Core.Tests/Model/events/TurnEventConversionTests.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core.Tests/Model", + "path": "../runtime/csharp/Prompty.Core.Tests/Model/events/TurnStartPayloadConversionTests.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core.Tests/Model", + "path": "../runtime/csharp/Prompty.Core.Tests/Model/events/TurnSummaryConversionTests.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core.Tests/Model", + "path": "../runtime/csharp/Prompty.Core.Tests/Model/events/TurnTraceConversionTests.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core.Tests/Model", + "path": "../runtime/csharp/Prompty.Core.Tests/Model/model/ModelConversionTests.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core.Tests/Model", + "path": "../runtime/csharp/Prompty.Core.Tests/Model/model/ModelInfoConversionTests.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core.Tests/Model", + "path": "../runtime/csharp/Prompty.Core.Tests/Model/model/ModelOptionsConversionTests.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core.Tests/Model", + "path": "../runtime/csharp/Prompty.Core.Tests/Model/model/TokenUsageConversionTests.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core.Tests/Model", + "path": "../runtime/csharp/Prompty.Core.Tests/Model/pipeline/CompactionConfigConversionTests.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core.Tests/Model", + "path": "../runtime/csharp/Prompty.Core.Tests/Model/pipeline/ReplayJournalRecordConversionTests.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core.Tests/Model", + "path": "../runtime/csharp/Prompty.Core.Tests/Model/pipeline/ReplayMismatchConversionTests.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core.Tests/Model", + "path": "../runtime/csharp/Prompty.Core.Tests/Model/pipeline/ReplayVerificationRequestConversionTests.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core.Tests/Model", + "path": "../runtime/csharp/Prompty.Core.Tests/Model/pipeline/ReplayVerificationResultConversionTests.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core.Tests/Model", + "path": "../runtime/csharp/Prompty.Core.Tests/Model/pipeline/RunTurnRequestConversionTests.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core.Tests/Model", + "path": "../runtime/csharp/Prompty.Core.Tests/Model/pipeline/RunTurnResultConversionTests.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core.Tests/Model", + "path": "../runtime/csharp/Prompty.Core.Tests/Model/pipeline/TurnModelRequestConversionTests.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core.Tests/Model", + "path": "../runtime/csharp/Prompty.Core.Tests/Model/pipeline/TurnModelResponseConversionTests.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core.Tests/Model", + "path": "../runtime/csharp/Prompty.Core.Tests/Model/pipeline/TurnOptionsConversionTests.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core.Tests/Model", + "path": "../runtime/csharp/Prompty.Core.Tests/Model/streaming/StreamOptionsConversionTests.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core.Tests/Model", + "path": "../runtime/csharp/Prompty.Core.Tests/Model/template/FormatConfigConversionTests.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core.Tests/Model", + "path": "../runtime/csharp/Prompty.Core.Tests/Model/template/ParserConfigConversionTests.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core.Tests/Model", + "path": "../runtime/csharp/Prompty.Core.Tests/Model/template/TemplateConversionTests.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core.Tests/Model", + "path": "../runtime/csharp/Prompty.Core.Tests/Model/tools/BindingConversionTests.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core.Tests/Model", + "path": "../runtime/csharp/Prompty.Core.Tests/Model/tools/CustomToolConversionTests.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core.Tests/Model", + "path": "../runtime/csharp/Prompty.Core.Tests/Model/tools/FunctionToolConversionTests.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core.Tests/Model", + "path": "../runtime/csharp/Prompty.Core.Tests/Model/tools/McpApprovalModeConversionTests.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core.Tests/Model", + "path": "../runtime/csharp/Prompty.Core.Tests/Model/tools/McpToolConversionTests.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core.Tests/Model", + "path": "../runtime/csharp/Prompty.Core.Tests/Model/tools/OpenApiToolConversionTests.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core.Tests/Model", + "path": "../runtime/csharp/Prompty.Core.Tests/Model/tools/PromptyToolConversionTests.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core.Tests/Model", + "path": "../runtime/csharp/Prompty.Core.Tests/Model/tools/ToolContextConversionTests.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core.Tests/Model", + "path": "../runtime/csharp/Prompty.Core.Tests/Model/tools/ToolConversionTests.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core.Tests/Model", + "path": "../runtime/csharp/Prompty.Core.Tests/Model/tools/ToolDispatchResultConversionTests.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core.Tests/Model", + "path": "../runtime/csharp/Prompty.Core.Tests/Model/tracing/TraceFileConversionTests.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core.Tests/Model", + "path": "../runtime/csharp/Prompty.Core.Tests/Model/tracing/TraceSpanConversionTests.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core.Tests/Model", + "path": "../runtime/csharp/Prompty.Core.Tests/Model/tracing/TraceTimeConversionTests.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core.Tests/Model", + "path": "../runtime/csharp/Prompty.Core.Tests/Model/wire/AnthropicImageBlockConversionTests.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core.Tests/Model", + "path": "../runtime/csharp/Prompty.Core.Tests/Model/wire/AnthropicImageSourceConversionTests.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core.Tests/Model", + "path": "../runtime/csharp/Prompty.Core.Tests/Model/wire/AnthropicMessagesRequestConversionTests.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core.Tests/Model", + "path": "../runtime/csharp/Prompty.Core.Tests/Model/wire/AnthropicMessagesResponseConversionTests.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core.Tests/Model", + "path": "../runtime/csharp/Prompty.Core.Tests/Model/wire/AnthropicTextBlockConversionTests.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core.Tests/Model", + "path": "../runtime/csharp/Prompty.Core.Tests/Model/wire/AnthropicToolDefinitionConversionTests.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core.Tests/Model", + "path": "../runtime/csharp/Prompty.Core.Tests/Model/wire/AnthropicToolResultBlockConversionTests.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core.Tests/Model", + "path": "../runtime/csharp/Prompty.Core.Tests/Model/wire/AnthropicToolUseBlockConversionTests.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core.Tests/Model", + "path": "../runtime/csharp/Prompty.Core.Tests/Model/wire/AnthropicUsageConversionTests.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core.Tests/Model", + "path": "../runtime/csharp/Prompty.Core.Tests/Model/wire/AnthropicWireMessageConversionTests.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core/Model", + "path": "../runtime/csharp/Prompty.Core/Model/agent/GuardrailResult.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core/Model", + "path": "../runtime/csharp/Prompty.Core/Model/agent/Prompty.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core/Model", + "path": "../runtime/csharp/Prompty.Core/Model/connection/AnonymousConnection.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core/Model", + "path": "../runtime/csharp/Prompty.Core/Model/connection/ApiKeyConnection.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core/Model", + "path": "../runtime/csharp/Prompty.Core/Model/connection/AuthenticationMode.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core/Model", + "path": "../runtime/csharp/Prompty.Core/Model/connection/Connection.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core/Model", + "path": "../runtime/csharp/Prompty.Core/Model/connection/FoundryConnection.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core/Model", + "path": "../runtime/csharp/Prompty.Core/Model/connection/OAuthConnection.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core/Model", + "path": "../runtime/csharp/Prompty.Core/Model/connection/ReferenceConnection.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core/Model", + "path": "../runtime/csharp/Prompty.Core/Model/connection/RemoteConnection.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core/Model", + "path": "../runtime/csharp/Prompty.Core/Model/Context.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core/Model", + "path": "../runtime/csharp/Prompty.Core/Model/conversation/AudioPart.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core/Model", + "path": "../runtime/csharp/Prompty.Core/Model/conversation/ContentPart.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core/Model", + "path": "../runtime/csharp/Prompty.Core/Model/conversation/FilePart.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core/Model", + "path": "../runtime/csharp/Prompty.Core/Model/conversation/ImagePart.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core/Model", + "path": "../runtime/csharp/Prompty.Core/Model/conversation/Message.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core/Model", + "path": "../runtime/csharp/Prompty.Core/Model/conversation/Role.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core/Model", + "path": "../runtime/csharp/Prompty.Core/Model/conversation/TextPart.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core/Model", + "path": "../runtime/csharp/Prompty.Core/Model/conversation/ThreadMarker.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core/Model", + "path": "../runtime/csharp/Prompty.Core/Model/conversation/ToolCall.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core/Model", + "path": "../runtime/csharp/Prompty.Core/Model/conversation/ToolResult.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core/Model", + "path": "../runtime/csharp/Prompty.Core/Model/conversation/ToolResultStatus.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core/Model", + "path": "../runtime/csharp/Prompty.Core/Model/core/ArrayProperty.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core/Model", + "path": "../runtime/csharp/Prompty.Core/Model/core/FileNotFoundError.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core/Model", + "path": "../runtime/csharp/Prompty.Core/Model/core/InvokerError.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core/Model", + "path": "../runtime/csharp/Prompty.Core/Model/core/ObjectProperty.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core/Model", + "path": "../runtime/csharp/Prompty.Core/Model/core/Property.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core/Model", + "path": "../runtime/csharp/Prompty.Core/Model/core/ValidationError.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core/Model", + "path": "../runtime/csharp/Prompty.Core/Model/core/ValidationResult.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core/Model", + "path": "../runtime/csharp/Prompty.Core/Model/events/Checkpoint.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core/Model", + "path": "../runtime/csharp/Prompty.Core/Model/events/CompactionCompletePayload.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core/Model", + "path": "../runtime/csharp/Prompty.Core/Model/events/CompactionFailedPayload.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core/Model", + "path": "../runtime/csharp/Prompty.Core/Model/events/CompactionStartPayload.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core/Model", + "path": "../runtime/csharp/Prompty.Core/Model/events/DoneEventPayload.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core/Model", + "path": "../runtime/csharp/Prompty.Core/Model/events/ErrorChunk.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core/Model", + "path": "../runtime/csharp/Prompty.Core/Model/events/ErrorEventPayload.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core/Model", + "path": "../runtime/csharp/Prompty.Core/Model/events/HarnessContext.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core/Model", + "path": "../runtime/csharp/Prompty.Core/Model/events/HookEndPayload.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core/Model", + "path": "../runtime/csharp/Prompty.Core/Model/events/HookEndScope.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core/Model", + "path": "../runtime/csharp/Prompty.Core/Model/events/HookStartPayload.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core/Model", + "path": "../runtime/csharp/Prompty.Core/Model/events/HookStartScope.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core/Model", + "path": "../runtime/csharp/Prompty.Core/Model/events/HostToolRequest.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core/Model", + "path": "../runtime/csharp/Prompty.Core/Model/events/HostToolResult.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core/Model", + "path": "../runtime/csharp/Prompty.Core/Model/events/LlmCompletePayload.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core/Model", + "path": "../runtime/csharp/Prompty.Core/Model/events/LlmStartPayload.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core/Model", + "path": "../runtime/csharp/Prompty.Core/Model/events/MessagesUpdatedPayload.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core/Model", + "path": "../runtime/csharp/Prompty.Core/Model/events/PermissionCompletedPayload.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core/Model", + "path": "../runtime/csharp/Prompty.Core/Model/events/PermissionDecision.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core/Model", + "path": "../runtime/csharp/Prompty.Core/Model/events/PermissionRequest.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core/Model", + "path": "../runtime/csharp/Prompty.Core/Model/events/PermissionRequestedPayload.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core/Model", + "path": "../runtime/csharp/Prompty.Core/Model/events/RedactedField.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core/Model", + "path": "../runtime/csharp/Prompty.Core/Model/events/RedactionMetadata.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core/Model", + "path": "../runtime/csharp/Prompty.Core/Model/events/RedactionMode.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core/Model", + "path": "../runtime/csharp/Prompty.Core/Model/events/RetryPayload.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core/Model", + "path": "../runtime/csharp/Prompty.Core/Model/events/SessionEndPayload.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core/Model", + "path": "../runtime/csharp/Prompty.Core/Model/events/SessionEndStatus.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core/Model", + "path": "../runtime/csharp/Prompty.Core/Model/events/SessionEvent.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core/Model", + "path": "../runtime/csharp/Prompty.Core/Model/events/SessionEventType.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core/Model", + "path": "../runtime/csharp/Prompty.Core/Model/events/SessionFileRef.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core/Model", + "path": "../runtime/csharp/Prompty.Core/Model/events/SessionRef.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core/Model", + "path": "../runtime/csharp/Prompty.Core/Model/events/SessionStartPayload.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core/Model", + "path": "../runtime/csharp/Prompty.Core/Model/events/SessionSummary.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core/Model", + "path": "../runtime/csharp/Prompty.Core/Model/events/SessionSummaryStatus.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core/Model", + "path": "../runtime/csharp/Prompty.Core/Model/events/SessionTrace.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core/Model", + "path": "../runtime/csharp/Prompty.Core/Model/events/SessionWarningPayload.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core/Model", + "path": "../runtime/csharp/Prompty.Core/Model/events/StatusEventPayload.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core/Model", + "path": "../runtime/csharp/Prompty.Core/Model/events/StreamChunk.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core/Model", + "path": "../runtime/csharp/Prompty.Core/Model/events/TextChunk.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core/Model", + "path": "../runtime/csharp/Prompty.Core/Model/events/ThinkingChunk.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core/Model", + "path": "../runtime/csharp/Prompty.Core/Model/events/ThinkingEventPayload.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core/Model", + "path": "../runtime/csharp/Prompty.Core/Model/events/TokenEventPayload.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core/Model", + "path": "../runtime/csharp/Prompty.Core/Model/events/ToolCallCompletePayload.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core/Model", + "path": "../runtime/csharp/Prompty.Core/Model/events/ToolCallStartPayload.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core/Model", + "path": "../runtime/csharp/Prompty.Core/Model/events/ToolChunk.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core/Model", + "path": "../runtime/csharp/Prompty.Core/Model/events/ToolExecutionCompletePayload.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core/Model", + "path": "../runtime/csharp/Prompty.Core/Model/events/ToolExecutionStartPayload.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core/Model", + "path": "../runtime/csharp/Prompty.Core/Model/events/ToolResultPayload.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core/Model", + "path": "../runtime/csharp/Prompty.Core/Model/events/TrajectoryEvent.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core/Model", + "path": "../runtime/csharp/Prompty.Core/Model/events/TurnEndPayload.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core/Model", + "path": "../runtime/csharp/Prompty.Core/Model/events/TurnEvent.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core/Model", + "path": "../runtime/csharp/Prompty.Core/Model/events/TurnEventType.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core/Model", + "path": "../runtime/csharp/Prompty.Core/Model/events/TurnStartPayload.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core/Model", + "path": "../runtime/csharp/Prompty.Core/Model/events/TurnStatus.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core/Model", + "path": "../runtime/csharp/Prompty.Core/Model/events/TurnSummary.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core/Model", + "path": "../runtime/csharp/Prompty.Core/Model/events/TurnTrace.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core/Model", + "path": "../runtime/csharp/Prompty.Core/Model/model/Model.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core/Model", + "path": "../runtime/csharp/Prompty.Core/Model/model/ModelInfo.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core/Model", + "path": "../runtime/csharp/Prompty.Core/Model/model/ModelOptions.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core/Model", + "path": "../runtime/csharp/Prompty.Core/Model/model/TokenUsage.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core/Model", + "path": "../runtime/csharp/Prompty.Core/Model/pipeline/CheckpointStore.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core/Model", + "path": "../runtime/csharp/Prompty.Core/Model/pipeline/CompactionConfig.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core/Model", + "path": "../runtime/csharp/Prompty.Core/Model/pipeline/EventJournalWriter.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core/Model", + "path": "../runtime/csharp/Prompty.Core/Model/pipeline/EventSink.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core/Model", + "path": "../runtime/csharp/Prompty.Core/Model/pipeline/Executor.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core/Model", + "path": "../runtime/csharp/Prompty.Core/Model/pipeline/HostToolExecutor.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core/Model", + "path": "../runtime/csharp/Prompty.Core/Model/pipeline/Parser.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core/Model", + "path": "../runtime/csharp/Prompty.Core/Model/pipeline/PermissionResolver.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core/Model", + "path": "../runtime/csharp/Prompty.Core/Model/pipeline/Processor.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core/Model", + "path": "../runtime/csharp/Prompty.Core/Model/pipeline/Renderer.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core/Model", + "path": "../runtime/csharp/Prompty.Core/Model/pipeline/ReplayJournalRecord.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core/Model", + "path": "../runtime/csharp/Prompty.Core/Model/pipeline/ReplayMismatch.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core/Model", + "path": "../runtime/csharp/Prompty.Core/Model/pipeline/ReplayRecordKind.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core/Model", + "path": "../runtime/csharp/Prompty.Core/Model/pipeline/ReplayRecordStatus.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core/Model", + "path": "../runtime/csharp/Prompty.Core/Model/pipeline/ReplayVerificationRequest.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core/Model", + "path": "../runtime/csharp/Prompty.Core/Model/pipeline/ReplayVerificationResult.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core/Model", + "path": "../runtime/csharp/Prompty.Core/Model/pipeline/ReplayVerificationStatus.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core/Model", + "path": "../runtime/csharp/Prompty.Core/Model/pipeline/RunTurnRequest.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core/Model", + "path": "../runtime/csharp/Prompty.Core/Model/pipeline/RunTurnResult.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core/Model", + "path": "../runtime/csharp/Prompty.Core/Model/pipeline/RunTurnStatus.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core/Model", + "path": "../runtime/csharp/Prompty.Core/Model/pipeline/TurnModelRequest.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core/Model", + "path": "../runtime/csharp/Prompty.Core/Model/pipeline/TurnModelResponse.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core/Model", + "path": "../runtime/csharp/Prompty.Core/Model/pipeline/TurnOptions.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core/Model", + "path": "../runtime/csharp/Prompty.Core/Model/streaming/StreamOptions.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core/Model", + "path": "../runtime/csharp/Prompty.Core/Model/template/FormatConfig.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core/Model", + "path": "../runtime/csharp/Prompty.Core/Model/template/ParserConfig.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core/Model", + "path": "../runtime/csharp/Prompty.Core/Model/template/Template.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core/Model", + "path": "../runtime/csharp/Prompty.Core/Model/tools/Binding.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core/Model", + "path": "../runtime/csharp/Prompty.Core/Model/tools/CustomTool.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core/Model", + "path": "../runtime/csharp/Prompty.Core/Model/tools/FunctionTool.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core/Model", + "path": "../runtime/csharp/Prompty.Core/Model/tools/McpApprovalMode.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core/Model", + "path": "../runtime/csharp/Prompty.Core/Model/tools/McpApprovalModeKind.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core/Model", + "path": "../runtime/csharp/Prompty.Core/Model/tools/McpTool.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core/Model", + "path": "../runtime/csharp/Prompty.Core/Model/tools/OpenApiTool.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core/Model", + "path": "../runtime/csharp/Prompty.Core/Model/tools/PromptyTool.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core/Model", + "path": "../runtime/csharp/Prompty.Core/Model/tools/Tool.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core/Model", + "path": "../runtime/csharp/Prompty.Core/Model/tools/ToolContext.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core/Model", + "path": "../runtime/csharp/Prompty.Core/Model/tools/ToolDispatchResult.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core/Model", + "path": "../runtime/csharp/Prompty.Core/Model/tracing/TraceFile.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core/Model", + "path": "../runtime/csharp/Prompty.Core/Model/tracing/TraceSpan.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core/Model", + "path": "../runtime/csharp/Prompty.Core/Model/tracing/TraceTime.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core/Model", + "path": "../runtime/csharp/Prompty.Core/Model/Utils.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core/Model", + "path": "../runtime/csharp/Prompty.Core/Model/wire/AnthropicImageBlock.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core/Model", + "path": "../runtime/csharp/Prompty.Core/Model/wire/AnthropicImageSource.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core/Model", + "path": "../runtime/csharp/Prompty.Core/Model/wire/AnthropicMessagesRequest.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core/Model", + "path": "../runtime/csharp/Prompty.Core/Model/wire/AnthropicMessagesResponse.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core/Model", + "path": "../runtime/csharp/Prompty.Core/Model/wire/AnthropicTextBlock.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core/Model", + "path": "../runtime/csharp/Prompty.Core/Model/wire/AnthropicToolDefinition.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core/Model", + "path": "../runtime/csharp/Prompty.Core/Model/wire/AnthropicToolResultBlock.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core/Model", + "path": "../runtime/csharp/Prompty.Core/Model/wire/AnthropicToolUseBlock.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core/Model", + "path": "../runtime/csharp/Prompty.Core/Model/wire/AnthropicUsage.cs", + "marker": true + }, + { + "outputRoot": "../runtime/csharp/Prompty.Core/Model", + "path": "../runtime/csharp/Prompty.Core/Model/wire/AnthropicWireMessage.cs", + "marker": true + }, + { + "outputRoot": "../runtime/go/prompty/model", + "path": "../runtime/go/prompty/model/anonymous_connection_test.go", + "marker": true + }, + { + "outputRoot": "../runtime/go/prompty/model", + "path": "../runtime/go/prompty/model/anthropic_image_block_test.go", + "marker": true + }, + { + "outputRoot": "../runtime/go/prompty/model", + "path": "../runtime/go/prompty/model/anthropic_image_block.go", + "marker": true + }, + { + "outputRoot": "../runtime/go/prompty/model", + "path": "../runtime/go/prompty/model/anthropic_image_source_test.go", + "marker": true + }, + { + "outputRoot": "../runtime/go/prompty/model", + "path": "../runtime/go/prompty/model/anthropic_image_source.go", + "marker": true + }, + { + "outputRoot": "../runtime/go/prompty/model", + "path": "../runtime/go/prompty/model/anthropic_messages_request_test.go", + "marker": true + }, + { + "outputRoot": "../runtime/go/prompty/model", + "path": "../runtime/go/prompty/model/anthropic_messages_request.go", + "marker": true + }, + { + "outputRoot": "../runtime/go/prompty/model", + "path": "../runtime/go/prompty/model/anthropic_messages_response_test.go", + "marker": true + }, + { + "outputRoot": "../runtime/go/prompty/model", + "path": "../runtime/go/prompty/model/anthropic_messages_response.go", + "marker": true + }, + { + "outputRoot": "../runtime/go/prompty/model", + "path": "../runtime/go/prompty/model/anthropic_text_block_test.go", + "marker": true + }, + { + "outputRoot": "../runtime/go/prompty/model", + "path": "../runtime/go/prompty/model/anthropic_text_block.go", + "marker": true + }, + { + "outputRoot": "../runtime/go/prompty/model", + "path": "../runtime/go/prompty/model/anthropic_tool_definition_test.go", + "marker": true + }, + { + "outputRoot": "../runtime/go/prompty/model", + "path": "../runtime/go/prompty/model/anthropic_tool_definition.go", + "marker": true + }, + { + "outputRoot": "../runtime/go/prompty/model", + "path": "../runtime/go/prompty/model/anthropic_tool_result_block_test.go", + "marker": true + }, + { + "outputRoot": "../runtime/go/prompty/model", + "path": "../runtime/go/prompty/model/anthropic_tool_result_block.go", + "marker": true + }, + { + "outputRoot": "../runtime/go/prompty/model", + "path": "../runtime/go/prompty/model/anthropic_tool_use_block_test.go", + "marker": true + }, + { + "outputRoot": "../runtime/go/prompty/model", + "path": "../runtime/go/prompty/model/anthropic_tool_use_block.go", + "marker": true + }, + { + "outputRoot": "../runtime/go/prompty/model", + "path": "../runtime/go/prompty/model/anthropic_usage_test.go", + "marker": true + }, + { + "outputRoot": "../runtime/go/prompty/model", + "path": "../runtime/go/prompty/model/anthropic_usage.go", + "marker": true + }, + { + "outputRoot": "../runtime/go/prompty/model", + "path": "../runtime/go/prompty/model/anthropic_wire_message_test.go", + "marker": true + }, + { + "outputRoot": "../runtime/go/prompty/model", + "path": "../runtime/go/prompty/model/anthropic_wire_message.go", + "marker": true + }, + { + "outputRoot": "../runtime/go/prompty/model", + "path": "../runtime/go/prompty/model/api_key_connection_test.go", + "marker": true + }, + { + "outputRoot": "../runtime/go/prompty/model", + "path": "../runtime/go/prompty/model/array_property_test.go", + "marker": true + }, + { + "outputRoot": "../runtime/go/prompty/model", + "path": "../runtime/go/prompty/model/audio_part_test.go", + "marker": true + }, + { + "outputRoot": "../runtime/go/prompty/model", + "path": "../runtime/go/prompty/model/binding_test.go", + "marker": true + }, + { + "outputRoot": "../runtime/go/prompty/model", + "path": "../runtime/go/prompty/model/binding.go", + "marker": true + }, + { + "outputRoot": "../runtime/go/prompty/model", + "path": "../runtime/go/prompty/model/checkpoint_store.go", + "marker": true + }, + { + "outputRoot": "../runtime/go/prompty/model", + "path": "../runtime/go/prompty/model/checkpoint_test.go", + "marker": true + }, + { + "outputRoot": "../runtime/go/prompty/model", + "path": "../runtime/go/prompty/model/checkpoint.go", + "marker": true + }, + { + "outputRoot": "../runtime/go/prompty/model", + "path": "../runtime/go/prompty/model/compaction_complete_payload_test.go", + "marker": true + }, + { + "outputRoot": "../runtime/go/prompty/model", + "path": "../runtime/go/prompty/model/compaction_complete_payload.go", + "marker": true + }, + { + "outputRoot": "../runtime/go/prompty/model", + "path": "../runtime/go/prompty/model/compaction_config_test.go", + "marker": true + }, + { + "outputRoot": "../runtime/go/prompty/model", + "path": "../runtime/go/prompty/model/compaction_config.go", + "marker": true + }, + { + "outputRoot": "../runtime/go/prompty/model", + "path": "../runtime/go/prompty/model/compaction_failed_payload_test.go", + "marker": true + }, + { + "outputRoot": "../runtime/go/prompty/model", + "path": "../runtime/go/prompty/model/compaction_failed_payload.go", + "marker": true + }, + { + "outputRoot": "../runtime/go/prompty/model", + "path": "../runtime/go/prompty/model/compaction_start_payload_test.go", + "marker": true + }, + { + "outputRoot": "../runtime/go/prompty/model", + "path": "../runtime/go/prompty/model/compaction_start_payload.go", + "marker": true + }, + { + "outputRoot": "../runtime/go/prompty/model", + "path": "../runtime/go/prompty/model/connection_test.go", + "marker": true + }, + { + "outputRoot": "../runtime/go/prompty/model", + "path": "../runtime/go/prompty/model/connection.go", + "marker": true + }, + { + "outputRoot": "../runtime/go/prompty/model", + "path": "../runtime/go/prompty/model/content_part_test.go", + "marker": true + }, + { + "outputRoot": "../runtime/go/prompty/model", + "path": "../runtime/go/prompty/model/content_part.go", + "marker": true + }, + { + "outputRoot": "../runtime/go/prompty/model", + "path": "../runtime/go/prompty/model/context.go", + "marker": true + }, + { + "outputRoot": "../runtime/go/prompty/model", + "path": "../runtime/go/prompty/model/custom_tool_test.go", + "marker": true + }, + { + "outputRoot": "../runtime/go/prompty/model", + "path": "../runtime/go/prompty/model/done_event_payload_test.go", + "marker": true + }, + { + "outputRoot": "../runtime/go/prompty/model", + "path": "../runtime/go/prompty/model/done_event_payload.go", + "marker": true + }, + { + "outputRoot": "../runtime/go/prompty/model", + "path": "../runtime/go/prompty/model/error_chunk_test.go", + "marker": true + }, + { + "outputRoot": "../runtime/go/prompty/model", + "path": "../runtime/go/prompty/model/error_event_payload_test.go", + "marker": true + }, + { + "outputRoot": "../runtime/go/prompty/model", + "path": "../runtime/go/prompty/model/error_event_payload.go", + "marker": true + }, + { + "outputRoot": "../runtime/go/prompty/model", + "path": "../runtime/go/prompty/model/event_journal_writer.go", + "marker": true + }, + { + "outputRoot": "../runtime/go/prompty/model", + "path": "../runtime/go/prompty/model/event_sink.go", + "marker": true + }, + { + "outputRoot": "../runtime/go/prompty/model", + "path": "../runtime/go/prompty/model/executor.go", + "marker": true + }, + { + "outputRoot": "../runtime/go/prompty/model", + "path": "../runtime/go/prompty/model/file_not_found_error_test.go", + "marker": true + }, + { + "outputRoot": "../runtime/go/prompty/model", + "path": "../runtime/go/prompty/model/file_not_found_error.go", + "marker": true + }, + { + "outputRoot": "../runtime/go/prompty/model", + "path": "../runtime/go/prompty/model/file_part_test.go", + "marker": true + }, + { + "outputRoot": "../runtime/go/prompty/model", + "path": "../runtime/go/prompty/model/format_config_test.go", + "marker": true + }, + { + "outputRoot": "../runtime/go/prompty/model", + "path": "../runtime/go/prompty/model/format_config.go", + "marker": true + }, + { + "outputRoot": "../runtime/go/prompty/model", + "path": "../runtime/go/prompty/model/foundry_connection_test.go", + "marker": true + }, + { + "outputRoot": "../runtime/go/prompty/model", + "path": "../runtime/go/prompty/model/function_tool_test.go", + "marker": true + }, + { + "outputRoot": "../runtime/go/prompty/model", + "path": "../runtime/go/prompty/model/guardrail_result_test.go", + "marker": true + }, + { + "outputRoot": "../runtime/go/prompty/model", + "path": "../runtime/go/prompty/model/guardrail_result.go", + "marker": true + }, + { + "outputRoot": "../runtime/go/prompty/model", + "path": "../runtime/go/prompty/model/harness_context_test.go", + "marker": true + }, + { + "outputRoot": "../runtime/go/prompty/model", + "path": "../runtime/go/prompty/model/harness_context.go", + "marker": true + }, + { + "outputRoot": "../runtime/go/prompty/model", + "path": "../runtime/go/prompty/model/hook_end_payload_test.go", + "marker": true + }, + { + "outputRoot": "../runtime/go/prompty/model", + "path": "../runtime/go/prompty/model/hook_end_payload.go", + "marker": true + }, + { + "outputRoot": "../runtime/go/prompty/model", + "path": "../runtime/go/prompty/model/hook_start_payload_test.go", + "marker": true + }, + { + "outputRoot": "../runtime/go/prompty/model", + "path": "../runtime/go/prompty/model/hook_start_payload.go", + "marker": true + }, + { + "outputRoot": "../runtime/go/prompty/model", + "path": "../runtime/go/prompty/model/host_tool_executor.go", + "marker": true + }, + { + "outputRoot": "../runtime/go/prompty/model", + "path": "../runtime/go/prompty/model/host_tool_request_test.go", + "marker": true + }, + { + "outputRoot": "../runtime/go/prompty/model", + "path": "../runtime/go/prompty/model/host_tool_request.go", + "marker": true + }, + { + "outputRoot": "../runtime/go/prompty/model", + "path": "../runtime/go/prompty/model/host_tool_result_test.go", + "marker": true + }, + { + "outputRoot": "../runtime/go/prompty/model", + "path": "../runtime/go/prompty/model/host_tool_result.go", + "marker": true + }, + { + "outputRoot": "../runtime/go/prompty/model", + "path": "../runtime/go/prompty/model/image_part_test.go", + "marker": true + }, + { + "outputRoot": "../runtime/go/prompty/model", + "path": "../runtime/go/prompty/model/invoker_error_test.go", + "marker": true + }, + { + "outputRoot": "../runtime/go/prompty/model", + "path": "../runtime/go/prompty/model/invoker_error.go", + "marker": true + }, + { + "outputRoot": "../runtime/go/prompty/model", + "path": "../runtime/go/prompty/model/llm_complete_payload_test.go", + "marker": true + }, + { + "outputRoot": "../runtime/go/prompty/model", + "path": "../runtime/go/prompty/model/llm_complete_payload.go", + "marker": true + }, + { + "outputRoot": "../runtime/go/prompty/model", + "path": "../runtime/go/prompty/model/llm_start_payload_test.go", + "marker": true + }, + { + "outputRoot": "../runtime/go/prompty/model", + "path": "../runtime/go/prompty/model/llm_start_payload.go", + "marker": true + }, + { + "outputRoot": "../runtime/go/prompty/model", + "path": "../runtime/go/prompty/model/mcp_approval_mode_test.go", + "marker": true + }, + { + "outputRoot": "../runtime/go/prompty/model", + "path": "../runtime/go/prompty/model/mcp_approval_mode.go", + "marker": true + }, + { + "outputRoot": "../runtime/go/prompty/model", + "path": "../runtime/go/prompty/model/mcp_tool_test.go", + "marker": true + }, + { + "outputRoot": "../runtime/go/prompty/model", + "path": "../runtime/go/prompty/model/message_test.go", + "marker": true + }, + { + "outputRoot": "../runtime/go/prompty/model", + "path": "../runtime/go/prompty/model/message.go", + "marker": true + }, + { + "outputRoot": "../runtime/go/prompty/model", + "path": "../runtime/go/prompty/model/messages_updated_payload_test.go", + "marker": true + }, + { + "outputRoot": "../runtime/go/prompty/model", + "path": "../runtime/go/prompty/model/messages_updated_payload.go", + "marker": true + }, + { + "outputRoot": "../runtime/go/prompty/model", + "path": "../runtime/go/prompty/model/model_info_test.go", + "marker": true + }, + { + "outputRoot": "../runtime/go/prompty/model", + "path": "../runtime/go/prompty/model/model_info.go", + "marker": true + }, + { + "outputRoot": "../runtime/go/prompty/model", + "path": "../runtime/go/prompty/model/model_options_test.go", + "marker": true + }, + { + "outputRoot": "../runtime/go/prompty/model", + "path": "../runtime/go/prompty/model/model_options.go", + "marker": true + }, + { + "outputRoot": "../runtime/go/prompty/model", + "path": "../runtime/go/prompty/model/model_test.go", + "marker": true + }, + { + "outputRoot": "../runtime/go/prompty/model", + "path": "../runtime/go/prompty/model/model.go", + "marker": true + }, + { + "outputRoot": "../runtime/go/prompty/model", + "path": "../runtime/go/prompty/model/o_auth_connection_test.go", + "marker": true + }, + { + "outputRoot": "../runtime/go/prompty/model", + "path": "../runtime/go/prompty/model/object_property_test.go", + "marker": true + }, + { + "outputRoot": "../runtime/go/prompty/model", + "path": "../runtime/go/prompty/model/open_api_tool_test.go", + "marker": true + }, + { + "outputRoot": "../runtime/go/prompty/model", + "path": "../runtime/go/prompty/model/parser_config_test.go", + "marker": true + }, + { + "outputRoot": "../runtime/go/prompty/model", + "path": "../runtime/go/prompty/model/parser_config.go", + "marker": true + }, + { + "outputRoot": "../runtime/go/prompty/model", + "path": "../runtime/go/prompty/model/parser.go", + "marker": true + }, + { + "outputRoot": "../runtime/go/prompty/model", + "path": "../runtime/go/prompty/model/permission_completed_payload_test.go", + "marker": true + }, + { + "outputRoot": "../runtime/go/prompty/model", + "path": "../runtime/go/prompty/model/permission_completed_payload.go", + "marker": true + }, + { + "outputRoot": "../runtime/go/prompty/model", + "path": "../runtime/go/prompty/model/permission_decision_test.go", + "marker": true + }, + { + "outputRoot": "../runtime/go/prompty/model", + "path": "../runtime/go/prompty/model/permission_decision.go", + "marker": true + }, + { + "outputRoot": "../runtime/go/prompty/model", + "path": "../runtime/go/prompty/model/permission_request_test.go", + "marker": true + }, + { + "outputRoot": "../runtime/go/prompty/model", + "path": "../runtime/go/prompty/model/permission_request.go", + "marker": true + }, + { + "outputRoot": "../runtime/go/prompty/model", + "path": "../runtime/go/prompty/model/permission_requested_payload_test.go", + "marker": true + }, + { + "outputRoot": "../runtime/go/prompty/model", + "path": "../runtime/go/prompty/model/permission_requested_payload.go", + "marker": true + }, + { + "outputRoot": "../runtime/go/prompty/model", + "path": "../runtime/go/prompty/model/permission_resolver.go", + "marker": true + }, + { + "outputRoot": "../runtime/go/prompty/model", + "path": "../runtime/go/prompty/model/processor.go", + "marker": true + }, + { + "outputRoot": "../runtime/go/prompty/model", + "path": "../runtime/go/prompty/model/prompty_test.go", + "marker": true + }, + { + "outputRoot": "../runtime/go/prompty/model", + "path": "../runtime/go/prompty/model/prompty_tool_test.go", + "marker": true + }, + { + "outputRoot": "../runtime/go/prompty/model", + "path": "../runtime/go/prompty/model/prompty.go", + "marker": true + }, + { + "outputRoot": "../runtime/go/prompty/model", + "path": "../runtime/go/prompty/model/property_test.go", + "marker": true + }, + { + "outputRoot": "../runtime/go/prompty/model", + "path": "../runtime/go/prompty/model/property.go", + "marker": true + }, + { + "outputRoot": "../runtime/go/prompty/model", + "path": "../runtime/go/prompty/model/redacted_field_test.go", + "marker": true + }, + { + "outputRoot": "../runtime/go/prompty/model", + "path": "../runtime/go/prompty/model/redacted_field.go", + "marker": true + }, + { + "outputRoot": "../runtime/go/prompty/model", + "path": "../runtime/go/prompty/model/redaction_metadata_test.go", + "marker": true + }, + { + "outputRoot": "../runtime/go/prompty/model", + "path": "../runtime/go/prompty/model/redaction_metadata.go", + "marker": true + }, + { + "outputRoot": "../runtime/go/prompty/model", + "path": "../runtime/go/prompty/model/reference_connection_test.go", + "marker": true + }, + { + "outputRoot": "../runtime/go/prompty/model", + "path": "../runtime/go/prompty/model/remote_connection_test.go", + "marker": true + }, + { + "outputRoot": "../runtime/go/prompty/model", + "path": "../runtime/go/prompty/model/renderer.go", + "marker": true + }, + { + "outputRoot": "../runtime/go/prompty/model", + "path": "../runtime/go/prompty/model/replay_journal_record_test.go", + "marker": true + }, + { + "outputRoot": "../runtime/go/prompty/model", + "path": "../runtime/go/prompty/model/replay_journal_record.go", + "marker": true + }, + { + "outputRoot": "../runtime/go/prompty/model", + "path": "../runtime/go/prompty/model/replay_mismatch_test.go", + "marker": true + }, + { + "outputRoot": "../runtime/go/prompty/model", + "path": "../runtime/go/prompty/model/replay_mismatch.go", + "marker": true + }, + { + "outputRoot": "../runtime/go/prompty/model", + "path": "../runtime/go/prompty/model/replay_verification_request_test.go", + "marker": true + }, + { + "outputRoot": "../runtime/go/prompty/model", + "path": "../runtime/go/prompty/model/replay_verification_request.go", + "marker": true + }, + { + "outputRoot": "../runtime/go/prompty/model", + "path": "../runtime/go/prompty/model/replay_verification_result_test.go", + "marker": true + }, + { + "outputRoot": "../runtime/go/prompty/model", + "path": "../runtime/go/prompty/model/replay_verification_result.go", + "marker": true + }, + { + "outputRoot": "../runtime/go/prompty/model", + "path": "../runtime/go/prompty/model/retry_payload_test.go", + "marker": true + }, + { + "outputRoot": "../runtime/go/prompty/model", + "path": "../runtime/go/prompty/model/retry_payload.go", + "marker": true + }, + { + "outputRoot": "../runtime/go/prompty/model", + "path": "../runtime/go/prompty/model/run_turn_request_test.go", + "marker": true + }, + { + "outputRoot": "../runtime/go/prompty/model", + "path": "../runtime/go/prompty/model/run_turn_request.go", + "marker": true + }, + { + "outputRoot": "../runtime/go/prompty/model", + "path": "../runtime/go/prompty/model/run_turn_result_test.go", + "marker": true + }, + { + "outputRoot": "../runtime/go/prompty/model", + "path": "../runtime/go/prompty/model/run_turn_result.go", + "marker": true + }, + { + "outputRoot": "../runtime/go/prompty/model", + "path": "../runtime/go/prompty/model/session_end_payload_test.go", + "marker": true + }, + { + "outputRoot": "../runtime/go/prompty/model", + "path": "../runtime/go/prompty/model/session_end_payload.go", + "marker": true + }, + { + "outputRoot": "../runtime/go/prompty/model", + "path": "../runtime/go/prompty/model/session_event_test.go", + "marker": true + }, + { + "outputRoot": "../runtime/go/prompty/model", + "path": "../runtime/go/prompty/model/session_event.go", + "marker": true + }, + { + "outputRoot": "../runtime/go/prompty/model", + "path": "../runtime/go/prompty/model/session_file_ref_test.go", + "marker": true + }, + { + "outputRoot": "../runtime/go/prompty/model", + "path": "../runtime/go/prompty/model/session_file_ref.go", + "marker": true + }, + { + "outputRoot": "../runtime/go/prompty/model", + "path": "../runtime/go/prompty/model/session_ref_test.go", + "marker": true + }, + { + "outputRoot": "../runtime/go/prompty/model", + "path": "../runtime/go/prompty/model/session_ref.go", + "marker": true + }, + { + "outputRoot": "../runtime/go/prompty/model", + "path": "../runtime/go/prompty/model/session_start_payload_test.go", + "marker": true + }, + { + "outputRoot": "../runtime/go/prompty/model", + "path": "../runtime/go/prompty/model/session_start_payload.go", + "marker": true + }, + { + "outputRoot": "../runtime/go/prompty/model", + "path": "../runtime/go/prompty/model/session_summary_test.go", + "marker": true + }, + { + "outputRoot": "../runtime/go/prompty/model", + "path": "../runtime/go/prompty/model/session_summary.go", + "marker": true + }, + { + "outputRoot": "../runtime/go/prompty/model", + "path": "../runtime/go/prompty/model/session_trace_test.go", + "marker": true + }, + { + "outputRoot": "../runtime/go/prompty/model", + "path": "../runtime/go/prompty/model/session_trace.go", + "marker": true + }, + { + "outputRoot": "../runtime/go/prompty/model", + "path": "../runtime/go/prompty/model/session_warning_payload_test.go", + "marker": true + }, + { + "outputRoot": "../runtime/go/prompty/model", + "path": "../runtime/go/prompty/model/session_warning_payload.go", + "marker": true + }, + { + "outputRoot": "../runtime/go/prompty/model", + "path": "../runtime/go/prompty/model/status_event_payload_test.go", + "marker": true + }, + { + "outputRoot": "../runtime/go/prompty/model", + "path": "../runtime/go/prompty/model/status_event_payload.go", + "marker": true + }, + { + "outputRoot": "../runtime/go/prompty/model", + "path": "../runtime/go/prompty/model/stream_chunk_test.go", + "marker": true + }, + { + "outputRoot": "../runtime/go/prompty/model", + "path": "../runtime/go/prompty/model/stream_chunk.go", + "marker": true + }, + { + "outputRoot": "../runtime/go/prompty/model", + "path": "../runtime/go/prompty/model/stream_options_test.go", + "marker": true + }, + { + "outputRoot": "../runtime/go/prompty/model", + "path": "../runtime/go/prompty/model/stream_options.go", + "marker": true + }, + { + "outputRoot": "../runtime/go/prompty/model", + "path": "../runtime/go/prompty/model/template_test.go", + "marker": true + }, + { + "outputRoot": "../runtime/go/prompty/model", + "path": "../runtime/go/prompty/model/template.go", + "marker": true + }, + { + "outputRoot": "../runtime/go/prompty/model", + "path": "../runtime/go/prompty/model/text_chunk_test.go", + "marker": true + }, + { + "outputRoot": "../runtime/go/prompty/model", + "path": "../runtime/go/prompty/model/text_part_test.go", + "marker": true + }, + { + "outputRoot": "../runtime/go/prompty/model", + "path": "../runtime/go/prompty/model/thinking_chunk_test.go", + "marker": true + }, + { + "outputRoot": "../runtime/go/prompty/model", + "path": "../runtime/go/prompty/model/thinking_event_payload_test.go", + "marker": true + }, + { + "outputRoot": "../runtime/go/prompty/model", + "path": "../runtime/go/prompty/model/thinking_event_payload.go", + "marker": true + }, + { + "outputRoot": "../runtime/go/prompty/model", + "path": "../runtime/go/prompty/model/thread_marker_test.go", + "marker": true + }, + { + "outputRoot": "../runtime/go/prompty/model", + "path": "../runtime/go/prompty/model/thread_marker.go", + "marker": true + }, + { + "outputRoot": "../runtime/go/prompty/model", + "path": "../runtime/go/prompty/model/token_event_payload_test.go", + "marker": true + }, + { + "outputRoot": "../runtime/go/prompty/model", + "path": "../runtime/go/prompty/model/token_event_payload.go", + "marker": true + }, + { + "outputRoot": "../runtime/go/prompty/model", + "path": "../runtime/go/prompty/model/token_usage_test.go", + "marker": true + }, + { + "outputRoot": "../runtime/go/prompty/model", + "path": "../runtime/go/prompty/model/token_usage.go", + "marker": true + }, + { + "outputRoot": "../runtime/go/prompty/model", + "path": "../runtime/go/prompty/model/tool_call_complete_payload_test.go", + "marker": true + }, + { + "outputRoot": "../runtime/go/prompty/model", + "path": "../runtime/go/prompty/model/tool_call_complete_payload.go", + "marker": true + }, + { + "outputRoot": "../runtime/go/prompty/model", + "path": "../runtime/go/prompty/model/tool_call_start_payload_test.go", + "marker": true + }, + { + "outputRoot": "../runtime/go/prompty/model", + "path": "../runtime/go/prompty/model/tool_call_start_payload.go", + "marker": true + }, + { + "outputRoot": "../runtime/go/prompty/model", + "path": "../runtime/go/prompty/model/tool_call_test.go", + "marker": true + }, + { + "outputRoot": "../runtime/go/prompty/model", + "path": "../runtime/go/prompty/model/tool_call.go", + "marker": true + }, + { + "outputRoot": "../runtime/go/prompty/model", + "path": "../runtime/go/prompty/model/tool_chunk_test.go", + "marker": true + }, + { + "outputRoot": "../runtime/go/prompty/model", + "path": "../runtime/go/prompty/model/tool_context_test.go", + "marker": true + }, + { + "outputRoot": "../runtime/go/prompty/model", + "path": "../runtime/go/prompty/model/tool_context.go", + "marker": true + }, + { + "outputRoot": "../runtime/go/prompty/model", + "path": "../runtime/go/prompty/model/tool_dispatch_result_test.go", + "marker": true + }, + { + "outputRoot": "../runtime/go/prompty/model", + "path": "../runtime/go/prompty/model/tool_dispatch_result.go", + "marker": true + }, + { + "outputRoot": "../runtime/go/prompty/model", + "path": "../runtime/go/prompty/model/tool_execution_complete_payload_test.go", + "marker": true + }, + { + "outputRoot": "../runtime/go/prompty/model", + "path": "../runtime/go/prompty/model/tool_execution_complete_payload.go", + "marker": true + }, + { + "outputRoot": "../runtime/go/prompty/model", + "path": "../runtime/go/prompty/model/tool_execution_start_payload_test.go", + "marker": true + }, + { + "outputRoot": "../runtime/go/prompty/model", + "path": "../runtime/go/prompty/model/tool_execution_start_payload.go", + "marker": true + }, + { + "outputRoot": "../runtime/go/prompty/model", + "path": "../runtime/go/prompty/model/tool_result_payload_test.go", + "marker": true + }, + { + "outputRoot": "../runtime/go/prompty/model", + "path": "../runtime/go/prompty/model/tool_result_payload.go", + "marker": true + }, + { + "outputRoot": "../runtime/go/prompty/model", + "path": "../runtime/go/prompty/model/tool_result_test.go", + "marker": true + }, + { + "outputRoot": "../runtime/go/prompty/model", + "path": "../runtime/go/prompty/model/tool_result.go", + "marker": true + }, + { + "outputRoot": "../runtime/go/prompty/model", + "path": "../runtime/go/prompty/model/tool_test.go", + "marker": true + }, + { + "outputRoot": "../runtime/go/prompty/model", + "path": "../runtime/go/prompty/model/tool.go", + "marker": true + }, + { + "outputRoot": "../runtime/go/prompty/model", + "path": "../runtime/go/prompty/model/trace_file_test.go", + "marker": true + }, + { + "outputRoot": "../runtime/go/prompty/model", + "path": "../runtime/go/prompty/model/trace_file.go", + "marker": true + }, + { + "outputRoot": "../runtime/go/prompty/model", + "path": "../runtime/go/prompty/model/trace_span_test.go", + "marker": true + }, + { + "outputRoot": "../runtime/go/prompty/model", + "path": "../runtime/go/prompty/model/trace_span.go", + "marker": true + }, + { + "outputRoot": "../runtime/go/prompty/model", + "path": "../runtime/go/prompty/model/trace_time_test.go", + "marker": true + }, + { + "outputRoot": "../runtime/go/prompty/model", + "path": "../runtime/go/prompty/model/trace_time.go", + "marker": true + }, + { + "outputRoot": "../runtime/go/prompty/model", + "path": "../runtime/go/prompty/model/trajectory_event_test.go", + "marker": true + }, + { + "outputRoot": "../runtime/go/prompty/model", + "path": "../runtime/go/prompty/model/trajectory_event.go", + "marker": true + }, + { + "outputRoot": "../runtime/go/prompty/model", + "path": "../runtime/go/prompty/model/turn_end_payload_test.go", + "marker": true + }, + { + "outputRoot": "../runtime/go/prompty/model", + "path": "../runtime/go/prompty/model/turn_end_payload.go", + "marker": true + }, + { + "outputRoot": "../runtime/go/prompty/model", + "path": "../runtime/go/prompty/model/turn_event_test.go", + "marker": true + }, + { + "outputRoot": "../runtime/go/prompty/model", + "path": "../runtime/go/prompty/model/turn_event.go", + "marker": true + }, + { + "outputRoot": "../runtime/go/prompty/model", + "path": "../runtime/go/prompty/model/turn_model_request_test.go", + "marker": true + }, + { + "outputRoot": "../runtime/go/prompty/model", + "path": "../runtime/go/prompty/model/turn_model_request.go", + "marker": true + }, + { + "outputRoot": "../runtime/go/prompty/model", + "path": "../runtime/go/prompty/model/turn_model_response_test.go", + "marker": true + }, + { + "outputRoot": "../runtime/go/prompty/model", + "path": "../runtime/go/prompty/model/turn_model_response.go", + "marker": true + }, + { + "outputRoot": "../runtime/go/prompty/model", + "path": "../runtime/go/prompty/model/turn_options_test.go", + "marker": true + }, + { + "outputRoot": "../runtime/go/prompty/model", + "path": "../runtime/go/prompty/model/turn_options.go", + "marker": true + }, + { + "outputRoot": "../runtime/go/prompty/model", + "path": "../runtime/go/prompty/model/turn_start_payload_test.go", + "marker": true + }, + { + "outputRoot": "../runtime/go/prompty/model", + "path": "../runtime/go/prompty/model/turn_start_payload.go", + "marker": true + }, + { + "outputRoot": "../runtime/go/prompty/model", + "path": "../runtime/go/prompty/model/turn_summary_test.go", + "marker": true + }, + { + "outputRoot": "../runtime/go/prompty/model", + "path": "../runtime/go/prompty/model/turn_summary.go", + "marker": true + }, + { + "outputRoot": "../runtime/go/prompty/model", + "path": "../runtime/go/prompty/model/turn_trace_test.go", + "marker": true + }, + { + "outputRoot": "../runtime/go/prompty/model", + "path": "../runtime/go/prompty/model/turn_trace.go", + "marker": true + }, + { + "outputRoot": "../runtime/go/prompty/model", + "path": "../runtime/go/prompty/model/validation_error_test.go", + "marker": true + }, + { + "outputRoot": "../runtime/go/prompty/model", + "path": "../runtime/go/prompty/model/validation_error.go", + "marker": true + }, + { + "outputRoot": "../runtime/go/prompty/model", + "path": "../runtime/go/prompty/model/validation_result_test.go", + "marker": true + }, + { + "outputRoot": "../runtime/go/prompty/model", + "path": "../runtime/go/prompty/model/validation_result.go", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/prompty/model", + "path": "../runtime/python/prompty/prompty/model/__init__.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/prompty/model", + "path": "../runtime/python/prompty/prompty/model/_context.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/prompty/model", + "path": "../runtime/python/prompty/prompty/model/agent/__init__.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/prompty/model", + "path": "../runtime/python/prompty/prompty/model/agent/_GuardrailResult.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/prompty/model", + "path": "../runtime/python/prompty/prompty/model/agent/_Prompty.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/prompty/model", + "path": "../runtime/python/prompty/prompty/model/connection/__init__.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/prompty/model", + "path": "../runtime/python/prompty/prompty/model/connection/_Connection.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/prompty/model", + "path": "../runtime/python/prompty/prompty/model/conversation/__init__.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/prompty/model", + "path": "../runtime/python/prompty/prompty/model/conversation/_ContentPart.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/prompty/model", + "path": "../runtime/python/prompty/prompty/model/conversation/_Message.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/prompty/model", + "path": "../runtime/python/prompty/prompty/model/conversation/_ThreadMarker.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/prompty/model", + "path": "../runtime/python/prompty/prompty/model/conversation/_ToolCall.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/prompty/model", + "path": "../runtime/python/prompty/prompty/model/conversation/_ToolResult.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/prompty/model", + "path": "../runtime/python/prompty/prompty/model/core/__init__.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/prompty/model", + "path": "../runtime/python/prompty/prompty/model/core/_FileNotFoundError.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/prompty/model", + "path": "../runtime/python/prompty/prompty/model/core/_InvokerError.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/prompty/model", + "path": "../runtime/python/prompty/prompty/model/core/_Property.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/prompty/model", + "path": "../runtime/python/prompty/prompty/model/core/_ValidationError.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/prompty/model", + "path": "../runtime/python/prompty/prompty/model/core/_ValidationResult.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/prompty/model", + "path": "../runtime/python/prompty/prompty/model/events/__init__.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/prompty/model", + "path": "../runtime/python/prompty/prompty/model/events/_Checkpoint.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/prompty/model", + "path": "../runtime/python/prompty/prompty/model/events/_CompactionCompletePayload.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/prompty/model", + "path": "../runtime/python/prompty/prompty/model/events/_CompactionFailedPayload.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/prompty/model", + "path": "../runtime/python/prompty/prompty/model/events/_CompactionStartPayload.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/prompty/model", + "path": "../runtime/python/prompty/prompty/model/events/_DoneEventPayload.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/prompty/model", + "path": "../runtime/python/prompty/prompty/model/events/_ErrorEventPayload.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/prompty/model", + "path": "../runtime/python/prompty/prompty/model/events/_HarnessContext.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/prompty/model", + "path": "../runtime/python/prompty/prompty/model/events/_HookEndPayload.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/prompty/model", + "path": "../runtime/python/prompty/prompty/model/events/_HookStartPayload.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/prompty/model", + "path": "../runtime/python/prompty/prompty/model/events/_HostToolRequest.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/prompty/model", + "path": "../runtime/python/prompty/prompty/model/events/_HostToolResult.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/prompty/model", + "path": "../runtime/python/prompty/prompty/model/events/_LlmCompletePayload.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/prompty/model", + "path": "../runtime/python/prompty/prompty/model/events/_LlmStartPayload.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/prompty/model", + "path": "../runtime/python/prompty/prompty/model/events/_MessagesUpdatedPayload.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/prompty/model", + "path": "../runtime/python/prompty/prompty/model/events/_PermissionCompletedPayload.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/prompty/model", + "path": "../runtime/python/prompty/prompty/model/events/_PermissionDecision.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/prompty/model", + "path": "../runtime/python/prompty/prompty/model/events/_PermissionRequest.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/prompty/model", + "path": "../runtime/python/prompty/prompty/model/events/_PermissionRequestedPayload.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/prompty/model", + "path": "../runtime/python/prompty/prompty/model/events/_RedactedField.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/prompty/model", + "path": "../runtime/python/prompty/prompty/model/events/_RedactionMetadata.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/prompty/model", + "path": "../runtime/python/prompty/prompty/model/events/_RetryPayload.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/prompty/model", + "path": "../runtime/python/prompty/prompty/model/events/_SessionEndPayload.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/prompty/model", + "path": "../runtime/python/prompty/prompty/model/events/_SessionEvent.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/prompty/model", + "path": "../runtime/python/prompty/prompty/model/events/_SessionFileRef.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/prompty/model", + "path": "../runtime/python/prompty/prompty/model/events/_SessionRef.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/prompty/model", + "path": "../runtime/python/prompty/prompty/model/events/_SessionStartPayload.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/prompty/model", + "path": "../runtime/python/prompty/prompty/model/events/_SessionSummary.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/prompty/model", + "path": "../runtime/python/prompty/prompty/model/events/_SessionTrace.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/prompty/model", + "path": "../runtime/python/prompty/prompty/model/events/_SessionWarningPayload.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/prompty/model", + "path": "../runtime/python/prompty/prompty/model/events/_StatusEventPayload.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/prompty/model", + "path": "../runtime/python/prompty/prompty/model/events/_StreamChunk.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/prompty/model", + "path": "../runtime/python/prompty/prompty/model/events/_ThinkingEventPayload.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/prompty/model", + "path": "../runtime/python/prompty/prompty/model/events/_TokenEventPayload.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/prompty/model", + "path": "../runtime/python/prompty/prompty/model/events/_ToolCallCompletePayload.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/prompty/model", + "path": "../runtime/python/prompty/prompty/model/events/_ToolCallStartPayload.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/prompty/model", + "path": "../runtime/python/prompty/prompty/model/events/_ToolExecutionCompletePayload.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/prompty/model", + "path": "../runtime/python/prompty/prompty/model/events/_ToolExecutionStartPayload.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/prompty/model", + "path": "../runtime/python/prompty/prompty/model/events/_ToolResultPayload.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/prompty/model", + "path": "../runtime/python/prompty/prompty/model/events/_TrajectoryEvent.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/prompty/model", + "path": "../runtime/python/prompty/prompty/model/events/_TurnEndPayload.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/prompty/model", + "path": "../runtime/python/prompty/prompty/model/events/_TurnEvent.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/prompty/model", + "path": "../runtime/python/prompty/prompty/model/events/_TurnStartPayload.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/prompty/model", + "path": "../runtime/python/prompty/prompty/model/events/_TurnSummary.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/prompty/model", + "path": "../runtime/python/prompty/prompty/model/events/_TurnTrace.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/prompty/model", + "path": "../runtime/python/prompty/prompty/model/model/__init__.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/prompty/model", + "path": "../runtime/python/prompty/prompty/model/model/_Model.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/prompty/model", + "path": "../runtime/python/prompty/prompty/model/model/_ModelInfo.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/prompty/model", + "path": "../runtime/python/prompty/prompty/model/model/_ModelOptions.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/prompty/model", + "path": "../runtime/python/prompty/prompty/model/model/_TokenUsage.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/prompty/model", + "path": "../runtime/python/prompty/prompty/model/pipeline/__init__.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/prompty/model", + "path": "../runtime/python/prompty/prompty/model/pipeline/_CheckpointStore.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/prompty/model", + "path": "../runtime/python/prompty/prompty/model/pipeline/_CompactionConfig.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/prompty/model", + "path": "../runtime/python/prompty/prompty/model/pipeline/_EventJournalWriter.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/prompty/model", + "path": "../runtime/python/prompty/prompty/model/pipeline/_EventSink.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/prompty/model", + "path": "../runtime/python/prompty/prompty/model/pipeline/_Executor.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/prompty/model", + "path": "../runtime/python/prompty/prompty/model/pipeline/_HostToolExecutor.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/prompty/model", + "path": "../runtime/python/prompty/prompty/model/pipeline/_Parser.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/prompty/model", + "path": "../runtime/python/prompty/prompty/model/pipeline/_PermissionResolver.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/prompty/model", + "path": "../runtime/python/prompty/prompty/model/pipeline/_Processor.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/prompty/model", + "path": "../runtime/python/prompty/prompty/model/pipeline/_Renderer.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/prompty/model", + "path": "../runtime/python/prompty/prompty/model/pipeline/_ReplayJournalRecord.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/prompty/model", + "path": "../runtime/python/prompty/prompty/model/pipeline/_ReplayMismatch.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/prompty/model", + "path": "../runtime/python/prompty/prompty/model/pipeline/_ReplayVerificationRequest.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/prompty/model", + "path": "../runtime/python/prompty/prompty/model/pipeline/_ReplayVerificationResult.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/prompty/model", + "path": "../runtime/python/prompty/prompty/model/pipeline/_RunTurnRequest.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/prompty/model", + "path": "../runtime/python/prompty/prompty/model/pipeline/_RunTurnResult.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/prompty/model", + "path": "../runtime/python/prompty/prompty/model/pipeline/_TurnModelRequest.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/prompty/model", + "path": "../runtime/python/prompty/prompty/model/pipeline/_TurnModelResponse.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/prompty/model", + "path": "../runtime/python/prompty/prompty/model/pipeline/_TurnOptions.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/prompty/model", + "path": "../runtime/python/prompty/prompty/model/py.typed", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/prompty/model", + "path": "../runtime/python/prompty/prompty/model/streaming/__init__.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/prompty/model", + "path": "../runtime/python/prompty/prompty/model/streaming/_StreamOptions.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/prompty/model", + "path": "../runtime/python/prompty/prompty/model/template/__init__.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/prompty/model", + "path": "../runtime/python/prompty/prompty/model/template/_FormatConfig.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/prompty/model", + "path": "../runtime/python/prompty/prompty/model/template/_ParserConfig.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/prompty/model", + "path": "../runtime/python/prompty/prompty/model/template/_Template.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/prompty/model", + "path": "../runtime/python/prompty/prompty/model/tools/__init__.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/prompty/model", + "path": "../runtime/python/prompty/prompty/model/tools/_Binding.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/prompty/model", + "path": "../runtime/python/prompty/prompty/model/tools/_McpApprovalMode.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/prompty/model", + "path": "../runtime/python/prompty/prompty/model/tools/_Tool.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/prompty/model", + "path": "../runtime/python/prompty/prompty/model/tools/_ToolContext.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/prompty/model", + "path": "../runtime/python/prompty/prompty/model/tools/_ToolDispatchResult.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/prompty/model", + "path": "../runtime/python/prompty/prompty/model/tracing/__init__.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/prompty/model", + "path": "../runtime/python/prompty/prompty/model/tracing/_TraceFile.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/prompty/model", + "path": "../runtime/python/prompty/prompty/model/tracing/_TraceSpan.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/prompty/model", + "path": "../runtime/python/prompty/prompty/model/tracing/_TraceTime.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/prompty/model", + "path": "../runtime/python/prompty/prompty/model/wire/__init__.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/prompty/model", + "path": "../runtime/python/prompty/prompty/model/wire/_AnthropicImageBlock.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/prompty/model", + "path": "../runtime/python/prompty/prompty/model/wire/_AnthropicImageSource.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/prompty/model", + "path": "../runtime/python/prompty/prompty/model/wire/_AnthropicMessagesRequest.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/prompty/model", + "path": "../runtime/python/prompty/prompty/model/wire/_AnthropicMessagesResponse.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/prompty/model", + "path": "../runtime/python/prompty/prompty/model/wire/_AnthropicTextBlock.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/prompty/model", + "path": "../runtime/python/prompty/prompty/model/wire/_AnthropicToolDefinition.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/prompty/model", + "path": "../runtime/python/prompty/prompty/model/wire/_AnthropicToolResultBlock.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/prompty/model", + "path": "../runtime/python/prompty/prompty/model/wire/_AnthropicToolUseBlock.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/prompty/model", + "path": "../runtime/python/prompty/prompty/model/wire/_AnthropicUsage.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/prompty/model", + "path": "../runtime/python/prompty/prompty/model/wire/_AnthropicWireMessage.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/tests/model", + "path": "../runtime/python/prompty/tests/model/agent/test_guardrail_result.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/tests/model", + "path": "../runtime/python/prompty/tests/model/agent/test_prompty.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/tests/model", + "path": "../runtime/python/prompty/tests/model/connection/test_anonymous_connection.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/tests/model", + "path": "../runtime/python/prompty/tests/model/connection/test_api_key_connection.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/tests/model", + "path": "../runtime/python/prompty/tests/model/connection/test_connection.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/tests/model", + "path": "../runtime/python/prompty/tests/model/connection/test_foundry_connection.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/tests/model", + "path": "../runtime/python/prompty/tests/model/connection/test_o_auth_connection.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/tests/model", + "path": "../runtime/python/prompty/tests/model/connection/test_reference_connection.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/tests/model", + "path": "../runtime/python/prompty/tests/model/connection/test_remote_connection.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/tests/model", + "path": "../runtime/python/prompty/tests/model/conversation/test_audio_part.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/tests/model", + "path": "../runtime/python/prompty/tests/model/conversation/test_content_part.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/tests/model", + "path": "../runtime/python/prompty/tests/model/conversation/test_file_part.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/tests/model", + "path": "../runtime/python/prompty/tests/model/conversation/test_image_part.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/tests/model", + "path": "../runtime/python/prompty/tests/model/conversation/test_message.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/tests/model", + "path": "../runtime/python/prompty/tests/model/conversation/test_text_part.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/tests/model", + "path": "../runtime/python/prompty/tests/model/conversation/test_thread_marker.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/tests/model", + "path": "../runtime/python/prompty/tests/model/conversation/test_tool_call.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/tests/model", + "path": "../runtime/python/prompty/tests/model/conversation/test_tool_result.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/tests/model", + "path": "../runtime/python/prompty/tests/model/core/test_array_property.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/tests/model", + "path": "../runtime/python/prompty/tests/model/core/test_file_not_found_error.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/tests/model", + "path": "../runtime/python/prompty/tests/model/core/test_invoker_error.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/tests/model", + "path": "../runtime/python/prompty/tests/model/core/test_object_property.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/tests/model", + "path": "../runtime/python/prompty/tests/model/core/test_property.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/tests/model", + "path": "../runtime/python/prompty/tests/model/core/test_validation_error.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/tests/model", + "path": "../runtime/python/prompty/tests/model/core/test_validation_result.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/tests/model", + "path": "../runtime/python/prompty/tests/model/events/test_checkpoint.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/tests/model", + "path": "../runtime/python/prompty/tests/model/events/test_compaction_complete_payload.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/tests/model", + "path": "../runtime/python/prompty/tests/model/events/test_compaction_failed_payload.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/tests/model", + "path": "../runtime/python/prompty/tests/model/events/test_compaction_start_payload.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/tests/model", + "path": "../runtime/python/prompty/tests/model/events/test_done_event_payload.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/tests/model", + "path": "../runtime/python/prompty/tests/model/events/test_error_chunk.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/tests/model", + "path": "../runtime/python/prompty/tests/model/events/test_error_event_payload.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/tests/model", + "path": "../runtime/python/prompty/tests/model/events/test_harness_context.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/tests/model", + "path": "../runtime/python/prompty/tests/model/events/test_hook_end_payload.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/tests/model", + "path": "../runtime/python/prompty/tests/model/events/test_hook_start_payload.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/tests/model", + "path": "../runtime/python/prompty/tests/model/events/test_host_tool_request.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/tests/model", + "path": "../runtime/python/prompty/tests/model/events/test_host_tool_result.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/tests/model", + "path": "../runtime/python/prompty/tests/model/events/test_llm_complete_payload.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/tests/model", + "path": "../runtime/python/prompty/tests/model/events/test_llm_start_payload.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/tests/model", + "path": "../runtime/python/prompty/tests/model/events/test_messages_updated_payload.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/tests/model", + "path": "../runtime/python/prompty/tests/model/events/test_permission_completed_payload.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/tests/model", + "path": "../runtime/python/prompty/tests/model/events/test_permission_decision.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/tests/model", + "path": "../runtime/python/prompty/tests/model/events/test_permission_request.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/tests/model", + "path": "../runtime/python/prompty/tests/model/events/test_permission_requested_payload.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/tests/model", + "path": "../runtime/python/prompty/tests/model/events/test_redacted_field.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/tests/model", + "path": "../runtime/python/prompty/tests/model/events/test_redaction_metadata.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/tests/model", + "path": "../runtime/python/prompty/tests/model/events/test_retry_payload.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/tests/model", + "path": "../runtime/python/prompty/tests/model/events/test_session_end_payload.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/tests/model", + "path": "../runtime/python/prompty/tests/model/events/test_session_event.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/tests/model", + "path": "../runtime/python/prompty/tests/model/events/test_session_file_ref.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/tests/model", + "path": "../runtime/python/prompty/tests/model/events/test_session_ref.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/tests/model", + "path": "../runtime/python/prompty/tests/model/events/test_session_start_payload.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/tests/model", + "path": "../runtime/python/prompty/tests/model/events/test_session_summary.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/tests/model", + "path": "../runtime/python/prompty/tests/model/events/test_session_trace.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/tests/model", + "path": "../runtime/python/prompty/tests/model/events/test_session_warning_payload.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/tests/model", + "path": "../runtime/python/prompty/tests/model/events/test_status_event_payload.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/tests/model", + "path": "../runtime/python/prompty/tests/model/events/test_stream_chunk.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/tests/model", + "path": "../runtime/python/prompty/tests/model/events/test_text_chunk.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/tests/model", + "path": "../runtime/python/prompty/tests/model/events/test_thinking_chunk.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/tests/model", + "path": "../runtime/python/prompty/tests/model/events/test_thinking_event_payload.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/tests/model", + "path": "../runtime/python/prompty/tests/model/events/test_token_event_payload.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/tests/model", + "path": "../runtime/python/prompty/tests/model/events/test_tool_call_complete_payload.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/tests/model", + "path": "../runtime/python/prompty/tests/model/events/test_tool_call_start_payload.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/tests/model", + "path": "../runtime/python/prompty/tests/model/events/test_tool_chunk.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/tests/model", + "path": "../runtime/python/prompty/tests/model/events/test_tool_execution_complete_payload.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/tests/model", + "path": "../runtime/python/prompty/tests/model/events/test_tool_execution_start_payload.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/tests/model", + "path": "../runtime/python/prompty/tests/model/events/test_tool_result_payload.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/tests/model", + "path": "../runtime/python/prompty/tests/model/events/test_trajectory_event.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/tests/model", + "path": "../runtime/python/prompty/tests/model/events/test_turn_end_payload.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/tests/model", + "path": "../runtime/python/prompty/tests/model/events/test_turn_event.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/tests/model", + "path": "../runtime/python/prompty/tests/model/events/test_turn_start_payload.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/tests/model", + "path": "../runtime/python/prompty/tests/model/events/test_turn_summary.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/tests/model", + "path": "../runtime/python/prompty/tests/model/events/test_turn_trace.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/tests/model", + "path": "../runtime/python/prompty/tests/model/model/test_model_info.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/tests/model", + "path": "../runtime/python/prompty/tests/model/model/test_model_options.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/tests/model", + "path": "../runtime/python/prompty/tests/model/model/test_model.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/tests/model", + "path": "../runtime/python/prompty/tests/model/model/test_token_usage.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/tests/model", + "path": "../runtime/python/prompty/tests/model/pipeline/test_compaction_config.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/tests/model", + "path": "../runtime/python/prompty/tests/model/pipeline/test_replay_journal_record.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/tests/model", + "path": "../runtime/python/prompty/tests/model/pipeline/test_replay_mismatch.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/tests/model", + "path": "../runtime/python/prompty/tests/model/pipeline/test_replay_verification_request.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/tests/model", + "path": "../runtime/python/prompty/tests/model/pipeline/test_replay_verification_result.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/tests/model", + "path": "../runtime/python/prompty/tests/model/pipeline/test_run_turn_request.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/tests/model", + "path": "../runtime/python/prompty/tests/model/pipeline/test_run_turn_result.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/tests/model", + "path": "../runtime/python/prompty/tests/model/pipeline/test_turn_model_request.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/tests/model", + "path": "../runtime/python/prompty/tests/model/pipeline/test_turn_model_response.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/tests/model", + "path": "../runtime/python/prompty/tests/model/pipeline/test_turn_options.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/tests/model", + "path": "../runtime/python/prompty/tests/model/streaming/test_stream_options.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/tests/model", + "path": "../runtime/python/prompty/tests/model/template/test_format_config.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/tests/model", + "path": "../runtime/python/prompty/tests/model/template/test_parser_config.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/tests/model", + "path": "../runtime/python/prompty/tests/model/template/test_template.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/tests/model", + "path": "../runtime/python/prompty/tests/model/test_context.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/tests/model", + "path": "../runtime/python/prompty/tests/model/tools/test_binding.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/tests/model", + "path": "../runtime/python/prompty/tests/model/tools/test_custom_tool.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/tests/model", + "path": "../runtime/python/prompty/tests/model/tools/test_function_tool.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/tests/model", + "path": "../runtime/python/prompty/tests/model/tools/test_mcp_approval_mode.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/tests/model", + "path": "../runtime/python/prompty/tests/model/tools/test_mcp_tool.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/tests/model", + "path": "../runtime/python/prompty/tests/model/tools/test_open_api_tool.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/tests/model", + "path": "../runtime/python/prompty/tests/model/tools/test_prompty_tool.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/tests/model", + "path": "../runtime/python/prompty/tests/model/tools/test_tool_context.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/tests/model", + "path": "../runtime/python/prompty/tests/model/tools/test_tool_dispatch_result.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/tests/model", + "path": "../runtime/python/prompty/tests/model/tools/test_tool.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/tests/model", + "path": "../runtime/python/prompty/tests/model/tracing/test_trace_file.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/tests/model", + "path": "../runtime/python/prompty/tests/model/tracing/test_trace_span.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/tests/model", + "path": "../runtime/python/prompty/tests/model/tracing/test_trace_time.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/tests/model", + "path": "../runtime/python/prompty/tests/model/wire/test_anthropic_image_block.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/tests/model", + "path": "../runtime/python/prompty/tests/model/wire/test_anthropic_image_source.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/tests/model", + "path": "../runtime/python/prompty/tests/model/wire/test_anthropic_messages_request.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/tests/model", + "path": "../runtime/python/prompty/tests/model/wire/test_anthropic_messages_response.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/tests/model", + "path": "../runtime/python/prompty/tests/model/wire/test_anthropic_text_block.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/tests/model", + "path": "../runtime/python/prompty/tests/model/wire/test_anthropic_tool_definition.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/tests/model", + "path": "../runtime/python/prompty/tests/model/wire/test_anthropic_tool_result_block.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/tests/model", + "path": "../runtime/python/prompty/tests/model/wire/test_anthropic_tool_use_block.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/tests/model", + "path": "../runtime/python/prompty/tests/model/wire/test_anthropic_usage.py", + "marker": true + }, + { + "outputRoot": "../runtime/python/prompty/tests/model", + "path": "../runtime/python/prompty/tests/model/wire/test_anthropic_wire_message.py", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/src/model", + "path": "../runtime/rust/prompty/src/model/agent/guardrail_result.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/src/model", + "path": "../runtime/rust/prompty/src/model/agent/mod.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/src/model", + "path": "../runtime/rust/prompty/src/model/agent/prompty.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/src/model", + "path": "../runtime/rust/prompty/src/model/connection/connection.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/src/model", + "path": "../runtime/rust/prompty/src/model/connection/mod.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/src/model", + "path": "../runtime/rust/prompty/src/model/context.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/src/model", + "path": "../runtime/rust/prompty/src/model/conversation/content_part.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/src/model", + "path": "../runtime/rust/prompty/src/model/conversation/message.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/src/model", + "path": "../runtime/rust/prompty/src/model/conversation/mod.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/src/model", + "path": "../runtime/rust/prompty/src/model/conversation/thread_marker.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/src/model", + "path": "../runtime/rust/prompty/src/model/conversation/tool_call.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/src/model", + "path": "../runtime/rust/prompty/src/model/conversation/tool_result.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/src/model", + "path": "../runtime/rust/prompty/src/model/core/file_not_found_error.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/src/model", + "path": "../runtime/rust/prompty/src/model/core/invoker_error.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/src/model", + "path": "../runtime/rust/prompty/src/model/core/mod.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/src/model", + "path": "../runtime/rust/prompty/src/model/core/property.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/src/model", + "path": "../runtime/rust/prompty/src/model/core/validation_error.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/src/model", + "path": "../runtime/rust/prompty/src/model/core/validation_result.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/src/model", + "path": "../runtime/rust/prompty/src/model/events/checkpoint.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/src/model", + "path": "../runtime/rust/prompty/src/model/events/compaction_complete_payload.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/src/model", + "path": "../runtime/rust/prompty/src/model/events/compaction_failed_payload.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/src/model", + "path": "../runtime/rust/prompty/src/model/events/compaction_start_payload.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/src/model", + "path": "../runtime/rust/prompty/src/model/events/done_event_payload.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/src/model", + "path": "../runtime/rust/prompty/src/model/events/error_event_payload.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/src/model", + "path": "../runtime/rust/prompty/src/model/events/harness_context.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/src/model", + "path": "../runtime/rust/prompty/src/model/events/hook_end_payload.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/src/model", + "path": "../runtime/rust/prompty/src/model/events/hook_start_payload.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/src/model", + "path": "../runtime/rust/prompty/src/model/events/host_tool_request.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/src/model", + "path": "../runtime/rust/prompty/src/model/events/host_tool_result.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/src/model", + "path": "../runtime/rust/prompty/src/model/events/llm_complete_payload.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/src/model", + "path": "../runtime/rust/prompty/src/model/events/llm_start_payload.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/src/model", + "path": "../runtime/rust/prompty/src/model/events/messages_updated_payload.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/src/model", + "path": "../runtime/rust/prompty/src/model/events/mod.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/src/model", + "path": "../runtime/rust/prompty/src/model/events/permission_completed_payload.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/src/model", + "path": "../runtime/rust/prompty/src/model/events/permission_decision.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/src/model", + "path": "../runtime/rust/prompty/src/model/events/permission_request.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/src/model", + "path": "../runtime/rust/prompty/src/model/events/permission_requested_payload.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/src/model", + "path": "../runtime/rust/prompty/src/model/events/redacted_field.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/src/model", + "path": "../runtime/rust/prompty/src/model/events/redaction_metadata.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/src/model", + "path": "../runtime/rust/prompty/src/model/events/retry_payload.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/src/model", + "path": "../runtime/rust/prompty/src/model/events/session_end_payload.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/src/model", + "path": "../runtime/rust/prompty/src/model/events/session_event.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/src/model", + "path": "../runtime/rust/prompty/src/model/events/session_file_ref.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/src/model", + "path": "../runtime/rust/prompty/src/model/events/session_ref.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/src/model", + "path": "../runtime/rust/prompty/src/model/events/session_start_payload.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/src/model", + "path": "../runtime/rust/prompty/src/model/events/session_summary.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/src/model", + "path": "../runtime/rust/prompty/src/model/events/session_trace.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/src/model", + "path": "../runtime/rust/prompty/src/model/events/session_warning_payload.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/src/model", + "path": "../runtime/rust/prompty/src/model/events/status_event_payload.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/src/model", + "path": "../runtime/rust/prompty/src/model/events/stream_chunk.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/src/model", + "path": "../runtime/rust/prompty/src/model/events/thinking_event_payload.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/src/model", + "path": "../runtime/rust/prompty/src/model/events/token_event_payload.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/src/model", + "path": "../runtime/rust/prompty/src/model/events/tool_call_complete_payload.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/src/model", + "path": "../runtime/rust/prompty/src/model/events/tool_call_start_payload.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/src/model", + "path": "../runtime/rust/prompty/src/model/events/tool_execution_complete_payload.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/src/model", + "path": "../runtime/rust/prompty/src/model/events/tool_execution_start_payload.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/src/model", + "path": "../runtime/rust/prompty/src/model/events/tool_result_payload.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/src/model", + "path": "../runtime/rust/prompty/src/model/events/trajectory_event.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/src/model", + "path": "../runtime/rust/prompty/src/model/events/turn_end_payload.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/src/model", + "path": "../runtime/rust/prompty/src/model/events/turn_event.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/src/model", + "path": "../runtime/rust/prompty/src/model/events/turn_start_payload.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/src/model", + "path": "../runtime/rust/prompty/src/model/events/turn_summary.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/src/model", + "path": "../runtime/rust/prompty/src/model/events/turn_trace.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/src/model", + "path": "../runtime/rust/prompty/src/model/mod.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/src/model", + "path": "../runtime/rust/prompty/src/model/model/mod.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/src/model", + "path": "../runtime/rust/prompty/src/model/model/model_info.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/src/model", + "path": "../runtime/rust/prompty/src/model/model/model_options.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/src/model", + "path": "../runtime/rust/prompty/src/model/model/model.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/src/model", + "path": "../runtime/rust/prompty/src/model/model/token_usage.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/src/model", + "path": "../runtime/rust/prompty/src/model/pipeline/checkpoint_store.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/src/model", + "path": "../runtime/rust/prompty/src/model/pipeline/compaction_config.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/src/model", + "path": "../runtime/rust/prompty/src/model/pipeline/event_journal_writer.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/src/model", + "path": "../runtime/rust/prompty/src/model/pipeline/event_sink.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/src/model", + "path": "../runtime/rust/prompty/src/model/pipeline/executor.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/src/model", + "path": "../runtime/rust/prompty/src/model/pipeline/host_tool_executor.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/src/model", + "path": "../runtime/rust/prompty/src/model/pipeline/mod.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/src/model", + "path": "../runtime/rust/prompty/src/model/pipeline/parser.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/src/model", + "path": "../runtime/rust/prompty/src/model/pipeline/permission_resolver.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/src/model", + "path": "../runtime/rust/prompty/src/model/pipeline/processor.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/src/model", + "path": "../runtime/rust/prompty/src/model/pipeline/renderer.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/src/model", + "path": "../runtime/rust/prompty/src/model/pipeline/replay_journal_record.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/src/model", + "path": "../runtime/rust/prompty/src/model/pipeline/replay_mismatch.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/src/model", + "path": "../runtime/rust/prompty/src/model/pipeline/replay_verification_request.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/src/model", + "path": "../runtime/rust/prompty/src/model/pipeline/replay_verification_result.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/src/model", + "path": "../runtime/rust/prompty/src/model/pipeline/run_turn_request.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/src/model", + "path": "../runtime/rust/prompty/src/model/pipeline/run_turn_result.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/src/model", + "path": "../runtime/rust/prompty/src/model/pipeline/turn_model_request.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/src/model", + "path": "../runtime/rust/prompty/src/model/pipeline/turn_model_response.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/src/model", + "path": "../runtime/rust/prompty/src/model/pipeline/turn_options.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/src/model", + "path": "../runtime/rust/prompty/src/model/streaming/mod.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/src/model", + "path": "../runtime/rust/prompty/src/model/streaming/stream_options.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/src/model", + "path": "../runtime/rust/prompty/src/model/template/format_config.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/src/model", + "path": "../runtime/rust/prompty/src/model/template/mod.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/src/model", + "path": "../runtime/rust/prompty/src/model/template/parser_config.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/src/model", + "path": "../runtime/rust/prompty/src/model/template/template.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/src/model", + "path": "../runtime/rust/prompty/src/model/tools/binding.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/src/model", + "path": "../runtime/rust/prompty/src/model/tools/mcp_approval_mode.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/src/model", + "path": "../runtime/rust/prompty/src/model/tools/mod.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/src/model", + "path": "../runtime/rust/prompty/src/model/tools/tool_context.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/src/model", + "path": "../runtime/rust/prompty/src/model/tools/tool_dispatch_result.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/src/model", + "path": "../runtime/rust/prompty/src/model/tools/tool.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/src/model", + "path": "../runtime/rust/prompty/src/model/tracing/mod.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/src/model", + "path": "../runtime/rust/prompty/src/model/tracing/trace_file.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/src/model", + "path": "../runtime/rust/prompty/src/model/tracing/trace_span.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/src/model", + "path": "../runtime/rust/prompty/src/model/tracing/trace_time.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/src/model", + "path": "../runtime/rust/prompty/src/model/wire/anthropic_image_block.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/src/model", + "path": "../runtime/rust/prompty/src/model/wire/anthropic_image_source.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/src/model", + "path": "../runtime/rust/prompty/src/model/wire/anthropic_messages_request.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/src/model", + "path": "../runtime/rust/prompty/src/model/wire/anthropic_messages_response.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/src/model", + "path": "../runtime/rust/prompty/src/model/wire/anthropic_text_block.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/src/model", + "path": "../runtime/rust/prompty/src/model/wire/anthropic_tool_definition.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/src/model", + "path": "../runtime/rust/prompty/src/model/wire/anthropic_tool_result_block.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/src/model", + "path": "../runtime/rust/prompty/src/model/wire/anthropic_tool_use_block.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/src/model", + "path": "../runtime/rust/prompty/src/model/wire/anthropic_usage.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/src/model", + "path": "../runtime/rust/prompty/src/model/wire/anthropic_wire_message.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/src/model", + "path": "../runtime/rust/prompty/src/model/wire/mod.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/tests/model", + "path": "../runtime/rust/prompty/tests/model/agent/guardrail_result_test.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/tests/model", + "path": "../runtime/rust/prompty/tests/model/agent/mod.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/tests/model", + "path": "../runtime/rust/prompty/tests/model/agent/prompty_test.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/tests/model", + "path": "../runtime/rust/prompty/tests/model/connection/connection_test.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/tests/model", + "path": "../runtime/rust/prompty/tests/model/connection/mod.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/tests/model", + "path": "../runtime/rust/prompty/tests/model/conversation/content_part_test.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/tests/model", + "path": "../runtime/rust/prompty/tests/model/conversation/message_test.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/tests/model", + "path": "../runtime/rust/prompty/tests/model/conversation/mod.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/tests/model", + "path": "../runtime/rust/prompty/tests/model/conversation/thread_marker_test.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/tests/model", + "path": "../runtime/rust/prompty/tests/model/conversation/tool_call_test.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/tests/model", + "path": "../runtime/rust/prompty/tests/model/conversation/tool_result_test.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/tests/model", + "path": "../runtime/rust/prompty/tests/model/core/file_not_found_error_test.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/tests/model", + "path": "../runtime/rust/prompty/tests/model/core/invoker_error_test.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/tests/model", + "path": "../runtime/rust/prompty/tests/model/core/mod.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/tests/model", + "path": "../runtime/rust/prompty/tests/model/core/property_test.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/tests/model", + "path": "../runtime/rust/prompty/tests/model/core/validation_error_test.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/tests/model", + "path": "../runtime/rust/prompty/tests/model/core/validation_result_test.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/tests/model", + "path": "../runtime/rust/prompty/tests/model/events/checkpoint_test.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/tests/model", + "path": "../runtime/rust/prompty/tests/model/events/compaction_complete_payload_test.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/tests/model", + "path": "../runtime/rust/prompty/tests/model/events/compaction_failed_payload_test.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/tests/model", + "path": "../runtime/rust/prompty/tests/model/events/compaction_start_payload_test.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/tests/model", + "path": "../runtime/rust/prompty/tests/model/events/done_event_payload_test.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/tests/model", + "path": "../runtime/rust/prompty/tests/model/events/error_event_payload_test.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/tests/model", + "path": "../runtime/rust/prompty/tests/model/events/harness_context_test.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/tests/model", + "path": "../runtime/rust/prompty/tests/model/events/hook_end_payload_test.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/tests/model", + "path": "../runtime/rust/prompty/tests/model/events/hook_start_payload_test.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/tests/model", + "path": "../runtime/rust/prompty/tests/model/events/host_tool_request_test.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/tests/model", + "path": "../runtime/rust/prompty/tests/model/events/host_tool_result_test.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/tests/model", + "path": "../runtime/rust/prompty/tests/model/events/llm_complete_payload_test.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/tests/model", + "path": "../runtime/rust/prompty/tests/model/events/llm_start_payload_test.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/tests/model", + "path": "../runtime/rust/prompty/tests/model/events/messages_updated_payload_test.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/tests/model", + "path": "../runtime/rust/prompty/tests/model/events/mod.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/tests/model", + "path": "../runtime/rust/prompty/tests/model/events/permission_completed_payload_test.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/tests/model", + "path": "../runtime/rust/prompty/tests/model/events/permission_decision_test.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/tests/model", + "path": "../runtime/rust/prompty/tests/model/events/permission_request_test.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/tests/model", + "path": "../runtime/rust/prompty/tests/model/events/permission_requested_payload_test.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/tests/model", + "path": "../runtime/rust/prompty/tests/model/events/redacted_field_test.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/tests/model", + "path": "../runtime/rust/prompty/tests/model/events/redaction_metadata_test.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/tests/model", + "path": "../runtime/rust/prompty/tests/model/events/retry_payload_test.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/tests/model", + "path": "../runtime/rust/prompty/tests/model/events/session_end_payload_test.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/tests/model", + "path": "../runtime/rust/prompty/tests/model/events/session_event_test.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/tests/model", + "path": "../runtime/rust/prompty/tests/model/events/session_file_ref_test.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/tests/model", + "path": "../runtime/rust/prompty/tests/model/events/session_ref_test.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/tests/model", + "path": "../runtime/rust/prompty/tests/model/events/session_start_payload_test.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/tests/model", + "path": "../runtime/rust/prompty/tests/model/events/session_summary_test.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/tests/model", + "path": "../runtime/rust/prompty/tests/model/events/session_trace_test.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/tests/model", + "path": "../runtime/rust/prompty/tests/model/events/session_warning_payload_test.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/tests/model", + "path": "../runtime/rust/prompty/tests/model/events/status_event_payload_test.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/tests/model", + "path": "../runtime/rust/prompty/tests/model/events/stream_chunk_test.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/tests/model", + "path": "../runtime/rust/prompty/tests/model/events/thinking_event_payload_test.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/tests/model", + "path": "../runtime/rust/prompty/tests/model/events/token_event_payload_test.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/tests/model", + "path": "../runtime/rust/prompty/tests/model/events/tool_call_complete_payload_test.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/tests/model", + "path": "../runtime/rust/prompty/tests/model/events/tool_call_start_payload_test.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/tests/model", + "path": "../runtime/rust/prompty/tests/model/events/tool_execution_complete_payload_test.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/tests/model", + "path": "../runtime/rust/prompty/tests/model/events/tool_execution_start_payload_test.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/tests/model", + "path": "../runtime/rust/prompty/tests/model/events/tool_result_payload_test.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/tests/model", + "path": "../runtime/rust/prompty/tests/model/events/trajectory_event_test.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/tests/model", + "path": "../runtime/rust/prompty/tests/model/events/turn_end_payload_test.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/tests/model", + "path": "../runtime/rust/prompty/tests/model/events/turn_event_test.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/tests/model", + "path": "../runtime/rust/prompty/tests/model/events/turn_start_payload_test.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/tests/model", + "path": "../runtime/rust/prompty/tests/model/events/turn_summary_test.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/tests/model", + "path": "../runtime/rust/prompty/tests/model/events/turn_trace_test.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/tests/model", + "path": "../runtime/rust/prompty/tests/model/main.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/tests/model", + "path": "../runtime/rust/prompty/tests/model/model/mod.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/tests/model", + "path": "../runtime/rust/prompty/tests/model/model/model_info_test.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/tests/model", + "path": "../runtime/rust/prompty/tests/model/model/model_options_test.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/tests/model", + "path": "../runtime/rust/prompty/tests/model/model/model_test.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/tests/model", + "path": "../runtime/rust/prompty/tests/model/model/token_usage_test.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/tests/model", + "path": "../runtime/rust/prompty/tests/model/pipeline/compaction_config_test.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/tests/model", + "path": "../runtime/rust/prompty/tests/model/pipeline/mod.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/tests/model", + "path": "../runtime/rust/prompty/tests/model/pipeline/replay_journal_record_test.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/tests/model", + "path": "../runtime/rust/prompty/tests/model/pipeline/replay_mismatch_test.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/tests/model", + "path": "../runtime/rust/prompty/tests/model/pipeline/replay_verification_request_test.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/tests/model", + "path": "../runtime/rust/prompty/tests/model/pipeline/replay_verification_result_test.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/tests/model", + "path": "../runtime/rust/prompty/tests/model/pipeline/run_turn_request_test.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/tests/model", + "path": "../runtime/rust/prompty/tests/model/pipeline/run_turn_result_test.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/tests/model", + "path": "../runtime/rust/prompty/tests/model/pipeline/turn_model_request_test.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/tests/model", + "path": "../runtime/rust/prompty/tests/model/pipeline/turn_model_response_test.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/tests/model", + "path": "../runtime/rust/prompty/tests/model/pipeline/turn_options_test.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/tests/model", + "path": "../runtime/rust/prompty/tests/model/streaming/mod.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/tests/model", + "path": "../runtime/rust/prompty/tests/model/streaming/stream_options_test.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/tests/model", + "path": "../runtime/rust/prompty/tests/model/template/format_config_test.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/tests/model", + "path": "../runtime/rust/prompty/tests/model/template/mod.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/tests/model", + "path": "../runtime/rust/prompty/tests/model/template/parser_config_test.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/tests/model", + "path": "../runtime/rust/prompty/tests/model/template/template_test.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/tests/model", + "path": "../runtime/rust/prompty/tests/model/tools/binding_test.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/tests/model", + "path": "../runtime/rust/prompty/tests/model/tools/mcp_approval_mode_test.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/tests/model", + "path": "../runtime/rust/prompty/tests/model/tools/mod.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/tests/model", + "path": "../runtime/rust/prompty/tests/model/tools/tool_context_test.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/tests/model", + "path": "../runtime/rust/prompty/tests/model/tools/tool_dispatch_result_test.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/tests/model", + "path": "../runtime/rust/prompty/tests/model/tools/tool_test.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/tests/model", + "path": "../runtime/rust/prompty/tests/model/tracing/mod.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/tests/model", + "path": "../runtime/rust/prompty/tests/model/tracing/trace_file_test.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/tests/model", + "path": "../runtime/rust/prompty/tests/model/tracing/trace_span_test.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/tests/model", + "path": "../runtime/rust/prompty/tests/model/tracing/trace_time_test.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/tests/model", + "path": "../runtime/rust/prompty/tests/model/wire/anthropic_image_block_test.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/tests/model", + "path": "../runtime/rust/prompty/tests/model/wire/anthropic_image_source_test.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/tests/model", + "path": "../runtime/rust/prompty/tests/model/wire/anthropic_messages_request_test.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/tests/model", + "path": "../runtime/rust/prompty/tests/model/wire/anthropic_messages_response_test.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/tests/model", + "path": "../runtime/rust/prompty/tests/model/wire/anthropic_text_block_test.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/tests/model", + "path": "../runtime/rust/prompty/tests/model/wire/anthropic_tool_definition_test.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/tests/model", + "path": "../runtime/rust/prompty/tests/model/wire/anthropic_tool_result_block_test.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/tests/model", + "path": "../runtime/rust/prompty/tests/model/wire/anthropic_tool_use_block_test.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/tests/model", + "path": "../runtime/rust/prompty/tests/model/wire/anthropic_usage_test.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/tests/model", + "path": "../runtime/rust/prompty/tests/model/wire/anthropic_wire_message_test.rs", + "marker": true + }, + { + "outputRoot": "../runtime/rust/prompty/tests/model", + "path": "../runtime/rust/prompty/tests/model/wire/mod.rs", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/src", + "path": "../runtime/typescript/packages/core/src/eslint.config.js", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/src/model", + "path": "../runtime/typescript/packages/core/src/model/agent/guardrail-result.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/src/model", + "path": "../runtime/typescript/packages/core/src/model/agent/index.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/src/model", + "path": "../runtime/typescript/packages/core/src/model/agent/prompty.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/src/model", + "path": "../runtime/typescript/packages/core/src/model/connection/connection.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/src/model", + "path": "../runtime/typescript/packages/core/src/model/connection/index.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/src/model", + "path": "../runtime/typescript/packages/core/src/model/context.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/src/model", + "path": "../runtime/typescript/packages/core/src/model/conversation/content-part.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/src/model", + "path": "../runtime/typescript/packages/core/src/model/conversation/index.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/src/model", + "path": "../runtime/typescript/packages/core/src/model/conversation/message.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/src/model", + "path": "../runtime/typescript/packages/core/src/model/conversation/thread-marker.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/src/model", + "path": "../runtime/typescript/packages/core/src/model/conversation/tool-call.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/src/model", + "path": "../runtime/typescript/packages/core/src/model/conversation/tool-result.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/src/model", + "path": "../runtime/typescript/packages/core/src/model/core/file-not-found-error.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/src/model", + "path": "../runtime/typescript/packages/core/src/model/core/index.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/src/model", + "path": "../runtime/typescript/packages/core/src/model/core/invoker-error.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/src/model", + "path": "../runtime/typescript/packages/core/src/model/core/property.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/src/model", + "path": "../runtime/typescript/packages/core/src/model/core/validation-error.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/src/model", + "path": "../runtime/typescript/packages/core/src/model/core/validation-result.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/src/model", + "path": "../runtime/typescript/packages/core/src/model/events/checkpoint.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/src/model", + "path": "../runtime/typescript/packages/core/src/model/events/compaction-complete-payload.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/src/model", + "path": "../runtime/typescript/packages/core/src/model/events/compaction-failed-payload.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/src/model", + "path": "../runtime/typescript/packages/core/src/model/events/compaction-start-payload.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/src/model", + "path": "../runtime/typescript/packages/core/src/model/events/done-event-payload.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/src/model", + "path": "../runtime/typescript/packages/core/src/model/events/error-event-payload.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/src/model", + "path": "../runtime/typescript/packages/core/src/model/events/harness-context.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/src/model", + "path": "../runtime/typescript/packages/core/src/model/events/hook-end-payload.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/src/model", + "path": "../runtime/typescript/packages/core/src/model/events/hook-start-payload.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/src/model", + "path": "../runtime/typescript/packages/core/src/model/events/host-tool-request.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/src/model", + "path": "../runtime/typescript/packages/core/src/model/events/host-tool-result.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/src/model", + "path": "../runtime/typescript/packages/core/src/model/events/index.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/src/model", + "path": "../runtime/typescript/packages/core/src/model/events/llm-complete-payload.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/src/model", + "path": "../runtime/typescript/packages/core/src/model/events/llm-start-payload.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/src/model", + "path": "../runtime/typescript/packages/core/src/model/events/messages-updated-payload.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/src/model", + "path": "../runtime/typescript/packages/core/src/model/events/permission-completed-payload.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/src/model", + "path": "../runtime/typescript/packages/core/src/model/events/permission-decision.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/src/model", + "path": "../runtime/typescript/packages/core/src/model/events/permission-request.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/src/model", + "path": "../runtime/typescript/packages/core/src/model/events/permission-requested-payload.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/src/model", + "path": "../runtime/typescript/packages/core/src/model/events/redacted-field.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/src/model", + "path": "../runtime/typescript/packages/core/src/model/events/redaction-metadata.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/src/model", + "path": "../runtime/typescript/packages/core/src/model/events/retry-payload.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/src/model", + "path": "../runtime/typescript/packages/core/src/model/events/session-end-payload.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/src/model", + "path": "../runtime/typescript/packages/core/src/model/events/session-event.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/src/model", + "path": "../runtime/typescript/packages/core/src/model/events/session-file-ref.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/src/model", + "path": "../runtime/typescript/packages/core/src/model/events/session-ref.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/src/model", + "path": "../runtime/typescript/packages/core/src/model/events/session-start-payload.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/src/model", + "path": "../runtime/typescript/packages/core/src/model/events/session-summary.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/src/model", + "path": "../runtime/typescript/packages/core/src/model/events/session-trace.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/src/model", + "path": "../runtime/typescript/packages/core/src/model/events/session-warning-payload.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/src/model", + "path": "../runtime/typescript/packages/core/src/model/events/status-event-payload.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/src/model", + "path": "../runtime/typescript/packages/core/src/model/events/stream-chunk.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/src/model", + "path": "../runtime/typescript/packages/core/src/model/events/thinking-event-payload.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/src/model", + "path": "../runtime/typescript/packages/core/src/model/events/token-event-payload.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/src/model", + "path": "../runtime/typescript/packages/core/src/model/events/tool-call-complete-payload.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/src/model", + "path": "../runtime/typescript/packages/core/src/model/events/tool-call-start-payload.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/src/model", + "path": "../runtime/typescript/packages/core/src/model/events/tool-execution-complete-payload.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/src/model", + "path": "../runtime/typescript/packages/core/src/model/events/tool-execution-start-payload.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/src/model", + "path": "../runtime/typescript/packages/core/src/model/events/tool-result-payload.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/src/model", + "path": "../runtime/typescript/packages/core/src/model/events/trajectory-event.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/src/model", + "path": "../runtime/typescript/packages/core/src/model/events/turn-end-payload.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/src/model", + "path": "../runtime/typescript/packages/core/src/model/events/turn-event.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/src/model", + "path": "../runtime/typescript/packages/core/src/model/events/turn-start-payload.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/src/model", + "path": "../runtime/typescript/packages/core/src/model/events/turn-summary.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/src/model", + "path": "../runtime/typescript/packages/core/src/model/events/turn-trace.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/src/model", + "path": "../runtime/typescript/packages/core/src/model/index.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/src/model", + "path": "../runtime/typescript/packages/core/src/model/model/index.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/src/model", + "path": "../runtime/typescript/packages/core/src/model/model/model-info.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/src/model", + "path": "../runtime/typescript/packages/core/src/model/model/model-options.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/src/model", + "path": "../runtime/typescript/packages/core/src/model/model/model.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/src/model", + "path": "../runtime/typescript/packages/core/src/model/model/token-usage.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/src/model", + "path": "../runtime/typescript/packages/core/src/model/pipeline/checkpoint-store.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/src/model", + "path": "../runtime/typescript/packages/core/src/model/pipeline/compaction-config.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/src/model", + "path": "../runtime/typescript/packages/core/src/model/pipeline/event-journal-writer.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/src/model", + "path": "../runtime/typescript/packages/core/src/model/pipeline/event-sink.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/src/model", + "path": "../runtime/typescript/packages/core/src/model/pipeline/executor.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/src/model", + "path": "../runtime/typescript/packages/core/src/model/pipeline/host-tool-executor.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/src/model", + "path": "../runtime/typescript/packages/core/src/model/pipeline/index.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/src/model", + "path": "../runtime/typescript/packages/core/src/model/pipeline/parser.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/src/model", + "path": "../runtime/typescript/packages/core/src/model/pipeline/permission-resolver.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/src/model", + "path": "../runtime/typescript/packages/core/src/model/pipeline/processor.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/src/model", + "path": "../runtime/typescript/packages/core/src/model/pipeline/renderer.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/src/model", + "path": "../runtime/typescript/packages/core/src/model/pipeline/replay-journal-record.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/src/model", + "path": "../runtime/typescript/packages/core/src/model/pipeline/replay-mismatch.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/src/model", + "path": "../runtime/typescript/packages/core/src/model/pipeline/replay-verification-request.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/src/model", + "path": "../runtime/typescript/packages/core/src/model/pipeline/replay-verification-result.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/src/model", + "path": "../runtime/typescript/packages/core/src/model/pipeline/run-turn-request.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/src/model", + "path": "../runtime/typescript/packages/core/src/model/pipeline/run-turn-result.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/src/model", + "path": "../runtime/typescript/packages/core/src/model/pipeline/turn-model-request.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/src/model", + "path": "../runtime/typescript/packages/core/src/model/pipeline/turn-model-response.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/src/model", + "path": "../runtime/typescript/packages/core/src/model/pipeline/turn-options.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/src/model", + "path": "../runtime/typescript/packages/core/src/model/streaming/index.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/src/model", + "path": "../runtime/typescript/packages/core/src/model/streaming/stream-options.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/src/model", + "path": "../runtime/typescript/packages/core/src/model/template/format-config.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/src/model", + "path": "../runtime/typescript/packages/core/src/model/template/index.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/src/model", + "path": "../runtime/typescript/packages/core/src/model/template/parser-config.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/src/model", + "path": "../runtime/typescript/packages/core/src/model/template/template.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/src/model", + "path": "../runtime/typescript/packages/core/src/model/tools/binding.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/src/model", + "path": "../runtime/typescript/packages/core/src/model/tools/index.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/src/model", + "path": "../runtime/typescript/packages/core/src/model/tools/mcp-approval-mode.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/src/model", + "path": "../runtime/typescript/packages/core/src/model/tools/tool-context.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/src/model", + "path": "../runtime/typescript/packages/core/src/model/tools/tool-dispatch-result.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/src/model", + "path": "../runtime/typescript/packages/core/src/model/tools/tool.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/src/model", + "path": "../runtime/typescript/packages/core/src/model/tracing/index.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/src/model", + "path": "../runtime/typescript/packages/core/src/model/tracing/trace-file.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/src/model", + "path": "../runtime/typescript/packages/core/src/model/tracing/trace-span.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/src/model", + "path": "../runtime/typescript/packages/core/src/model/tracing/trace-time.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/src/model", + "path": "../runtime/typescript/packages/core/src/model/wire/anthropic-image-block.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/src/model", + "path": "../runtime/typescript/packages/core/src/model/wire/anthropic-image-source.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/src/model", + "path": "../runtime/typescript/packages/core/src/model/wire/anthropic-messages-request.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/src/model", + "path": "../runtime/typescript/packages/core/src/model/wire/anthropic-messages-response.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/src/model", + "path": "../runtime/typescript/packages/core/src/model/wire/anthropic-text-block.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/src/model", + "path": "../runtime/typescript/packages/core/src/model/wire/anthropic-tool-definition.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/src/model", + "path": "../runtime/typescript/packages/core/src/model/wire/anthropic-tool-result-block.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/src/model", + "path": "../runtime/typescript/packages/core/src/model/wire/anthropic-tool-use-block.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/src/model", + "path": "../runtime/typescript/packages/core/src/model/wire/anthropic-usage.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/src/model", + "path": "../runtime/typescript/packages/core/src/model/wire/anthropic-wire-message.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/src/model", + "path": "../runtime/typescript/packages/core/src/model/wire/index.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/tests/model", + "path": "../runtime/typescript/packages/core/tests/model/agent/guardrail-result.test.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/tests/model", + "path": "../runtime/typescript/packages/core/tests/model/agent/prompty.test.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/tests/model", + "path": "../runtime/typescript/packages/core/tests/model/connection/anonymous-connection.test.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/tests/model", + "path": "../runtime/typescript/packages/core/tests/model/connection/api-key-connection.test.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/tests/model", + "path": "../runtime/typescript/packages/core/tests/model/connection/connection.test.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/tests/model", + "path": "../runtime/typescript/packages/core/tests/model/connection/foundry-connection.test.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/tests/model", + "path": "../runtime/typescript/packages/core/tests/model/connection/o-auth-connection.test.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/tests/model", + "path": "../runtime/typescript/packages/core/tests/model/connection/reference-connection.test.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/tests/model", + "path": "../runtime/typescript/packages/core/tests/model/connection/remote-connection.test.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/tests/model", + "path": "../runtime/typescript/packages/core/tests/model/conversation/audio-part.test.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/tests/model", + "path": "../runtime/typescript/packages/core/tests/model/conversation/content-part.test.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/tests/model", + "path": "../runtime/typescript/packages/core/tests/model/conversation/file-part.test.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/tests/model", + "path": "../runtime/typescript/packages/core/tests/model/conversation/image-part.test.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/tests/model", + "path": "../runtime/typescript/packages/core/tests/model/conversation/message.test.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/tests/model", + "path": "../runtime/typescript/packages/core/tests/model/conversation/text-part.test.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/tests/model", + "path": "../runtime/typescript/packages/core/tests/model/conversation/thread-marker.test.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/tests/model", + "path": "../runtime/typescript/packages/core/tests/model/conversation/tool-call.test.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/tests/model", + "path": "../runtime/typescript/packages/core/tests/model/conversation/tool-result.test.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/tests/model", + "path": "../runtime/typescript/packages/core/tests/model/core/array-property.test.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/tests/model", + "path": "../runtime/typescript/packages/core/tests/model/core/file-not-found-error.test.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/tests/model", + "path": "../runtime/typescript/packages/core/tests/model/core/invoker-error.test.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/tests/model", + "path": "../runtime/typescript/packages/core/tests/model/core/object-property.test.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/tests/model", + "path": "../runtime/typescript/packages/core/tests/model/core/property.test.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/tests/model", + "path": "../runtime/typescript/packages/core/tests/model/core/validation-error.test.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/tests/model", + "path": "../runtime/typescript/packages/core/tests/model/core/validation-result.test.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/tests/model", + "path": "../runtime/typescript/packages/core/tests/model/events/checkpoint.test.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/tests/model", + "path": "../runtime/typescript/packages/core/tests/model/events/compaction-complete-payload.test.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/tests/model", + "path": "../runtime/typescript/packages/core/tests/model/events/compaction-failed-payload.test.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/tests/model", + "path": "../runtime/typescript/packages/core/tests/model/events/compaction-start-payload.test.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/tests/model", + "path": "../runtime/typescript/packages/core/tests/model/events/done-event-payload.test.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/tests/model", + "path": "../runtime/typescript/packages/core/tests/model/events/error-chunk.test.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/tests/model", + "path": "../runtime/typescript/packages/core/tests/model/events/error-event-payload.test.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/tests/model", + "path": "../runtime/typescript/packages/core/tests/model/events/harness-context.test.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/tests/model", + "path": "../runtime/typescript/packages/core/tests/model/events/hook-end-payload.test.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/tests/model", + "path": "../runtime/typescript/packages/core/tests/model/events/hook-start-payload.test.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/tests/model", + "path": "../runtime/typescript/packages/core/tests/model/events/host-tool-request.test.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/tests/model", + "path": "../runtime/typescript/packages/core/tests/model/events/host-tool-result.test.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/tests/model", + "path": "../runtime/typescript/packages/core/tests/model/events/llm-complete-payload.test.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/tests/model", + "path": "../runtime/typescript/packages/core/tests/model/events/llm-start-payload.test.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/tests/model", + "path": "../runtime/typescript/packages/core/tests/model/events/messages-updated-payload.test.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/tests/model", + "path": "../runtime/typescript/packages/core/tests/model/events/permission-completed-payload.test.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/tests/model", + "path": "../runtime/typescript/packages/core/tests/model/events/permission-decision.test.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/tests/model", + "path": "../runtime/typescript/packages/core/tests/model/events/permission-request.test.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/tests/model", + "path": "../runtime/typescript/packages/core/tests/model/events/permission-requested-payload.test.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/tests/model", + "path": "../runtime/typescript/packages/core/tests/model/events/redacted-field.test.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/tests/model", + "path": "../runtime/typescript/packages/core/tests/model/events/redaction-metadata.test.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/tests/model", + "path": "../runtime/typescript/packages/core/tests/model/events/retry-payload.test.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/tests/model", + "path": "../runtime/typescript/packages/core/tests/model/events/session-end-payload.test.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/tests/model", + "path": "../runtime/typescript/packages/core/tests/model/events/session-event.test.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/tests/model", + "path": "../runtime/typescript/packages/core/tests/model/events/session-file-ref.test.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/tests/model", + "path": "../runtime/typescript/packages/core/tests/model/events/session-ref.test.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/tests/model", + "path": "../runtime/typescript/packages/core/tests/model/events/session-start-payload.test.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/tests/model", + "path": "../runtime/typescript/packages/core/tests/model/events/session-summary.test.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/tests/model", + "path": "../runtime/typescript/packages/core/tests/model/events/session-trace.test.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/tests/model", + "path": "../runtime/typescript/packages/core/tests/model/events/session-warning-payload.test.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/tests/model", + "path": "../runtime/typescript/packages/core/tests/model/events/status-event-payload.test.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/tests/model", + "path": "../runtime/typescript/packages/core/tests/model/events/stream-chunk.test.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/tests/model", + "path": "../runtime/typescript/packages/core/tests/model/events/text-chunk.test.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/tests/model", + "path": "../runtime/typescript/packages/core/tests/model/events/thinking-chunk.test.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/tests/model", + "path": "../runtime/typescript/packages/core/tests/model/events/thinking-event-payload.test.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/tests/model", + "path": "../runtime/typescript/packages/core/tests/model/events/token-event-payload.test.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/tests/model", + "path": "../runtime/typescript/packages/core/tests/model/events/tool-call-complete-payload.test.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/tests/model", + "path": "../runtime/typescript/packages/core/tests/model/events/tool-call-start-payload.test.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/tests/model", + "path": "../runtime/typescript/packages/core/tests/model/events/tool-chunk.test.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/tests/model", + "path": "../runtime/typescript/packages/core/tests/model/events/tool-execution-complete-payload.test.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/tests/model", + "path": "../runtime/typescript/packages/core/tests/model/events/tool-execution-start-payload.test.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/tests/model", + "path": "../runtime/typescript/packages/core/tests/model/events/tool-result-payload.test.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/tests/model", + "path": "../runtime/typescript/packages/core/tests/model/events/trajectory-event.test.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/tests/model", + "path": "../runtime/typescript/packages/core/tests/model/events/turn-end-payload.test.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/tests/model", + "path": "../runtime/typescript/packages/core/tests/model/events/turn-event.test.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/tests/model", + "path": "../runtime/typescript/packages/core/tests/model/events/turn-start-payload.test.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/tests/model", + "path": "../runtime/typescript/packages/core/tests/model/events/turn-summary.test.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/tests/model", + "path": "../runtime/typescript/packages/core/tests/model/events/turn-trace.test.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/tests/model", + "path": "../runtime/typescript/packages/core/tests/model/model/model-info.test.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/tests/model", + "path": "../runtime/typescript/packages/core/tests/model/model/model-options.test.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/tests/model", + "path": "../runtime/typescript/packages/core/tests/model/model/model.test.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/tests/model", + "path": "../runtime/typescript/packages/core/tests/model/model/token-usage.test.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/tests/model", + "path": "../runtime/typescript/packages/core/tests/model/pipeline/compaction-config.test.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/tests/model", + "path": "../runtime/typescript/packages/core/tests/model/pipeline/replay-journal-record.test.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/tests/model", + "path": "../runtime/typescript/packages/core/tests/model/pipeline/replay-mismatch.test.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/tests/model", + "path": "../runtime/typescript/packages/core/tests/model/pipeline/replay-verification-request.test.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/tests/model", + "path": "../runtime/typescript/packages/core/tests/model/pipeline/replay-verification-result.test.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/tests/model", + "path": "../runtime/typescript/packages/core/tests/model/pipeline/run-turn-request.test.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/tests/model", + "path": "../runtime/typescript/packages/core/tests/model/pipeline/run-turn-result.test.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/tests/model", + "path": "../runtime/typescript/packages/core/tests/model/pipeline/turn-model-request.test.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/tests/model", + "path": "../runtime/typescript/packages/core/tests/model/pipeline/turn-model-response.test.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/tests/model", + "path": "../runtime/typescript/packages/core/tests/model/pipeline/turn-options.test.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/tests/model", + "path": "../runtime/typescript/packages/core/tests/model/streaming/stream-options.test.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/tests/model", + "path": "../runtime/typescript/packages/core/tests/model/template/format-config.test.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/tests/model", + "path": "../runtime/typescript/packages/core/tests/model/template/parser-config.test.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/tests/model", + "path": "../runtime/typescript/packages/core/tests/model/template/template.test.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/tests/model", + "path": "../runtime/typescript/packages/core/tests/model/tools/binding.test.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/tests/model", + "path": "../runtime/typescript/packages/core/tests/model/tools/custom-tool.test.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/tests/model", + "path": "../runtime/typescript/packages/core/tests/model/tools/function-tool.test.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/tests/model", + "path": "../runtime/typescript/packages/core/tests/model/tools/mcp-approval-mode.test.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/tests/model", + "path": "../runtime/typescript/packages/core/tests/model/tools/mcp-tool.test.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/tests/model", + "path": "../runtime/typescript/packages/core/tests/model/tools/open-api-tool.test.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/tests/model", + "path": "../runtime/typescript/packages/core/tests/model/tools/prompty-tool.test.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/tests/model", + "path": "../runtime/typescript/packages/core/tests/model/tools/tool-context.test.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/tests/model", + "path": "../runtime/typescript/packages/core/tests/model/tools/tool-dispatch-result.test.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/tests/model", + "path": "../runtime/typescript/packages/core/tests/model/tools/tool.test.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/tests/model", + "path": "../runtime/typescript/packages/core/tests/model/tracing/trace-file.test.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/tests/model", + "path": "../runtime/typescript/packages/core/tests/model/tracing/trace-span.test.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/tests/model", + "path": "../runtime/typescript/packages/core/tests/model/tracing/trace-time.test.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/tests/model", + "path": "../runtime/typescript/packages/core/tests/model/wire/anthropic-image-block.test.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/tests/model", + "path": "../runtime/typescript/packages/core/tests/model/wire/anthropic-image-source.test.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/tests/model", + "path": "../runtime/typescript/packages/core/tests/model/wire/anthropic-messages-request.test.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/tests/model", + "path": "../runtime/typescript/packages/core/tests/model/wire/anthropic-messages-response.test.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/tests/model", + "path": "../runtime/typescript/packages/core/tests/model/wire/anthropic-text-block.test.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/tests/model", + "path": "../runtime/typescript/packages/core/tests/model/wire/anthropic-tool-definition.test.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/tests/model", + "path": "../runtime/typescript/packages/core/tests/model/wire/anthropic-tool-result-block.test.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/tests/model", + "path": "../runtime/typescript/packages/core/tests/model/wire/anthropic-tool-use-block.test.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/tests/model", + "path": "../runtime/typescript/packages/core/tests/model/wire/anthropic-usage.test.ts", + "marker": true + }, + { + "outputRoot": "../runtime/typescript/packages/core/tests/model", + "path": "../runtime/typescript/packages/core/tests/model/wire/anthropic-wire-message.test.ts", + "marker": true + }, + { + "outputRoot": "../web/src/content/docs/reference", + "path": "../web/src/content/docs/reference/AnonymousConnection.md", + "marker": true + }, + { + "outputRoot": "../web/src/content/docs/reference", + "path": "../web/src/content/docs/reference/AnthropicImageBlock.md", + "marker": true + }, + { + "outputRoot": "../web/src/content/docs/reference", + "path": "../web/src/content/docs/reference/AnthropicImageSource.md", + "marker": true + }, + { + "outputRoot": "../web/src/content/docs/reference", + "path": "../web/src/content/docs/reference/AnthropicMessagesRequest.md", + "marker": true + }, + { + "outputRoot": "../web/src/content/docs/reference", + "path": "../web/src/content/docs/reference/AnthropicMessagesResponse.md", + "marker": true + }, + { + "outputRoot": "../web/src/content/docs/reference", + "path": "../web/src/content/docs/reference/AnthropicTextBlock.md", + "marker": true + }, + { + "outputRoot": "../web/src/content/docs/reference", + "path": "../web/src/content/docs/reference/AnthropicToolDefinition.md", + "marker": true + }, + { + "outputRoot": "../web/src/content/docs/reference", + "path": "../web/src/content/docs/reference/AnthropicToolResultBlock.md", + "marker": true + }, + { + "outputRoot": "../web/src/content/docs/reference", + "path": "../web/src/content/docs/reference/AnthropicToolUseBlock.md", + "marker": true + }, + { + "outputRoot": "../web/src/content/docs/reference", + "path": "../web/src/content/docs/reference/AnthropicUsage.md", + "marker": true + }, + { + "outputRoot": "../web/src/content/docs/reference", + "path": "../web/src/content/docs/reference/AnthropicWireMessage.md", + "marker": true + }, + { + "outputRoot": "../web/src/content/docs/reference", + "path": "../web/src/content/docs/reference/ApiKeyConnection.md", + "marker": true + }, + { + "outputRoot": "../web/src/content/docs/reference", + "path": "../web/src/content/docs/reference/ArrayProperty.md", + "marker": true + }, + { + "outputRoot": "../web/src/content/docs/reference", + "path": "../web/src/content/docs/reference/AudioPart.md", + "marker": true + }, + { + "outputRoot": "../web/src/content/docs/reference", + "path": "../web/src/content/docs/reference/Binding.md", + "marker": true + }, + { + "outputRoot": "../web/src/content/docs/reference", + "path": "../web/src/content/docs/reference/Checkpoint.md", + "marker": true + }, + { + "outputRoot": "../web/src/content/docs/reference", + "path": "../web/src/content/docs/reference/CheckpointStore.md", + "marker": true + }, + { + "outputRoot": "../web/src/content/docs/reference", + "path": "../web/src/content/docs/reference/CompactionCompletePayload.md", + "marker": true + }, + { + "outputRoot": "../web/src/content/docs/reference", + "path": "../web/src/content/docs/reference/CompactionConfig.md", + "marker": true + }, + { + "outputRoot": "../web/src/content/docs/reference", + "path": "../web/src/content/docs/reference/CompactionFailedPayload.md", + "marker": true + }, + { + "outputRoot": "../web/src/content/docs/reference", + "path": "../web/src/content/docs/reference/CompactionStartPayload.md", + "marker": true + }, + { + "outputRoot": "../web/src/content/docs/reference", + "path": "../web/src/content/docs/reference/Connection.md", + "marker": true + }, + { + "outputRoot": "../web/src/content/docs/reference", + "path": "../web/src/content/docs/reference/ContentPart.md", + "marker": true + }, + { + "outputRoot": "../web/src/content/docs/reference", + "path": "../web/src/content/docs/reference/CustomTool.md", + "marker": true + }, + { + "outputRoot": "../web/src/content/docs/reference", + "path": "../web/src/content/docs/reference/DoneEventPayload.md", + "marker": true + }, + { + "outputRoot": "../web/src/content/docs/reference", + "path": "../web/src/content/docs/reference/ErrorChunk.md", + "marker": true + }, + { + "outputRoot": "../web/src/content/docs/reference", + "path": "../web/src/content/docs/reference/ErrorEventPayload.md", + "marker": true + }, + { + "outputRoot": "../web/src/content/docs/reference", + "path": "../web/src/content/docs/reference/EventJournalWriter.md", + "marker": true + }, + { + "outputRoot": "../web/src/content/docs/reference", + "path": "../web/src/content/docs/reference/EventSink.md", + "marker": true + }, + { + "outputRoot": "../web/src/content/docs/reference", + "path": "../web/src/content/docs/reference/Executor.md", + "marker": true + }, + { + "outputRoot": "../web/src/content/docs/reference", + "path": "../web/src/content/docs/reference/FileNotFoundError.md", + "marker": true + }, + { + "outputRoot": "../web/src/content/docs/reference", + "path": "../web/src/content/docs/reference/FilePart.md", + "marker": true + }, + { + "outputRoot": "../web/src/content/docs/reference", + "path": "../web/src/content/docs/reference/FormatConfig.md", + "marker": true + }, + { + "outputRoot": "../web/src/content/docs/reference", + "path": "../web/src/content/docs/reference/FoundryConnection.md", + "marker": true + }, + { + "outputRoot": "../web/src/content/docs/reference", + "path": "../web/src/content/docs/reference/FunctionTool.md", + "marker": true + }, + { + "outputRoot": "../web/src/content/docs/reference", + "path": "../web/src/content/docs/reference/GuardrailResult.md", + "marker": true + }, + { + "outputRoot": "../web/src/content/docs/reference", + "path": "../web/src/content/docs/reference/HarnessContext.md", + "marker": true + }, + { + "outputRoot": "../web/src/content/docs/reference", + "path": "../web/src/content/docs/reference/HookEndPayload.md", + "marker": true + }, + { + "outputRoot": "../web/src/content/docs/reference", + "path": "../web/src/content/docs/reference/HookStartPayload.md", + "marker": true + }, + { + "outputRoot": "../web/src/content/docs/reference", + "path": "../web/src/content/docs/reference/HostToolExecutor.md", + "marker": true + }, + { + "outputRoot": "../web/src/content/docs/reference", + "path": "../web/src/content/docs/reference/HostToolRequest.md", + "marker": true + }, + { + "outputRoot": "../web/src/content/docs/reference", + "path": "../web/src/content/docs/reference/HostToolResult.md", + "marker": true + }, + { + "outputRoot": "../web/src/content/docs/reference", + "path": "../web/src/content/docs/reference/ImagePart.md", + "marker": true + }, + { + "outputRoot": "../web/src/content/docs/reference", + "path": "../web/src/content/docs/reference/index.md", + "marker": true + }, + { + "outputRoot": "../web/src/content/docs/reference", + "path": "../web/src/content/docs/reference/InvokerError.md", + "marker": true + }, + { + "outputRoot": "../web/src/content/docs/reference", + "path": "../web/src/content/docs/reference/LlmCompletePayload.md", + "marker": true + }, + { + "outputRoot": "../web/src/content/docs/reference", + "path": "../web/src/content/docs/reference/LlmStartPayload.md", + "marker": true + }, + { + "outputRoot": "../web/src/content/docs/reference", + "path": "../web/src/content/docs/reference/McpApprovalMode.md", + "marker": true + }, + { + "outputRoot": "../web/src/content/docs/reference", + "path": "../web/src/content/docs/reference/McpTool.md", + "marker": true + }, + { + "outputRoot": "../web/src/content/docs/reference", + "path": "../web/src/content/docs/reference/Message.md", + "marker": true + }, + { + "outputRoot": "../web/src/content/docs/reference", + "path": "../web/src/content/docs/reference/MessagesUpdatedPayload.md", + "marker": true + }, + { + "outputRoot": "../web/src/content/docs/reference", + "path": "../web/src/content/docs/reference/Model.md", + "marker": true + }, + { + "outputRoot": "../web/src/content/docs/reference", + "path": "../web/src/content/docs/reference/ModelInfo.md", + "marker": true + }, + { + "outputRoot": "../web/src/content/docs/reference", + "path": "../web/src/content/docs/reference/ModelOptions.md", + "marker": true + }, + { + "outputRoot": "../web/src/content/docs/reference", + "path": "../web/src/content/docs/reference/OAuthConnection.md", + "marker": true + }, + { + "outputRoot": "../web/src/content/docs/reference", + "path": "../web/src/content/docs/reference/ObjectProperty.md", + "marker": true + }, + { + "outputRoot": "../web/src/content/docs/reference", + "path": "../web/src/content/docs/reference/OpenApiTool.md", + "marker": true + }, + { + "outputRoot": "../web/src/content/docs/reference", + "path": "../web/src/content/docs/reference/Parser.md", + "marker": true + }, + { + "outputRoot": "../web/src/content/docs/reference", + "path": "../web/src/content/docs/reference/ParserConfig.md", + "marker": true + }, + { + "outputRoot": "../web/src/content/docs/reference", + "path": "../web/src/content/docs/reference/PermissionCompletedPayload.md", + "marker": true + }, + { + "outputRoot": "../web/src/content/docs/reference", + "path": "../web/src/content/docs/reference/PermissionDecision.md", + "marker": true + }, + { + "outputRoot": "../web/src/content/docs/reference", + "path": "../web/src/content/docs/reference/PermissionRequest.md", + "marker": true + }, + { + "outputRoot": "../web/src/content/docs/reference", + "path": "../web/src/content/docs/reference/PermissionRequestedPayload.md", + "marker": true + }, + { + "outputRoot": "../web/src/content/docs/reference", + "path": "../web/src/content/docs/reference/PermissionResolver.md", + "marker": true + }, + { + "outputRoot": "../web/src/content/docs/reference", + "path": "../web/src/content/docs/reference/Processor.md", + "marker": true + }, + { + "outputRoot": "../web/src/content/docs/reference", + "path": "../web/src/content/docs/reference/Prompty.md", + "marker": true + }, + { + "outputRoot": "../web/src/content/docs/reference", + "path": "../web/src/content/docs/reference/PromptyTool.md", + "marker": true + }, + { + "outputRoot": "../web/src/content/docs/reference", + "path": "../web/src/content/docs/reference/Property.md", + "marker": true + }, + { + "outputRoot": "../web/src/content/docs/reference", + "path": "../web/src/content/docs/reference/RedactedField.md", + "marker": true + }, + { + "outputRoot": "../web/src/content/docs/reference", + "path": "../web/src/content/docs/reference/RedactionMetadata.md", + "marker": true + }, + { + "outputRoot": "../web/src/content/docs/reference", + "path": "../web/src/content/docs/reference/ReferenceConnection.md", + "marker": true + }, + { + "outputRoot": "../web/src/content/docs/reference", + "path": "../web/src/content/docs/reference/RemoteConnection.md", + "marker": true + }, + { + "outputRoot": "../web/src/content/docs/reference", + "path": "../web/src/content/docs/reference/Renderer.md", + "marker": true + }, + { + "outputRoot": "../web/src/content/docs/reference", + "path": "../web/src/content/docs/reference/ReplayJournalRecord.md", + "marker": true + }, + { + "outputRoot": "../web/src/content/docs/reference", + "path": "../web/src/content/docs/reference/ReplayMismatch.md", + "marker": true + }, + { + "outputRoot": "../web/src/content/docs/reference", + "path": "../web/src/content/docs/reference/ReplayVerificationRequest.md", + "marker": true + }, + { + "outputRoot": "../web/src/content/docs/reference", + "path": "../web/src/content/docs/reference/ReplayVerificationResult.md", + "marker": true + }, + { + "outputRoot": "../web/src/content/docs/reference", + "path": "../web/src/content/docs/reference/RetryPayload.md", + "marker": true + }, + { + "outputRoot": "../web/src/content/docs/reference", + "path": "../web/src/content/docs/reference/RunTurnRequest.md", + "marker": true + }, + { + "outputRoot": "../web/src/content/docs/reference", + "path": "../web/src/content/docs/reference/RunTurnResult.md", + "marker": true + }, + { + "outputRoot": "../web/src/content/docs/reference", + "path": "../web/src/content/docs/reference/SessionEndPayload.md", + "marker": true + }, + { + "outputRoot": "../web/src/content/docs/reference", + "path": "../web/src/content/docs/reference/SessionEvent.md", + "marker": true + }, + { + "outputRoot": "../web/src/content/docs/reference", + "path": "../web/src/content/docs/reference/SessionFileRef.md", + "marker": true + }, + { + "outputRoot": "../web/src/content/docs/reference", + "path": "../web/src/content/docs/reference/SessionRef.md", + "marker": true + }, + { + "outputRoot": "../web/src/content/docs/reference", + "path": "../web/src/content/docs/reference/SessionStartPayload.md", + "marker": true + }, + { + "outputRoot": "../web/src/content/docs/reference", + "path": "../web/src/content/docs/reference/SessionSummary.md", + "marker": true + }, + { + "outputRoot": "../web/src/content/docs/reference", + "path": "../web/src/content/docs/reference/SessionTrace.md", + "marker": true + }, + { + "outputRoot": "../web/src/content/docs/reference", + "path": "../web/src/content/docs/reference/SessionWarningPayload.md", + "marker": true + }, + { + "outputRoot": "../web/src/content/docs/reference", + "path": "../web/src/content/docs/reference/StatusEventPayload.md", + "marker": true + }, + { + "outputRoot": "../web/src/content/docs/reference", + "path": "../web/src/content/docs/reference/StreamChunk.md", + "marker": true + }, + { + "outputRoot": "../web/src/content/docs/reference", + "path": "../web/src/content/docs/reference/StreamOptions.md", + "marker": true + }, + { + "outputRoot": "../web/src/content/docs/reference", + "path": "../web/src/content/docs/reference/Template.md", + "marker": true + }, + { + "outputRoot": "../web/src/content/docs/reference", + "path": "../web/src/content/docs/reference/TextChunk.md", + "marker": true + }, + { + "outputRoot": "../web/src/content/docs/reference", + "path": "../web/src/content/docs/reference/TextPart.md", + "marker": true + }, + { + "outputRoot": "../web/src/content/docs/reference", + "path": "../web/src/content/docs/reference/ThinkingChunk.md", + "marker": true + }, + { + "outputRoot": "../web/src/content/docs/reference", + "path": "../web/src/content/docs/reference/ThinkingEventPayload.md", + "marker": true + }, + { + "outputRoot": "../web/src/content/docs/reference", + "path": "../web/src/content/docs/reference/ThreadMarker.md", + "marker": true + }, + { + "outputRoot": "../web/src/content/docs/reference", + "path": "../web/src/content/docs/reference/TokenEventPayload.md", + "marker": true + }, + { + "outputRoot": "../web/src/content/docs/reference", + "path": "../web/src/content/docs/reference/TokenUsage.md", + "marker": true + }, + { + "outputRoot": "../web/src/content/docs/reference", + "path": "../web/src/content/docs/reference/Tool.md", + "marker": true + }, + { + "outputRoot": "../web/src/content/docs/reference", + "path": "../web/src/content/docs/reference/ToolCall.md", + "marker": true + }, + { + "outputRoot": "../web/src/content/docs/reference", + "path": "../web/src/content/docs/reference/ToolCallCompletePayload.md", + "marker": true + }, + { + "outputRoot": "../web/src/content/docs/reference", + "path": "../web/src/content/docs/reference/ToolCallStartPayload.md", + "marker": true + }, + { + "outputRoot": "../web/src/content/docs/reference", + "path": "../web/src/content/docs/reference/ToolChunk.md", + "marker": true + }, + { + "outputRoot": "../web/src/content/docs/reference", + "path": "../web/src/content/docs/reference/ToolContext.md", + "marker": true + }, + { + "outputRoot": "../web/src/content/docs/reference", + "path": "../web/src/content/docs/reference/ToolDispatchResult.md", + "marker": true + }, + { + "outputRoot": "../web/src/content/docs/reference", + "path": "../web/src/content/docs/reference/ToolExecutionCompletePayload.md", + "marker": true + }, + { + "outputRoot": "../web/src/content/docs/reference", + "path": "../web/src/content/docs/reference/ToolExecutionStartPayload.md", + "marker": true + }, + { + "outputRoot": "../web/src/content/docs/reference", + "path": "../web/src/content/docs/reference/ToolResult.md", + "marker": true + }, + { + "outputRoot": "../web/src/content/docs/reference", + "path": "../web/src/content/docs/reference/ToolResultPayload.md", + "marker": true + }, + { + "outputRoot": "../web/src/content/docs/reference", + "path": "../web/src/content/docs/reference/TraceFile.md", + "marker": true + }, + { + "outputRoot": "../web/src/content/docs/reference", + "path": "../web/src/content/docs/reference/TraceSpan.md", + "marker": true + }, + { + "outputRoot": "../web/src/content/docs/reference", + "path": "../web/src/content/docs/reference/TraceTime.md", + "marker": true + }, + { + "outputRoot": "../web/src/content/docs/reference", + "path": "../web/src/content/docs/reference/TrajectoryEvent.md", + "marker": true + }, + { + "outputRoot": "../web/src/content/docs/reference", + "path": "../web/src/content/docs/reference/TurnEndPayload.md", + "marker": true + }, + { + "outputRoot": "../web/src/content/docs/reference", + "path": "../web/src/content/docs/reference/TurnEvent.md", + "marker": true + }, + { + "outputRoot": "../web/src/content/docs/reference", + "path": "../web/src/content/docs/reference/TurnModelRequest.md", + "marker": true + }, + { + "outputRoot": "../web/src/content/docs/reference", + "path": "../web/src/content/docs/reference/TurnModelResponse.md", + "marker": true + }, + { + "outputRoot": "../web/src/content/docs/reference", + "path": "../web/src/content/docs/reference/TurnOptions.md", + "marker": true + }, + { + "outputRoot": "../web/src/content/docs/reference", + "path": "../web/src/content/docs/reference/TurnStartPayload.md", + "marker": true + }, + { + "outputRoot": "../web/src/content/docs/reference", + "path": "../web/src/content/docs/reference/TurnSummary.md", + "marker": true + }, + { + "outputRoot": "../web/src/content/docs/reference", + "path": "../web/src/content/docs/reference/TurnTrace.md", + "marker": true + }, + { + "outputRoot": "../web/src/content/docs/reference", + "path": "../web/src/content/docs/reference/ValidationError.md", + "marker": true + }, + { + "outputRoot": "../web/src/content/docs/reference", + "path": "../web/src/content/docs/reference/ValidationResult.md", + "marker": true + }, + { + "outputRoot": "tsp-output/json-ast", + "path": "tsp-output/json-ast/model.json", + "marker": false + } + ] +} diff --git a/schema/tsp-output/json-ast/model.json b/schema/tsp-output/json-ast/model.json new file mode 100644 index 00000000..01930b1c --- /dev/null +++ b/schema/tsp-output/json-ast/model.json @@ -0,0 +1,4018 @@ +{ + "typeName": { + "namespace": "Prompty", + "name": "Prompty" + }, + "description": "A Prompty is a markdown file format for LLM prompts. The frontmatter defines\nstructured metadata including model configuration, input/output schemas, tools,\nand template settings. The markdown body becomes the instructions.\n\nThis is the single root type for the Prompty schema — there is no abstract base\nclass or kind discriminator. A .prompty file always produces a Prompty instance.\n\nRuntime loaders may resolve frontmatter references such as `${env:VAR}` and\n`${file:relative/path}`. File references must be treated as a host-controlled\ncapability: by default they are scoped to the containing .prompty file's\ndirectory tree after canonicalization, and any additional allowed roots must\nbe supplied by the host application's load options rather than frontmatter.", + "base": {}, + "isAbstract": false, + "isProtocol": false, + "coercions": [], + "factories": [], + "methods": [], + "childTypes": [], + "properties": [ + { + "name": "name", + "typeName": { + "namespace": "", + "name": "string" + }, + "description": "Human-readable name of the prompt", + "samples": [ + { + "sample": { + "name": "basic-prompt" + }, + "title": "", + "description": "" + } + ], + "knownAs": [], + "defaultFor": [], + "isScalar": true, + "isOptional": false, + "isCollection": false, + "isAny": false, + "isDict": false, + "defaultValue": "null", + "allowedValues": [], + "enumName": null, + "isOpenEnum": false + }, + { + "name": "displayName", + "typeName": { + "namespace": "", + "name": "string" + }, + "description": "Display name for UI purposes", + "samples": [ + { + "sample": { + "displayName": "Basic Prompt" + }, + "title": "", + "description": "" + } + ], + "knownAs": [], + "defaultFor": [], + "isScalar": true, + "isOptional": true, + "isCollection": false, + "isAny": false, + "isDict": false, + "defaultValue": "null", + "allowedValues": [], + "enumName": null, + "isOpenEnum": false + }, + { + "name": "description", + "typeName": { + "namespace": "", + "name": "string" + }, + "description": "Description of the prompt's purpose", + "samples": [ + { + "sample": { + "description": "A basic prompt that uses the GPT-3 chat API to answer questions" + }, + "title": "", + "description": "" + } + ], + "knownAs": [], + "defaultFor": [], + "isScalar": true, + "isOptional": true, + "isCollection": false, + "isAny": false, + "isDict": false, + "defaultValue": "null", + "allowedValues": [], + "enumName": null, + "isOpenEnum": false + }, + { + "name": "metadata", + "typeName": { + "namespace": "", + "name": "dictionary" + }, + "description": "Additional metadata including authors, tags, and other arbitrary properties", + "samples": [ + { + "sample": { + "metadata": { + "authors": [ + "sethjuarez", + "jietong" + ], + "tags": [ + "example", + "prompt" + ] + } + }, + "title": "", + "description": "" + } + ], + "knownAs": [], + "defaultFor": [], + "isScalar": true, + "isOptional": true, + "isCollection": false, + "isAny": false, + "isDict": true, + "defaultValue": "null", + "allowedValues": [], + "enumName": null, + "isOpenEnum": false + }, + { + "name": "inputs", + "typeName": { + "namespace": "Prompty", + "name": "Property" + }, + "description": "Input parameters that participate in template rendering", + "samples": [ + { + "sample": { + "inputs": { + "firstName": { + "kind": "string", + "default": "Jane" + }, + "lastName": { + "kind": "string", + "default": "Doe" + }, + "question": { + "kind": "string", + "default": "What is the meaning of life?" + } + } + }, + "title": "", + "description": "" + }, + { + "sample": { + "inputs": [ + { + "name": "firstName", + "kind": "string", + "default": "Jane" + }, + { + "name": "lastName", + "kind": "string", + "default": "Doe" + }, + { + "name": "question", + "kind": "string", + "default": "What is the meaning of life?" + } + ] + }, + "title": "", + "description": "" + } + ], + "knownAs": [], + "defaultFor": [], + "isScalar": false, + "isOptional": true, + "isCollection": true, + "isAny": false, + "isDict": false, + "defaultValue": "null", + "allowedValues": [], + "enumName": null, + "isOpenEnum": false, + "type": { + "typeName": { + "namespace": "Prompty", + "name": "Property" + }, + "description": "Represents a single property.\n\n- This model defines the structure of properties that can be used in prompts,\nincluding their type, description, whether they are required, and other attributes.\n- It allows for the definition of dynamic inputs that can be filled with data\nand processed to generate prompts for AI models.", + "base": {}, + "isAbstract": false, + "isProtocol": false, + "discriminator": "kind", + "coercions": [ + { + "scalar": "boolean", + "expansion": { + "kind": "boolean", + "example": "{value}" + }, + "title": "input", + "description": "Simple construction with just a kind of boolean" + }, + { + "scalar": "float32", + "expansion": { + "kind": "float", + "example": "{value}" + }, + "title": "input", + "description": "Simple construction with just a kind of float" + }, + { + "scalar": "integer", + "expansion": { + "kind": "integer", + "example": "{value}" + }, + "example": 4, + "title": "input", + "description": "Simple construction with just a kind of integer" + }, + { + "scalar": "string", + "expansion": { + "kind": "string", + "example": "{value}" + }, + "title": "input", + "description": "Simple construction with just a kind of string" + } + ], + "factories": [], + "methods": [], + "childTypes": [ + { + "typeName": { + "namespace": "Prompty", + "name": "ArrayProperty" + }, + "description": "Represents an array property.\nThis extends the base Property model to represent an array of items.", + "base": { + "namespace": "Prompty", + "name": "Property" + }, + "isAbstract": false, + "isProtocol": false, + "coercions": [], + "factories": [], + "methods": [], + "childTypes": [], + "properties": [ + { + "name": "kind", + "typeName": { + "namespace": "", + "name": "string" + }, + "description": "", + "samples": [], + "knownAs": [], + "defaultFor": [], + "isScalar": true, + "isOptional": false, + "isCollection": false, + "isAny": false, + "isDict": false, + "defaultValue": "array", + "allowedValues": [], + "enumName": null, + "isOpenEnum": false + }, + { + "name": "items", + "typeName": { + "namespace": "Prompty", + "name": "Property" + }, + "description": "The type of items contained in the array", + "samples": [ + { + "sample": { + "items": { + "kind": "string" + } + }, + "title": "", + "description": "" + } + ], + "knownAs": [], + "defaultFor": [], + "isScalar": false, + "isOptional": false, + "isCollection": false, + "isAny": false, + "isDict": false, + "defaultValue": "null", + "allowedValues": [], + "enumName": null, + "isOpenEnum": false, + "type": { + "typeName": { + "namespace": "Prompty", + "name": "Property" + }, + "description": "Represents a single property.\n\n- This model defines the structure of properties that can be used in prompts,\nincluding their type, description, whether they are required, and other attributes.\n- It allows for the definition of dynamic inputs that can be filled with data\nand processed to generate prompts for AI models.", + "base": {}, + "isAbstract": false, + "isProtocol": false, + "discriminator": "kind", + "coercions": [ + { + "scalar": "boolean", + "expansion": { + "kind": "boolean", + "example": "{value}" + }, + "title": "input", + "description": "Simple construction with just a kind of boolean" + }, + { + "scalar": "float32", + "expansion": { + "kind": "float", + "example": "{value}" + }, + "title": "input", + "description": "Simple construction with just a kind of float" + }, + { + "scalar": "integer", + "expansion": { + "kind": "integer", + "example": "{value}" + }, + "example": 4, + "title": "input", + "description": "Simple construction with just a kind of integer" + }, + { + "scalar": "string", + "expansion": { + "kind": "string", + "example": "{value}" + }, + "title": "input", + "description": "Simple construction with just a kind of string" + } + ], + "factories": [], + "methods": [], + "childTypes": [ + { + "typeName": { + "namespace": "Prompty", + "name": "ObjectProperty" + }, + "description": "Represents an object property.\nThis extends the base Property model to represent a structured object.", + "base": { + "namespace": "Prompty", + "name": "Property" + }, + "isAbstract": false, + "isProtocol": false, + "coercions": [], + "factories": [], + "methods": [], + "childTypes": [], + "properties": [ + { + "name": "kind", + "typeName": { + "namespace": "", + "name": "string" + }, + "description": "", + "samples": [], + "knownAs": [], + "defaultFor": [], + "isScalar": true, + "isOptional": false, + "isCollection": false, + "isAny": false, + "isDict": false, + "defaultValue": "object", + "allowedValues": [], + "enumName": null, + "isOpenEnum": false + }, + { + "name": "properties", + "typeName": { + "namespace": "Prompty", + "name": "Property" + }, + "description": "The properties contained in the object", + "samples": [ + { + "sample": { + "properties": { + "property1": { + "kind": "string" + }, + "property2": { + "kind": "number" + } + } + }, + "title": "", + "description": "" + } + ], + "knownAs": [], + "defaultFor": [], + "isScalar": false, + "isOptional": false, + "isCollection": true, + "isAny": false, + "isDict": false, + "defaultValue": "null", + "allowedValues": [], + "enumName": null, + "isOpenEnum": false, + "type": { + "typeName": { + "namespace": "Prompty", + "name": "Property" + }, + "description": "Represents a single property.\n\n- This model defines the structure of properties that can be used in prompts,\nincluding their type, description, whether they are required, and other attributes.\n- It allows for the definition of dynamic inputs that can be filled with data\nand processed to generate prompts for AI models.", + "base": {}, + "isAbstract": false, + "isProtocol": false, + "discriminator": "kind", + "coercions": [ + { + "scalar": "boolean", + "expansion": { + "kind": "boolean", + "example": "{value}" + }, + "title": "input", + "description": "Simple construction with just a kind of boolean" + }, + { + "scalar": "float32", + "expansion": { + "kind": "float", + "example": "{value}" + }, + "title": "input", + "description": "Simple construction with just a kind of float" + }, + { + "scalar": "integer", + "expansion": { + "kind": "integer", + "example": "{value}" + }, + "example": 4, + "title": "input", + "description": "Simple construction with just a kind of integer" + }, + { + "scalar": "string", + "expansion": { + "kind": "string", + "example": "{value}" + }, + "title": "input", + "description": "Simple construction with just a kind of string" + } + ], + "factories": [], + "methods": [], + "childTypes": [], + "properties": [ + { + "name": "name", + "typeName": { + "namespace": "", + "name": "string" + }, + "description": "Name of the property", + "samples": [ + { + "sample": { + "name": "my-input" + }, + "title": "", + "description": "" + } + ], + "knownAs": [], + "defaultFor": [], + "isScalar": true, + "isOptional": false, + "isCollection": false, + "isAny": false, + "isDict": false, + "defaultValue": "null", + "allowedValues": [], + "enumName": null, + "isOpenEnum": false + }, + { + "name": "kind", + "typeName": { + "namespace": "", + "name": "string" + }, + "description": "The data type of the input property", + "samples": [ + { + "sample": { + "kind": "string" + }, + "title": "", + "description": "" + } + ], + "knownAs": [], + "defaultFor": [], + "isScalar": true, + "isOptional": false, + "isCollection": false, + "isAny": false, + "isDict": false, + "defaultValue": "null", + "allowedValues": [ + "string", + "integer", + "float", + "boolean", + "array", + "object", + "thread", + "image", + "audio", + "file" + ], + "enumName": "SimpleTypes", + "isOpenEnum": false + }, + { + "name": "description", + "typeName": { + "namespace": "", + "name": "string" + }, + "description": "A short description of the input property", + "samples": [ + { + "sample": { + "description": "A description of the input property" + }, + "title": "", + "description": "" + } + ], + "knownAs": [], + "defaultFor": [], + "isScalar": true, + "isOptional": true, + "isCollection": false, + "isAny": false, + "isDict": false, + "defaultValue": "null", + "allowedValues": [], + "enumName": null, + "isOpenEnum": false + }, + { + "name": "required", + "typeName": { + "namespace": "", + "name": "boolean" + }, + "description": "Whether the property is required", + "samples": [ + { + "sample": { + "required": true + }, + "title": "", + "description": "" + } + ], + "knownAs": [], + "defaultFor": [], + "isScalar": true, + "isOptional": true, + "isCollection": false, + "isAny": false, + "isDict": false, + "defaultValue": "null", + "allowedValues": [], + "enumName": null, + "isOpenEnum": false + }, + { + "name": "default", + "typeName": { + "namespace": "", + "name": "unknown" + }, + "description": "The default value of the property - this represents the default value if none is provided", + "samples": [ + { + "sample": { + "default": "default value" + }, + "title": "", + "description": "" + } + ], + "knownAs": [], + "defaultFor": [], + "isScalar": true, + "isOptional": true, + "isCollection": false, + "isAny": true, + "isDict": false, + "defaultValue": "null", + "allowedValues": [], + "enumName": null, + "isOpenEnum": false + }, + { + "name": "example", + "typeName": { + "namespace": "", + "name": "unknown" + }, + "description": "Example value used for either initialization or tooling", + "samples": [ + { + "sample": { + "example": "example value" + }, + "title": "", + "description": "" + } + ], + "knownAs": [], + "defaultFor": [], + "isScalar": true, + "isOptional": true, + "isCollection": false, + "isAny": true, + "isDict": false, + "defaultValue": "null", + "allowedValues": [], + "enumName": null, + "isOpenEnum": false + }, + { + "name": "enumValues", + "typeName": { + "namespace": "", + "name": "unknown" + }, + "description": "Allowed enumeration values for the property", + "samples": [ + { + "sample": { + "enumValues": [ + "value1", + "value2", + "value3" + ] + }, + "title": "", + "description": "" + } + ], + "knownAs": [], + "defaultFor": [], + "isScalar": true, + "isOptional": true, + "isCollection": true, + "isAny": true, + "isDict": false, + "defaultValue": "null", + "allowedValues": [], + "enumName": null, + "isOpenEnum": false + } + ] + } + } + ] + } + ], + "properties": [ + { + "name": "kind", + "typeName": { + "namespace": "", + "name": "string" + }, + "description": "The data type of the input property", + "samples": [ + { + "sample": { + "kind": "string" + }, + "title": "", + "description": "" + } + ], + "knownAs": [], + "defaultFor": [], + "isScalar": true, + "isOptional": false, + "isCollection": false, + "isAny": false, + "isDict": false, + "defaultValue": "null", + "allowedValues": [ + "string", + "integer", + "float", + "boolean", + "array", + "object", + "thread", + "image", + "audio", + "file" + ], + "enumName": "SimpleTypes", + "isOpenEnum": false + }, + { + "name": "description", + "typeName": { + "namespace": "", + "name": "string" + }, + "description": "A short description of the input property", + "samples": [ + { + "sample": { + "description": "A description of the input property" + }, + "title": "", + "description": "" + } + ], + "knownAs": [], + "defaultFor": [], + "isScalar": true, + "isOptional": true, + "isCollection": false, + "isAny": false, + "isDict": false, + "defaultValue": "null", + "allowedValues": [], + "enumName": null, + "isOpenEnum": false + }, + { + "name": "required", + "typeName": { + "namespace": "", + "name": "boolean" + }, + "description": "Whether the property is required", + "samples": [ + { + "sample": { + "required": true + }, + "title": "", + "description": "" + } + ], + "knownAs": [], + "defaultFor": [], + "isScalar": true, + "isOptional": true, + "isCollection": false, + "isAny": false, + "isDict": false, + "defaultValue": "null", + "allowedValues": [], + "enumName": null, + "isOpenEnum": false + }, + { + "name": "default", + "typeName": { + "namespace": "", + "name": "unknown" + }, + "description": "The default value of the property - this represents the default value if none is provided", + "samples": [ + { + "sample": { + "default": "default value" + }, + "title": "", + "description": "" + } + ], + "knownAs": [], + "defaultFor": [], + "isScalar": true, + "isOptional": true, + "isCollection": false, + "isAny": true, + "isDict": false, + "defaultValue": "null", + "allowedValues": [], + "enumName": null, + "isOpenEnum": false + }, + { + "name": "example", + "typeName": { + "namespace": "", + "name": "unknown" + }, + "description": "Example value used for either initialization or tooling", + "samples": [ + { + "sample": { + "example": "example value" + }, + "title": "", + "description": "" + } + ], + "knownAs": [], + "defaultFor": [], + "isScalar": true, + "isOptional": true, + "isCollection": false, + "isAny": true, + "isDict": false, + "defaultValue": "null", + "allowedValues": [], + "enumName": null, + "isOpenEnum": false + }, + { + "name": "enumValues", + "typeName": { + "namespace": "", + "name": "unknown" + }, + "description": "Allowed enumeration values for the property", + "samples": [ + { + "sample": { + "enumValues": [ + "value1", + "value2", + "value3" + ] + }, + "title": "", + "description": "" + } + ], + "knownAs": [], + "defaultFor": [], + "isScalar": true, + "isOptional": true, + "isCollection": true, + "isAny": true, + "isDict": false, + "defaultValue": "null", + "allowedValues": [], + "enumName": null, + "isOpenEnum": false + } + ] + } + } + ] + }, + { + "typeName": { + "namespace": "Prompty", + "name": "ObjectProperty" + }, + "description": "Represents an object property.\nThis extends the base Property model to represent a structured object.", + "base": { + "namespace": "Prompty", + "name": "Property" + }, + "isAbstract": false, + "isProtocol": false, + "coercions": [], + "factories": [], + "methods": [], + "childTypes": [], + "properties": [ + { + "name": "kind", + "typeName": { + "namespace": "", + "name": "string" + }, + "description": "", + "samples": [], + "knownAs": [], + "defaultFor": [], + "isScalar": true, + "isOptional": false, + "isCollection": false, + "isAny": false, + "isDict": false, + "defaultValue": "object", + "allowedValues": [], + "enumName": null, + "isOpenEnum": false + }, + { + "name": "properties", + "typeName": { + "namespace": "Prompty", + "name": "Property" + }, + "description": "The properties contained in the object", + "samples": [ + { + "sample": { + "properties": { + "property1": { + "kind": "string" + }, + "property2": { + "kind": "number" + } + } + }, + "title": "", + "description": "" + } + ], + "knownAs": [], + "defaultFor": [], + "isScalar": false, + "isOptional": false, + "isCollection": true, + "isAny": false, + "isDict": false, + "defaultValue": "null", + "allowedValues": [], + "enumName": null, + "isOpenEnum": false + } + ] + } + ], + "properties": [ + { + "name": "name", + "typeName": { + "namespace": "", + "name": "string" + }, + "description": "Name of the property", + "samples": [ + { + "sample": { + "name": "my-input" + }, + "title": "", + "description": "" + } + ], + "knownAs": [], + "defaultFor": [], + "isScalar": true, + "isOptional": false, + "isCollection": false, + "isAny": false, + "isDict": false, + "defaultValue": "null", + "allowedValues": [], + "enumName": null, + "isOpenEnum": false + }, + { + "name": "kind", + "typeName": { + "namespace": "", + "name": "string" + }, + "description": "The data type of the input property", + "samples": [ + { + "sample": { + "kind": "string" + }, + "title": "", + "description": "" + } + ], + "knownAs": [], + "defaultFor": [], + "isScalar": true, + "isOptional": false, + "isCollection": false, + "isAny": false, + "isDict": false, + "defaultValue": "null", + "allowedValues": [ + "string", + "integer", + "float", + "boolean", + "array", + "object", + "thread", + "image", + "audio", + "file" + ], + "enumName": "SimpleTypes", + "isOpenEnum": false + }, + { + "name": "description", + "typeName": { + "namespace": "", + "name": "string" + }, + "description": "A short description of the input property", + "samples": [ + { + "sample": { + "description": "A description of the input property" + }, + "title": "", + "description": "" + } + ], + "knownAs": [], + "defaultFor": [], + "isScalar": true, + "isOptional": true, + "isCollection": false, + "isAny": false, + "isDict": false, + "defaultValue": "null", + "allowedValues": [], + "enumName": null, + "isOpenEnum": false + }, + { + "name": "required", + "typeName": { + "namespace": "", + "name": "boolean" + }, + "description": "Whether the property is required", + "samples": [ + { + "sample": { + "required": true + }, + "title": "", + "description": "" + } + ], + "knownAs": [], + "defaultFor": [], + "isScalar": true, + "isOptional": true, + "isCollection": false, + "isAny": false, + "isDict": false, + "defaultValue": "null", + "allowedValues": [], + "enumName": null, + "isOpenEnum": false + }, + { + "name": "default", + "typeName": { + "namespace": "", + "name": "unknown" + }, + "description": "The default value of the property - this represents the default value if none is provided", + "samples": [ + { + "sample": { + "default": "default value" + }, + "title": "", + "description": "" + } + ], + "knownAs": [], + "defaultFor": [], + "isScalar": true, + "isOptional": true, + "isCollection": false, + "isAny": true, + "isDict": false, + "defaultValue": "null", + "allowedValues": [], + "enumName": null, + "isOpenEnum": false + }, + { + "name": "example", + "typeName": { + "namespace": "", + "name": "unknown" + }, + "description": "Example value used for either initialization or tooling", + "samples": [ + { + "sample": { + "example": "example value" + }, + "title": "", + "description": "" + } + ], + "knownAs": [], + "defaultFor": [], + "isScalar": true, + "isOptional": true, + "isCollection": false, + "isAny": true, + "isDict": false, + "defaultValue": "null", + "allowedValues": [], + "enumName": null, + "isOpenEnum": false + }, + { + "name": "enumValues", + "typeName": { + "namespace": "", + "name": "unknown" + }, + "description": "Allowed enumeration values for the property", + "samples": [ + { + "sample": { + "enumValues": [ + "value1", + "value2", + "value3" + ] + }, + "title": "", + "description": "" + } + ], + "knownAs": [], + "defaultFor": [], + "isScalar": true, + "isOptional": true, + "isCollection": true, + "isAny": true, + "isDict": false, + "defaultValue": "null", + "allowedValues": [], + "enumName": null, + "isOpenEnum": false + } + ] + } + }, + { + "name": "outputs", + "typeName": { + "namespace": "Prompty", + "name": "Property" + }, + "description": "Expected output format and structure", + "samples": [ + { + "sample": { + "outputs": { + "answer": { + "kind": "string", + "description": "The answer to the user's question." + } + } + }, + "title": "", + "description": "" + }, + { + "sample": { + "outputs": [ + { + "name": "answer", + "kind": "string", + "description": "The answer to the user's question." + } + ] + }, + "title": "", + "description": "" + } + ], + "knownAs": [], + "defaultFor": [], + "isScalar": false, + "isOptional": true, + "isCollection": true, + "isAny": false, + "isDict": false, + "defaultValue": "null", + "allowedValues": [], + "enumName": null, + "isOpenEnum": false + }, + { + "name": "model", + "typeName": { + "namespace": "Prompty", + "name": "Model" + }, + "description": "AI model configuration", + "samples": [ + { + "sample": { + "model": { + "id": "gpt-35-turbo", + "connection": { + "kind": "key", + "endpoint": "https://{your-custom-endpoint}.openai.azure.com/", + "apiKey": "{your-api-key}" + } + } + }, + "title": "", + "description": "" + } + ], + "knownAs": [], + "defaultFor": [], + "isScalar": false, + "isOptional": false, + "isCollection": false, + "isAny": false, + "isDict": false, + "defaultValue": "null", + "allowedValues": [], + "enumName": null, + "isOpenEnum": false, + "type": { + "typeName": { + "namespace": "Prompty", + "name": "Model" + }, + "description": "Model for defining the structure and behavior of AI agents.\nThis model includes properties for specifying the model's provider, connection details, and various options.\nIt allows for flexible configuration of AI models to suit different use cases and requirements.", + "base": {}, + "isAbstract": false, + "isProtocol": false, + "coercions": [ + { + "scalar": "string", + "expansion": { + "id": "{value}" + }, + "title": "model", + "description": "Simple construction with just an id" + } + ], + "factories": [], + "methods": [], + "childTypes": [], + "properties": [ + { + "name": "id", + "typeName": { + "namespace": "", + "name": "string" + }, + "description": "The unique identifier of the model - can be used as the single property shorthand", + "samples": [ + { + "sample": { + "id": "gpt-35-turbo" + }, + "title": "", + "description": "" + } + ], + "knownAs": [], + "defaultFor": [], + "isScalar": true, + "isOptional": false, + "isCollection": false, + "isAny": false, + "isDict": false, + "defaultValue": "null", + "allowedValues": [], + "enumName": null, + "isOpenEnum": false + }, + { + "name": "provider", + "typeName": { + "namespace": "", + "name": "string" + }, + "description": "The provider of the model (e.g., 'openai', 'foundry', 'anthropic')", + "samples": [ + { + "sample": { + "provider": "foundry" + }, + "title": "", + "description": "" + } + ], + "knownAs": [], + "defaultFor": [], + "isScalar": true, + "isOptional": true, + "isCollection": false, + "isAny": false, + "isDict": false, + "defaultValue": "null", + "allowedValues": [], + "enumName": null, + "isOpenEnum": false + }, + { + "name": "apiType", + "typeName": { + "namespace": "", + "name": "string" + }, + "description": "The type of API to use for the model (e.g., 'chat', 'response', etc.)", + "samples": [ + { + "sample": { + "apiType": "chat" + }, + "title": "", + "description": "" + } + ], + "knownAs": [], + "defaultFor": [], + "isScalar": true, + "isOptional": true, + "isCollection": false, + "isAny": false, + "isDict": false, + "defaultValue": "null", + "allowedValues": [ + "chat", + "embedding", + "image", + "responses" + ], + "enumName": "apiType", + "isOpenEnum": true + }, + { + "name": "connection", + "typeName": { + "namespace": "Prompty", + "name": "Connection" + }, + "description": "The connection configuration for the model", + "samples": [ + { + "sample": { + "connection": { + "kind": "key", + "endpoint": "https://{your-custom-endpoint}.openai.azure.com/", + "key": "{your-api-key}" + } + }, + "title": "", + "description": "" + } + ], + "knownAs": [], + "defaultFor": [], + "isScalar": false, + "isOptional": true, + "isCollection": false, + "isAny": false, + "isDict": false, + "defaultValue": "null", + "allowedValues": [], + "enumName": null, + "isOpenEnum": false, + "type": { + "typeName": { + "namespace": "Prompty", + "name": "Connection" + }, + "description": "Connection configuration for AI agents.\n`provider`, `kind`, and `endpoint` are required properties here,\nbut this section can accept additional via options.", + "base": {}, + "isAbstract": true, + "isProtocol": false, + "discriminator": "kind", + "coercions": [], + "factories": [], + "methods": [], + "childTypes": [ + { + "typeName": { + "namespace": "Prompty", + "name": "ReferenceConnection" + }, + "description": "Connection configuration for AI services using named connections.", + "base": { + "namespace": "Prompty", + "name": "Connection" + }, + "isAbstract": false, + "isProtocol": false, + "coercions": [], + "factories": [], + "methods": [], + "childTypes": [], + "properties": [ + { + "name": "kind", + "typeName": { + "namespace": "", + "name": "string" + }, + "description": "The Authentication kind for the AI service (e.g., 'key' for API key, 'oauth' for OAuth tokens)", + "samples": [ + { + "sample": { + "kind": "reference" + }, + "title": "", + "description": "" + } + ], + "knownAs": [], + "defaultFor": [], + "isScalar": true, + "isOptional": false, + "isCollection": false, + "isAny": false, + "isDict": false, + "defaultValue": "reference", + "allowedValues": [], + "enumName": null, + "isOpenEnum": false + }, + { + "name": "name", + "typeName": { + "namespace": "", + "name": "string" + }, + "description": "The name of the connection", + "samples": [ + { + "sample": { + "name": "my-reference-connection" + }, + "title": "", + "description": "" + } + ], + "knownAs": [], + "defaultFor": [], + "isScalar": true, + "isOptional": false, + "isCollection": false, + "isAny": false, + "isDict": false, + "defaultValue": "null", + "allowedValues": [], + "enumName": null, + "isOpenEnum": false + }, + { + "name": "target", + "typeName": { + "namespace": "", + "name": "string" + }, + "description": "The target resource or service that this connection refers to", + "samples": [ + { + "sample": { + "target": "my-target-resource" + }, + "title": "", + "description": "" + } + ], + "knownAs": [], + "defaultFor": [], + "isScalar": true, + "isOptional": true, + "isCollection": false, + "isAny": false, + "isDict": false, + "defaultValue": "null", + "allowedValues": [], + "enumName": null, + "isOpenEnum": false + } + ] + }, + { + "typeName": { + "namespace": "Prompty", + "name": "RemoteConnection" + }, + "description": "Connection configuration for AI services using named connections.", + "base": { + "namespace": "Prompty", + "name": "Connection" + }, + "isAbstract": false, + "isProtocol": false, + "coercions": [], + "factories": [], + "methods": [], + "childTypes": [], + "properties": [ + { + "name": "kind", + "typeName": { + "namespace": "", + "name": "string" + }, + "description": "The Authentication kind for the AI service (e.g., 'key' for API key, 'oauth' for OAuth tokens)", + "samples": [ + { + "sample": { + "kind": "remote" + }, + "title": "", + "description": "" + } + ], + "knownAs": [], + "defaultFor": [], + "isScalar": true, + "isOptional": false, + "isCollection": false, + "isAny": false, + "isDict": false, + "defaultValue": "remote", + "allowedValues": [], + "enumName": null, + "isOpenEnum": false + }, + { + "name": "name", + "typeName": { + "namespace": "", + "name": "string" + }, + "description": "The name of the connection", + "samples": [ + { + "sample": { + "name": "my-reference-connection" + }, + "title": "", + "description": "" + } + ], + "knownAs": [], + "defaultFor": [], + "isScalar": true, + "isOptional": false, + "isCollection": false, + "isAny": false, + "isDict": false, + "defaultValue": "null", + "allowedValues": [], + "enumName": null, + "isOpenEnum": false + }, + { + "name": "endpoint", + "typeName": { + "namespace": "", + "name": "string" + }, + "description": "The endpoint URL for the AI service", + "samples": [ + { + "sample": { + "endpoint": "https://{your-custom-endpoint}.openai.azure.com/" + }, + "title": "", + "description": "" + } + ], + "knownAs": [], + "defaultFor": [], + "isScalar": true, + "isOptional": false, + "isCollection": false, + "isAny": false, + "isDict": false, + "defaultValue": "null", + "allowedValues": [], + "enumName": null, + "isOpenEnum": false + } + ] + }, + { + "typeName": { + "namespace": "Prompty", + "name": "ApiKeyConnection" + }, + "description": "Connection configuration for AI services using API keys.", + "base": { + "namespace": "Prompty", + "name": "Connection" + }, + "isAbstract": false, + "isProtocol": false, + "coercions": [], + "factories": [], + "methods": [], + "childTypes": [], + "properties": [ + { + "name": "kind", + "typeName": { + "namespace": "", + "name": "string" + }, + "description": "The Authentication kind for the AI service (e.g., 'key' for API key, 'oauth' for OAuth tokens)", + "samples": [ + { + "sample": { + "kind": "key" + }, + "title": "", + "description": "" + } + ], + "knownAs": [], + "defaultFor": [], + "isScalar": true, + "isOptional": false, + "isCollection": false, + "isAny": false, + "isDict": false, + "defaultValue": "key", + "allowedValues": [], + "enumName": null, + "isOpenEnum": false + }, + { + "name": "endpoint", + "typeName": { + "namespace": "", + "name": "string" + }, + "description": "The endpoint URL for the AI service", + "samples": [ + { + "sample": { + "endpoint": "https://{your-custom-endpoint}.openai.azure.com/" + }, + "title": "", + "description": "" + } + ], + "knownAs": [], + "defaultFor": [], + "isScalar": true, + "isOptional": false, + "isCollection": false, + "isAny": false, + "isDict": false, + "defaultValue": "null", + "allowedValues": [], + "enumName": null, + "isOpenEnum": false + }, + { + "name": "apiKey", + "typeName": { + "namespace": "", + "name": "string" + }, + "description": "The API key for authenticating with the AI service", + "samples": [ + { + "sample": { + "apiKey": "your-api-key" + }, + "title": "", + "description": "" + } + ], + "knownAs": [], + "defaultFor": [], + "isScalar": true, + "isOptional": false, + "isCollection": false, + "isAny": false, + "isDict": false, + "defaultValue": "null", + "allowedValues": [], + "enumName": null, + "isOpenEnum": false + } + ] + }, + { + "typeName": { + "namespace": "Prompty", + "name": "AnonymousConnection" + }, + "description": "", + "base": { + "namespace": "Prompty", + "name": "Connection" + }, + "isAbstract": false, + "isProtocol": false, + "coercions": [], + "factories": [], + "methods": [], + "childTypes": [], + "properties": [ + { + "name": "kind", + "typeName": { + "namespace": "", + "name": "string" + }, + "description": "The Authentication kind for the AI service (e.g., 'key' for API key, 'oauth' for OAuth tokens)", + "samples": [ + { + "sample": { + "kind": "anonymous" + }, + "title": "", + "description": "" + } + ], + "knownAs": [], + "defaultFor": [], + "isScalar": true, + "isOptional": false, + "isCollection": false, + "isAny": false, + "isDict": false, + "defaultValue": "anonymous", + "allowedValues": [], + "enumName": null, + "isOpenEnum": false + }, + { + "name": "endpoint", + "typeName": { + "namespace": "", + "name": "string" + }, + "description": "The endpoint for authenticating with the AI service", + "samples": [ + { + "sample": { + "endpoint": "https://{your-custom-endpoint}.openai.azure.com/" + }, + "title": "", + "description": "" + } + ], + "knownAs": [], + "defaultFor": [], + "isScalar": true, + "isOptional": false, + "isCollection": false, + "isAny": false, + "isDict": false, + "defaultValue": "null", + "allowedValues": [], + "enumName": null, + "isOpenEnum": false + } + ] + }, + { + "typeName": { + "namespace": "Prompty", + "name": "OAuthConnection" + }, + "description": "Connection configuration using OAuth 2.0 client credentials.\nUseful for tools and services that require OAuth authentication,\nsuch as MCP servers, OpenAPI endpoints, or other REST APIs.", + "base": { + "namespace": "Prompty", + "name": "Connection" + }, + "isAbstract": false, + "isProtocol": false, + "coercions": [], + "factories": [], + "methods": [], + "childTypes": [], + "properties": [ + { + "name": "kind", + "typeName": { + "namespace": "", + "name": "string" + }, + "description": "The connection kind for OAuth authentication", + "samples": [ + { + "sample": { + "kind": "oauth" + }, + "title": "", + "description": "" + } + ], + "knownAs": [], + "defaultFor": [], + "isScalar": true, + "isOptional": false, + "isCollection": false, + "isAny": false, + "isDict": false, + "defaultValue": "oauth", + "allowedValues": [], + "enumName": null, + "isOpenEnum": false + }, + { + "name": "endpoint", + "typeName": { + "namespace": "", + "name": "string" + }, + "description": "The endpoint URL for the service", + "samples": [ + { + "sample": { + "endpoint": "https://api.example.com" + }, + "title": "", + "description": "" + } + ], + "knownAs": [], + "defaultFor": [], + "isScalar": true, + "isOptional": false, + "isCollection": false, + "isAny": false, + "isDict": false, + "defaultValue": "null", + "allowedValues": [], + "enumName": null, + "isOpenEnum": false + }, + { + "name": "clientId", + "typeName": { + "namespace": "", + "name": "string" + }, + "description": "The OAuth client ID", + "samples": [ + { + "sample": { + "clientId": "your-client-id" + }, + "title": "", + "description": "" + } + ], + "knownAs": [], + "defaultFor": [], + "isScalar": true, + "isOptional": false, + "isCollection": false, + "isAny": false, + "isDict": false, + "defaultValue": "null", + "allowedValues": [], + "enumName": null, + "isOpenEnum": false + }, + { + "name": "clientSecret", + "typeName": { + "namespace": "", + "name": "string" + }, + "description": "The OAuth client secret", + "samples": [ + { + "sample": { + "clientSecret": "your-client-secret" + }, + "title": "", + "description": "" + } + ], + "knownAs": [], + "defaultFor": [], + "isScalar": true, + "isOptional": false, + "isCollection": false, + "isAny": false, + "isDict": false, + "defaultValue": "null", + "allowedValues": [], + "enumName": null, + "isOpenEnum": false + }, + { + "name": "tokenUrl", + "typeName": { + "namespace": "", + "name": "string" + }, + "description": "The OAuth token endpoint URL", + "samples": [ + { + "sample": { + "tokenUrl": "https://login.microsoftonline.com/{tenant}/oauth2/v2.0/token" + }, + "title": "", + "description": "" + } + ], + "knownAs": [], + "defaultFor": [], + "isScalar": true, + "isOptional": false, + "isCollection": false, + "isAny": false, + "isDict": false, + "defaultValue": "null", + "allowedValues": [], + "enumName": null, + "isOpenEnum": false + }, + { + "name": "scopes", + "typeName": { + "namespace": "", + "name": "string" + }, + "description": "OAuth scopes to request", + "samples": [ + { + "sample": { + "scopes": [ + "https://cognitiveservices.azure.com/.default" + ] + }, + "title": "", + "description": "" + } + ], + "knownAs": [], + "defaultFor": [], + "isScalar": true, + "isOptional": true, + "isCollection": true, + "isAny": false, + "isDict": false, + "defaultValue": "null", + "allowedValues": [], + "enumName": null, + "isOpenEnum": false + } + ] + }, + { + "typeName": { + "namespace": "Prompty", + "name": "FoundryConnection" + }, + "description": "Connection configuration for Microsoft Foundry projects.\nProvides project-scoped access to models, tools, and services\nvia Entra ID (DefaultAzureCredential) authentication.", + "base": { + "namespace": "Prompty", + "name": "Connection" + }, + "isAbstract": false, + "isProtocol": false, + "coercions": [], + "factories": [], + "methods": [], + "childTypes": [], + "properties": [ + { + "name": "kind", + "typeName": { + "namespace": "", + "name": "string" + }, + "description": "The connection kind for Foundry project access", + "samples": [ + { + "sample": { + "kind": "foundry" + }, + "title": "", + "description": "" + } + ], + "knownAs": [], + "defaultFor": [], + "isScalar": true, + "isOptional": false, + "isCollection": false, + "isAny": false, + "isDict": false, + "defaultValue": "foundry", + "allowedValues": [], + "enumName": null, + "isOpenEnum": false + }, + { + "name": "endpoint", + "typeName": { + "namespace": "", + "name": "string" + }, + "description": "The Foundry project endpoint URL", + "samples": [ + { + "sample": { + "endpoint": "https://myresource.services.ai.azure.com/api/projects/myproject" + }, + "title": "", + "description": "" + } + ], + "knownAs": [], + "defaultFor": [], + "isScalar": true, + "isOptional": false, + "isCollection": false, + "isAny": false, + "isDict": false, + "defaultValue": "null", + "allowedValues": [], + "enumName": null, + "isOpenEnum": false + }, + { + "name": "name", + "typeName": { + "namespace": "", + "name": "string" + }, + "description": "The named connection within the Foundry project", + "samples": [ + { + "sample": { + "name": "my-openai-connection" + }, + "title": "", + "description": "" + } + ], + "knownAs": [], + "defaultFor": [], + "isScalar": true, + "isOptional": true, + "isCollection": false, + "isAny": false, + "isDict": false, + "defaultValue": "null", + "allowedValues": [], + "enumName": null, + "isOpenEnum": false + }, + { + "name": "connectionType", + "typeName": { + "namespace": "", + "name": "string" + }, + "description": "The connection type within the Foundry project (e.g., 'model', 'index', 'storage')", + "samples": [ + { + "sample": { + "connectionType": "model" + }, + "title": "", + "description": "" + } + ], + "knownAs": [], + "defaultFor": [], + "isScalar": true, + "isOptional": true, + "isCollection": false, + "isAny": false, + "isDict": false, + "defaultValue": "null", + "allowedValues": [], + "enumName": null, + "isOpenEnum": false + } + ] + } + ], + "properties": [ + { + "name": "kind", + "typeName": { + "namespace": "", + "name": "string" + }, + "description": "The Authentication kind for the AI service (e.g., 'key' for API key, 'oauth' for OAuth tokens)", + "samples": [ + { + "sample": { + "kind": "reference" + }, + "title": "", + "description": "" + } + ], + "knownAs": [], + "defaultFor": [], + "isScalar": true, + "isOptional": false, + "isCollection": false, + "isAny": false, + "isDict": false, + "defaultValue": "null", + "allowedValues": [ + "remote", + "reference", + "key", + "anonymous", + "foundry", + "oauth" + ], + "enumName": "ConnectionType", + "isOpenEnum": false + }, + { + "name": "authenticationMode", + "typeName": { + "namespace": "", + "name": "string" + }, + "description": "The authority level for the connection, indicating under whose authority the connection is made (e.g., 'user', 'agent', 'system')", + "samples": [ + { + "sample": { + "authenticationMode": "system" + }, + "title": "", + "description": "" + } + ], + "knownAs": [], + "defaultFor": [], + "isScalar": true, + "isOptional": true, + "isCollection": false, + "isAny": false, + "isDict": false, + "defaultValue": "null", + "allowedValues": [ + "user", + "system" + ], + "enumName": "AuthenticationMode", + "isOpenEnum": false + }, + { + "name": "usageDescription", + "typeName": { + "namespace": "", + "name": "string" + }, + "description": "The usage description for the connection, providing context on how this connection will be used", + "samples": [ + { + "sample": { + "usageDescription": "This will allow the agent to respond to an email on your behalf" + }, + "title": "", + "description": "" + } + ], + "knownAs": [], + "defaultFor": [], + "isScalar": true, + "isOptional": true, + "isCollection": false, + "isAny": false, + "isDict": false, + "defaultValue": "null", + "allowedValues": [], + "enumName": null, + "isOpenEnum": false + } + ] + } + }, + { + "name": "options", + "typeName": { + "namespace": "Prompty", + "name": "ModelOptions" + }, + "description": "Additional options for the model", + "samples": [ + { + "sample": { + "options": { + "type": "chat", + "temperature": 0.7, + "maxOutputTokens": 1000 + } + }, + "title": "", + "description": "" + } + ], + "knownAs": [], + "defaultFor": [], + "isScalar": false, + "isOptional": true, + "isCollection": false, + "isAny": false, + "isDict": false, + "defaultValue": "null", + "allowedValues": [], + "enumName": null, + "isOpenEnum": false, + "type": { + "typeName": { + "namespace": "Prompty", + "name": "ModelOptions" + }, + "description": "Options for configuring the behavior of the AI model.", + "base": {}, + "isAbstract": false, + "isProtocol": false, + "coercions": [], + "factories": [], + "methods": [], + "childTypes": [], + "properties": [ + { + "name": "frequencyPenalty", + "typeName": { + "namespace": "", + "name": "float32" + }, + "description": "The frequency penalty to apply to the model's output", + "samples": [ + { + "sample": { + "frequencyPenalty": 0.5 + }, + "title": "", + "description": "" + } + ], + "knownAs": [ + { + "provider": "openai", + "name": "frequency_penalty" + } + ], + "defaultFor": [], + "isScalar": true, + "isOptional": true, + "isCollection": false, + "isAny": false, + "isDict": false, + "defaultValue": "null", + "allowedValues": [], + "enumName": null, + "isOpenEnum": false + }, + { + "name": "maxOutputTokens", + "typeName": { + "namespace": "", + "name": "int32" + }, + "description": "The maximum number of tokens to generate in the output", + "samples": [ + { + "sample": { + "maxOutputTokens": 2048 + }, + "title": "", + "description": "" + } + ], + "knownAs": [ + { + "provider": "openai", + "name": "max_completion_tokens" + }, + { + "provider": "responses", + "name": "max_output_tokens" + }, + { + "provider": "anthropic", + "name": "max_tokens" + } + ], + "defaultFor": [], + "isScalar": true, + "isOptional": true, + "isCollection": false, + "isAny": false, + "isDict": false, + "defaultValue": "null", + "allowedValues": [], + "enumName": null, + "isOpenEnum": false + }, + { + "name": "presencePenalty", + "typeName": { + "namespace": "", + "name": "float32" + }, + "description": "The presence penalty to apply to the model's output", + "samples": [ + { + "sample": { + "presencePenalty": 0.3 + }, + "title": "", + "description": "" + } + ], + "knownAs": [ + { + "provider": "openai", + "name": "presence_penalty" + } + ], + "defaultFor": [], + "isScalar": true, + "isOptional": true, + "isCollection": false, + "isAny": false, + "isDict": false, + "defaultValue": "null", + "allowedValues": [], + "enumName": null, + "isOpenEnum": false + }, + { + "name": "seed", + "typeName": { + "namespace": "", + "name": "int32" + }, + "description": "A random seed for deterministic output", + "samples": [ + { + "sample": { + "seed": 42 + }, + "title": "", + "description": "" + } + ], + "knownAs": [ + { + "provider": "openai", + "name": "seed" + } + ], + "defaultFor": [], + "isScalar": true, + "isOptional": true, + "isCollection": false, + "isAny": false, + "isDict": false, + "defaultValue": "null", + "allowedValues": [], + "enumName": null, + "isOpenEnum": false + }, + { + "name": "temperature", + "typeName": { + "namespace": "", + "name": "float32" + }, + "description": "The temperature to use for sampling", + "samples": [ + { + "sample": { + "temperature": 0.7 + }, + "title": "", + "description": "" + } + ], + "knownAs": [ + { + "provider": "openai", + "name": "temperature" + }, + { + "provider": "responses", + "name": "temperature" + }, + { + "provider": "anthropic", + "name": "temperature" + } + ], + "defaultFor": [], + "isScalar": true, + "isOptional": true, + "isCollection": false, + "isAny": false, + "isDict": false, + "defaultValue": "null", + "allowedValues": [], + "enumName": null, + "isOpenEnum": false + }, + { + "name": "topK", + "typeName": { + "namespace": "", + "name": "int32" + }, + "description": "The top-K sampling value", + "samples": [ + { + "sample": { + "topK": 40 + }, + "title": "", + "description": "" + } + ], + "knownAs": [ + { + "provider": "openai", + "name": "top_k" + }, + { + "provider": "anthropic", + "name": "top_k" + } + ], + "defaultFor": [], + "isScalar": true, + "isOptional": true, + "isCollection": false, + "isAny": false, + "isDict": false, + "defaultValue": "null", + "allowedValues": [], + "enumName": null, + "isOpenEnum": false + }, + { + "name": "topP", + "typeName": { + "namespace": "", + "name": "float32" + }, + "description": "The top-P sampling value", + "samples": [ + { + "sample": { + "topP": 0.9 + }, + "title": "", + "description": "" + } + ], + "knownAs": [ + { + "provider": "openai", + "name": "top_p" + }, + { + "provider": "responses", + "name": "top_p" + }, + { + "provider": "anthropic", + "name": "top_p" + } + ], + "defaultFor": [], + "isScalar": true, + "isOptional": true, + "isCollection": false, + "isAny": false, + "isDict": false, + "defaultValue": "null", + "allowedValues": [], + "enumName": null, + "isOpenEnum": false + }, + { + "name": "stopSequences", + "typeName": { + "namespace": "", + "name": "string" + }, + "description": "Stop sequences to end generation", + "samples": [ + { + "sample": { + "stopSequences": [ + "\n", + "###" + ] + }, + "title": "", + "description": "" + } + ], + "knownAs": [ + { + "provider": "openai", + "name": "stop" + }, + { + "provider": "anthropic", + "name": "stop_sequences" + } + ], + "defaultFor": [], + "isScalar": true, + "isOptional": true, + "isCollection": true, + "isAny": false, + "isDict": false, + "defaultValue": "null", + "allowedValues": [], + "enumName": null, + "isOpenEnum": false + }, + { + "name": "allowMultipleToolCalls", + "typeName": { + "namespace": "", + "name": "boolean" + }, + "description": "Whether to allow multiple tool calls in a single response", + "samples": [ + { + "sample": { + "allowMultipleToolCalls": true + }, + "title": "", + "description": "" + } + ], + "knownAs": [ + { + "provider": "openai", + "name": "parallel_tool_calls" + } + ], + "defaultFor": [], + "isScalar": true, + "isOptional": true, + "isCollection": false, + "isAny": false, + "isDict": false, + "defaultValue": "null", + "allowedValues": [], + "enumName": null, + "isOpenEnum": false + }, + { + "name": "additionalProperties", + "typeName": { + "namespace": "", + "name": "dictionary" + }, + "description": "Additional custom properties for model options", + "samples": [ + { + "sample": { + "additionalProperties": { + "customProperty": "value", + "anotherProperty": "anotherValue" + } + }, + "title": "", + "description": "" + } + ], + "knownAs": [], + "defaultFor": [], + "isScalar": true, + "isOptional": true, + "isCollection": false, + "isAny": false, + "isDict": true, + "defaultValue": "null", + "allowedValues": [], + "enumName": null, + "isOpenEnum": false + } + ] + } + } + ] + } + }, + { + "name": "tools", + "typeName": { + "namespace": "Prompty", + "name": "Tool" + }, + "description": "Tools available for extended functionality", + "samples": [ + { + "sample": { + "tools": [ + { + "name": "getCurrentWeather", + "kind": "function", + "description": "Get the current weather in a given location", + "parameters": { + "location": { + "kind": "string", + "description": "The city and state, e.g. San Francisco, CA" + }, + "unit": { + "kind": "string", + "description": "The unit of temperature, e.g. Celsius or Fahrenheit" + } + } + } + ] + }, + "title": "", + "description": "" + }, + { + "sample": { + "tools": { + "getCurrentWeather": { + "kind": "function", + "description": "Get the current weather in a given location", + "parameters": { + "location": { + "kind": "string", + "description": "The city and state, e.g. San Francisco, CA" + }, + "unit": { + "kind": "string", + "description": "The unit of temperature, e.g. Celsius or Fahrenheit" + } + } + } + } + }, + "title": "", + "description": "" + } + ], + "knownAs": [], + "defaultFor": [], + "isScalar": false, + "isOptional": true, + "isCollection": true, + "isAny": false, + "isDict": false, + "defaultValue": "null", + "allowedValues": [], + "enumName": null, + "isOpenEnum": false, + "type": { + "typeName": { + "namespace": "Prompty", + "name": "Tool" + }, + "description": "Represents a tool that can be used in prompts.", + "base": {}, + "isAbstract": true, + "isProtocol": false, + "discriminator": "kind", + "coercions": [], + "factories": [], + "methods": [], + "childTypes": [ + { + "typeName": { + "namespace": "Prompty", + "name": "FunctionTool" + }, + "description": "Represents a local function tool.", + "base": { + "namespace": "Prompty", + "name": "Tool" + }, + "isAbstract": false, + "isProtocol": false, + "coercions": [], + "factories": [], + "methods": [], + "childTypes": [], + "properties": [ + { + "name": "kind", + "typeName": { + "namespace": "", + "name": "string" + }, + "description": "The kind identifier for function tools", + "samples": [ + { + "sample": { + "kind": "function" + }, + "title": "", + "description": "" + } + ], + "knownAs": [], + "defaultFor": [], + "isScalar": true, + "isOptional": false, + "isCollection": false, + "isAny": false, + "isDict": false, + "defaultValue": "function", + "allowedValues": [], + "enumName": null, + "isOpenEnum": false + }, + { + "name": "parameters", + "typeName": { + "namespace": "Prompty", + "name": "Property" + }, + "description": "Parameters accepted by the function tool", + "samples": [ + { + "sample": { + "parameters": { + "firstName": { + "kind": "string", + "default": "Jane" + }, + "lastName": { + "kind": "string", + "default": "Doe" + }, + "question": { + "kind": "string", + "default": "What is the meaning of life?" + } + } + }, + "title": "", + "description": "" + }, + { + "sample": { + "parameters": [ + { + "name": "firstName", + "kind": "string", + "default": "Jane" + }, + { + "name": "lastName", + "kind": "string", + "default": "Doe" + }, + { + "name": "question", + "kind": "string", + "default": "What is the meaning of life?" + } + ] + }, + "title": "", + "description": "" + } + ], + "knownAs": [], + "defaultFor": [], + "isScalar": false, + "isOptional": false, + "isCollection": true, + "isAny": false, + "isDict": false, + "defaultValue": "null", + "allowedValues": [], + "enumName": null, + "isOpenEnum": false + }, + { + "name": "strict", + "typeName": { + "namespace": "", + "name": "boolean" + }, + "description": "Indicates whether the function tool enforces strict validation on its parameters", + "samples": [ + { + "sample": { + "strict": true + }, + "title": "", + "description": "" + } + ], + "knownAs": [], + "defaultFor": [], + "isScalar": true, + "isOptional": true, + "isCollection": false, + "isAny": false, + "isDict": false, + "defaultValue": true, + "allowedValues": [], + "enumName": null, + "isOpenEnum": false + } + ] + }, + { + "typeName": { + "namespace": "Prompty", + "name": "CustomTool" + }, + "description": "Represents a generic server tool that runs on a server\nThis tool kind is designed for operations that require server-side execution\nIt may include features such as authentication, data storage, and long-running processes\nThis tool kind is ideal for tasks that involve complex computations or access to secure resources\nServer tools can be used to offload heavy processing from client applications", + "base": { + "namespace": "Prompty", + "name": "Tool" + }, + "isAbstract": false, + "isProtocol": false, + "coercions": [], + "factories": [], + "methods": [], + "childTypes": [], + "properties": [ + { + "name": "kind", + "typeName": { + "namespace": "", + "name": "string" + }, + "description": "The kind identifier for server tools. This is a wildcard and can represent any server tool type not explicitly defined.", + "samples": [], + "knownAs": [], + "defaultFor": [], + "isScalar": true, + "isOptional": false, + "isCollection": false, + "isAny": false, + "isDict": false, + "defaultValue": "*", + "allowedValues": [], + "enumName": null, + "isOpenEnum": false + }, + { + "name": "connection", + "typeName": { + "namespace": "Prompty", + "name": "Connection" + }, + "description": "Connection configuration for the server tool", + "samples": [ + { + "sample": { + "connection": { + "kind": "reference" + } + }, + "title": "", + "description": "" + } + ], + "knownAs": [], + "defaultFor": [], + "isScalar": false, + "isOptional": false, + "isCollection": false, + "isAny": false, + "isDict": false, + "defaultValue": "null", + "allowedValues": [], + "enumName": null, + "isOpenEnum": false + }, + { + "name": "options", + "typeName": { + "namespace": "", + "name": "dictionary" + }, + "description": "Configuration options for the server tool", + "samples": [ + { + "sample": { + "options": { + "timeout": 30, + "retries": 3 + } + }, + "title": "", + "description": "" + } + ], + "knownAs": [], + "defaultFor": [], + "isScalar": true, + "isOptional": false, + "isCollection": false, + "isAny": false, + "isDict": true, + "defaultValue": "null", + "allowedValues": [], + "enumName": null, + "isOpenEnum": false + } + ] + }, + { + "typeName": { + "namespace": "Prompty", + "name": "McpTool" + }, + "description": "The MCP Server tool.", + "base": { + "namespace": "Prompty", + "name": "Tool" + }, + "isAbstract": false, + "isProtocol": false, + "coercions": [], + "factories": [], + "methods": [], + "childTypes": [], + "properties": [ + { + "name": "kind", + "typeName": { + "namespace": "", + "name": "string" + }, + "description": "The kind identifier for MCP tools", + "samples": [ + { + "sample": { + "kind": "mcp" + }, + "title": "", + "description": "" + } + ], + "knownAs": [], + "defaultFor": [], + "isScalar": true, + "isOptional": false, + "isCollection": false, + "isAny": false, + "isDict": false, + "defaultValue": "mcp", + "allowedValues": [], + "enumName": null, + "isOpenEnum": false + }, + { + "name": "connection", + "typeName": { + "namespace": "Prompty", + "name": "Connection" + }, + "description": "The connection configuration for the MCP tool", + "samples": [ + { + "sample": { + "connection": { + "kind": "reference" + } + }, + "title": "", + "description": "" + } + ], + "knownAs": [], + "defaultFor": [], + "isScalar": false, + "isOptional": false, + "isCollection": false, + "isAny": false, + "isDict": false, + "defaultValue": "null", + "allowedValues": [], + "enumName": null, + "isOpenEnum": false + }, + { + "name": "serverName", + "typeName": { + "namespace": "", + "name": "string" + }, + "description": "The server name of the MCP tool", + "samples": [ + { + "sample": { + "serverName": "My MCP Server" + }, + "title": "", + "description": "" + } + ], + "knownAs": [], + "defaultFor": [], + "isScalar": true, + "isOptional": false, + "isCollection": false, + "isAny": false, + "isDict": false, + "defaultValue": "null", + "allowedValues": [], + "enumName": null, + "isOpenEnum": false + }, + { + "name": "serverDescription", + "typeName": { + "namespace": "", + "name": "string" + }, + "description": "The description of the MCP tool", + "samples": [ + { + "sample": { + "serverDescription": "This tool allows access to MCP services." + }, + "title": "", + "description": "" + } + ], + "knownAs": [], + "defaultFor": [], + "isScalar": true, + "isOptional": true, + "isCollection": false, + "isAny": false, + "isDict": false, + "defaultValue": "null", + "allowedValues": [], + "enumName": null, + "isOpenEnum": false + }, + { + "name": "approvalMode", + "typeName": { + "namespace": "Prompty", + "name": "McpApprovalMode" + }, + "description": "The approval mode for the MCP tool", + "samples": [ + { + "sample": { + "approvalMode": { + "kind": "always" + } + }, + "title": "", + "description": "" + } + ], + "knownAs": [], + "defaultFor": [], + "isScalar": false, + "isOptional": false, + "isCollection": false, + "isAny": false, + "isDict": false, + "defaultValue": "null", + "allowedValues": [], + "enumName": null, + "isOpenEnum": false, + "type": { + "typeName": { + "namespace": "Prompty", + "name": "McpApprovalMode" + }, + "description": "The approval mode for MCP server tools.\nWhen kind is \"specify\", use alwaysRequireApprovalTools and neverRequireApprovalTools\nto control per-tool approval. For \"always\" and \"never\", those fields are ignored.", + "base": {}, + "isAbstract": false, + "isProtocol": false, + "coercions": [ + { + "scalar": "string", + "expansion": { + "kind": "{value}" + }, + "example": "never", + "title": "kind", + "description": "Mcp Approval Mode" + } + ], + "factories": [], + "methods": [], + "childTypes": [], + "properties": [ + { + "name": "kind", + "typeName": { + "namespace": "", + "name": "string" + }, + "description": "The approval mode: 'always', 'never', or 'specify'", + "samples": [ + { + "sample": { + "kind": "never" + }, + "title": "", + "description": "" + } + ], + "knownAs": [], + "defaultFor": [], + "isScalar": true, + "isOptional": false, + "isCollection": false, + "isAny": false, + "isDict": false, + "defaultValue": "null", + "allowedValues": [ + "always", + "never", + "specify" + ], + "enumName": "mcpApprovalModeKind", + "isOpenEnum": false + }, + { + "name": "alwaysRequireApprovalTools", + "typeName": { + "namespace": "", + "name": "string" + }, + "description": "List of tools that always require approval (only used when kind is 'specify')", + "samples": [ + { + "sample": { + "alwaysRequireApprovalTools": [ + "operation1" + ] + }, + "title": "", + "description": "" + } + ], + "knownAs": [], + "defaultFor": [], + "isScalar": true, + "isOptional": true, + "isCollection": true, + "isAny": false, + "isDict": false, + "defaultValue": "null", + "allowedValues": [], + "enumName": null, + "isOpenEnum": false + }, + { + "name": "neverRequireApprovalTools", + "typeName": { + "namespace": "", + "name": "string" + }, + "description": "List of tools that never require approval (only used when kind is 'specify')", + "samples": [ + { + "sample": { + "neverRequireApprovalTools": [ + "operation2" + ] + }, + "title": "", + "description": "" + } + ], + "knownAs": [], + "defaultFor": [], + "isScalar": true, + "isOptional": true, + "isCollection": true, + "isAny": false, + "isDict": false, + "defaultValue": "null", + "allowedValues": [], + "enumName": null, + "isOpenEnum": false + } + ] + } + }, + { + "name": "allowedTools", + "typeName": { + "namespace": "", + "name": "string" + }, + "description": "List of allowed operations or resources for the MCP tool", + "samples": [ + { + "sample": { + "allowedTools": [ + "operation1", + "operation2" + ] + }, + "title": "", + "description": "" + } + ], + "knownAs": [], + "defaultFor": [], + "isScalar": true, + "isOptional": true, + "isCollection": true, + "isAny": false, + "isDict": false, + "defaultValue": "null", + "allowedValues": [], + "enumName": null, + "isOpenEnum": false + } + ] + }, + { + "typeName": { + "namespace": "Prompty", + "name": "OpenApiTool" + }, + "description": "", + "base": { + "namespace": "Prompty", + "name": "Tool" + }, + "isAbstract": false, + "isProtocol": false, + "coercions": [], + "factories": [], + "methods": [], + "childTypes": [], + "properties": [ + { + "name": "kind", + "typeName": { + "namespace": "", + "name": "string" + }, + "description": "The kind identifier for OpenAPI tools", + "samples": [ + { + "sample": { + "kind": "openapi" + }, + "title": "", + "description": "" + } + ], + "knownAs": [], + "defaultFor": [], + "isScalar": true, + "isOptional": false, + "isCollection": false, + "isAny": false, + "isDict": false, + "defaultValue": "openapi", + "allowedValues": [], + "enumName": null, + "isOpenEnum": false + }, + { + "name": "connection", + "typeName": { + "namespace": "Prompty", + "name": "Connection" + }, + "description": "The connection configuration for the OpenAPI tool", + "samples": [ + { + "sample": { + "connection": { + "kind": "reference" + } + }, + "title": "", + "description": "" + } + ], + "knownAs": [], + "defaultFor": [], + "isScalar": false, + "isOptional": false, + "isCollection": false, + "isAny": false, + "isDict": false, + "defaultValue": "null", + "allowedValues": [], + "enumName": null, + "isOpenEnum": false + }, + { + "name": "specification", + "typeName": { + "namespace": "", + "name": "string" + }, + "description": "The full OpenAPI specification", + "samples": [ + { + "sample": { + "specification": "./openapi.json" + }, + "title": "", + "description": "" + } + ], + "knownAs": [], + "defaultFor": [], + "isScalar": true, + "isOptional": false, + "isCollection": false, + "isAny": false, + "isDict": false, + "defaultValue": "null", + "allowedValues": [], + "enumName": null, + "isOpenEnum": false + } + ] + }, + { + "typeName": { + "namespace": "Prompty", + "name": "PromptyTool" + }, + "description": "A tool that references another .prompty file to be invoked as a tool.\n\nThe child prompty is executed as a single prompt invocation. Nested agent\nloops are intentionally not started from PromptyTool.", + "base": { + "namespace": "Prompty", + "name": "Tool" + }, + "isAbstract": false, + "isProtocol": false, + "coercions": [], + "factories": [], + "methods": [], + "childTypes": [], + "properties": [ + { + "name": "kind", + "typeName": { + "namespace": "", + "name": "string" + }, + "description": "The kind identifier for prompty tools", + "samples": [ + { + "sample": { + "kind": "prompty" + }, + "title": "", + "description": "" + } + ], + "knownAs": [], + "defaultFor": [], + "isScalar": true, + "isOptional": false, + "isCollection": false, + "isAny": false, + "isDict": false, + "defaultValue": "prompty", + "allowedValues": [], + "enumName": null, + "isOpenEnum": false + }, + { + "name": "path", + "typeName": { + "namespace": "", + "name": "string" + }, + "description": "Path to the child .prompty file, relative to the parent", + "samples": [ + { + "sample": { + "path": "./summarize.prompty" + }, + "title": "", + "description": "" + } + ], + "knownAs": [], + "defaultFor": [], + "isScalar": true, + "isOptional": false, + "isCollection": false, + "isAny": false, + "isDict": false, + "defaultValue": "null", + "allowedValues": [], + "enumName": null, + "isOpenEnum": false + }, + { + "name": "mode", + "typeName": { + "namespace": "", + "name": "string" + }, + "description": "Execution mode. Only 'single' is supported; nested agent loops are not started from PromptyTool.", + "samples": [ + { + "sample": { + "mode": "single" + }, + "title": "", + "description": "" + } + ], + "knownAs": [], + "defaultFor": [], + "isScalar": true, + "isOptional": false, + "isCollection": false, + "isAny": false, + "isDict": false, + "defaultValue": "single", + "allowedValues": [], + "enumName": null, + "isOpenEnum": false + } + ] + } + ], + "properties": [ + { + "name": "name", + "typeName": { + "namespace": "", + "name": "string" + }, + "description": "Name of the tool. If a function tool, this is the function name, otherwise it is the type", + "samples": [ + { + "sample": { + "name": "my-tool" + }, + "title": "", + "description": "" + } + ], + "knownAs": [], + "defaultFor": [], + "isScalar": true, + "isOptional": false, + "isCollection": false, + "isAny": false, + "isDict": false, + "defaultValue": "null", + "allowedValues": [], + "enumName": null, + "isOpenEnum": false + }, + { + "name": "kind", + "typeName": { + "namespace": "", + "name": "string" + }, + "description": "The kind identifier for the tool", + "samples": [ + { + "sample": { + "kind": "function" + }, + "title": "", + "description": "" + } + ], + "knownAs": [], + "defaultFor": [], + "isScalar": true, + "isOptional": false, + "isCollection": false, + "isAny": false, + "isDict": false, + "defaultValue": "null", + "allowedValues": [ + "function", + "mcp", + "openapi", + "prompty" + ], + "enumName": "ToolTypes", + "isOpenEnum": true + }, + { + "name": "description", + "typeName": { + "namespace": "", + "name": "string" + }, + "description": "A short description of the tool for metadata purposes", + "samples": [ + { + "sample": { + "description": "A description of the tool" + }, + "title": "", + "description": "" + } + ], + "knownAs": [], + "defaultFor": [], + "isScalar": true, + "isOptional": true, + "isCollection": false, + "isAny": false, + "isDict": false, + "defaultValue": "null", + "allowedValues": [], + "enumName": null, + "isOpenEnum": false + }, + { + "name": "bindings", + "typeName": { + "namespace": "Prompty", + "name": "Binding" + }, + "description": "Tool argument bindings to input properties", + "samples": [ + { + "sample": { + "bindings": { + "input": "value" + } + }, + "title": "", + "description": "" + } + ], + "knownAs": [], + "defaultFor": [], + "isScalar": false, + "isOptional": true, + "isCollection": true, + "isAny": false, + "isDict": false, + "defaultValue": "null", + "allowedValues": [], + "enumName": null, + "isOpenEnum": false, + "type": { + "typeName": { + "namespace": "Prompty", + "name": "Binding" + }, + "description": "Represents a binding between an input property and a tool parameter.", + "base": {}, + "isAbstract": false, + "isProtocol": false, + "coercions": [ + { + "scalar": "string", + "expansion": { + "input": "{value}" + }, + "title": "", + "description": "" + } + ], + "factories": [], + "methods": [], + "childTypes": [], + "properties": [ + { + "name": "name", + "typeName": { + "namespace": "", + "name": "string" + }, + "description": "Name of the binding", + "samples": [ + { + "sample": { + "name": "my-tool" + }, + "title": "", + "description": "" + } + ], + "knownAs": [], + "defaultFor": [], + "isScalar": true, + "isOptional": false, + "isCollection": false, + "isAny": false, + "isDict": false, + "defaultValue": "null", + "allowedValues": [], + "enumName": null, + "isOpenEnum": false + }, + { + "name": "input", + "typeName": { + "namespace": "", + "name": "string" + }, + "description": "The input property that will be bound to the tool parameter argument", + "samples": [ + { + "sample": { + "input": "input-variable" + }, + "title": "", + "description": "" + } + ], + "knownAs": [], + "defaultFor": [], + "isScalar": true, + "isOptional": false, + "isCollection": false, + "isAny": false, + "isDict": false, + "defaultValue": "null", + "allowedValues": [], + "enumName": null, + "isOpenEnum": false + } + ] + } + } + ] + } + }, + { + "name": "template", + "typeName": { + "namespace": "Prompty", + "name": "Template" + }, + "description": "Template configuration for prompt rendering", + "samples": [ + { + "sample": { + "template": { + "format": "mustache", + "parser": "prompty" + } + }, + "title": "", + "description": "" + } + ], + "knownAs": [], + "defaultFor": [], + "isScalar": false, + "isOptional": true, + "isCollection": false, + "isAny": false, + "isDict": false, + "defaultValue": "null", + "allowedValues": [], + "enumName": null, + "isOpenEnum": false, + "type": { + "typeName": { + "namespace": "Prompty", + "name": "Template" + }, + "description": "Template model for defining prompt templates.\n\nThis model specifies the rendering engine used for slot filling prompts,\nthe parser used to process the rendered template into API-compatible format,\nand additional options for the template engine.\n\nIt allows for the creation of reusable templates that can be filled with dynamic data\nand processed to generate prompts for AI models.", + "base": {}, + "isAbstract": false, + "isProtocol": false, + "coercions": [], + "factories": [], + "methods": [], + "childTypes": [], + "properties": [ + { + "name": "format", + "typeName": { + "namespace": "Prompty", + "name": "FormatConfig" + }, + "description": "Template rendering engine used for slot filling prompts (e.g., mustache, jinja2)", + "samples": [ + { + "sample": { + "format": { + "kind": "mustache" + } + }, + "title": "", + "description": "" + } + ], + "knownAs": [], + "defaultFor": [], + "isScalar": false, + "isOptional": false, + "isCollection": false, + "isAny": false, + "isDict": false, + "defaultValue": "null", + "allowedValues": [], + "enumName": null, + "isOpenEnum": false, + "type": { + "typeName": { + "namespace": "Prompty", + "name": "FormatConfig" + }, + "description": "Template format definition", + "base": {}, + "isAbstract": false, + "isProtocol": false, + "discriminator": "kind", + "coercions": [ + { + "scalar": "string", + "expansion": { + "kind": "{value}" + }, + "title": "format", + "description": "Simple construction with just a \"kind\" string" + } + ], + "factories": [], + "methods": [], + "childTypes": [], + "properties": [ + { + "name": "kind", + "typeName": { + "namespace": "", + "name": "string" + }, + "description": "Template rendering engine used for slot filling prompts (e.g., mustache, jinja2)", + "samples": [ + { + "sample": { + "kind": "mustache" + }, + "title": "", + "description": "" + } + ], + "knownAs": [], + "defaultFor": [], + "isScalar": true, + "isOptional": false, + "isCollection": false, + "isAny": false, + "isDict": false, + "defaultValue": "*", + "allowedValues": [], + "enumName": null, + "isOpenEnum": false + }, + { + "name": "strict", + "typeName": { + "namespace": "", + "name": "boolean" + }, + "description": "Whether the template can emit structural text for parsing output", + "samples": [ + { + "sample": { + "strict": true + }, + "title": "", + "description": "" + } + ], + "knownAs": [], + "defaultFor": [], + "isScalar": true, + "isOptional": true, + "isCollection": false, + "isAny": false, + "isDict": false, + "defaultValue": "null", + "allowedValues": [], + "enumName": null, + "isOpenEnum": false + }, + { + "name": "options", + "typeName": { + "namespace": "", + "name": "dictionary" + }, + "description": "Options for the template engine", + "samples": [ + { + "sample": { + "options": { + "key": "value" + } + }, + "title": "", + "description": "" + } + ], + "knownAs": [], + "defaultFor": [], + "isScalar": true, + "isOptional": true, + "isCollection": false, + "isAny": false, + "isDict": true, + "defaultValue": "null", + "allowedValues": [], + "enumName": null, + "isOpenEnum": false + } + ] + } + }, + { + "name": "parser", + "typeName": { + "namespace": "Prompty", + "name": "ParserConfig" + }, + "description": "Parser used to process the rendered template into API-compatible format", + "samples": [ + { + "sample": { + "parser": { + "kind": "mustache" + } + }, + "title": "", + "description": "" + } + ], + "knownAs": [], + "defaultFor": [], + "isScalar": false, + "isOptional": false, + "isCollection": false, + "isAny": false, + "isDict": false, + "defaultValue": "null", + "allowedValues": [], + "enumName": null, + "isOpenEnum": false, + "type": { + "typeName": { + "namespace": "Prompty", + "name": "ParserConfig" + }, + "description": "Template parser definition", + "base": {}, + "isAbstract": false, + "isProtocol": false, + "discriminator": "kind", + "coercions": [ + { + "scalar": "string", + "expansion": { + "kind": "{value}" + }, + "title": "parser", + "description": "Simple construction with just a \"kind\" string" + } + ], + "factories": [], + "methods": [], + "childTypes": [], + "properties": [ + { + "name": "kind", + "typeName": { + "namespace": "", + "name": "string" + }, + "description": "Parser used to process the rendered template into API-compatible format", + "samples": [ + { + "sample": { + "kind": "prompty" + }, + "title": "", + "description": "" + } + ], + "knownAs": [], + "defaultFor": [], + "isScalar": true, + "isOptional": false, + "isCollection": false, + "isAny": false, + "isDict": false, + "defaultValue": "*", + "allowedValues": [], + "enumName": null, + "isOpenEnum": false + }, + { + "name": "options", + "typeName": { + "namespace": "", + "name": "dictionary" + }, + "description": "Options for the parser", + "samples": [ + { + "sample": { + "options": { + "key": "value" + } + }, + "title": "", + "description": "" + } + ], + "knownAs": [], + "defaultFor": [], + "isScalar": true, + "isOptional": true, + "isCollection": false, + "isAny": false, + "isDict": true, + "defaultValue": "null", + "allowedValues": [], + "enumName": null, + "isOpenEnum": false + } + ] + } + } + ] + } + }, + { + "name": "instructions", + "typeName": { + "namespace": "", + "name": "string" + }, + "description": "Clear directions on what the prompt should do. In .prompty files, this comes from the markdown body.", + "samples": [ + { + "sample": { + "instructions": "system:\nYou are an AI assistant who helps people find information.\nAs the assistant, you answer questions briefly, succinctly,\nand in a personable manner using markdown and even add some \npersonal flair with appropriate emojis.\n\n# Customer\nYou are helping {{firstName}} {{lastName}} to find answers to \ntheir questions. Use their name to address them in your responses.\nuser:\n{{question}}" + }, + "title": "", + "description": "" + } + ], + "knownAs": [], + "defaultFor": [], + "isScalar": true, + "isOptional": true, + "isCollection": false, + "isAny": false, + "isDict": false, + "defaultValue": "null", + "allowedValues": [], + "enumName": null, + "isOpenEnum": false + } + ] +} \ No newline at end of file diff --git a/schema/tspconfig.yaml b/schema/tspconfig.yaml index 680a1db2..a4be8b63 100644 --- a/schema/tspconfig.yaml +++ b/schema/tspconfig.yaml @@ -1,13 +1,13 @@ emit: - "@typespec/json-schema" - - "prompty-emitter" + - "@typra/emitter" options: "@typespec/json-schema": emitter-output-dir: "{cwd}/../vscode/prompty/schemas" emitAllModels: true emitAllRefs: true file-type: yaml - "prompty-emitter": + "@typra/emitter": emitter-output-dir: "{cwd}/tsp-output" root-object: "Prompty.Prompty" emit-targets: @@ -27,6 +27,7 @@ options: output-dir: "../runtime/go/prompty/model" test-dir: "../runtime/go/prompty/model" import-path: "prompty/model" + package-name: "prompty" - type: Rust output-dir: "../runtime/rust/prompty/src/model" test-dir: "../runtime/rust/prompty/tests/model" diff --git a/spec/spec.md b/spec/spec.md index fab60f86..10feaa3c 100644 --- a/spec/spec.md +++ b/spec/spec.md @@ -3804,6 +3804,48 @@ for (const userInput of messages) { } ``` +### §14.7 Reference Harness Contracts + +The reference harness turn runner is a lower-level deterministic orchestration +surface, not a model/provider runtime. Its structural contract is owned by +TypeSpec and emitted through Typra: + +| Contract | Purpose | +| -------- | ------- | +| `TurnOptions` | Canonical deterministic turn execution options | +| `TurnModelRequest` / `TurnModelResponse` | Boundary between orchestration and injected model/provider behavior | +| `RunTurnRequest` / `RunTurnResult` | Request/result shape for one deterministic reference turn | +| `ReplayJournalRecord` | Stable projection of replayable journal records | +| `ReplayVerificationRequest` / `ReplayVerificationResult` | Contract for normalized replay verification | + +Runtime reference implementations MUST compose the generated contracts with the +existing harness protocols (`EventSink`, `EventJournalWriter`, `CheckpointStore`, +`PermissionResolver`, and `HostToolExecutor`). They MUST NOT own LLM/provider +execution; that behavior stays behind injected callbacks. + +Replay verification compares `ReplayJournalRecord` values rather than raw event +payloads. Raw journals MAY include runtime-only fields such as durations, +telemetry, or provider payloads, but the normalized replay record MUST contain +only deterministic orchestration fields. + +### §14.8 Contract Ownership Audit + +Anything structural and cross-runtime SHOULD be represented in TypeSpec before +runtime code is added. Current harness ownership is: + +| Area | TypeSpec-owned | Runtime-owned | +| ---- | -------------- | ------------- | +| Turn runner inputs/results | `RunTurnRequest`, `RunTurnResult`, `RunTurnStatus` | Loop control and callback invocation | +| Model callback boundary | `TurnModelRequest`, `TurnModelResponse` | Provider-specific callback implementation | +| Events and summaries | `TurnEvent`, `SessionEvent`, `SessionSummary` | Emission order and durable write mechanics | +| Checkpoints | `Checkpoint`, `CheckpointStore` | Storage adapter behavior | +| Permissions and tools | `PermissionRequest`, `PermissionDecision`, `HostToolRequest`, `HostToolResult` | Host policy and execution handlers | +| Replay verification | `ReplayJournalRecord`, `ReplayVerificationRequest`, `ReplayVerificationResult`, `ReplayMismatch` | Normalization from raw journals and comparison execution | + +The remaining intentional runtime-only footprint is behavior that cannot be +expressed as a structural schema: clocks/ID factories, callback dispatch, +I/O adapters, permission policy, host tool execution, and journal normalization. + --- ## §15 Model Discovery diff --git a/spec/vectors/harness/replay_vectors.json b/spec/vectors/harness/replay_vectors.json new file mode 100644 index 00000000..fdf2eeb8 --- /dev/null +++ b/spec/vectors/harness/replay_vectors.json @@ -0,0 +1,108 @@ +{ + "version": 1, + "description": "Deterministic normalized journal sequences for the reference turn runner.", + "clock": "2026-06-28T00:00:00Z", + "sessionId": "session-1", + "turnId": "turn-1", + "scenarios": [ + { + "name": "no_tool", + "inputs": { "name": "Ada" }, + "maxIterations": 3, + "expected": [ + "session:session_start:session-1:turn-1", + "turn:turn_start:0", + "turn:llm_start:0", + "turn:llm_complete:0", + "session:checkpoint_created:session-1:turn-1", + "turn:turn_end:1:success", + "session:session_end:session-1:turn-1:success", + "summary:session-1:success:turns=1:checkpoints=1" + ] + }, + { + "name": "tool_success", + "expected": [ + "session:session_start:session-1:turn-1", + "turn:turn_start:0", + "turn:llm_start:0", + "turn:llm_complete:0", + "session:checkpoint_created:session-1:turn-1", + "turn:permission_requested:0:exec-1-permission", + "turn:permission_completed:0:true", + "turn:tool_execution_start:0:add", + "turn:tool_execution_complete:0:add:true", + "turn:tool_result:0:add:true", + "turn:messages_updated:0", + "turn:llm_start:1", + "turn:llm_complete:1", + "session:checkpoint_created:session-1:turn-1", + "turn:turn_end:2:success", + "session:session_end:session-1:turn-1:success", + "summary:session-1:success:turns=1:checkpoints=2" + ] + }, + { + "name": "permission_denied", + "expected": [ + "session:session_start:session-1:turn-1", + "turn:turn_start:0", + "turn:llm_start:0", + "turn:llm_complete:0", + "session:checkpoint_created:session-1:turn-1", + "turn:permission_requested:0:exec-1-permission", + "turn:permission_completed:0:false", + "turn:messages_updated:0", + "turn:llm_start:1", + "turn:llm_complete:1", + "session:checkpoint_created:session-1:turn-1", + "turn:turn_end:2:success", + "session:session_end:session-1:turn-1:success", + "summary:session-1:success:turns=1:checkpoints=2" + ] + }, + { + "name": "tool_failure", + "expected": [ + "session:session_start:session-1:turn-1", + "turn:turn_start:0", + "turn:llm_start:0", + "turn:llm_complete:0", + "session:checkpoint_created:session-1:turn-1", + "turn:permission_requested:0:exec-1-permission", + "turn:permission_completed:0:true", + "turn:tool_execution_start:0:fail", + "turn:tool_execution_complete:0:fail:false:exception", + "turn:tool_result:0:fail:false:exception", + "turn:messages_updated:0", + "turn:llm_start:1", + "turn:llm_complete:1", + "session:checkpoint_created:session-1:turn-1", + "turn:turn_end:2:success", + "session:session_end:session-1:turn-1:success", + "summary:session-1:success:turns=1:checkpoints=2" + ] + }, + { + "name": "max_iterations", + "maxIterations": 1, + "expected": [ + "session:session_start:session-1:turn-1", + "turn:turn_start:0", + "turn:llm_start:0", + "turn:llm_complete:0", + "session:checkpoint_created:session-1:turn-1", + "turn:permission_requested:0:exec-1-permission", + "turn:permission_completed:0:true", + "turn:tool_execution_start:0:add", + "turn:tool_execution_complete:0:add:true", + "turn:tool_result:0:add:true", + "turn:messages_updated:0", + "turn:error:1:max_iterations", + "turn:turn_end:1:error", + "session:session_end:session-1:turn-1:error", + "summary:session-1:error:turns=1:checkpoints=1" + ] + } + ] +} diff --git a/vscode/prompty/schemas/Checkpoint.yaml b/vscode/prompty/schemas/Checkpoint.yaml new file mode 100644 index 00000000..41d257f5 --- /dev/null +++ b/vscode/prompty/schemas/Checkpoint.yaml @@ -0,0 +1,42 @@ +$schema: https://json-schema.org/draft/2020-12/schema +$id: Checkpoint.yaml +type: object +properties: + id: + type: string + description: Stable checkpoint identifier + sessionId: + type: string + description: Stable session identifier + turnId: + type: string + description: Associated turn identifier, when the checkpoint was created inside a turn + checkpointNumber: + type: integer + minimum: -2147483648 + maximum: 2147483647 + description: Monotonic checkpoint number within the session + title: + type: string + description: Short checkpoint title + overview: + type: string + description: Short human-readable overview + state: + $ref: RecordUnknown.yaml + description: Portable checkpoint state needed to resume or hand off the session + summary: + type: string + description: Optional host-authored summary or handoff note + metadata: + $ref: RecordUnknown.yaml + description: Host-defined checkpoint metadata + createdAt: + type: string + description: ISO 8601 UTC timestamp when the checkpoint was created + redaction: + $ref: RedactionMetadata.yaml + description: Redaction state for sensitive checkpoint fields +required: + - title +description: A persisted handoff point for a harness session. diff --git a/vscode/prompty/schemas/CheckpointStore.yaml b/vscode/prompty/schemas/CheckpointStore.yaml new file mode 100644 index 00000000..6e6b4f50 --- /dev/null +++ b/vscode/prompty/schemas/CheckpointStore.yaml @@ -0,0 +1,5 @@ +$schema: https://json-schema.org/draft/2020-12/schema +$id: CheckpointStore.yaml +type: object +properties: {} +description: Stores and retrieves resumable session checkpoints. diff --git a/vscode/prompty/schemas/CompactionCompletePayload.yaml b/vscode/prompty/schemas/CompactionCompletePayload.yaml index cdf0551f..0e1ecded 100644 --- a/vscode/prompty/schemas/CompactionCompletePayload.yaml +++ b/vscode/prompty/schemas/CompactionCompletePayload.yaml @@ -12,6 +12,11 @@ properties: minimum: -2147483648 maximum: 2147483647 description: Number of messages remaining after compaction + summaryLength: + type: integer + minimum: -2147483648 + maximum: 2147483647 + description: Length of the generated summary, when a summarization strategy is used required: - removed - remaining diff --git a/vscode/prompty/schemas/CompactionStartPayload.yaml b/vscode/prompty/schemas/CompactionStartPayload.yaml new file mode 100644 index 00000000..124f3c52 --- /dev/null +++ b/vscode/prompty/schemas/CompactionStartPayload.yaml @@ -0,0 +1,12 @@ +$schema: https://json-schema.org/draft/2020-12/schema +$id: CompactionStartPayload.yaml +type: object +properties: + droppedCount: + type: integer + minimum: -2147483648 + maximum: 2147483647 + description: Number of messages selected for compaction +required: + - droppedCount +description: Payload for "compaction_start" events — context compaction is beginning. diff --git a/vscode/prompty/schemas/DoneEventPayload.yaml b/vscode/prompty/schemas/DoneEventPayload.yaml index e5275ae2..eaed7bb4 100644 --- a/vscode/prompty/schemas/DoneEventPayload.yaml +++ b/vscode/prompty/schemas/DoneEventPayload.yaml @@ -3,8 +3,7 @@ $id: DoneEventPayload.yaml type: object properties: response: - type: string - description: The final text response from the LLM + description: The final response from the LLM after processing messages: type: array items: diff --git a/vscode/prompty/schemas/ErrorEventPayload.yaml b/vscode/prompty/schemas/ErrorEventPayload.yaml index dd95d5d5..42538f12 100644 --- a/vscode/prompty/schemas/ErrorEventPayload.yaml +++ b/vscode/prompty/schemas/ErrorEventPayload.yaml @@ -5,6 +5,12 @@ properties: message: type: string description: Human-readable error description + errorKind: + type: string + description: Stable machine-readable error category + phase: + type: string + description: Operation or phase where the error occurred required: - message description: Payload for "error" events — an error occurred during the loop. diff --git a/vscode/prompty/schemas/EventJournalWriter.yaml b/vscode/prompty/schemas/EventJournalWriter.yaml new file mode 100644 index 00000000..cb0b5665 --- /dev/null +++ b/vscode/prompty/schemas/EventJournalWriter.yaml @@ -0,0 +1,5 @@ +$schema: https://json-schema.org/draft/2020-12/schema +$id: EventJournalWriter.yaml +type: object +properties: {} +description: Persists typed events to a durable replay journal. diff --git a/vscode/prompty/schemas/EventSink.yaml b/vscode/prompty/schemas/EventSink.yaml new file mode 100644 index 00000000..0f3c184e --- /dev/null +++ b/vscode/prompty/schemas/EventSink.yaml @@ -0,0 +1,5 @@ +$schema: https://json-schema.org/draft/2020-12/schema +$id: EventSink.yaml +type: object +properties: {} +description: Receives typed turn and session events from a harness. diff --git a/vscode/prompty/schemas/HarnessContext.yaml b/vscode/prompty/schemas/HarnessContext.yaml new file mode 100644 index 00000000..61b8cb0e --- /dev/null +++ b/vscode/prompty/schemas/HarnessContext.yaml @@ -0,0 +1,17 @@ +$schema: https://json-schema.org/draft/2020-12/schema +$id: HarnessContext.yaml +type: object +properties: + cwd: + type: string + description: Current working directory for the harness + gitRoot: + type: string + description: Git repository root, when known + metadata: + $ref: RecordUnknown.yaml + description: Host-defined context metadata, such as source-control or sandbox details +description: |- + Execution context associated with a harness session. Host-specific + environments can store detailed profiles in metadata without making the core + contract depend on one source-control provider or workspace shape. diff --git a/vscode/prompty/schemas/HookEndPayload.yaml b/vscode/prompty/schemas/HookEndPayload.yaml new file mode 100644 index 00000000..37b56ecc --- /dev/null +++ b/vscode/prompty/schemas/HookEndPayload.yaml @@ -0,0 +1,37 @@ +$schema: https://json-schema.org/draft/2020-12/schema +$id: HookEndPayload.yaml +type: object +properties: + hookInvocationId: + type: string + description: Stable hook invocation identifier + hookType: + type: string + description: Host-defined hook type + scope: + anyOf: + - type: string + const: turn + - type: string + const: session + description: Whether the hook is scoped to a turn or the outer session + success: + type: boolean + description: Whether the hook completed successfully + output: + $ref: RecordUnknown.yaml + description: Hook output after host-side sanitization + durationMs: + type: number + description: Hook execution duration in milliseconds + error: + type: string + description: Human-readable error when success is false + redaction: + $ref: RedactionMetadata.yaml + description: Redaction state for sensitive hook output fields +required: + - hookInvocationId + - hookType + - success +description: Payload for "hook_end" events — a host lifecycle hook finished. diff --git a/vscode/prompty/schemas/HookStartPayload.yaml b/vscode/prompty/schemas/HookStartPayload.yaml new file mode 100644 index 00000000..304cf577 --- /dev/null +++ b/vscode/prompty/schemas/HookStartPayload.yaml @@ -0,0 +1,27 @@ +$schema: https://json-schema.org/draft/2020-12/schema +$id: HookStartPayload.yaml +type: object +properties: + hookInvocationId: + type: string + description: Stable hook invocation identifier + hookType: + type: string + description: Host-defined hook type + scope: + anyOf: + - type: string + const: turn + - type: string + const: session + description: Whether the hook is scoped to a turn or the outer session + input: + $ref: RecordUnknown.yaml + description: Hook input after host-side sanitization + redaction: + $ref: RedactionMetadata.yaml + description: Redaction state for sensitive hook input fields +required: + - hookInvocationId + - hookType +description: Payload for "hook_start" events — a host lifecycle hook is beginning. diff --git a/vscode/prompty/schemas/HostToolExecutor.yaml b/vscode/prompty/schemas/HostToolExecutor.yaml new file mode 100644 index 00000000..63f870c5 --- /dev/null +++ b/vscode/prompty/schemas/HostToolExecutor.yaml @@ -0,0 +1,5 @@ +$schema: https://json-schema.org/draft/2020-12/schema +$id: HostToolExecutor.yaml +type: object +properties: {} +description: Executes host tools after policy and permission checks. diff --git a/vscode/prompty/schemas/HostToolRequest.yaml b/vscode/prompty/schemas/HostToolRequest.yaml new file mode 100644 index 00000000..5ddd117d --- /dev/null +++ b/vscode/prompty/schemas/HostToolRequest.yaml @@ -0,0 +1,22 @@ +$schema: https://json-schema.org/draft/2020-12/schema +$id: HostToolRequest.yaml +type: object +properties: + requestId: + type: string + description: Stable host execution request identifier + toolCallId: + type: string + description: Associated model tool call identifier, when available + toolName: + type: string + description: Name of the host tool being executed + arguments: + $ref: RecordUnknown.yaml + description: Tool arguments after host-side sanitization + workingDirectory: + type: string + description: Working directory or execution scope for the tool +required: + - toolName +description: Request passed to a host tool executor after policy and permission checks. diff --git a/vscode/prompty/schemas/HostToolResult.yaml b/vscode/prompty/schemas/HostToolResult.yaml new file mode 100644 index 00000000..07f86d34 --- /dev/null +++ b/vscode/prompty/schemas/HostToolResult.yaml @@ -0,0 +1,36 @@ +$schema: https://json-schema.org/draft/2020-12/schema +$id: HostToolResult.yaml +type: object +properties: + requestId: + type: string + description: Stable host execution request identifier + toolCallId: + type: string + description: Associated model tool call identifier, when available + toolName: + type: string + description: Name of the host tool that executed + success: + type: boolean + description: Whether the host execution completed successfully + result: + description: Host-normalized execution result + exitCode: + type: integer + minimum: -2147483648 + maximum: 2147483647 + description: Process or host exit code, when applicable + durationMs: + type: number + description: Tool execution duration in milliseconds + errorKind: + type: string + description: Machine-readable error category when success is false + telemetry: + $ref: RecordUnknown.yaml + description: Host-specific telemetry for the execution +required: + - toolName + - success +description: Result returned by a host tool executor. diff --git a/vscode/prompty/schemas/LlmCompletePayload.yaml b/vscode/prompty/schemas/LlmCompletePayload.yaml new file mode 100644 index 00000000..b586f2ec --- /dev/null +++ b/vscode/prompty/schemas/LlmCompletePayload.yaml @@ -0,0 +1,17 @@ +$schema: https://json-schema.org/draft/2020-12/schema +$id: LlmCompletePayload.yaml +type: object +properties: + requestId: + type: string + description: Provider request identifier, when supplied by the SDK/API + serviceRequestId: + type: string + description: Service request identifier, when supplied by the SDK/API + usage: + $ref: TokenUsage.yaml + description: Token usage reported by the provider + durationMs: + type: number + description: LLM call duration in milliseconds +description: Payload for "llm_complete" events — an LLM request completed. diff --git a/vscode/prompty/schemas/LlmStartPayload.yaml b/vscode/prompty/schemas/LlmStartPayload.yaml new file mode 100644 index 00000000..4f6adb80 --- /dev/null +++ b/vscode/prompty/schemas/LlmStartPayload.yaml @@ -0,0 +1,21 @@ +$schema: https://json-schema.org/draft/2020-12/schema +$id: LlmStartPayload.yaml +type: object +properties: + provider: + type: string + description: Provider identifier used for the request + modelId: + type: string + description: Model or deployment identifier used for the request + messageCount: + type: integer + minimum: -2147483648 + maximum: 2147483647 + description: Number of messages sent to the provider + attempt: + type: integer + minimum: -2147483648 + maximum: 2147483647 + description: Retry attempt number, zero for the initial attempt +description: Payload for "llm_start" events — an LLM request is about to be sent. diff --git a/vscode/prompty/schemas/MessagesUpdatedPayload.yaml b/vscode/prompty/schemas/MessagesUpdatedPayload.yaml index ba647364..450f5748 100644 --- a/vscode/prompty/schemas/MessagesUpdatedPayload.yaml +++ b/vscode/prompty/schemas/MessagesUpdatedPayload.yaml @@ -7,6 +7,17 @@ properties: items: $ref: Message.yaml description: The current full message list after the update -required: - - messages + reason: + type: string + description: Why the message list changed + appended: + type: array + items: + $ref: Message.yaml + description: Messages appended by this update, when available + removed: + type: integer + minimum: -2147483648 + maximum: 2147483647 + description: Number of messages removed by this update, when available description: Payload for "messages_updated" events — the conversation state has changed. diff --git a/vscode/prompty/schemas/PermissionCompletedPayload.yaml b/vscode/prompty/schemas/PermissionCompletedPayload.yaml new file mode 100644 index 00000000..12938e84 --- /dev/null +++ b/vscode/prompty/schemas/PermissionCompletedPayload.yaml @@ -0,0 +1,29 @@ +$schema: https://json-schema.org/draft/2020-12/schema +$id: PermissionCompletedPayload.yaml +type: object +properties: + requestId: + type: string + description: Stable permission request identifier + toolCallId: + type: string + description: Associated tool call identifier, when the permission gated a tool call + permission: + type: string + description: Permission/action name that was decided + approved: + type: boolean + description: Whether the requested permission was approved + reason: + type: string + description: Decision reason, if available + result: + $ref: RecordUnknown.yaml + description: Host-specific decision result, such as a durable approval token or denial details + redaction: + $ref: RedactionMetadata.yaml + description: Redaction state for sensitive decision fields +required: + - permission + - approved +description: Payload for permission completion events — an approval decision was made. diff --git a/vscode/prompty/schemas/PermissionDecision.yaml b/vscode/prompty/schemas/PermissionDecision.yaml new file mode 100644 index 00000000..55b25174 --- /dev/null +++ b/vscode/prompty/schemas/PermissionDecision.yaml @@ -0,0 +1,26 @@ +$schema: https://json-schema.org/draft/2020-12/schema +$id: PermissionDecision.yaml +type: object +properties: + requestId: + type: string + description: Stable permission request identifier + toolCallId: + type: string + description: Associated tool call identifier, when the permission gated a tool call + permission: + type: string + description: Permission/action name that was decided + approved: + type: boolean + description: Whether the requested permission was approved + reason: + type: string + description: Decision reason, if available + result: + $ref: RecordUnknown.yaml + description: Host-specific decision result, such as a durable approval token or denial details +required: + - permission + - approved +description: Decision returned by a permission resolver. diff --git a/vscode/prompty/schemas/PermissionRequest.yaml b/vscode/prompty/schemas/PermissionRequest.yaml new file mode 100644 index 00000000..4239ae8e --- /dev/null +++ b/vscode/prompty/schemas/PermissionRequest.yaml @@ -0,0 +1,30 @@ +$schema: https://json-schema.org/draft/2020-12/schema +$id: PermissionRequest.yaml +type: object +properties: + requestId: + type: string + description: Stable permission request identifier + toolCallId: + type: string + description: Associated tool call identifier, when the permission gates a tool call + permission: + type: string + description: Permission/action name being requested + target: + type: string + description: Resource or tool the permission applies to + details: + $ref: RecordUnknown.yaml + description: Additional host-specific permission details + promptRequest: + type: string + description: Human-readable prompt or rationale that can be shown to an approval UI + policy: + $ref: RecordUnknown.yaml + description: Policy metadata used to evaluate or explain the permission request +required: + - permission +description: |- + Request passed to a permission resolver. This is the live protocol shape; the + event payloads above can include trace-only metadata such as redaction state. diff --git a/vscode/prompty/schemas/PermissionRequestedPayload.yaml b/vscode/prompty/schemas/PermissionRequestedPayload.yaml new file mode 100644 index 00000000..a96bef7e --- /dev/null +++ b/vscode/prompty/schemas/PermissionRequestedPayload.yaml @@ -0,0 +1,31 @@ +$schema: https://json-schema.org/draft/2020-12/schema +$id: PermissionRequestedPayload.yaml +type: object +properties: + requestId: + type: string + description: Stable permission request identifier + toolCallId: + type: string + description: Associated tool call identifier, when the permission gates a tool call + permission: + type: string + description: Permission/action name being requested + target: + type: string + description: Resource or tool the permission applies to + details: + $ref: RecordUnknown.yaml + description: Additional host-specific permission details + promptRequest: + type: string + description: Human-readable prompt or rationale that can be shown to an approval UI + policy: + $ref: RecordUnknown.yaml + description: Policy metadata used to evaluate or explain the permission request + redaction: + $ref: RedactionMetadata.yaml + description: Redaction state for sensitive request fields +required: + - permission +description: Payload for permission request events — a host is asked to approve an action. diff --git a/vscode/prompty/schemas/PermissionResolver.yaml b/vscode/prompty/schemas/PermissionResolver.yaml new file mode 100644 index 00000000..7bc3d895 --- /dev/null +++ b/vscode/prompty/schemas/PermissionResolver.yaml @@ -0,0 +1,5 @@ +$schema: https://json-schema.org/draft/2020-12/schema +$id: PermissionResolver.yaml +type: object +properties: {} +description: Resolves host permission requests for potentially sensitive actions. diff --git a/vscode/prompty/schemas/Prompty.yaml b/vscode/prompty/schemas/Prompty.yaml index 9cc19515..b5801063 100644 --- a/vscode/prompty/schemas/Prompty.yaml +++ b/vscode/prompty/schemas/Prompty.yaml @@ -196,4 +196,10 @@ description: |- This is the single root type for the Prompty schema — there is no abstract base class or kind discriminator. A .prompty file always produces a Prompty instance. + + Runtime loaders may resolve frontmatter references such as `${env:VAR}` and + `${file:relative/path}`. File references must be treated as a host-controlled + capability: by default they are scoped to the containing .prompty file's + directory tree after canonicalization, and any additional allowed roots must + be supplied by the host application's load options rather than frontmatter. additionalProperties: false diff --git a/vscode/prompty/schemas/RedactedField.yaml b/vscode/prompty/schemas/RedactedField.yaml new file mode 100644 index 00000000..53887a45 --- /dev/null +++ b/vscode/prompty/schemas/RedactedField.yaml @@ -0,0 +1,27 @@ +$schema: https://json-schema.org/draft/2020-12/schema +$id: RedactedField.yaml +type: object +properties: + path: + type: string + description: JSONPath-like field path, relative to the containing payload + mode: + anyOf: + - type: string + const: none + - type: string + const: redacted + - type: string + const: hashed + - type: string + const: summary + - type: string + const: reference + description: How the field was represented + reason: + type: string + description: Human-readable reason or policy that caused this handling +required: + - path + - mode +description: Redaction handling for one JSON-shaped field path. diff --git a/vscode/prompty/schemas/RedactionMetadata.yaml b/vscode/prompty/schemas/RedactionMetadata.yaml new file mode 100644 index 00000000..6a398f3e --- /dev/null +++ b/vscode/prompty/schemas/RedactionMetadata.yaml @@ -0,0 +1,17 @@ +$schema: https://json-schema.org/draft/2020-12/schema +$id: RedactionMetadata.yaml +type: object +properties: + sanitized: + type: boolean + default: false + description: Whether the payload has been sanitized for persistence or external display + fields: + type: array + items: + $ref: RedactedField.yaml + description: Field-level redaction details + policy: + type: string + description: Host policy or sanitizer version that produced this metadata +description: Metadata describing whether and how a payload was sanitized. diff --git a/vscode/prompty/schemas/ReplayJournalRecord.yaml b/vscode/prompty/schemas/ReplayJournalRecord.yaml new file mode 100644 index 00000000..c4331c5c --- /dev/null +++ b/vscode/prompty/schemas/ReplayJournalRecord.yaml @@ -0,0 +1,66 @@ +$schema: https://json-schema.org/draft/2020-12/schema +$id: ReplayJournalRecord.yaml +type: object +properties: + kind: + anyOf: + - type: string + const: session + - type: string + const: turn + - type: string + const: summary + description: Journal record kind + type: + type: string + description: Turn or session event type, when kind is not summary + sessionId: + type: string + description: Stable harness session identifier + turnId: + type: string + description: Stable turn identifier within the session + iteration: + type: integer + minimum: -2147483648 + maximum: 2147483647 + description: Zero-based model loop iteration for turn records + status: + anyOf: + - type: string + const: success + - type: string + const: error + - type: string + const: cancelled + description: Final semantic status for turn/session/summary records + requestId: + type: string + description: Permission request identifier for permission request records + toolName: + type: string + description: Host tool name for tool execution/result records + success: + type: boolean + description: Whether a permission or host tool operation succeeded + errorKind: + type: string + description: Stable error discriminator for failed records + turns: + type: integer + minimum: -2147483648 + maximum: 2147483647 + description: Number of turns represented by a summary record + checkpoints: + type: integer + minimum: -2147483648 + maximum: 2147483647 + description: Number of checkpoints represented by a summary record +required: + - kind +description: |- + Stable, replay-comparable projection of a journal record. + + Runtime journal records may carry additional payload fields, durations, telemetry, + or provider-specific data. Replay verification compares this normalized shape so + deterministic orchestration semantics are mechanically shared across runtimes. diff --git a/vscode/prompty/schemas/ReplayMismatch.yaml b/vscode/prompty/schemas/ReplayMismatch.yaml new file mode 100644 index 00000000..00d36bf8 --- /dev/null +++ b/vscode/prompty/schemas/ReplayMismatch.yaml @@ -0,0 +1,22 @@ +$schema: https://json-schema.org/draft/2020-12/schema +$id: ReplayMismatch.yaml +type: object +properties: + index: + type: integer + minimum: -2147483648 + maximum: 2147483647 + description: Zero-based record index where the mismatch was found + expected: + $ref: ReplayJournalRecord.yaml + description: Expected record at this index, when present + actual: + $ref: ReplayJournalRecord.yaml + description: Actual record at this index, when present + message: + type: string + description: Human-readable mismatch explanation +required: + - index + - message +description: A single mismatch produced by replay verification. diff --git a/vscode/prompty/schemas/ReplayVerificationRequest.yaml b/vscode/prompty/schemas/ReplayVerificationRequest.yaml new file mode 100644 index 00000000..23c2f735 --- /dev/null +++ b/vscode/prompty/schemas/ReplayVerificationRequest.yaml @@ -0,0 +1,20 @@ +$schema: https://json-schema.org/draft/2020-12/schema +$id: ReplayVerificationRequest.yaml +type: object +properties: + expected: + type: array + items: + $ref: ReplayJournalRecord.yaml + default: [] + description: Expected normalized replay records + actual: + type: array + items: + $ref: ReplayJournalRecord.yaml + default: [] + description: Actual normalized replay records +required: + - expected + - actual +description: Request accepted by a replay verifier implementation. diff --git a/vscode/prompty/schemas/ReplayVerificationResult.yaml b/vscode/prompty/schemas/ReplayVerificationResult.yaml new file mode 100644 index 00000000..9ca6a281 --- /dev/null +++ b/vscode/prompty/schemas/ReplayVerificationResult.yaml @@ -0,0 +1,32 @@ +$schema: https://json-schema.org/draft/2020-12/schema +$id: ReplayVerificationResult.yaml +type: object +properties: + status: + anyOf: + - type: string + const: passed + - type: string + const: failed + description: Replay verification status + mismatches: + type: array + items: + $ref: ReplayMismatch.yaml + default: [] + description: Record mismatches, empty when verification passed + expectedCount: + type: integer + minimum: -2147483648 + maximum: 2147483647 + description: Number of expected records + actualCount: + type: integer + minimum: -2147483648 + maximum: 2147483647 + description: Number of actual records +required: + - status + - expectedCount + - actualCount +description: Result returned by a replay verifier implementation. diff --git a/vscode/prompty/schemas/RetryPayload.yaml b/vscode/prompty/schemas/RetryPayload.yaml new file mode 100644 index 00000000..edc88627 --- /dev/null +++ b/vscode/prompty/schemas/RetryPayload.yaml @@ -0,0 +1,27 @@ +$schema: https://json-schema.org/draft/2020-12/schema +$id: RetryPayload.yaml +type: object +properties: + operation: + type: string + description: Operation being retried + attempt: + type: integer + minimum: -2147483648 + maximum: 2147483647 + description: Attempt number about to run + maxAttempts: + type: integer + minimum: -2147483648 + maximum: 2147483647 + description: Maximum configured attempts + delayMs: + type: number + description: Backoff delay before the next attempt in milliseconds + reason: + type: string + description: Reason for the retry +required: + - operation + - attempt +description: Payload for "retry" events — a transient operation will be retried. diff --git a/vscode/prompty/schemas/RunTurnRequest.yaml b/vscode/prompty/schemas/RunTurnRequest.yaml new file mode 100644 index 00000000..2de75fcd --- /dev/null +++ b/vscode/prompty/schemas/RunTurnRequest.yaml @@ -0,0 +1,21 @@ +$schema: https://json-schema.org/draft/2020-12/schema +$id: RunTurnRequest.yaml +type: object +properties: + sessionId: + type: string + description: Stable harness session identifier + turnId: + type: string + description: Stable turn identifier within the session + inputs: + $ref: RecordUnknown.yaml + default: {} + description: Inputs supplied to the deterministic single-turn run + options: + $ref: TurnOptions.yaml + description: Canonical turn execution options +required: + - sessionId + - turnId +description: Request accepted by a reference turn runner implementation. diff --git a/vscode/prompty/schemas/RunTurnResult.yaml b/vscode/prompty/schemas/RunTurnResult.yaml new file mode 100644 index 00000000..05d4b9d1 --- /dev/null +++ b/vscode/prompty/schemas/RunTurnResult.yaml @@ -0,0 +1,44 @@ +$schema: https://json-schema.org/draft/2020-12/schema +$id: RunTurnResult.yaml +type: object +properties: + sessionId: + type: string + description: Stable harness session identifier + turnId: + type: string + description: Stable turn identifier within the session + status: + anyOf: + - type: string + const: success + - type: string + const: error + - type: string + const: cancelled + description: Final semantic status for the deterministic turn + output: + description: Provider-neutral final output returned by the injected model callback + iterations: + type: integer + minimum: -2147483648 + maximum: 2147483647 + description: Number of model loop iterations executed + toolResults: + type: array + items: + $ref: HostToolResult.yaml + default: [] + description: Host tool results produced during the turn + checkpoints: + type: array + items: + $ref: Checkpoint.yaml + default: [] + description: Checkpoints created during the turn +required: + - sessionId + - turnId + - status + - iterations +description: Result returned by a reference turn runner implementation. diff --git a/vscode/prompty/schemas/SessionEndPayload.yaml b/vscode/prompty/schemas/SessionEndPayload.yaml new file mode 100644 index 00000000..33ca32ac --- /dev/null +++ b/vscode/prompty/schemas/SessionEndPayload.yaml @@ -0,0 +1,25 @@ +$schema: https://json-schema.org/draft/2020-12/schema +$id: SessionEndPayload.yaml +type: object +properties: + sessionId: + type: string + description: Stable session identifier + status: + anyOf: + - type: string + const: success + - type: string + const: error + - type: string + const: cancelled + - type: string + const: interrupted + description: Final session status + reason: + type: string + description: Host-specific reason the session ended + durationMs: + type: number + description: Total elapsed session duration in milliseconds +description: Payload for "session_end" events. diff --git a/vscode/prompty/schemas/SessionEvent.yaml b/vscode/prompty/schemas/SessionEvent.yaml new file mode 100644 index 00000000..c1af6a3c --- /dev/null +++ b/vscode/prompty/schemas/SessionEvent.yaml @@ -0,0 +1,52 @@ +$schema: https://json-schema.org/draft/2020-12/schema +$id: SessionEvent.yaml +type: object +properties: + id: + type: string + description: Unique identifier for this event + type: + anyOf: + - type: string + const: session_start + - type: string + const: session_end + - type: string + const: session_warning + - type: string + const: session_hook_start + - type: string + const: session_hook_end + - type: string + const: checkpoint_created + - type: string + const: trajectory_event + description: Event type discriminator + timestamp: + type: string + description: ISO 8601 UTC timestamp when the event was emitted + sessionId: + type: string + description: Stable identifier for the outer session + turnId: + type: string + description: Associated turn identifier, when this session event is linked to a turn + parentId: + type: string + description: Parent event or span identifier for reconstructing event hierarchy + spanId: + type: string + description: Trace span identifier associated with this event + payload: + $ref: RecordUnknown.yaml + default: {} + description: Event-specific payload. Use the typed payload model matching 'type'. + redaction: + $ref: RedactionMetadata.yaml + description: Redaction state for sensitive payload fields +required: + - id + - type + - timestamp + - payload +description: A canonical event envelope emitted by an outer harness session. diff --git a/vscode/prompty/schemas/SessionFileRef.yaml b/vscode/prompty/schemas/SessionFileRef.yaml new file mode 100644 index 00000000..1d79ae62 --- /dev/null +++ b/vscode/prompty/schemas/SessionFileRef.yaml @@ -0,0 +1,24 @@ +$schema: https://json-schema.org/draft/2020-12/schema +$id: SessionFileRef.yaml +type: object +properties: + sessionId: + type: string + description: Stable session identifier + path: + type: string + description: File path, relative to the harness workspace when possible + toolName: + type: string + description: Tool that first observed the file, when known + turnIndex: + type: integer + minimum: -2147483648 + maximum: 2147483647 + description: Zero-based turn index where the file was first observed + firstSeenAt: + type: string + description: ISO 8601 UTC timestamp when the file was first observed +required: + - path +description: A file observed or touched by a harness session. diff --git a/vscode/prompty/schemas/SessionRef.yaml b/vscode/prompty/schemas/SessionRef.yaml new file mode 100644 index 00000000..18c1fce3 --- /dev/null +++ b/vscode/prompty/schemas/SessionRef.yaml @@ -0,0 +1,25 @@ +$schema: https://json-schema.org/draft/2020-12/schema +$id: SessionRef.yaml +type: object +properties: + sessionId: + type: string + description: Stable session identifier + refType: + type: string + description: Reference category + refValue: + type: string + description: Reference value + turnIndex: + type: integer + minimum: -2147483648 + maximum: 2147483647 + description: Zero-based turn index where the reference was first observed + createdAt: + type: string + description: ISO 8601 UTC timestamp when the reference was recorded +required: + - refType + - refValue +description: A non-file reference observed by a harness session. diff --git a/vscode/prompty/schemas/SessionStartPayload.yaml b/vscode/prompty/schemas/SessionStartPayload.yaml new file mode 100644 index 00000000..75174bf7 --- /dev/null +++ b/vscode/prompty/schemas/SessionStartPayload.yaml @@ -0,0 +1,35 @@ +$schema: https://json-schema.org/draft/2020-12/schema +$id: SessionStartPayload.yaml +type: object +properties: + sessionId: + type: string + description: Stable session identifier + schemaVersion: + type: string + default: "1" + description: Session event schema version + producer: + type: string + description: Producer that started the session + runtime: + type: string + description: Runtime that produced the session + promptyVersion: + type: string + description: Prompty library version + startTime: + type: string + description: ISO 8601 UTC timestamp when the session started + selectedModel: + type: string + description: Selected model identifier, when known + reasoningEffort: + type: string + description: Selected reasoning effort or equivalent model setting, when known + context: + $ref: HarnessContext.yaml + description: Repository and execution context +required: + - sessionId +description: Payload for "session_start" events. diff --git a/vscode/prompty/schemas/SessionSummary.yaml b/vscode/prompty/schemas/SessionSummary.yaml new file mode 100644 index 00000000..d2e1d3c3 --- /dev/null +++ b/vscode/prompty/schemas/SessionSummary.yaml @@ -0,0 +1,37 @@ +$schema: https://json-schema.org/draft/2020-12/schema +$id: SessionSummary.yaml +type: object +properties: + sessionId: + type: string + description: Stable session identifier + status: + anyOf: + - type: string + const: success + - type: string + const: error + - type: string + const: cancelled + - type: string + const: interrupted + description: Final session status + turns: + type: integer + minimum: -2147483648 + maximum: 2147483647 + description: Number of user/assistant turns in the session + checkpoints: + type: integer + minimum: -2147483648 + maximum: 2147483647 + description: Number of checkpoints created + usage: + $ref: TokenUsage.yaml + description: Aggregated token usage for the session + durationMs: + type: number + description: Total elapsed session duration in milliseconds +required: + - sessionId +description: Summary statistics for a completed session trace. diff --git a/vscode/prompty/schemas/SessionTrace.yaml b/vscode/prompty/schemas/SessionTrace.yaml new file mode 100644 index 00000000..1a3af4b9 --- /dev/null +++ b/vscode/prompty/schemas/SessionTrace.yaml @@ -0,0 +1,54 @@ +$schema: https://json-schema.org/draft/2020-12/schema +$id: SessionTrace.yaml +type: object +properties: + version: + type: string + default: "1" + description: Trace schema version + runtime: + type: string + description: Runtime name that produced the trace + promptyVersion: + type: string + description: Prompty library version that produced the trace + sessionId: + type: string + description: Stable session identifier + events: + type: array + items: + $ref: SessionEvent.yaml + description: Recorded session events in emission order + turns: + type: array + items: + $ref: TurnTrace.yaml + description: Recorded turn traces associated with the session + checkpoints: + type: array + items: + $ref: Checkpoint.yaml + description: Checkpoints created during the session + trajectory: + type: array + items: + $ref: TrajectoryEvent.yaml + description: Compact trajectory records associated with the session + files: + type: array + items: + $ref: SessionFileRef.yaml + description: Files observed or touched during the session + refs: + type: array + items: + $ref: SessionRef.yaml + description: Non-file references observed during the session + summary: + $ref: SessionSummary.yaml + description: Optional summary computed from the event stream +required: + - version + - events +description: Portable replay container for an outer harness session. diff --git a/vscode/prompty/schemas/SessionWarningPayload.yaml b/vscode/prompty/schemas/SessionWarningPayload.yaml new file mode 100644 index 00000000..faa3c038 --- /dev/null +++ b/vscode/prompty/schemas/SessionWarningPayload.yaml @@ -0,0 +1,17 @@ +$schema: https://json-schema.org/draft/2020-12/schema +$id: SessionWarningPayload.yaml +type: object +properties: + warningType: + type: string + description: Stable machine-readable warning category + message: + type: string + description: Human-readable warning message + details: + $ref: RecordUnknown.yaml + description: Additional host-specific warning details +required: + - warningType + - message +description: Payload for "session_warning" events. diff --git a/vscode/prompty/schemas/ToolCallCompletePayload.yaml b/vscode/prompty/schemas/ToolCallCompletePayload.yaml new file mode 100644 index 00000000..cb1e9903 --- /dev/null +++ b/vscode/prompty/schemas/ToolCallCompletePayload.yaml @@ -0,0 +1,26 @@ +$schema: https://json-schema.org/draft/2020-12/schema +$id: ToolCallCompletePayload.yaml +type: object +properties: + id: + type: string + description: The unique identifier of the tool call + name: + type: string + description: The name of the tool that completed + success: + type: boolean + description: Whether the tool dispatch succeeded semantically + result: + $ref: ToolResult.yaml + description: Normalized tool result + durationMs: + type: number + description: Tool execution duration in milliseconds + errorKind: + type: string + description: Machine-readable error category when success is false +required: + - name + - success +description: Payload for "tool_call_complete" events — a tool dispatch finished. diff --git a/vscode/prompty/schemas/ToolCallStartPayload.yaml b/vscode/prompty/schemas/ToolCallStartPayload.yaml index d41ce044..0ce05515 100644 --- a/vscode/prompty/schemas/ToolCallStartPayload.yaml +++ b/vscode/prompty/schemas/ToolCallStartPayload.yaml @@ -2,6 +2,9 @@ $schema: https://json-schema.org/draft/2020-12/schema $id: ToolCallStartPayload.yaml type: object properties: + id: + type: string + description: The unique identifier of the tool call name: type: string description: The name of the tool being called diff --git a/vscode/prompty/schemas/ToolExecutionCompletePayload.yaml b/vscode/prompty/schemas/ToolExecutionCompletePayload.yaml new file mode 100644 index 00000000..7fa7e9da --- /dev/null +++ b/vscode/prompty/schemas/ToolExecutionCompletePayload.yaml @@ -0,0 +1,39 @@ +$schema: https://json-schema.org/draft/2020-12/schema +$id: ToolExecutionCompletePayload.yaml +type: object +properties: + requestId: + type: string + description: Stable host execution request identifier + toolCallId: + type: string + description: Associated model tool call identifier, when available + toolName: + type: string + description: Name of the host tool that executed + success: + type: boolean + description: Whether the host execution completed successfully + result: + description: Host-normalized execution result + exitCode: + type: integer + minimum: -2147483648 + maximum: 2147483647 + description: Process or host exit code, when applicable + durationMs: + type: number + description: Tool execution duration in milliseconds + errorKind: + type: string + description: Machine-readable error category when success is false + telemetry: + $ref: RecordUnknown.yaml + description: Host-specific telemetry for the execution + redaction: + $ref: RedactionMetadata.yaml + description: Redaction state for sensitive result fields +required: + - toolName + - success +description: Payload for "tool_execution_complete" events — a concrete host tool execution finished. diff --git a/vscode/prompty/schemas/ToolExecutionStartPayload.yaml b/vscode/prompty/schemas/ToolExecutionStartPayload.yaml new file mode 100644 index 00000000..3d2a117b --- /dev/null +++ b/vscode/prompty/schemas/ToolExecutionStartPayload.yaml @@ -0,0 +1,29 @@ +$schema: https://json-schema.org/draft/2020-12/schema +$id: ToolExecutionStartPayload.yaml +type: object +properties: + requestId: + type: string + description: Stable host execution request identifier + toolCallId: + type: string + description: Associated model tool call identifier, when available + toolName: + type: string + description: Name of the host tool being executed + arguments: + $ref: RecordUnknown.yaml + description: Tool arguments after host-side sanitization + workingDirectory: + type: string + description: Working directory or execution scope for the tool + redaction: + $ref: RedactionMetadata.yaml + description: Redaction state for sensitive argument fields +required: + - toolName +description: |- + Payload for "tool_execution_start" events — the host is about to execute a concrete tool request. + + This is distinct from "tool_call_start", which records the model requesting a tool. + Tool execution events capture the harness-side action after policy and permission checks. diff --git a/vscode/prompty/schemas/ToolResult.yaml b/vscode/prompty/schemas/ToolResult.yaml index a4f3a2db..195e080d 100644 --- a/vscode/prompty/schemas/ToolResult.yaml +++ b/vscode/prompty/schemas/ToolResult.yaml @@ -7,6 +7,26 @@ properties: items: $ref: ContentPart.yaml description: The content parts of the tool result + status: + anyOf: + - type: string + const: success + - type: string + const: error + - type: string + const: cancelled + - type: string + const: timeout + description: Semantic execution status for the tool result + errorKind: + type: string + description: Stable machine-readable error category when status is not success + errorMessage: + type: string + description: Human-readable error message when status is not success + durationMs: + type: number + description: Tool execution duration in milliseconds required: - parts description: |- diff --git a/vscode/prompty/schemas/TrajectoryEvent.yaml b/vscode/prompty/schemas/TrajectoryEvent.yaml new file mode 100644 index 00000000..6fdcbbdc --- /dev/null +++ b/vscode/prompty/schemas/TrajectoryEvent.yaml @@ -0,0 +1,36 @@ +$schema: https://json-schema.org/draft/2020-12/schema +$id: TrajectoryEvent.yaml +type: object +properties: + id: + type: string + description: Stable trajectory event identifier + sessionId: + type: string + description: Stable session identifier + turnId: + type: string + description: Associated turn identifier, when available + toolCallId: + type: string + description: Associated tool call identifier, when available + turnIndex: + type: integer + minimum: -2147483648 + maximum: 2147483647 + description: Zero-based turn index in the session + eventType: + type: string + description: Host-defined trajectory event category + data: + $ref: RecordUnknown.yaml + description: Sanitized event data + createdAt: + type: string + description: ISO 8601 UTC timestamp when the trajectory event was recorded + redaction: + $ref: RedactionMetadata.yaml + description: Redaction state for sensitive trajectory fields +required: + - eventType +description: A compact, replay-oriented record of one harness-side action or observation. diff --git a/vscode/prompty/schemas/TurnEndPayload.yaml b/vscode/prompty/schemas/TurnEndPayload.yaml new file mode 100644 index 00000000..f9cb2c1a --- /dev/null +++ b/vscode/prompty/schemas/TurnEndPayload.yaml @@ -0,0 +1,24 @@ +$schema: https://json-schema.org/draft/2020-12/schema +$id: TurnEndPayload.yaml +type: object +properties: + iterations: + type: integer + minimum: -2147483648 + maximum: 2147483647 + description: Number of tool-call iterations performed + status: + anyOf: + - type: string + const: success + - type: string + const: error + - type: string + const: cancelled + description: Final semantic status of the turn + response: + description: Final response after processing, if available + durationMs: + type: number + description: Total elapsed turn duration in milliseconds +description: Payload for "turn_end" events — a turn has completed. diff --git a/vscode/prompty/schemas/TurnEvent.yaml b/vscode/prompty/schemas/TurnEvent.yaml new file mode 100644 index 00000000..6551bac3 --- /dev/null +++ b/vscode/prompty/schemas/TurnEvent.yaml @@ -0,0 +1,88 @@ +$schema: https://json-schema.org/draft/2020-12/schema +$id: TurnEvent.yaml +type: object +properties: + id: + type: string + description: Unique identifier for this event + type: + anyOf: + - type: string + const: turn_start + - type: string + const: turn_end + - type: string + const: llm_start + - type: string + const: llm_complete + - type: string + const: retry + - type: string + const: permission_requested + - type: string + const: permission_completed + - type: string + const: token + - type: string + const: thinking + - type: string + const: tool_call_start + - type: string + const: tool_call_complete + - type: string + const: tool_execution_start + - type: string + const: tool_execution_complete + - type: string + const: tool_result + - type: string + const: hook_start + - type: string + const: hook_end + - type: string + const: status + - type: string + const: messages_updated + - type: string + const: done + - type: string + const: error + - type: string + const: cancelled + - type: string + const: compaction_start + - type: string + const: compaction_complete + - type: string + const: compaction_failed + description: Event type discriminator + timestamp: + type: string + description: ISO 8601 UTC timestamp when the event was emitted + turnId: + type: string + description: Stable identifier for the outer turn + iteration: + type: integer + minimum: -2147483648 + maximum: 2147483647 + description: Zero-based agent-loop iteration associated with the event + parentId: + type: string + description: Parent event or span identifier for reconstructing event hierarchy + spanId: + type: string + description: Trace span identifier associated with this event + payload: + $ref: RecordUnknown.yaml + default: {} + description: Event-specific payload. Use the typed payload model matching 'type'. +required: + - id + - type + - timestamp + - payload +description: |- + A canonical event envelope emitted by the turn harness. The payload is kept + JSON-shaped so runtimes can load all events even when newer payload types are + added; event-specific typed payload models below define the canonical shapes. diff --git a/vscode/prompty/schemas/TurnModelRequest.yaml b/vscode/prompty/schemas/TurnModelRequest.yaml new file mode 100644 index 00000000..c35ea4c2 --- /dev/null +++ b/vscode/prompty/schemas/TurnModelRequest.yaml @@ -0,0 +1,37 @@ +$schema: https://json-schema.org/draft/2020-12/schema +$id: TurnModelRequest.yaml +type: object +properties: + sessionId: + type: string + description: Stable harness session identifier + turnId: + type: string + description: Stable turn identifier within the session + iteration: + type: integer + minimum: -2147483648 + maximum: 2147483647 + description: Zero-based model loop iteration + inputs: + $ref: RecordUnknown.yaml + default: {} + description: Inputs supplied to the deterministic single-turn run + options: + $ref: TurnOptions.yaml + description: Canonical turn execution options + toolResults: + type: array + items: + $ref: HostToolResult.yaml + default: [] + description: Host tool results produced by the previous iteration +required: + - sessionId + - turnId + - iteration +description: |- + Request passed by the reference turn runner to the injected model callback. + + The runner owns deterministic orchestration semantics; model/provider-specific + execution stays behind this callback boundary. diff --git a/vscode/prompty/schemas/TurnModelResponse.yaml b/vscode/prompty/schemas/TurnModelResponse.yaml new file mode 100644 index 00000000..1b9e727c --- /dev/null +++ b/vscode/prompty/schemas/TurnModelResponse.yaml @@ -0,0 +1,17 @@ +$schema: https://json-schema.org/draft/2020-12/schema +$id: TurnModelResponse.yaml +type: object +properties: + output: + description: Provider-neutral final model output for the turn when no more tools are requested + toolRequests: + type: array + items: + $ref: HostToolRequest.yaml + default: [] + description: Host tool execution requests emitted by the model callback + checkpointState: + $ref: RecordUnknown.yaml + default: {} + description: Additional deterministic state to merge into the iteration checkpoint +description: Response returned by the injected model callback to the reference turn runner. diff --git a/vscode/prompty/schemas/TurnStartPayload.yaml b/vscode/prompty/schemas/TurnStartPayload.yaml new file mode 100644 index 00000000..de6c6393 --- /dev/null +++ b/vscode/prompty/schemas/TurnStartPayload.yaml @@ -0,0 +1,16 @@ +$schema: https://json-schema.org/draft/2020-12/schema +$id: TurnStartPayload.yaml +type: object +properties: + agent: + type: string + description: Name of the loaded prompt/agent, when available + inputs: + $ref: RecordUnknown.yaml + description: Input values supplied to the turn after host-side sanitization + maxIterations: + type: integer + minimum: -2147483648 + maximum: 2147483647 + description: Configured maximum tool-call iterations +description: Payload for "turn_start" events — a turn is beginning. diff --git a/vscode/prompty/schemas/TurnSummary.yaml b/vscode/prompty/schemas/TurnSummary.yaml new file mode 100644 index 00000000..ed3ff3b9 --- /dev/null +++ b/vscode/prompty/schemas/TurnSummary.yaml @@ -0,0 +1,41 @@ +$schema: https://json-schema.org/draft/2020-12/schema +$id: TurnSummary.yaml +type: object +properties: + turnId: + type: string + description: Stable identifier for the outer turn + status: + type: string + description: "Final turn status: 'success', 'error', or 'cancelled'" + iterations: + type: integer + minimum: -2147483648 + maximum: 2147483647 + description: Number of agent-loop iterations + llmCalls: + type: integer + minimum: -2147483648 + maximum: 2147483647 + description: Number of LLM calls made during the turn + toolCalls: + type: integer + minimum: -2147483648 + maximum: 2147483647 + description: Number of tool calls dispatched during the turn + retries: + type: integer + minimum: -2147483648 + maximum: 2147483647 + description: Number of retry events during the turn + usage: + $ref: TokenUsage.yaml + description: Aggregated token usage for the turn + durationMs: + type: number + description: Total elapsed turn duration in milliseconds +required: + - turnId + - status + - iterations +description: Summary statistics for a completed turn trace. diff --git a/vscode/prompty/schemas/TurnTrace.yaml b/vscode/prompty/schemas/TurnTrace.yaml new file mode 100644 index 00000000..baeb5bef --- /dev/null +++ b/vscode/prompty/schemas/TurnTrace.yaml @@ -0,0 +1,26 @@ +$schema: https://json-schema.org/draft/2020-12/schema +$id: TurnTrace.yaml +type: object +properties: + version: + type: string + default: "1" + description: Trace schema version + runtime: + type: string + description: Runtime name that produced the trace + promptyVersion: + type: string + description: Prompty library version that produced the trace + events: + type: array + items: + $ref: TurnEvent.yaml + description: Recorded turn events in emission order + summary: + $ref: TurnSummary.yaml + description: Optional summary computed from the event stream +required: + - version + - events +description: Portable JSONL/replay container for a recorded turn harness run. diff --git a/web/src/content/docs/how-to/openai.mdx b/web/src/content/docs/how-to/openai.mdx index ba61cc63..442856b5 100644 --- a/web/src/content/docs/how-to/openai.mdx +++ b/web/src/content/docs/how-to/openai.mdx @@ -77,6 +77,27 @@ user: To target a direct OpenAI-compatible endpoint or proxy, keep `provider: openai` and add `endpoint` under `connection`. +```yaml +model: + id: gpt-4o-mini + provider: openai + apiType: chat + connection: + kind: key + endpoint: ${env:OPENAI_BASE_URL:https://api.openai.com/v1} + apiKey: ${env:OPENAI_API_KEY} +``` + +For example, to route through Tuning Engines: + +```bash +export OPENAI_BASE_URL=https://api.tuningengines.com/v1 +export OPENAI_API_KEY=sk-te-your-inference-key +``` + +The `.prompty` file stays unchanged while the endpoint provides routing, +policy, usage tracking, or trace correlation around OpenAI-compatible calls. +