{"id":51083111,"url":"https://github.com/managedcode/geminisharpsdk","last_synced_at":"2026-06-23T20:01:39.552Z","repository":{"id":363366636,"uuid":"1185044148","full_name":"managedcode/GeminiSharpSDK","owner":"managedcode","description":"CLI-first .NET 10 / C# SDK for Google Gemini CLI with typed thread API, streamed JSONL events, structured outputs","archived":false,"fork":false,"pushed_at":"2026-04-09T19:26:46.000Z","size":201,"stargazers_count":2,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-22T13:05:22.325Z","etag":null,"topics":["csharp","dotnet","gemini","gemini-cli","gemini-sdk-csharp","google","sdk"],"latest_commit_sha":null,"homepage":"","language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/managedcode.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":"AGENTS.md","dco":null,"cla":null}},"created_at":"2026-03-18T07:20:44.000Z","updated_at":"2026-05-18T16:36:27.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/managedcode/GeminiSharpSDK","commit_stats":null,"previous_names":["managedcode/geminisharpsdk"],"tags_count":2,"template":false,"template_full_name":null,"purl":"pkg:github/managedcode/GeminiSharpSDK","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/managedcode%2FGeminiSharpSDK","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/managedcode%2FGeminiSharpSDK/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/managedcode%2FGeminiSharpSDK/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/managedcode%2FGeminiSharpSDK/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/managedcode","download_url":"https://codeload.github.com/managedcode/GeminiSharpSDK/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/managedcode%2FGeminiSharpSDK/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34704748,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-23T02:00:07.161Z","response_time":65,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["csharp","dotnet","gemini","gemini-cli","gemini-sdk-csharp","google","sdk"],"created_at":"2026-06-23T20:01:34.222Z","updated_at":"2026-06-23T20:01:39.545Z","avatar_url":"https://github.com/managedcode.png","language":"C#","funding_links":[],"categories":[],"sub_categories":[],"readme":"# ManagedCode.GeminiSharpSDK\n\n[![CI](https://github.com/managedcode/GeminiSharpSDK/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/managedcode/GeminiSharpSDK/actions/workflows/ci.yml)\n[![Release](https://github.com/managedcode/GeminiSharpSDK/actions/workflows/release.yml/badge.svg?branch=main)](https://github.com/managedcode/GeminiSharpSDK/actions/workflows/release.yml)\n[![CodeQL](https://github.com/managedcode/GeminiSharpSDK/actions/workflows/codeql.yml/badge.svg?branch=main)](https://github.com/managedcode/GeminiSharpSDK/actions/workflows/codeql.yml)\n[![NuGet](https://img.shields.io/nuget/v/ManagedCode.GeminiSharpSDK.svg)](https://www.nuget.org/packages/ManagedCode.GeminiSharpSDK)\n[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://github.com/managedcode/GeminiSharpSDK/blob/main/LICENSE)\n\n`ManagedCode.GeminiSharpSDK` is an open-source .NET SDK for driving the Gemini CLI from C#.\n\nIt is a CLI-first `.NET 10 / C# 14` SDK aligned with real `gemini` runtime behavior, with:\n- thread-based API (`start` / `resume`)\n- streamed JSONL events\n- structured output schema support\n- image attachments\n- `--config` flattening to TOML\n- NativeAOT-friendly implementation and tests on TUnit\n\nAll consumer usage examples are documented in this README; this repository intentionally does not keep standalone sample projects.\n\n## Installation\n\n```bash\ndotnet add package ManagedCode.GeminiSharpSDK\n```\n\n## Prerequisites\n\nBefore using this SDK, you must have:\n- `gemini` CLI installed and available in `PATH`\n- an already authenticated Gemini session (`gemini login`)\n\nQuick check:\n\n```bash\ngemini --version\ngemini login\n```\n\n## Quickstart\n\n```csharp\nusing ManagedCode.GeminiSharpSDK;\n\nusing var client = new GeminiClient();\n\nvar thread = client.StartThread(new ThreadOptions\n{\n    Model = GeminiModels.Gemini25Pro,\n});\n\nvar turn = await thread.RunAsync(\"Diagnose failing tests and propose a fix\");\n\nConsole.WriteLine(turn.FinalResponse);\nConsole.WriteLine($\"Items: {turn.Items.Count}\");\n```\n\n`AutoStart` is enabled by default, so `StartThread()` works immediately.\n\n## Advanced Configuration (Optional)\n\n```csharp\nusing var client = new GeminiClient(new GeminiClientOptions\n{\n    GeminiOptions = new GeminiOptions\n    {\n        // Override only when `gemini` is not discoverable via npm/PATH.\n        GeminiExecutablePath = \"/custom/path/to/gemini\",\n    },\n});\n\nvar thread = client.StartThread(new ThreadOptions\n{\n    Model = GeminiModels.Gemini25Pro,\n    ApprovalPolicy = ApprovalMode.Default,\n    AdditionalDirectories = [\"/workspace/shared\"],\n});\n```\n\n## Extended CLI Options\n\n`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.\n\nFresh SDK-started runs persist per Gemini project/working directory and are visible via `gemini --list-sessions` for that same project.\n\n```csharp\nvar thread = client.StartThread(new ThreadOptions\n{\n    Model = GeminiModels.AutoGemini3,\n    ApprovalPolicy = ApprovalMode.Default,\n    AdditionalDirectories = [\"/workspace/shared\"],\n    SandboxMode = SandboxMode.WorkspaceWrite,\n    AdditionalCliArguments = [\"--some-future-flag\", \"custom-value\"],\n});\n```\n\n## Gemini CLI Metadata\n\n```csharp\nusing var client = new GeminiClient();\n\nvar metadata = client.GetCliMetadata();\nConsole.WriteLine($\"Installed gemini-cli: {metadata.InstalledVersion}\");\nConsole.WriteLine($\"Default model: {metadata.DefaultModel ?? \"(not set)\"}\");\n\nforeach (var model in metadata.Models.Where(model =\u003e model.IsListed))\n{\n    Console.WriteLine(model.Slug);\n}\n```\n\n`GetCliMetadata()` reads:\n- installed CLI version from `gemini --version`\n- default model from `~/.gemini/config.toml`\n- model catalog from `~/.gemini/models_cache.json`\n\n```csharp\nvar update = client.GetCliUpdateStatus();\nif (update.IsUpdateAvailable)\n{\n    Console.WriteLine(update.UpdateMessage);\n    Console.WriteLine(update.UpdateCommand);\n}\n```\n\n`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`).\n\nWhen thread-level web search options are omitted, SDK does not emit a `web_search` override and leaves your existing CLI/config value as-is.\n\n## Client Lifecycle and Thread Safety\n\n- `GeminiClient` is safe for concurrent use from multiple threads.\n- `StartAsync()` is idempotent and guarded.\n- `StopAsync()` cleanly disconnects client state.\n- `Dispose()` transitions client to `Disposed`.\n- A single `GeminiThread` instance serializes turns (`RunAsync` and `RunStreamedAsync`) to prevent race conditions in shared conversation state.\n\n## Streaming\n\n```csharp\nvar streamed = await thread.RunStreamedAsync(\"Implement the fix\");\n\nawait foreach (var evt in streamed.Events)\n{\n    switch (evt)\n    {\n        case InitEvent init:\n            Console.WriteLine($\"Session: {init.SessionId}\");\n            break;\n        case MessageEvent { Role: \"assistant\" } message:\n            Console.Write(message.Content);\n            break;\n        case ResultEvent { Usage: not null } result:\n            Console.WriteLine($\"Output tokens: {result.Usage.OutputTokens}\");\n            break;\n    }\n}\n```\n\n## Structured Output\n\n```csharp\nusing System.Text.Json.Serialization;\n\npublic sealed record RepositorySummary(string Summary, string Status);\n\n[JsonSerializable(typeof(RepositorySummary))]\ninternal sealed partial class AppJsonContext : JsonSerializerContext;\n\nvar schema = StructuredOutputSchema.Map\u003cRepositorySummary\u003e(\n    additionalProperties: false,\n    (response =\u003e response.Summary, StructuredOutputSchema.PlainText()),\n    (response =\u003e response.Status, StructuredOutputSchema.PlainText()));\n\nvar result = await thread.RunAsync\u003cRepositorySummary\u003e(\n    \"Summarize repository status\",\n    schema,\n    AppJsonContext.Default.RepositorySummary);\nConsole.WriteLine(result.TypedResponse.Status);\nConsole.WriteLine(result.TypedResponse.Summary);\n```\n\nFor advanced options (for example cancellation), use the `TurnOptions` overload:\n\n```csharp\nusing var cancellation = new CancellationTokenSource(TimeSpan.FromSeconds(30));\n\nvar result = await thread.RunAsync\u003cRepositorySummary\u003e(\n    \"Summarize repository status\",\n    AppJsonContext.Default.RepositorySummary,\n    new TurnOptions\n    {\n        OutputSchema = schema,\n        CancellationToken = cancellation.Token,\n    });\n```\n\n`RunAsync\u003cTResponse\u003e` always requires `OutputSchema` (direct parameter or `TurnOptions.OutputSchema`).\nFor AOT/trimming-safe typed deserialization, pass `JsonTypeInfo\u003cTResponse\u003e` from a source-generated context.\nOverloads without `JsonTypeInfo\u003cTResponse\u003e` are explicitly marked with `RequiresDynamicCode` and `RequiresUnreferencedCode`.\n\n## Diagnostics Logging (Optional)\n\n```csharp\nusing Microsoft.Extensions.Logging;\n\npublic sealed class ConsoleGeminiLogger : ILogger\n{\n    public IDisposable BeginScope\u003cTState\u003e(TState state)\n        where TState : notnull\n    {\n        return NullScope.Instance;\n    }\n\n    public bool IsEnabled(LogLevel logLevel)\n    {\n        return true;\n    }\n\n    public void Log\u003cTState\u003e(\n        LogLevel logLevel,\n        EventId eventId,\n        TState state,\n        Exception? exception,\n        Func\u003cTState, Exception?, string\u003e formatter)\n    {\n        Console.WriteLine($\"[{logLevel}] {formatter(state, exception)}\");\n        if (exception is not null)\n        {\n            Console.WriteLine(exception);\n        }\n    }\n\n    private sealed class NullScope : IDisposable\n    {\n        public static NullScope Instance { get; } = new();\n        public void Dispose() { }\n    }\n}\n\nusing var client = new GeminiClient(new GeminiOptions\n{\n    Logger = new ConsoleGeminiLogger(),\n});\n```\n\n## Images + Text Input\n\n```csharp\nusing var imageStream = File.OpenRead(\"./photo.png\");\n\nvar result = await thread.RunAsync(\n[\n    new TextInput(\"Describe these images\"),\n    new LocalImageInput(\"./ui.png\"),\n    new LocalImageInput(new FileInfo(\"./diagram.jpg\")),\n    new LocalImageInput(imageStream, \"photo.png\"),\n]);\n```\n\n## Resume an Existing GeminiThread\n\n```csharp\nvar resumed = client.ResumeThread(\"thread_123\");\nawait resumed.RunAsync(\"Continue from previous plan\");\n```\n\n## Microsoft.Extensions.AI Integration\n\nAn optional adapter package lets you use GeminiSharpSDK through the standard `IChatClient` interface from `Microsoft.Extensions.AI`.\n\n```bash\ndotnet add package ManagedCode.GeminiSharpSDK.Extensions.AI\n```\n\n### Basic usage\n\n```csharp\nusing Microsoft.Extensions.AI;\nusing ManagedCode.GeminiSharpSDK.Extensions.AI;\n\nIChatClient client = new GeminiChatClient(new GeminiChatClientOptions\n{\n    DefaultModel = GeminiModels.Gemini25Pro,\n});\n\nvar response = await client.GetResponseAsync(\"Diagnose failing tests and propose a fix\");\nConsole.WriteLine(response.Text);\n```\n\n### DI registration\n\n```csharp\nusing ManagedCode.GeminiSharpSDK.Extensions.AI.Extensions;\n\nbuilder.Services.AddGeminiChatClient(options =\u003e\n{\n    options.DefaultModel = GeminiModels.Gemini25Pro;\n});\n\n// Then inject IChatClient anywhere:\napp.MapGet(\"/ask\", async (IChatClient client) =\u003e\n{\n    var response = await client.GetResponseAsync(\"Summarize the repo\");\n    return response.Text;\n});\n```\n\n### Resolve `IChatClient` from `IServiceProvider`\n\n```csharp\nusing Microsoft.Extensions.AI;\nusing Microsoft.Extensions.DependencyInjection;\nusing ManagedCode.GeminiSharpSDK.Models;\nusing ManagedCode.GeminiSharpSDK.Extensions.AI.Extensions;\n\nvar services = new ServiceCollection();\nservices.AddGeminiChatClient(options =\u003e\n{\n    options.DefaultModel = GeminiModels.Gemini25Pro;\n});\n\nusing var provider = services.BuildServiceProvider();\nvar chatClient = provider.GetRequiredService\u003cIChatClient\u003e();\n```\n\nKeyed registration is also supported:\n\n```csharp\nusing Microsoft.Extensions.AI;\nusing Microsoft.Extensions.DependencyInjection;\nusing ManagedCode.GeminiSharpSDK.Models;\nusing ManagedCode.GeminiSharpSDK.Extensions.AI.Extensions;\n\nvar services = new ServiceCollection();\nservices.AddKeyedGeminiChatClient(\"gemini-main\", options =\u003e\n{\n    options.DefaultModel = GeminiModels.Gemini25Pro;\n});\n\nusing var provider = services.BuildServiceProvider();\nvar keyedChatClient = provider.GetRequiredKeyedService\u003cIChatClient\u003e(\"gemini-main\");\n```\n\n### Streaming\n\n```csharp\nawait foreach (var update in client.GetStreamingResponseAsync(\"Implement the fix\"))\n{\n    Console.Write(update.Text);\n}\n```\n\n### Gemini-specific options via ChatOptions\n\n```csharp\nvar options = new ChatOptions\n{\n    ModelId = GeminiModels.Gemini25Pro,\n    AdditionalProperties = new AdditionalPropertiesDictionary\n    {\n        [\"gemini:sandbox_mode\"] = \"workspace-write\",\n        [\"gemini:reasoning_effort\"] = \"high\",\n    },\n};\n\nvar response = await client.GetResponseAsync(\"Refactor the auth module\", options);\n```\n\n### Rich content types\n\nGemini-specific output items (commands, file changes, MCP tool calls, web searches) are preserved as typed `AIContent` subclasses:\n\n```csharp\nforeach (var content in response.Messages.SelectMany(m =\u003e m.Contents))\n{\n    switch (content)\n    {\n        case CommandExecutionContent cmd:\n            Console.WriteLine($\"Command: {cmd.Command} (exit {cmd.ExitCode})\");\n            break;\n        case FileChangeContent file:\n            Console.WriteLine($\"File changes: {file.Changes.Count}\");\n            break;\n    }\n}\n```\n\nSee [docs/Features/meai-integration.md](https://github.com/managedcode/GeminiSharpSDK/blob/main/docs/Features/meai-integration.md) and [ADR 003](https://github.com/managedcode/GeminiSharpSDK/blob/main/docs/ADR/003-microsoft-extensions-ai-integration.md) for full details.\n\n## Microsoft Agent Framework Integration\n\nAn optional adapter package lets you use GeminiSharpSDK with Microsoft Agent Framework `AIAgent`.\n\n```bash\ndotnet add package ManagedCode.GeminiSharpSDK.Extensions.AgentFramework\n```\n\nThis package now ships on the same stable version track as the core SDK because `Microsoft.Agents.AI` is stable.\n\n### Basic usage\n\n```csharp\nusing ManagedCode.GeminiSharpSDK.Extensions.AI;\nusing Microsoft.Agents.AI;\nusing Microsoft.Extensions.AI;\n\nIChatClient chatClient = new GeminiChatClient();\n\nAIAgent agent = chatClient.AsAIAgent(\n    name: \"GeminiAssistant\",\n    instructions: \"You are a helpful coding assistant.\");\n\nAgentResponse response = await agent.RunAsync(\"Summarize the repository\");\nConsole.WriteLine(response);\n```\n\n### DI registration\n\n```csharp\nusing ManagedCode.GeminiSharpSDK.Extensions.AgentFramework.Extensions;\nusing Microsoft.Agents.AI;\nusing Microsoft.Extensions.AI;\n\nbuilder.Services.AddGeminiAIAgent(\n    configureAgent: options =\u003e\n    {\n        options.Name = \"GeminiAssistant\";\n        options.ChatOptions = new ChatOptions\n        {\n            Instructions = \"You are a helpful coding assistant.\"\n        };\n    });\n\napp.MapGet(\"/agent\", async (AIAgent agent) =\u003e\n{\n    var response = await agent.RunAsync(\"Summarize the repository\");\n    return response.ToString();\n});\n```\n\n### Keyed DI registration\n\n```csharp\nusing ManagedCode.GeminiSharpSDK.Extensions.AgentFramework.Extensions;\nusing Microsoft.Agents.AI;\nusing Microsoft.Extensions.AI;\nusing Microsoft.Extensions.DependencyInjection;\n\nvar services = new ServiceCollection();\nservices.AddKeyedGeminiAIAgent(\n    \"gemini-main\",\n    configureAgent: options =\u003e\n    {\n        options.Name = \"GeminiAssistant\";\n        options.ChatOptions = new ChatOptions\n        {\n            Instructions = \"You are a helpful coding assistant.\"\n        };\n    });\n\nusing var provider = services.BuildServiceProvider();\nvar keyedAgent = provider.GetRequiredKeyedService\u003cAIAgent\u003e(\"gemini-main\");\n```\n\nThis 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.\n\nSee [docs/Features/agent-framework-integration.md](https://github.com/managedcode/GeminiSharpSDK/blob/main/docs/Features/agent-framework-integration.md) and [ADR 004](https://github.com/managedcode/GeminiSharpSDK/blob/main/docs/ADR/004-microsoft-agent-framework-integration.md) for full details.\n\n## Build and Test\n\n```bash\ndotnet build ManagedCode.GeminiSharpSDK.slnx -c Release -warnaserror\ndotnet test --solution ManagedCode.GeminiSharpSDK.slnx -c Release\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmanagedcode%2Fgeminisharpsdk","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmanagedcode%2Fgeminisharpsdk","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmanagedcode%2Fgeminisharpsdk/lists"}