Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ csharp_new_line_before_finally = true
csharp_new_line_before_open_brace = all

# Modifier preferences
dotnet_style_require_accessibility_modifiers = for_non_interface_members:error
dotnet_style_require_accessibility_modifiers = error

# Code-block preferences
csharp_prefer_braces = true:error
Expand Down
98 changes: 60 additions & 38 deletions .github/workflows/Publish-Nuget.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,24 +41,49 @@ env:
jobs:
build:
if: ${{ !github.event_name == 'pull_request' || !github.event.pull_request.draft }}
env:
PACKAGE_VERSION: ''
COVERAGE_FILE_PATH: ''
runs-on: ubuntu-latest

defaults:
run:
shell: pwsh
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
with:
fetch-depth: 0

- uses: actions/setup-dotnet@v4
- uses: actions/setup-dotnet@v5
with:
dotnet-version: ${{ env.DOTNET_VERSION }}

- run: dotnet restore

- run: dotnet build --configuration Release --no-restore

- name: Get Version
id: version
run: |
$projectPath = "${{ github.workspace }}\OpenAI-DotNet\OpenAI-DotNet.csproj"

if (-Not (Test-Path $projectPath)) {
Write-Host "Project file not found at $projectPath"
exit 1
}

[xml]$csproj = Get-Content $projectPath

if ($csproj -eq $null) {
Write-Host "Failed to load csproj file."
exit 1
}

$version = $csproj.Project.PropertyGroup.Version

if ([string]::IsNullOrEmpty($version)) {
Write-Host "Version not found in csproj."
exit 1
}

Write-Host "Project Version: $version"
echo "PACKAGE_VERSION=$version" >> $GITHUB_OUTPUT

- name: Test Packages
if: ${{ github.ref != 'refs/heads/main' && github.event_name != 'push' }}
run: dotnet test --configuration Release --collect:"XPlat Code Coverage" --logger:trx --no-build --no-restore --results-directory ./test-results
Expand All @@ -68,83 +93,82 @@ jobs:

- name: Publish Test Results
if: ${{ github.ref != 'refs/heads/main' && github.event_name != 'push' && always() }}
uses: EnricoMi/publish-unit-test-result-action@v2
uses: EnricoMi/publish-unit-test-result-action@34d7c956a59aed1bfebf31df77b8de55db9bbaaf # v2.11.0
with:
files: test-results/**/*.trx
comment_mode: off
report_individual_runs: true
compare_to_earlier_commit: false
large_files: true

- name: Determine Coverage File Path
if: ${{ github.ref != 'refs/heads/main' && github.event_name != 'push' && always() }}
id: coverage-path
shell: bash
run: |
COVERAGE_FILE_PATH=$(find ./test-results -name 'coverage.cobertura.xml' | head -n 1)
echo "COVERAGE_FILE_PATH=$COVERAGE_FILE_PATH" >> $GITHUB_ENV
echo "COVERAGE_FILE_PATH=$COVERAGE_FILE_PATH" >> $GITHUB_OUTPUT

- name: Code Coverage Summary Report
if: ${{ github.ref != 'refs/heads/main' && github.event_name != 'push' && always() }}
uses: irongut/CodeCoverageSummary@v1.3.0
uses: irongut/CodeCoverageSummary@51cc3a756ddcd398d447c044c02cb6aa83fdae95 # v1.3.0
with:
filename: ${{ env.COVERAGE_FILE_PATH }}
filename: ${{ steps.coverage-path.outputs.COVERAGE_FILE_PATH }}
badge: true
format: 'markdown'
output: 'both'
format: markdown
output: both

- name: Write Coverage Job Summary
if: ${{ github.ref != 'refs/heads/main' && github.event_name != 'push' && always() }}
shell: bash
run: cat code-coverage-results.md >> $GITHUB_STEP_SUMMARY

- name: Pack and Publish NuGet Package
run: |
$projectPath = "${{ github.workspace }}\OpenAI-DotNet"
$proxyProjectPath = "${{ github.workspace }}\OpenAI-DotNet-Proxy"

# pack OpenAI-DotNet
dotnet pack $projectPath --configuration Release --include-symbols
$out = "$projectPath\bin\Release"
$packagePath = Get-ChildItem -Path $out -File -Include '*.nupkg' -Exclude '*symbols*' -Recurse -ErrorAction SilentlyContinue

if ($packagePath) {
Write-Host Package path: $packagePath
} else {
Write-Host Failed to find package at $out
exit 1
}

# pack OpenAI-DotNet-Proxy
dotnet pack $proxyProjectPath --configuration Release --include-symbols
$proxyOut = "$proxyProjectPath\bin\Release"
$proxyPackagePath = Get-ChildItem -Path $proxyOut -File -Include '*.nupkg' -Exclude '*symbols*' -Recurse -ErrorAction SilentlyContinue

if ($proxyPackagePath) {
Write-Host Package path: $proxyPackagePath
} else {
Write-Host Failed to find package at $proxyOut
exit 1
}

$isRelease = "${{ github.ref == 'refs/heads/main' }}"

if ($isRelease -eq 'true') {
dotnet nuget push $packagePath.FullName --api-key ${{ secrets.NUGET_API_KEY }} --source https://api.nuget.org/v3/index.json --skip-duplicate
dotnet nuget push $proxyPackagePath.FullName --api-key ${{ secrets.NUGET_API_KEY }} --source https://api.nuget.org/v3/index.json --skip-duplicate
}

$version = $packagePath.Name -replace "^OpenAI-DotNet.(.*).nupkg$",'$1'
echo "PACKAGE_VERSION=$version" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
shell: pwsh

- uses: actions/upload-artifact@v4
- uses: actions/upload-artifact@v6
if: always()
with:
name: OpenAI-DotNet.${{ env.PACKAGE_VERSION }}
name: OpenAI-DotNet.${{ steps.version.outputs.PACKAGE_VERSION }}-artifacts
path: |
${{ github.workspace }}/test-results
${{ github.workspace }}/OpenAI-DotNet/bin/Release/OpenAI-DotNet.${{ env.PACKAGE_VERSION }}.nupkg
${{ github.workspace }}/OpenAI-DotNet/bin/Release/OpenAI-DotNet.${{ env.PACKAGE_VERSION }}.symbols.nupkg
${{ github.workspace }}/OpenAI-DotNet/bin/Release/OpenAI-DotNet-Proxy.${{ env.PACKAGE_VERSION }}.nupkg
${{ github.workspace }}/OpenAI-DotNet/bin/Release/OpenAI-DotNet-Proxy.${{ env.PACKAGE_VERSION }}.symbols.nupkg
${{ github.workspace }}/OpenAI-DotNet/bin/Release/OpenAI-DotNet.${{ steps.version.outputs.PACKAGE_VERSION }}.nupkg
${{ github.workspace }}/OpenAI-DotNet/bin/Release/OpenAI-DotNet.${{ steps.version.outputs.PACKAGE_VERSION }}.symbols.nupkg
${{ github.workspace }}/OpenAI-DotNet/bin/Release/OpenAI-DotNet-Proxy.${{ steps.version.outputs.PACKAGE_VERSION }}.nupkg
${{ github.workspace }}/OpenAI-DotNet/bin/Release/OpenAI-DotNet-Proxy.${{ steps.version.outputs.PACKAGE_VERSION }}.symbols.nupkg
if-no-files-found: ignore

docs:
Expand All @@ -154,25 +178,23 @@ jobs:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
runs-on: ubuntu-latest

defaults:
run:
shell: bash
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
with:
fetch-depth: 0

- uses: actions/setup-dotnet@v4
- uses: actions/setup-dotnet@v5
with:
dotnet-version: ${{ env.DOTNET_VERSION }}

- name: build docfx
run: |
dotnet tool update -g docfx
docfx .docs/docfx.json

- uses: actions/upload-pages-artifact@v3
- uses: actions/upload-pages-artifact@7b1f4a764d45c48632c6b24a0339c27f5614fb0b # v4.0.0
with:
path: '_site'

- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4.0.3
uses: actions/deploy-pages@d6db90164ac5ed86f2b6aed7e0febac5b3c0c03e # v4.0.5
6 changes: 6 additions & 0 deletions OpenAI-DotNet/Extensions/BaseEndpointExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,12 @@ public static async Task<HttpResponseMessage> StreamEventsAsync(
request.Content = payload;
var response = await baseEndpoint.ServerSentEventStreamAsync(request, cancellationToken).ConfigureAwait(false);
await response.CheckResponseAsync(false, payload, cancellationToken: cancellationToken).ConfigureAwait(false);

if (baseEndpoint.EnableDebug)
{
await response.Content.LoadIntoBufferAsync().ConfigureAwait(false);
}

await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
var events = new Stack<ServerSentEvent>();
using var reader = new StreamReader(stream);
Expand Down
50 changes: 50 additions & 0 deletions OpenAI-DotNet/Models/Model.cs
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,18 @@ public Model(string id, string ownedBy = null)

#region Reasoning Models

/// <summary>
/// GPT-5.2 pro is available in the Responses API only to enable support for multi-turn model interactions before responding to API requests,
/// and other advanced API features in the future. Since GPT-5.2 pro is designed to tackle tough problems,
/// some requests may take several minutes to finish. To avoid timeouts, try using background mode.
/// GPT-5.2 pro supports reasoning.effort: medium, high, xhigh.
/// </summary>
/// <remarks>
/// - Context Window: 400,000 context window<br/>
/// - Max Output Tokens: 128,000 max output tokens
/// </remarks>
public static Model GPT5_2_Pro { get; } = new("gpt-5.2-pro", "openai");

/// <summary>
/// The o1 series of models are trained with reinforcement learning to perform complex reasoning.
/// o1 models think before they answer, producing a long internal chain of thought before responding to the user.
Expand Down Expand Up @@ -189,6 +201,15 @@ public Model(string id, string ownedBy = null)

#region Chat Models

/// <summary>
/// GPT-5.2 is our flagship model for coding and agentic tasks across industries.
/// </summary>
/// <remarks>
/// - Context Window: 400,000 context window<br/>
/// - Max Output Tokens: 128,000 max output tokens
/// </remarks>
public static Model GPT5_2 { get; } = new("gpt-5.2", "openai");

/// <summary>
/// GPT-5 is our flagship model for coding, reasoning, and agentic tasks across domains.
/// </summary>
Expand Down Expand Up @@ -498,6 +519,35 @@ public Model(string id, string ownedBy = null)

#region Specialized Models

/// <summary>
/// GPT-5.1-Codex-Max is purpose-built for agentic coding.
/// It's only available in the Responses API.
/// </summary>
/// <remarks>
/// - Context Window: 400,000 tokens<br/>
/// - Max Output Tokens: 128,000 tokens
/// </remarks>
public static Model GPT5_1_CodexMax { get; } = new("gpt-5.1-codex-max", "openai");

/// <summary>
/// GPT-5.1-Codex is a version of GPT-5 optimized for agentic coding tasks in Codex or similar environments.
/// It's available in the Responses API
Comment thread
StephenHodgson marked this conversation as resolved.
/// </summary>
/// <remarks>
/// - Context Window: 400,000 tokens<br/>
/// - Max Output Tokens: 128,000 tokens
/// </remarks>
public static Model GPT5_1_Codex { get; } = new("gpt-5.1-codex", "openai");

/// <summary>
/// GPT-5.1 Codex mini is a smaller, more cost-effective, less-capable version of GPT-5.1-Codex.
/// </summary>
/// <remarks>
/// - Context Window: 400,000 tokens<br/>
/// - Max Output Tokens: 128,000 tokens
/// </remarks>
public static Model GPT5_1_CodexMini { get; } = new("gpt-5.1-codex-mini", "openai");

/// <summary>
/// GPT-5-Codex is a version of GPT-5 optimized for agentic coding tasks in Codex or similar environments.
/// It's available in the Responses API only and the underlying model snapshot will be regularly updated.
Expand Down
9 changes: 8 additions & 1 deletion OpenAI-DotNet/OpenAI-DotNet.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,15 @@ More context [on Roger Pincombe's blog](https://rogerpincombe.com/openai-dotnet-
<AssemblyOriginatorKeyFile>OpenAI-DotNet.pfx</AssemblyOriginatorKeyFile>
<IncludeSymbols>true</IncludeSymbols>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<Version>8.8.7</Version>
<Version>8.8.8</Version>
<PackageReleaseNotes>
Version 8.8.8
- Allow setting Responses.TextContent.Type to OutputText for Role.Assistant messages
- Fixed stream consumed with debug logging enabled
- Fixed wrapped server sent event error object
- Fixed ability to create MCPApprovalResponse for mcp tool approvals
- Fixed MCPToolCall.Error deserialization
- Updated default models
Version 8.8.7
- Fix VAD serialization not properly setting disabled values
Version 8.8.6
Expand Down
2 changes: 1 addition & 1 deletion OpenAI-DotNet/Responses/CreateResponseRequest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ public CreateResponseRequest(
{
Input = input?.ToArray() ?? throw new ArgumentNullException(nameof(input));
Model = string.IsNullOrWhiteSpace(model?.Id) && prompt == null
? Models.Model.GPT4oRealtime
? Models.Model.GPT5_Mini
: model;
Background = background;
Include = include?.ToList();
Expand Down
14 changes: 11 additions & 3 deletions OpenAI-DotNet/Responses/MCPApprovalResponse.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,14 @@ namespace OpenAI.Responses
{
public sealed class MCPApprovalResponse : BaseResponse, IResponseItem
{
public MCPApprovalResponse() { }

public MCPApprovalResponse(string approvalRequestId, bool approve)
{
ApprovalRequestId = approvalRequestId;
Approve = approve;
}

/// <inheritdoc />
[JsonInclude]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
Expand All @@ -32,19 +40,19 @@ public sealed class MCPApprovalResponse : BaseResponse, IResponseItem

[JsonInclude]
[JsonPropertyName("approval_request_id")]
public string ApprovalRequestId { get; }
public string ApprovalRequestId { get; private set; }

[JsonInclude]
[JsonIgnore(Condition = JsonIgnoreCondition.Never)]
[JsonPropertyName("approve")]
public bool Approve { get; }
public bool Approve { get; private set; }

/// <summary>
/// Optional reason for the decision.
/// </summary>
[JsonInclude]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
[JsonPropertyName("reason")]
public string Reason { get; }
public string Reason { get; private set; }
}
}
2 changes: 1 addition & 1 deletion OpenAI-DotNet/Responses/MCPToolCall.cs
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,6 @@ internal string Delta
[JsonInclude]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
[JsonPropertyName("error")]
public string Error { get; private set; }
public JsonNode Error { get; private set; }
}
}
2 changes: 1 addition & 1 deletion OpenAI-DotNet/Responses/Message.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ public sealed class Message : BaseResponse, IResponseItem
public Message() { }

public Message(Role role, string text)
: this(role, new TextContent(text))
: this(role, new TextContent(text, role == Role.Assistant ? ResponseContentType.OutputText : ResponseContentType.InputText))
{
}

Expand Down
3 changes: 2 additions & 1 deletion OpenAI-DotNet/Responses/ResponsesEndpoint.cs
Original file line number Diff line number Diff line change
Expand Up @@ -401,7 +401,8 @@ private async Task<Response> StreamResponseAsync(string endpoint, StringContent
}
case "error":
{
serverSentEvent = sseResponse.Deserialize<Error>(ssEvent, client);
var error = @object["error"]?.Deserialize<Error>();
serverSentEvent = error ?? sseResponse.Deserialize<Error>(ssEvent, client);
break;
}
// Event status messages with no data payloads:
Expand Down
9 changes: 7 additions & 2 deletions OpenAI-DotNet/Responses/TextContent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,14 @@ public sealed class TextContent : BaseResponse, IResponseContent

public TextContent() { }

public TextContent(string text)
public TextContent(string text, ResponseContentType type = ResponseContentType.InputText)
{
Type = ResponseContentType.InputText;
if (type != ResponseContentType.InputText && type != ResponseContentType.OutputText)
{
throw new ArgumentException("Invalid response content type. Must be InputText or OutputText.", nameof(type));
}

Type = type;
Text = text;
}

Expand Down
Loading
Loading