diff --git a/src/CodexSdk.MAF/CodexAIAgent.cs b/src/CodexSdk.MAF/CodexAIAgent.cs index 14eddf9..5f149cc 100644 --- a/src/CodexSdk.MAF/CodexAIAgent.cs +++ b/src/CodexSdk.MAF/CodexAIAgent.cs @@ -87,7 +87,7 @@ protected override async Task RunCoreAsync( try { - await foreach (var threadEvent in thread.RunStreamedAsync(inputLease.Input, cancellationToken: cancellationToken)) + await foreach (var threadEvent in thread.RunStreamedAsync(inputLease.Input, _options.TurnOptions, cancellationToken)) { if (threadEvent is ThreadStartedEvent started) { @@ -179,7 +179,7 @@ protected override async IAsyncEnumerable RunCoreStreamingA try { - await foreach (var threadEvent in thread.RunStreamedAsync(inputLease.Input, cancellationToken: cancellationToken)) + await foreach (var threadEvent in thread.RunStreamedAsync(inputLease.Input, _options.TurnOptions, cancellationToken)) { if (threadEvent is ThreadStartedEvent started) { diff --git a/src/CodexSdk.MAF/CodexAIAgentOptions.cs b/src/CodexSdk.MAF/CodexAIAgentOptions.cs index faa0241..85d6204 100644 --- a/src/CodexSdk.MAF/CodexAIAgentOptions.cs +++ b/src/CodexSdk.MAF/CodexAIAgentOptions.cs @@ -13,6 +13,12 @@ public sealed record CodexAIAgentOptions SkipGitRepoCheck = true, }; + /// + /// Per-turn options forwarded to on both the buffered and + /// streaming paths. Use to require structured output. + /// + public TurnOptions? TurnOptions { get; init; } + public Guid? ThreadId { get; init; } public bool IsResume { get; init; } diff --git a/tests/CodexSdk.MAF.Tests/CodexAIAgentTurnOptionsTests.cs b/tests/CodexSdk.MAF.Tests/CodexAIAgentTurnOptionsTests.cs new file mode 100644 index 0000000..42952b9 --- /dev/null +++ b/tests/CodexSdk.MAF.Tests/CodexAIAgentTurnOptionsTests.cs @@ -0,0 +1,79 @@ +using CodexSdk.TestSupport; +using OpenAI.CodexSdk; +using OpenAI.CodexSdk.MAF; +using Xunit; + +namespace CodexSdk.MAF.Tests; + +public sealed class CodexAIAgentTurnOptionsTests +{ + [Fact] + public async Task RunStreamingAsync_WithOutputSchema_PassesOutputSchemaFlagToCli() + { + // Arrange + using var cli = new FakeCodexCli(); + var agent = new CodexAIAgent( + new CodexAIAgentOptions + { + CodexOptions = cli.CreateOptions(), + TurnOptions = new TurnOptions + { + OutputSchema = new Dictionary { ["type"] = "object" }, + }, + } + ); + var session = await agent.CreateSessionAsync(TestContext.Current.CancellationToken); + + // Act + await foreach ( + var _ in agent.RunStreamingAsync("hi", session, cancellationToken: TestContext.Current.CancellationToken) + ) { } + + // Assert + var args = Assert.Single(cli.ReadArguments()); + Assert.Contains("--output-schema", args); + } + + [Fact] + public async Task RunAsync_WithOutputSchema_PassesOutputSchemaFlagToCli() + { + // Arrange + using var cli = new FakeCodexCli(); + var agent = new CodexAIAgent( + new CodexAIAgentOptions + { + CodexOptions = cli.CreateOptions(), + TurnOptions = new TurnOptions + { + OutputSchema = new Dictionary { ["type"] = "object" }, + }, + } + ); + var session = await agent.CreateSessionAsync(TestContext.Current.CancellationToken); + + // Act + await agent.RunAsync("hi", session, cancellationToken: TestContext.Current.CancellationToken); + + // Assert + var args = Assert.Single(cli.ReadArguments()); + Assert.Contains("--output-schema", args); + } + + [Fact] + public async Task RunStreamingAsync_WithoutTurnOptions_OmitsOutputSchemaFlag() + { + // Arrange + using var cli = new FakeCodexCli(); + var agent = new CodexAIAgent(new CodexAIAgentOptions { CodexOptions = cli.CreateOptions() }); + var session = await agent.CreateSessionAsync(TestContext.Current.CancellationToken); + + // Act + await foreach ( + var _ in agent.RunStreamingAsync("hi", session, cancellationToken: TestContext.Current.CancellationToken) + ) { } + + // Assert + var args = Assert.Single(cli.ReadArguments()); + Assert.DoesNotContain("--output-schema", args); + } +}