CodexSharpSDK
CLI-first .NET 10 / C# SDK for OpenAI Codex CLI with typed thread API, streamed JSONL events, structured outputs
Install / Use
/learn @managedcode/CodexSharpSDKREADME
ManagedCode.CodexSharpSDK
ManagedCode.CodexSharpSDK is an open-source .NET SDK for driving the Codex CLI from C#.
It is a CLI-first .NET 10 / C# 14 SDK aligned with real codex runtime behavior, with:
- thread-based API (
start/resume) - streamed JSONL events
- structured output schema support
- image attachments
--configflattening to TOML- NativeAOT-friendly implementation and tests on TUnit
All consumer usage examples are documented in this README; this repository intentionally does not keep standalone sample projects.
Installation
dotnet add package ManagedCode.CodexSharpSDK
Prerequisites
Before using this SDK, you must have:
codexCLI installed and available inPATH- an already authenticated Codex session (
codex login)
Quick check:
codex --version
codex login
Quickstart
using ManagedCode.CodexSharpSDK;
using var client = new CodexClient();
var thread = client.StartThread(new ThreadOptions
{
Model = CodexModels.Gpt54,
ModelReasoningEffort = ModelReasoningEffort.Medium,
});
var turn = await thread.RunAsync("Diagnose failing tests and propose a fix");
Console.WriteLine(turn.FinalResponse);
Console.WriteLine($"Items: {turn.Items.Count}");
AutoStart is enabled by default, so StartThread() works immediately.
Advanced Configuration (Optional)
using var client = new CodexClient(new CodexClientOptions
{
CodexOptions = new CodexOptions
{
// Override only when `codex` is not discoverable via npm/PATH.
CodexExecutablePath = "/custom/path/to/codex",
},
});
var thread = client.StartThread(new ThreadOptions
{
Model = CodexModels.Gpt54,
ModelReasoningEffort = ModelReasoningEffort.High,
SandboxMode = SandboxMode.WorkspaceWrite,
});
Extended CLI Options
ThreadOptions supports full codex exec control.
var thread = client.StartThread(new ThreadOptions
{
Profile = "strict",
UseOss = true,
LocalProvider = OssProvider.LmStudio,
FullAuto = true,
Ephemeral = true,
Color = ExecOutputColor.Auto,
EnabledFeatures = ["multi_agent"],
DisabledFeatures = ["steer"],
AdditionalCliArguments = ["--some-future-flag", "custom-value"],
});
Codex CLI Metadata
using var client = new CodexClient();
var metadata = client.GetCliMetadata();
Console.WriteLine($"Installed codex-cli: {metadata.InstalledVersion}");
Console.WriteLine($"Default model: {metadata.DefaultModel ?? "(not set)"}");
foreach (var model in metadata.Models.Where(model => model.IsListed))
{
Console.WriteLine(model.Slug);
}
GetCliMetadata() reads:
- installed CLI version from
codex --version - default model from
~/.codex/config.toml - model catalog from
~/.codex/models_cache.json
var update = client.GetCliUpdateStatus();
if (update.IsUpdateAvailable)
{
Console.WriteLine(update.UpdateMessage);
Console.WriteLine(update.UpdateCommand);
}
GetCliUpdateStatus() compares installed CLI version with latest published @openai/codex npm version and returns an update command matched to your install context (bun or npm).
When thread-level web search options are omitted, SDK does not emit a web_search override and leaves your existing CLI/config value as-is.
Client Lifecycle and Thread Safety
CodexClientis safe for concurrent use from multiple threads.StartAsync()is idempotent and guarded.StopAsync()cleanly disconnects client state.Dispose()transitions client toDisposed.- A single
CodexThreadinstance serializes turns (RunAsyncandRunStreamedAsync) to prevent race conditions in shared conversation state.
Streaming
var streamed = await thread.RunStreamedAsync("Implement the fix");
await foreach (var evt in streamed.Events)
{
switch (evt)
{
case ItemCompletedEvent completed:
Console.WriteLine($"Item: {completed.Item.Type}");
break;
case TurnCompletedEvent done:
Console.WriteLine($"Output tokens: {done.Usage.OutputTokens}");
break;
}
}
Structured Output
using System.Text.Json.Serialization;
public sealed record RepositorySummary(string Summary, string Status);
[JsonSerializable(typeof(RepositorySummary))]
internal sealed partial class AppJsonContext : JsonSerializerContext;
var schema = StructuredOutputSchema.Map<RepositorySummary>(
additionalProperties: false,
(response => response.Summary, StructuredOutputSchema.PlainText()),
(response => response.Status, StructuredOutputSchema.PlainText()));
var result = await thread.RunAsync<RepositorySummary>(
"Summarize repository status",
schema,
AppJsonContext.Default.RepositorySummary);
Console.WriteLine(result.TypedResponse.Status);
Console.WriteLine(result.TypedResponse.Summary);
For advanced options (for example cancellation), use the TurnOptions overload:
using var cancellation = new CancellationTokenSource(TimeSpan.FromSeconds(30));
var result = await thread.RunAsync<RepositorySummary>(
"Summarize repository status",
AppJsonContext.Default.RepositorySummary,
new TurnOptions
{
OutputSchema = schema,
CancellationToken = cancellation.Token,
});
RunAsync<TResponse> always requires OutputSchema (direct parameter or TurnOptions.OutputSchema).
For AOT/trimming-safe typed deserialization, pass JsonTypeInfo<TResponse> from a source-generated context.
Overloads without JsonTypeInfo<TResponse> are explicitly marked with RequiresDynamicCode and RequiresUnreferencedCode.
Diagnostics Logging (Optional)
using Microsoft.Extensions.Logging;
public sealed class ConsoleCodexLogger : ILogger
{
public IDisposable BeginScope<TState>(TState state)
where TState : notnull
{
return NullScope.Instance;
}
public bool IsEnabled(LogLevel logLevel)
{
return true;
}
public void Log<TState>(
LogLevel logLevel,
EventId eventId,
TState state,
Exception? exception,
Func<TState, Exception?, string> formatter)
{
Console.WriteLine($"[{logLevel}] {formatter(state, exception)}");
if (exception is not null)
{
Console.WriteLine(exception);
}
}
private sealed class NullScope : IDisposable
{
public static NullScope Instance { get; } = new();
public void Dispose() { }
}
}
using var client = new CodexClient(new CodexOptions
{
Logger = new ConsoleCodexLogger(),
});
Images + Text Input
using var imageStream = File.OpenRead("./photo.png");
var result = await thread.RunAsync(
[
new TextInput("Describe these images"),
new LocalImageInput("./ui.png"),
new LocalImageInput(new FileInfo("./diagram.jpg")),
new LocalImageInput(imageStream, "photo.png"),
]);
Resume an Existing CodexThread
var resumed = client.ResumeThread("thread_123");
await resumed.RunAsync("Continue from previous plan");
Microsoft.Extensions.AI Integration
An optional adapter package lets you use CodexSharpSDK through the standard IChatClient interface from Microsoft.Extensions.AI.
dotnet add package ManagedCode.CodexSharpSDK.Extensions.AI
Basic usage
using Microsoft.Extensions.AI;
using ManagedCode.CodexSharpSDK.Extensions.AI;
IChatClient client = new CodexChatClient(new CodexChatClientOptions
{
DefaultModel = CodexModels.Gpt54,
});
var response = await client.GetResponseAsync("Diagnose failing tests and propose a fix");
Console.WriteLine(response.Text);
DI registration
using ManagedCode.CodexSharpSDK.Extensions.AI.Extensions;
builder.Services.AddCodexChatClient(options =>
{
options.DefaultModel = CodexModels.Gpt54;
});
// Then inject IChatClient anywhere:
app.MapGet("/ask", async (IChatClient client) =>
{
var response = await client.GetResponseAsync("Summarize the repo");
return response.Text;
});
Resolve IChatClient from IServiceProvider
using Microsoft.Extensions.AI;
using Microsoft.Extensions.DependencyInjection;
using ManagedCode.CodexSharpSDK.Models;
using ManagedCode.CodexSharpSDK.Extensions.AI.Extensions;
var services = new ServiceCollection();
services.AddCodexChatClient(options =>
{
options.DefaultModel = CodexModels.Gpt54;
});
using var provider = services.BuildServiceProvider();
var chatClient = provider.GetRequiredService<IChatClient>();
Keyed registration is also supported:
using Microsoft.Extensions.AI;
using Microsoft.Extensions.DependencyInjection;
using ManagedCode.CodexSharpSDK.Models;
using ManagedCode.CodexSharpSDK.Extensions.AI.Extensions;
var services = new ServiceCollection();
services.AddKeyedCodexChatClient("codex-main", options =>
{
options.DefaultModel = CodexModels.Gpt54;
});
using var provider = services.BuildServiceProvider();
var keyedChatClient = provider.GetRequiredKeyedService<IChatClient>("codex-main");
Streaming
await foreach (var update in client.GetStreamingResponseAsync("Implement
Related Skills
node-connect
352.5kDiagnose OpenClaw node connection and pairing failures for Android, iOS, and macOS companion apps
AGENTS
352.5kExtensions Boundary This directory contains bundled plugins. Treat it as the same boundary that third-party plugins see. Public Contracts - Docs: - `docs/plugins/building-plugins.md` - `do
frontend-design
111.3kCreate distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, or applications. Generates creative, polished code that avoids generic AI aesthetics.
openai-whisper-api
352.5kTranscribe audio via OpenAI Audio Transcriptions API (Whisper).
