Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
85 commits
Select commit Hold shift + click to select a range
8558bd9
Add runtime types to TypeSpec, remove PromptyTool agentic mode, updat…
sethjuarez Apr 14, 2026
2a205de
fix: emitter bugs — Python save_parts serialization, test string esca…
sethjuarez Apr 14, 2026
0a33ad9
feat(emitter): add @factory and @helper decorators with cross-runtime…
sethjuarez Apr 14, 2026
c1a905c
feat: replace hand-written GuardrailResult with generated model type …
sethjuarez Apr 14, 2026
bbf8550
fix: Python emitter detects factory/field name collisions and prefixe…
sethjuarez Apr 14, 2026
e3c8d81
Replace hand-written ToolCall with generated model type across Rust, …
sethjuarez Apr 14, 2026
d19aa0a
fix: use snake_case attribute names in foundry executor
sethjuarez Apr 14, 2026
fa9142d
Fix camelCase attribute accesses on generated model types in test files
sethjuarez Apr 14, 2026
2367405
fix: Python emitter snake_case + PromptyTool mode field + test cleanup
sethjuarez Apr 14, 2026
d21bda4
fix: Rust types.rs ToolCall test uses emitted to_value() instead of s…
sethjuarez Apr 14, 2026
c1fa82e
perf: fix to_dict MagicMock recursion — 10min tests → 11s
sethjuarez Apr 14, 2026
c4ff72f
refactor: Python ContentPart/Message now re-exported from generated m…
sethjuarez Apr 14, 2026
b6d854d
refactor: C# replace hand-written types with generated Model types
sethjuarez Apr 15, 2026
86a8001
Update pipeline.test.ts to use new Message({...}) constructor
sethjuarez Apr 15, 2026
672bae3
Update structured-pipeline.test.ts to use new Message constructor API
sethjuarez Apr 15, 2026
51da9f5
refactor: update providers.test.ts to use generated class constructors
sethjuarez Apr 15, 2026
107d4f0
Update spec-vectors.test.ts to use new Message constructor API and Co…
sethjuarez Apr 15, 2026
42f810b
Update kinds.test.ts to use new Message constructor API
sethjuarez Apr 15, 2026
4d076aa
refactor: update remaining test files to use new Message({}) constructor
sethjuarez Apr 15, 2026
f3ef2c8
refactor: TypeScript replace hand-written types with generated Model …
sethjuarez Apr 15, 2026
2fe50f2
fix: TS emitter loadParts guard for types without name property
sethjuarez Apr 15, 2026
209d933
fix: update Rust runtime to use typed Vec<T> fields instead of serde_…
sethjuarez Apr 15, 2026
bf02093
fix: C# emitter string escaping, Message.role default, and Metadata n…
sethjuarez Apr 15, 2026
6dfb1df
feat: wire ToolResult through Rust runtime + remove PromptyTool mode
sethjuarez Apr 15, 2026
3ba1ae3
feat(typescript): wire ToolResult through pipeline, remove hand-rolle…
sethjuarez Apr 15, 2026
2a99e24
feat(python): wire ToolResult through pipeline, remove mode from Prom…
sethjuarez Apr 15, 2026
b6e0d94
feat(csharp): wire ToolResult through C# runtime, remove mode from Pr…
sethjuarez Apr 15, 2026
6d9a6e1
feat(emitter): add @helper stubs, @factory tests, and fix C# autoescape
sethjuarez Apr 15, 2026
713cfe9
chore: regenerate all runtimes with factory tests and helper stubs
sethjuarez Apr 15, 2026
9f5e36d
feat(emitter): add Expression IR — type-directed lowering + per-langu…
sethjuarez Apr 15, 2026
bc3d681
fix(emitter): add isOptional to FieldAssignment, Rust Some() wrapping
sethjuarez Apr 15, 2026
0a0432a
Rename alternates/shorthand to coercions/coercionProperty in python.ts
sethjuarez Apr 15, 2026
a149931
Rename template variables: alternates→coercions, shorthandProperty→co…
sethjuarez Apr 15, 2026
0f5b83e
feat(emitter): Phase 1a — decorator renames, FieldRead IR, new decora…
sethjuarez Apr 15, 2026
06391b3
feat(emitter): auto-discover all models in namespace, remove addition…
sethjuarez Apr 15, 2026
b567847
feat(emitter): wire expression IR into all 5 language emitters (Phase 2)
sethjuarez Apr 15, 2026
f483c27
Fix C# type conflicts between hand-written and generated partial classes
sethjuarez Apr 15, 2026
fcd3b94
fix(runtime): resolve Go, Rust, and C# blockers from typed model migr…
sethjuarez Apr 15, 2026
1f4ff15
feat(emitter): add nested factory methods + unified import model (Pha…
sethjuarez Apr 15, 2026
a4e557e
feat(emitter): Declaration IR, language-agnostic lowering, Python emi…
sethjuarez Apr 15, 2026
e139a31
refactor(emitter): reorganize into ir/, languages/, legacy/ directory…
sethjuarez Apr 15, 2026
35b456d
Add TypeScript code emitter replacing Nunjucks template
sethjuarez Apr 15, 2026
7e7106d
feat(emitter): add Rust code emitter replacing Nunjucks template
sethjuarez Apr 15, 2026
7036257
fix(rust-emitter): correct imports, polymorphic refs, and collection …
sethjuarez Apr 15, 2026
0981403
feat: add C# Declaration IR emitter replacing Nunjucks templates
sethjuarez Apr 15, 2026
ae0802b
chore: regenerate TypeScript model output after tsp compile
sethjuarez Apr 15, 2026
f9ac966
chore: clean up C# driver dead code, hoist loop-invariant computations
sethjuarez Apr 15, 2026
6b115c5
feat: replace Go Nunjucks templates with Declaration IR emitter
sethjuarez Apr 15, 2026
bfc11fe
Replace Nunjucks templates with inline TypeScript emit functions in T…
sethjuarez Apr 15, 2026
2b689ae
refactor(emitter): convert Python Nunjucks templates to inline TypeSc…
sethjuarez Apr 16, 2026
6a5cc35
refactor(csharp): replace Nunjucks templates with inline TypeScript e…
sethjuarez Apr 16, 2026
51fd4e0
Replace Go Nunjucks templates with inline TypeScript emitters
sethjuarez Apr 16, 2026
b76dcc7
Extract C# Nunjucks replacements into dedicated modules
sethjuarez Apr 16, 2026
0267137
refactor(emitter/ts): extract scaffolding and test-emitter from driver
sethjuarez Apr 16, 2026
a82cc41
refactor(rust-emitter): replace Nunjucks templates with inline TypeSc…
sethjuarez Apr 16, 2026
92a5313
refactor(emitter): extract Python inline emitters into separate modules
sethjuarez Apr 16, 2026
58b54d0
feat: remove all Nunjucks templates, replace with typed TypeScript em…
sethjuarez Apr 16, 2026
8dc01c9
chore: clean regeneration with formatting improvements from new emitters
sethjuarez Apr 16, 2026
a2769c1
refactor(emitter): remove vestigial templateDir parameter from Genera…
sethjuarez Apr 16, 2026
7ab0507
feat(go): add factory function and method stub generation
sethjuarez Apr 16, 2026
bd9aa9f
chore: regenerate Python and TypeScript model code
sethjuarez Apr 16, 2026
430fb20
feat: promote spec.md types to TypeSpec (pipeline, usage, events, tools)
sethjuarez Apr 16, 2026
8f96e23
feat: wire format toWire() generation via knownAs decorator
sethjuarez Apr 16, 2026
de43bf7
Replace hand-written field remapping with generated toWire() in TypeS…
sethjuarez Apr 16, 2026
9e11391
Replace hand-written field remapping with generated to_wire() in Pyth…
sethjuarez Apr 16, 2026
5b3a73e
feat: protocol emission via @protocol decorator + strict toWire() fix
sethjuarez Apr 16, 2026
edecc78
fix: skip null values from to_wire() in OpenAI and Anthropic apply_op…
sethjuarez Apr 16, 2026
3e31953
Rename FormatToolMessages to FormatToolMessagesAsync
sethjuarez Apr 16, 2026
9be0745
feat: fix remaining issues — nullable protocols, C# async migration, …
sethjuarez Apr 16, 2026
cb9760e
fix: format generated Python/C# code for CI compliance
sethjuarez Apr 16, 2026
f91a375
fix: resolve CodeQL security alerts
sethjuarez Apr 16, 2026
9f21dab
fix: use ruff format in Python emitter driver (matches CI)
sethjuarez Apr 16, 2026
adf50d1
Fix camelCase attribute access to match snake_case generated model fi…
sethjuarez Apr 16, 2026
b57a0da
Update mock parser ParseAsync signatures to include context parameter
sethjuarez Apr 16, 2026
a839628
feat(protocols): make generated protocols canonical with optional/syn…
sethjuarez Apr 16, 2026
51e00e5
Add streaming protocols, ThreadMarker, TypeSpec formatter, and metada…
sethjuarez Apr 16, 2026
6d1a757
Consolidate Python Message types: use generated model as canonical so…
sethjuarez Apr 16, 2026
0affa4a
emitter(python): generate Protocol class for @method stubs on concret…
sethjuarez Apr 16, 2026
f0da356
emitter(ts): generate Helpers interface for @method stubs on concrete…
sethjuarez Apr 16, 2026
18cf57d
emitter(csharp): generate IXHelpers interface for @method stubs
sethjuarez Apr 17, 2026
6534670
schema(tsp): organize model files into 11 semantic folders
sethjuarez Apr 17, 2026
22b81c7
schema(tsp): split large TSP files into concept-scoped files
sethjuarez Apr 17, 2026
cad6a82
emitter(python)+runtime(python): emit text as @property via verb heur…
sethjuarez Apr 17, 2026
35f169b
emitter(ts)+runtime(ts): emit text as readonly getter, wire MessageHe…
sethjuarez Apr 17, 2026
48b9511
emitter(md): add Helper Methods and Factory Methods sections to spec …
sethjuarez Apr 17, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
17 changes: 12 additions & 5 deletions runtime/csharp/Prompty.Anthropic/AnthropicExecutor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -105,11 +105,18 @@
if (stream)
body["stream"] = true;

// Apply model options via generated wire mapping
var opts = agent.Model?.Options;
if (opts?.Temperature is not null) body["temperature"] = opts.Temperature;
if (opts?.TopP is not null) body["top_p"] = opts.TopP;
if (opts?.TopK is not null) body["top_k"] = opts.TopK;
if (opts?.StopSequences is not null) body["stop_sequences"] = opts.StopSequences;
if (opts is not null)
{
var wireOpts = opts.ToWire("anthropic");
// max_tokens is handled separately above with DefaultMaxTokens fallback
wireOpts.Remove("max_tokens");
foreach (var (key, value) in wireOpts)
{
body[key] = value;
}
}

var tools = ToolsToWire(agent);
if (tools is not null) body["tools"] = tools;
Expand Down Expand Up @@ -147,7 +154,7 @@
// Handle tool results
if (msg.Role == Roles.Tool)
{
var toolCallId = msg.Metadata.TryGetValue("tool_call_id", out var id)
var toolCallId = msg.Metadata is not null && msg.Metadata.TryGetValue("tool_call_id", out var id)
? id?.ToString() ?? ""
: "";
return new Dictionary<string, object?>
Expand Down Expand Up @@ -331,7 +338,7 @@
Parts = !string.IsNullOrEmpty(textContent)
? [new TextPart { Value = textContent }]
: [],
Metadata = new Dictionary<string, object?> { ["content"] = rawContent },

Check warning on line 341 in runtime/csharp/Prompty.Anthropic/AnthropicExecutor.cs

View workflow job for this annotation

GitHub Actions / run unit tests on supported .net versions (9.0.x, windows-latest)

Nullability of reference types in value of type 'Dictionary<string, object?>' doesn't match target type 'IDictionary<string, object>'.

Check warning on line 341 in runtime/csharp/Prompty.Anthropic/AnthropicExecutor.cs

View workflow job for this annotation

GitHub Actions / run unit tests on supported .net versions (9.0.x, ubuntu-latest)

Nullability of reference types in value of type 'Dictionary<string, object?>' doesn't match target type 'IDictionary<string, object>'.

Check warning on line 341 in runtime/csharp/Prompty.Anthropic/AnthropicExecutor.cs

View workflow job for this annotation

GitHub Actions / run unit tests on supported .net versions (9.0.x, macOS-latest)

Nullability of reference types in value of type 'Dictionary<string, object?>' doesn't match target type 'IDictionary<string, object>'.
});

// --- Single user message with batched tool_result blocks ---
Expand All @@ -350,7 +357,7 @@
{
Role = Roles.User,
Parts = toolResults.Select(r => (ContentPart)new TextPart { Value = r }).ToList(),
Metadata = new Dictionary<string, object?> { ["tool_results"] = toolResultBlocks },

Check warning on line 360 in runtime/csharp/Prompty.Anthropic/AnthropicExecutor.cs

View workflow job for this annotation

GitHub Actions / run unit tests on supported .net versions (9.0.x, windows-latest)

Nullability of reference types in value of type 'Dictionary<string, object?>' doesn't match target type 'IDictionary<string, object>'.

Check warning on line 360 in runtime/csharp/Prompty.Anthropic/AnthropicExecutor.cs

View workflow job for this annotation

GitHub Actions / run unit tests on supported .net versions (9.0.x, ubuntu-latest)

Nullability of reference types in value of type 'Dictionary<string, object?>' doesn't match target type 'IDictionary<string, object>'.

Check warning on line 360 in runtime/csharp/Prompty.Anthropic/AnthropicExecutor.cs

View workflow job for this annotation

GitHub Actions / run unit tests on supported .net versions (9.0.x, macOS-latest)

Nullability of reference types in value of type 'Dictionary<string, object?>' doesn't match target type 'IDictionary<string, object>'.
});

return messages;
Expand Down
10 changes: 5 additions & 5 deletions runtime/csharp/Prompty.Core.Tests/AgentLoopIntegrationTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ public Task<string> RenderAsync(Prompty agent, string template, Dictionary<strin
/// </summary>
file class PassthroughParser : IParser
{
public Task<List<Message>> ParseAsync(Prompty agent, string rendered)
public Task<List<Message>> ParseAsync(Prompty agent, string rendered, Dictionary<string, object?>? context)
=> Task.FromResult(new List<Message>
{
new() { Role = Roles.User, Parts = [new TextPart { Value = rendered }] }
Expand All @@ -35,7 +35,7 @@ public Task<List<Message>> ParseAsync(Prompty agent, string rendered)
/// </summary>
file class MultiMessageParser : IParser
{
public Task<List<Message>> ParseAsync(Prompty agent, string rendered)
public Task<List<Message>> ParseAsync(Prompty agent, string rendered, Dictionary<string, object?>? context)
{
var filler = new string('z', 300);
return Task.FromResult(new List<Message>
Expand Down Expand Up @@ -282,7 +282,7 @@ public async Task InputGuardrail_RewritesMessages_ExecutorSeesRewritten()
};

var guardrails = new Guardrails(
input: _ => new GuardrailResult(true, Rewrite: rewritten));
input: _ => new GuardrailResult(true, rewrite: rewritten));

var result = await Pipeline.TurnAsync(agent, guardrails: guardrails);

Expand Down Expand Up @@ -327,7 +327,7 @@ public async Task OutputGuardrail_RewritesResponse_ReturnsSanitised()

var agent = AgentLoopHelper.CreateAgent();
var guardrails = new Guardrails(
output: _ => new GuardrailResult(true, Rewrite: "redacted answer"));
output: _ => new GuardrailResult(true, rewrite: "redacted answer"));

var result = await Pipeline.TurnAsync(agent, guardrails: guardrails);

Expand Down Expand Up @@ -367,7 +367,7 @@ public async Task ToolGuardrail_RewritesArgs_ToolReceivesRewrittenArgs()

var rewrittenArgs = new Dictionary<string, object?> { ["city"] = "Seattle" };
var guardrails = new Guardrails(
tool: (name, args) => new GuardrailResult(true, Rewrite: rewrittenArgs));
tool: (name, args) => new GuardrailResult(true, rewrite: rewrittenArgs));

var result = await Pipeline.TurnAsync(agent, tools: tools, guardrails: guardrails);

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@

using Xunit;

#pragma warning disable IDE0130
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@

using Xunit;

#pragma warning disable IDE0130
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@

using Xunit;

#pragma warning disable IDE0130
Expand Down
122 changes: 122 additions & 0 deletions runtime/csharp/Prompty.Core.Tests/Model/AudioPartConversionTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
using Xunit;

#pragma warning disable IDE0130
namespace Prompty.Core;
#pragma warning restore IDE0130


public class AudioPartConversionTests
{
[Fact]
public void LoadYamlInput()
{
string yamlData = """
source: "https://example.com/audio.wav"
mediaType: audio/wav

""";

var instance = AudioPart.FromYaml(yamlData);

Assert.NotNull(instance);
Assert.Equal("https://example.com/audio.wav", instance.Source);
Assert.Equal("audio/wav", instance.MediaType);
}

[Fact]
public void LoadJsonInput()
{
string jsonData = """
{
"source": "https://example.com/audio.wav",
"mediaType": "audio/wav"
}
""";

var instance = AudioPart.FromJson(jsonData);
Assert.NotNull(instance);
Assert.Equal("https://example.com/audio.wav", instance.Source);
Assert.Equal("audio/wav", instance.MediaType);
}

[Fact]
public void RoundtripJson()
{
// Test that FromJson -> ToJson -> FromJson produces equivalent data
string jsonData = """
{
"source": "https://example.com/audio.wav",
"mediaType": "audio/wav"
}
""";

var original = AudioPart.FromJson(jsonData);
Assert.NotNull(original);

var json = original.ToJson();
Assert.False(string.IsNullOrEmpty(json));

var reloaded = AudioPart.FromJson(json);
Assert.NotNull(reloaded);
Assert.Equal("https://example.com/audio.wav", reloaded.Source);
Assert.Equal("audio/wav", reloaded.MediaType);
}

[Fact]
public void RoundtripYaml()
{
// Test that FromYaml -> ToYaml -> FromYaml produces equivalent data
string yamlData = """
source: "https://example.com/audio.wav"
mediaType: audio/wav

""";

var original = AudioPart.FromYaml(yamlData);
Assert.NotNull(original);

var yaml = original.ToYaml();
Assert.False(string.IsNullOrEmpty(yaml));

var reloaded = AudioPart.FromYaml(yaml);
Assert.NotNull(reloaded);
Assert.Equal("https://example.com/audio.wav", reloaded.Source);
Assert.Equal("audio/wav", reloaded.MediaType);
}

[Fact]
public void ToJsonProducesValidJson()
{
string jsonData = """
{
"source": "https://example.com/audio.wav",
"mediaType": "audio/wav"
}
""";

var instance = AudioPart.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 = """
source: "https://example.com/audio.wav"
mediaType: audio/wav

""";

var instance = AudioPart.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<object>(yaml);
Assert.NotNull(parsed);
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@

using Xunit;

#pragma warning disable IDE0130
Expand Down Expand Up @@ -120,6 +119,7 @@ public void ToYamlProducesValidYaml()
var parsed = deserializer.Deserialize<object>(yaml);
Assert.NotNull(parsed);
}

[Fact]
public void LoadJsonFromString()
{
Expand All @@ -137,7 +137,7 @@ public void LoadYamlFromString()
var data = "\"example\"";
var instance = Binding.FromYaml(data);
Assert.NotNull(instance);

Assert.Equal("example", instance.Input);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
using Xunit;

#pragma warning disable IDE0130
namespace Prompty.Core;
#pragma warning restore IDE0130


public class CompactionCompletePayloadConversionTests
{
[Fact]
public void LoadYamlInput()
{
string yamlData = """
removed: 5
remaining: 3

""";

var instance = CompactionCompletePayload.FromYaml(yamlData);

Assert.NotNull(instance);
Assert.Equal(5, instance.Removed);
Assert.Equal(3, instance.Remaining);
}

[Fact]
public void LoadJsonInput()
{
string jsonData = """
{
"removed": 5,
"remaining": 3
}
""";

var instance = CompactionCompletePayload.FromJson(jsonData);
Assert.NotNull(instance);
Assert.Equal(5, instance.Removed);
Assert.Equal(3, instance.Remaining);
}

[Fact]
public void RoundtripJson()
{
// Test that FromJson -> ToJson -> FromJson produces equivalent data
string jsonData = """
{
"removed": 5,
"remaining": 3
}
""";

var original = CompactionCompletePayload.FromJson(jsonData);
Assert.NotNull(original);

var json = original.ToJson();
Assert.False(string.IsNullOrEmpty(json));

var reloaded = CompactionCompletePayload.FromJson(json);
Assert.NotNull(reloaded);
Assert.Equal(5, reloaded.Removed);
Assert.Equal(3, reloaded.Remaining);
}

[Fact]
public void RoundtripYaml()
{
// Test that FromYaml -> ToYaml -> FromYaml produces equivalent data
string yamlData = """
removed: 5
remaining: 3

""";

var original = CompactionCompletePayload.FromYaml(yamlData);
Assert.NotNull(original);

var yaml = original.ToYaml();
Assert.False(string.IsNullOrEmpty(yaml));

var reloaded = CompactionCompletePayload.FromYaml(yaml);
Assert.NotNull(reloaded);
Assert.Equal(5, reloaded.Removed);
Assert.Equal(3, reloaded.Remaining);
}

[Fact]
public void ToJsonProducesValidJson()
{
string jsonData = """
{
"removed": 5,
"remaining": 3
}
""";

var instance = CompactionCompletePayload.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 = """
removed: 5
remaining: 3

""";

var instance = CompactionCompletePayload.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<object>(yaml);
Assert.NotNull(parsed);
}
}
Loading
Loading