ManagedCode.GeminiSharpSDK.Extensions.AgentFramework 0.0.1-rc4

Prefix Reserved
This is a prerelease version of ManagedCode.GeminiSharpSDK.Extensions.AgentFramework.
dotnet add package ManagedCode.GeminiSharpSDK.Extensions.AgentFramework --version 0.0.1-rc4
                    
NuGet\Install-Package ManagedCode.GeminiSharpSDK.Extensions.AgentFramework -Version 0.0.1-rc4
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="ManagedCode.GeminiSharpSDK.Extensions.AgentFramework" Version="0.0.1-rc4" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="ManagedCode.GeminiSharpSDK.Extensions.AgentFramework" Version="0.0.1-rc4" />
                    
Directory.Packages.props
<PackageReference Include="ManagedCode.GeminiSharpSDK.Extensions.AgentFramework" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add ManagedCode.GeminiSharpSDK.Extensions.AgentFramework --version 0.0.1-rc4
                    
#r "nuget: ManagedCode.GeminiSharpSDK.Extensions.AgentFramework, 0.0.1-rc4"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package ManagedCode.GeminiSharpSDK.Extensions.AgentFramework@0.0.1-rc4
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=ManagedCode.GeminiSharpSDK.Extensions.AgentFramework&version=0.0.1-rc4&prerelease
                    
Install as a Cake Addin
#tool nuget:?package=ManagedCode.GeminiSharpSDK.Extensions.AgentFramework&version=0.0.1-rc4&prerelease
                    
Install as a Cake Tool

ManagedCode.GeminiSharpSDK

CI Release CodeQL NuGet License: MIT

ManagedCode.GeminiSharpSDK is an open-source .NET SDK for driving the Gemini CLI from C#.

It is a CLI-first .NET 10 / C# 14 SDK aligned with real gemini runtime behavior, with:

  • thread-based API (start / resume)
  • streamed JSONL events
  • structured output schema support
  • image attachments
  • --config flattening 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.GeminiSharpSDK

Prerequisites

Before using this SDK, you must have:

  • gemini CLI installed and available in PATH
  • an already authenticated Gemini session (gemini login)

Quick check:

gemini --version
gemini login

Quickstart

using ManagedCode.GeminiSharpSDK;

using var client = new GeminiClient();

var thread = client.StartThread(new ThreadOptions
{
    Model = GeminiModels.Gemini25Pro,
});

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 GeminiClient(new GeminiClientOptions
{
    GeminiOptions = new GeminiOptions
    {
        // Override only when `gemini` is not discoverable via npm/PATH.
        GeminiExecutablePath = "/custom/path/to/gemini",
    },
});

var thread = client.StartThread(new ThreadOptions
{
    Model = GeminiModels.Gemini25Pro,
    ApprovalPolicy = ApprovalMode.Default,
    AdditionalDirectories = ["/workspace/shared"],
});

Extended CLI Options

ThreadOptions maps to the current headless Gemini CLI surface (--prompt, --output-format stream-json, --resume, --approval-mode, --include-directories, and sandbox toggle). Unsupported legacy flags fail fast.

var thread = client.StartThread(new ThreadOptions
{
    Model = GeminiModels.AutoGemini3,
    ApprovalPolicy = ApprovalMode.Default,
    AdditionalDirectories = ["/workspace/shared"],
    SandboxMode = SandboxMode.WorkspaceWrite,
    AdditionalCliArguments = ["--some-future-flag", "custom-value"],
});

Gemini CLI Metadata

using var client = new GeminiClient();

var metadata = client.GetCliMetadata();
Console.WriteLine($"Installed gemini-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 gemini --version
  • default model from ~/.gemini/config.toml
  • model catalog from ~/.gemini/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 /gemini-cli 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

  • GeminiClient is safe for concurrent use from multiple threads.
  • StartAsync() is idempotent and guarded.
  • StopAsync() cleanly disconnects client state.
  • Dispose() transitions client to Disposed.
  • A single GeminiThread instance serializes turns (RunAsync and RunStreamedAsync) 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 InitEvent init:
            Console.WriteLine($"Session: {init.SessionId}");
            break;
        case MessageEvent { Role: "assistant" } message:
            Console.Write(message.Content);
            break;
        case ResultEvent { Usage: not null } result:
            Console.WriteLine($"Output tokens: {result.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 ConsoleGeminiLogger : 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 GeminiClient(new GeminiOptions
{
    Logger = new ConsoleGeminiLogger(),
});

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 GeminiThread

var resumed = client.ResumeThread("thread_123");
await resumed.RunAsync("Continue from previous plan");

Microsoft.Extensions.AI Integration

An optional adapter package lets you use GeminiSharpSDK through the standard IChatClient interface from Microsoft.Extensions.AI.

dotnet add package ManagedCode.GeminiSharpSDK.Extensions.AI

Basic usage

using Microsoft.Extensions.AI;
using ManagedCode.GeminiSharpSDK.Extensions.AI;

IChatClient client = new GeminiChatClient(new GeminiChatClientOptions
{
    DefaultModel = GeminiModels.Gemini25Pro,
});

var response = await client.GetResponseAsync("Diagnose failing tests and propose a fix");
Console.WriteLine(response.Text);

DI registration

using ManagedCode.GeminiSharpSDK.Extensions.AI.Extensions;

builder.Services.AddGeminiChatClient(options =>
{
    options.DefaultModel = GeminiModels.Gemini25Pro;
});

// 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.GeminiSharpSDK.Models;
using ManagedCode.GeminiSharpSDK.Extensions.AI.Extensions;

var services = new ServiceCollection();
services.AddGeminiChatClient(options =>
{
    options.DefaultModel = GeminiModels.Gemini25Pro;
});

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.GeminiSharpSDK.Models;
using ManagedCode.GeminiSharpSDK.Extensions.AI.Extensions;

var services = new ServiceCollection();
services.AddKeyedGeminiChatClient("gemini-main", options =>
{
    options.DefaultModel = GeminiModels.Gemini25Pro;
});

using var provider = services.BuildServiceProvider();
var keyedChatClient = provider.GetRequiredKeyedService<IChatClient>("gemini-main");

Streaming

await foreach (var update in client.GetStreamingResponseAsync("Implement the fix"))
{
    Console.Write(update.Text);
}

Gemini-specific options via ChatOptions

var options = new ChatOptions
{
    ModelId = GeminiModels.Gemini25Pro,
    AdditionalProperties = new AdditionalPropertiesDictionary
    {
        ["gemini:sandbox_mode"] = "workspace-write",
        ["gemini:reasoning_effort"] = "high",
    },
};

var response = await client.GetResponseAsync("Refactor the auth module", options);

Rich content types

Gemini-specific output items (commands, file changes, MCP tool calls, web searches) are preserved as typed AIContent subclasses:

foreach (var content in response.Messages.SelectMany(m => m.Contents))
{
    switch (content)
    {
        case CommandExecutionContent cmd:
            Console.WriteLine($"Command: {cmd.Command} (exit {cmd.ExitCode})");
            break;
        case FileChangeContent file:
            Console.WriteLine($"File changes: {file.Changes.Count}");
            break;
    }
}

See docs/Features/meai-integration.md and ADR 003 for full details.

Microsoft Agent Framework Integration

An optional adapter package lets you use GeminiSharpSDK with Microsoft Agent Framework AIAgent.

dotnet add package ManagedCode.GeminiSharpSDK.Extensions.AgentFramework --prerelease

This package currently ships as a prerelease because it depends on Microsoft.Agents.AI 1.0.0-rc4.

Basic usage

using ManagedCode.GeminiSharpSDK.Extensions.AI;
using Microsoft.Agents.AI;
using Microsoft.Extensions.AI;

IChatClient chatClient = new GeminiChatClient();

AIAgent agent = chatClient.AsAIAgent(
    name: "GeminiAssistant",
    instructions: "You are a helpful coding assistant.");

AgentResponse response = await agent.RunAsync("Summarize the repository");
Console.WriteLine(response);

DI registration

using ManagedCode.GeminiSharpSDK.Extensions.AgentFramework.Extensions;
using Microsoft.Agents.AI;
using Microsoft.Extensions.AI;

builder.Services.AddGeminiAIAgent(
    configureAgent: options =>
    {
        options.Name = "GeminiAssistant";
        options.ChatOptions = new ChatOptions
        {
            Instructions = "You are a helpful coding assistant."
        };
    });

app.MapGet("/agent", async (AIAgent agent) =>
{
    var response = await agent.RunAsync("Summarize the repository");
    return response.ToString();
});

Keyed DI registration

using ManagedCode.GeminiSharpSDK.Extensions.AgentFramework.Extensions;
using Microsoft.Agents.AI;
using Microsoft.Extensions.AI;
using Microsoft.Extensions.DependencyInjection;

var services = new ServiceCollection();
services.AddKeyedGeminiAIAgent(
    "gemini-main",
    configureAgent: options =>
    {
        options.Name = "GeminiAssistant";
        options.ChatOptions = new ChatOptions
        {
            Instructions = "You are a helpful coding assistant."
        };
    });

using var provider = services.BuildServiceProvider();
var keyedAgent = provider.GetRequiredKeyedService<AIAgent>("gemini-main");

This package builds on the existing IChatClient adapter, so the canonical MAF path remains IChatClient.AsAIAgent(...); the new package adds a supported Gemini-specific package boundary and DI convenience methods.

See docs/Features/agent-framework-integration.md and ADR 004 for full details.

Build and Test

dotnet build ManagedCode.GeminiSharpSDK.slnx -c Release -warnaserror
dotnet test --solution ManagedCode.GeminiSharpSDK.slnx -c Release
Product Compatible and additional computed target framework versions.
.NET net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
0.0.1-rc4 31 3/18/2026