Skip to content

Commit 43beb35

Browse files
committed
feat: Updated
1 parent 11e0bb8 commit 43beb35

18 files changed

Lines changed: 240 additions & 233 deletions

CLAUDE.md

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
# CLAUDE.md
2+
3+
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4+
5+
## Project Overview
6+
7+
C# SDK for the [OpenAI](https://openai.com/) API, auto-generated from the official OpenAI OpenAPI specification using [AutoSDK](https://github.com/tryAGI/AutoSDK). Published as the `tryAGI.OpenAI` NuGet package. Includes support for custom providers (Azure, DeepSeek, Groq, Ollama, OpenRouter, Together, and many more), a source generator for native C# function/tool definitions, pricing constants, and structured output helpers.
8+
9+
## Build Commands
10+
11+
```bash
12+
# Build the solution
13+
dotnet build OpenAI.sln
14+
15+
# Build for release (also produces NuGet package)
16+
dotnet build OpenAI.sln -c Release
17+
18+
# Run integration tests (requires OPENAI_API_KEY env var)
19+
dotnet test src/tests/OpenAI.IntegrationTests/OpenAI.IntegrationTests.csproj
20+
21+
# Run snapshot tests for CSharpToJsonSchema
22+
dotnet test src/tests/CSharpToJsonSchema.SnapshotTests/CSharpToJsonSchema.SnapshotTests.csproj
23+
24+
# Run unit tests for CSharpToJsonSchema
25+
dotnet test src/tests/CSharpToJsonSchema.UnitTests/CSharpToJsonSchema.UnitTests.csproj
26+
27+
# Regenerate SDK from OpenAPI spec
28+
cd src/libs/tryAGI.OpenAI && ./generate.sh
29+
```
30+
31+
## Architecture
32+
33+
### Code Generation Pipeline
34+
35+
The SDK code in `Generated/` is **auto-generated** -- do not manually edit files in `src/libs/tryAGI.OpenAI/Generated/`.
36+
37+
1. `src/libs/tryAGI.OpenAI/openapi.yaml` -- the OpenAI OpenAPI spec (fetched from Stainless)
38+
2. `src/helpers/FixOpenApiSpec/` -- converts OpenAPI 3.1 to 3.0 format for compatibility
39+
3. `src/libs/tryAGI.OpenAI/generate.sh` -- orchestrates: download spec, fix spec, run AutoSDK CLI, output to `Generated/`
40+
4. CI auto-updates the spec and creates PRs if changes are detected
41+
42+
### Hand-Written Extensions
43+
44+
Unlike most other tryAGI SDKs, this repo has significant **hand-written code** alongside the generated code:
45+
46+
| Path | Purpose |
47+
|------|---------|
48+
| `Metadata/` | Pricing constants and model metadata (chat, embedding, images, TTS, STT) |
49+
| `Conversions/` | Implicit conversions for `ChatCompletionRequestMessage`, `CreateMessageRequest`, etc. |
50+
| `Extensions/StringExtensions.cs` | Helper extension methods |
51+
| `CustomProviders.cs` | Factory methods for Azure, DeepSeek, Groq, Ollama, OpenRouter, etc. |
52+
| `ChatClient.CreateChatCompletion.AsStream.cs` | Streaming chat completion extensions |
53+
| `ChatClient.CreateChatCompletion.As.cs` | Structured output (`CreateChatCompletionAsAsync<T>`) |
54+
| `AssistantClient.CreateRun.AsStream.cs` | Streaming assistant run extensions |
55+
| `RealtimeConversationClient.cs` | Realtime API WebSocket client |
56+
| `OpenAiApi.Headers.cs` | Custom header helpers |
57+
| `TypeToSchemaHelpers.cs` | JSON schema generation helpers for tools |
58+
| `Image.Bytes.cs` | Image byte conversion helpers |
59+
60+
### Project Layout
61+
62+
| Project | Purpose |
63+
|---------|---------|
64+
| `src/libs/tryAGI.OpenAI/` | Main SDK library (`OpenAiClient` / `OpenAiApi`) |
65+
| `src/tests/OpenAI.IntegrationTests/` | Integration tests against real OpenAI API and custom providers |
66+
| `src/tests/CSharpToJsonSchema.SnapshotTests/` | Snapshot tests for schema generation |
67+
| `src/tests/CSharpToJsonSchema.UnitTests/` | Unit tests for schema generation |
68+
| `src/helpers/FixOpenApiSpec/` | OpenAPI spec fixer tool |
69+
| `src/helpers/GenerateDocs/` | Documentation generator from integration tests |
70+
| `src/helpers/TrimmingHelper/` | NativeAOT/trimming compatibility validator |
71+
72+
### Key Dependencies
73+
74+
- `CSharpToJsonSchema` -- source generator for defining tools/functions via C# interfaces
75+
- `Tiktoken` -- token counting for pricing calculations
76+
- `System.Net.ServerSentEvents` -- SSE parsing for streaming endpoints
77+
78+
### Build Configuration
79+
80+
- **Target:** `net10.0` (single target)
81+
- **Language:** C# preview with nullable reference types
82+
- **Signing:** Strong-named assemblies via `src/key.snk`
83+
- **Versioning:** Semantic versioning from git tags (`v` prefix) via MinVer
84+
- **Analysis:** All .NET analyzers enabled, AOT/trimming compatibility enforced
85+
- **Testing:** MSTest + FluentAssertions
86+
- **CLS Compliant:** Assembly marked as CLS-compliant
87+
88+
### CI/CD
89+
90+
- Uses shared workflows from `HavenDV/workflows` repo
91+
- Dependabot updates NuGet packages weekly (auto-merged)
92+
- Documentation deployed to GitHub Pages via MkDocs Material
93+
94+
## Key Conventions
95+
96+
- The NuGet package name is `tryAGI.OpenAI` (not just `OpenAI`) to avoid conflicts with the official OpenAI package
97+
- The main client class is `OpenAiApi` (for backward compatibility) with `OpenAiClient` as the generated variant
98+
- Custom providers are accessed via static factory methods on `CustomProviders` class (e.g., `CustomProviders.Azure(...)`)

OpenAI.sln

Lines changed: 0 additions & 85 deletions
This file was deleted.

OpenAI.slnx

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<Solution>
2+
<Folder Name="/helpers/">
3+
<Project Path="src/helpers/FixOpenApiSpec/FixOpenApiSpec.csproj" />
4+
<Project Path="src/helpers/GenerateDocs/GenerateDocs.csproj" />
5+
<Project Path="src/helpers/TrimmingHelper/TrimmingHelper.csproj" />
6+
</Folder>
7+
<Folder Name="/libs/">
8+
<File Path="src/libs/Directory.Build.props" />
9+
<Project Path="src/libs/tryAGI.OpenAI/tryAGI.OpenAI.csproj" />
10+
</Folder>
11+
<Folder Name="/Solution Items/">
12+
<File Path=".gitignore" />
13+
<File Path="LICENSE" />
14+
<File Path="mkdocs.yml" />
15+
<File Path="README.md" />
16+
<File Path="src/Directory.Build.props" />
17+
</Folder>
18+
<Folder Name="/Solution Items/workflows/">
19+
<File Path=".github/dependabot.yml" />
20+
<File Path=".github/workflows/auto-merge.yml" />
21+
<File Path=".github/workflows/auto-update.yml" />
22+
<File Path=".github/workflows/dotnet.yml" />
23+
<File Path=".github/workflows/mkdocs.yml" />
24+
<File Path=".github/workflows/pull-request.yml" />
25+
</Folder>
26+
<Folder Name="/tests/">
27+
<Project Path="src/tests/OpenAI.IntegrationTests/OpenAI.IntegrationTests.csproj" />
28+
</Folder>
29+
</Solution>

src/helpers/FixOpenApiSpec/FixOpenApiSpec.csproj

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@
1010

1111
<ItemGroup>
1212
<PackageReference Include="AutoSDK" Version="0.29.0" />
13-
<PackageReference Include="Microsoft.OpenApi.Readers" Version="1.6.28" />
13+
<PackageReference Include="Microsoft.OpenApi" Version="3.3.1" />
14+
<PackageReference Include="Microsoft.OpenApi.YamlReader" Version="3.3.1" />
1415
</ItemGroup>
1516

1617
</Project>
Lines changed: 25 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -1,86 +1,51 @@
1-
using AutoSDK.Helpers;
1+
using AutoSDK.Extensions;
2+
using AutoSDK.Models;
23
using Microsoft.OpenApi;
3-
using Microsoft.OpenApi.Extensions;
4-
using Microsoft.OpenApi.Models;
5-
using Microsoft.OpenApi.Readers;
64

75
var path = args[0];
8-
var jsonOrYaml = await File.ReadAllTextAsync(path);
6+
var yamlOrJson = await File.ReadAllTextAsync(path);
97

10-
jsonOrYaml = jsonOrYaml.Replace("minimum: -9223372036854776000", "");
11-
12-
if (OpenApi31Support.IsOpenApi31(jsonOrYaml))
13-
{
14-
jsonOrYaml = OpenApi31Support.ConvertToOpenApi30(jsonOrYaml);
15-
}
8+
yamlOrJson = yamlOrJson.Replace("minimum: -9223372036854776000", "");
169

1710
var realtimeText = await File.ReadAllTextAsync(path.Replace(".yaml", ".realtime.yaml"));
1811

19-
var openApiDocument = new OpenApiStringReader().Read(jsonOrYaml, out var diagnostics);
20-
var realtimeOpenApiDocument = new OpenApiStringReader().Read(realtimeText, out var realtimeDiagnostics);
21-
foreach (var schema in realtimeOpenApiDocument.Components.Schemas)
12+
var openApiDocument = yamlOrJson.GetOpenApiDocument(Settings.Default);
13+
var realtimeOpenApiDocument = realtimeText.GetOpenApiDocument(Settings.Default);
14+
foreach (var schema in realtimeOpenApiDocument.Components!.Schemas!)
2215
{
23-
openApiDocument.Components.Schemas[schema.Key] = schema.Value;
16+
openApiDocument.Components!.Schemas![schema.Key] = schema.Value;
2417
}
2518

26-
openApiDocument.Components.Schemas["ParallelToolCalls"]!.Default = null;
27-
openApiDocument.Components.Schemas["ParallelToolCalls"]!.Nullable = true;
19+
((OpenApiSchema)openApiDocument.Components!.Schemas!["ParallelToolCalls"]!).Default = null;
20+
((OpenApiSchema)openApiDocument.Components.Schemas["ParallelToolCalls"]!).Type |= JsonSchemaType.Null;
2821

29-
openApiDocument.Components.Schemas["CreateEmbeddingRequest"]!.Properties["dimensions"].Nullable = true;
22+
((OpenApiSchema)openApiDocument.Components.Schemas["CreateEmbeddingRequest"]!.Properties!["dimensions"]!).Type |= JsonSchemaType.Null;
3023

31-
openApiDocument.Components.Schemas["CreateChatCompletionResponse"]!.Properties["choices"].Items.Required.Remove("logprobs");
24+
((OpenApiSchema)openApiDocument.Components.Schemas["CreateChatCompletionResponse"]!.Properties!["choices"]!).Items!.Required!.Remove("logprobs");
3225

33-
openApiDocument.Components.Schemas["ChatCompletionResponseMessage"]!.Required.Remove("content");
34-
openApiDocument.Components.Schemas["ChatCompletionResponseMessage"]!.Required.Remove("refusal");
26+
((OpenApiSchema)openApiDocument.Components.Schemas["ChatCompletionResponseMessage"]!).Required!.Remove("content");
27+
((OpenApiSchema)openApiDocument.Components.Schemas["ChatCompletionResponseMessage"]!).Required!.Remove("refusal");
3528

36-
openApiDocument.Components.Schemas["MessageObject"]!.Required.Remove("status");
37-
openApiDocument.Components.Schemas["MessageObject"]!.Required.Remove("incomplete_details");
38-
openApiDocument.Components.Schemas["MessageObject"]!.Required.Remove("completed_at");
39-
openApiDocument.Components.Schemas["MessageObject"]!.Required.Remove("incomplete_at");
29+
((OpenApiSchema)openApiDocument.Components.Schemas["MessageObject"]!).Required!.Remove("status");
30+
((OpenApiSchema)openApiDocument.Components.Schemas["MessageObject"]!).Required!.Remove("incomplete_details");
31+
((OpenApiSchema)openApiDocument.Components.Schemas["MessageObject"]!).Required!.Remove("completed_at");
32+
((OpenApiSchema)openApiDocument.Components.Schemas["MessageObject"]!).Required!.Remove("incomplete_at");
4033

41-
openApiDocument.Components.Schemas["RunStepObject"]!.Required.Remove("expired_at");
42-
openApiDocument.Components.Schemas["RunStepObject"]!.Required.Remove("metadata");
34+
((OpenApiSchema)openApiDocument.Components.Schemas["RunStepObject"]!).Required!.Remove("expired_at");
35+
((OpenApiSchema)openApiDocument.Components.Schemas["RunStepObject"]!).Required!.Remove("metadata");
4336

44-
openApiDocument.Paths["/files/{file_id}/content"]!.Operations[OperationType.Get].Responses["200"]!.Content.Remove("application/json");
45-
openApiDocument.Paths["/files/{file_id}/content"]!.Operations[OperationType.Get].Responses["200"]!.Content.Add(
37+
openApiDocument.Paths!["/files/{file_id}/content"]!.Operations[System.Net.Http.HttpMethod.Get]!.Responses!["200"]!.Content!.Remove("application/json");
38+
openApiDocument.Paths["/files/{file_id}/content"]!.Operations[System.Net.Http.HttpMethod.Get]!.Responses!["200"]!.Content!.Add(
4639
"application/octet-stream",
4740
new OpenApiMediaType
4841
{
4942
Schema = new OpenApiSchema
5043
{
51-
Type = "string",
44+
Type = JsonSchemaType.String,
5245
Format = "binary",
5346
}
5447
});
55-
//
56-
// openApiDocument.Components.Schemas["CreateChatCompletionRequest"]!.Properties["model"].AnyOf[1].Enum =
57-
// openApiDocument.Components.Schemas["CreateChatCompletionRequest"]!.Properties["model"].AnyOf[1].Enum
58-
// .DistinctBy(x => (x as OpenApiString)?.Value)
59-
// .ToList();
60-
// openApiDocument.Components.Schemas["CreateAssistantRequest"]!.Properties["model"].AnyOf[1].Enum =
61-
// openApiDocument.Components.Schemas["CreateAssistantRequest"]!.Properties["model"].AnyOf[1].Enum
62-
// .DistinctBy(x => (x as OpenApiString)?.Value)
63-
// .ToList();
64-
// openApiDocument.Components.Schemas["CreateRunRequest"]!.Properties["model"].AnyOf[1].Enum =
65-
// openApiDocument.Components.Schemas["CreateRunRequest"]!.Properties["model"].AnyOf[1].Enum
66-
// .DistinctBy(x => (x as OpenApiString)?.Value)
67-
// .ToList();
68-
// openApiDocument.Components.Schemas["CreateThreadAndRunRequest"]!.Properties["model"].AnyOf[1].Enum =
69-
// openApiDocument.Components.Schemas["CreateThreadAndRunRequest"]!.Properties["model"].AnyOf[1].Enum
70-
// .DistinctBy(x => (x as OpenApiString)?.Value)
71-
// .ToList();
72-
73-
jsonOrYaml = openApiDocument.SerializeAsYaml(OpenApiSpecVersion.OpenApi3_0);
74-
_ = new OpenApiStringReader().Read(jsonOrYaml, out diagnostics);
7548

76-
if (diagnostics.Errors.Count > 0)
77-
{
78-
foreach (var error in diagnostics.Errors)
79-
{
80-
Console.WriteLine(error.Message);
81-
}
82-
// Return Exit code 1
83-
//Environment.Exit(1);
84-
}
49+
yamlOrJson = await openApiDocument.SerializeAsYamlAsync(OpenApiSpecVersion.OpenApi3_2);
8550

86-
await File.WriteAllTextAsync(path, jsonOrYaml);
51+
await File.WriteAllTextAsync(path, yamlOrJson);

src/helpers/TrimmingHelper/TrimmingHelper.csproj

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@
1313
</ItemGroup>
1414

1515
<ItemGroup>
16-
<PackageReference Include="Microsoft.NET.ILLink.Tasks" Version="10.0.3" />
1716
<TrimmerRootAssembly Include="tryAGI.OpenAI" />
1817
</ItemGroup>
1918

src/libs/tryAGI.OpenAI/AssistantClient.CreateRun.AsStream.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -191,15 +191,15 @@ public partial class AssistantsClient
191191
string? instructions = default,
192192
string? additionalInstructions = default,
193193
global::System.Collections.Generic.IList<global::tryAGI.OpenAI.CreateMessageRequest>? additionalMessages = default,
194-
global::System.Collections.Generic.IList<global::tryAGI.OpenAI.OneOf<global::tryAGI.OpenAI.AssistantToolsCode, global::tryAGI.OpenAI.AssistantToolsFileSearch, global::tryAGI.OpenAI.AssistantToolsFunction>>? tools = default,
194+
global::System.Collections.Generic.IList<global::tryAGI.OpenAI.AssistantTool>? tools = default,
195195
global::System.Collections.Generic.Dictionary<string, string>? metadata = default,
196196
double? temperature = default,
197197
double? topP = default,
198198
bool? stream = default,
199199
int? maxPromptTokens = default,
200200
int? maxCompletionTokens = default,
201-
global::tryAGI.OpenAI.AllOf<global::tryAGI.OpenAI.TruncationObject, object>? truncationStrategy = default,
202-
global::tryAGI.OpenAI.AllOf<global::tryAGI.OpenAI.AssistantsApiToolChoiceOption?, object>? toolChoice = default,
201+
global::tryAGI.OpenAI.TruncationObject? truncationStrategy = default,
202+
global::tryAGI.OpenAI.AssistantsApiToolChoiceOption? toolChoice = default,
203203
bool? parallelToolCalls = default,
204204
global::tryAGI.OpenAI.AssistantsApiResponseFormatOption? responseFormat = default,
205205
[EnumeratorCancellation] global::System.Threading.CancellationToken cancellationToken = default)

0 commit comments

Comments
 (0)