Skip to content

Commit 2fcfd3b

Browse files
committed
gemini
1 parent 6c5fcb4 commit 2fcfd3b

17 files changed

Lines changed: 243 additions & 6 deletions

File tree

DotPilot.Core/AgentBuilder/Services/AgentPromptDraftGenerator.cs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,14 @@ private async ValueTask<ProviderStatusDescriptor> ResolvePreferredProviderAsync(
102102
}
103103
}
104104

105+
var firstCreatableRealProvider = providers.FirstOrDefault(static provider =>
106+
provider.CanCreateAgents &&
107+
provider.Kind != AgentProviderKind.Debug);
108+
if (firstCreatableRealProvider is not null)
109+
{
110+
return firstCreatableRealProvider;
111+
}
112+
105113
return providers.FirstOrDefault(static provider => provider.Kind == AgentProviderKind.Debug)
106114
?? new ProviderStatusDescriptor(
107115
AgentSessionDeterministicIdentity.CreateProviderId("debug"),
@@ -125,6 +133,7 @@ private static IEnumerable<AgentProviderKind> ResolveProviderPreferences(string
125133
{
126134
yield return AgentProviderKind.Codex;
127135
yield return AgentProviderKind.GitHubCopilot;
136+
yield return AgentProviderKind.Gemini;
128137
yield return AgentProviderKind.ClaudeCode;
129138
yield return AgentProviderKind.Debug;
130139
yield break;
@@ -133,13 +142,15 @@ private static IEnumerable<AgentProviderKind> ResolveProviderPreferences(string
133142
if (ContainsAny(prompt, "research", "search", "summarize", "summary", "writing", "content", "docs", "analysis"))
134143
{
135144
yield return AgentProviderKind.ClaudeCode;
145+
yield return AgentProviderKind.Gemini;
136146
yield return AgentProviderKind.Codex;
137147
yield return AgentProviderKind.GitHubCopilot;
138148
yield return AgentProviderKind.Debug;
139149
yield break;
140150
}
141151

142152
yield return AgentProviderKind.Codex;
153+
yield return AgentProviderKind.Gemini;
143154
yield return AgentProviderKind.ClaudeCode;
144155
yield return AgentProviderKind.GitHubCopilot;
145156
yield return AgentProviderKind.Debug;

DotPilot.Core/ChatSessions/Diagnostics/AgentExecutionLoggingMiddleware.ChatClient.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ private IChatClient WrapChatClient(IChatClient chatClient, AgentRunLogContext ru
4141

4242
private static bool ShouldBridgeSystemInstructions(AgentProviderKind providerKind)
4343
{
44-
return providerKind is AgentProviderKind.Codex or AgentProviderKind.ClaudeCode;
44+
return providerKind is AgentProviderKind.Codex or AgentProviderKind.ClaudeCode or AgentProviderKind.Gemini;
4545
}
4646

4747
private async Task<ChatResponse> LogChatResponseAsync(

DotPilot.Core/ChatSessions/Execution/AgentRuntimeConversationFactory.cs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,17 @@
66
using ManagedCode.CodexSharpSDK.Client;
77
using ManagedCode.CodexSharpSDK.Configuration;
88
using ManagedCode.CodexSharpSDK.Extensions.AI;
9+
using ManagedCode.GeminiSharpSDK.Configuration;
10+
using ManagedCode.GeminiSharpSDK.Extensions.AI;
911
using Microsoft.Agents.AI;
1012
using Microsoft.Extensions.AI;
1113
using Microsoft.Extensions.DependencyInjection;
1214
using Microsoft.Extensions.Logging;
1315
using ClaudeThreadOptions = ManagedCode.ClaudeCodeSharpSDK.Client.ThreadOptions;
1416
using CodexThreadOptions = ManagedCode.CodexSharpSDK.Client.ThreadOptions;
17+
using GeminiApprovalMode = ManagedCode.GeminiSharpSDK.Client.ApprovalMode;
18+
using GeminiSandboxMode = ManagedCode.GeminiSharpSDK.Client.SandboxMode;
19+
using GeminiThreadOptions = ManagedCode.GeminiSharpSDK.Client.ThreadOptions;
1520

1621
namespace DotPilot.Core.ChatSessions;
1722

@@ -210,6 +215,26 @@ private IChatClient CreateChatClient(
210215
});
211216
}
212217

218+
if (providerKind == AgentProviderKind.Gemini)
219+
{
220+
var geminiExecutablePath = ResolveExecutablePath(providerKind);
221+
return new GeminiChatClient(new GeminiChatClientOptions
222+
{
223+
GeminiOptions = new GeminiOptions
224+
{
225+
GeminiExecutablePath = geminiExecutablePath,
226+
},
227+
DefaultModel = modelName,
228+
DefaultThreadOptions = new GeminiThreadOptions
229+
{
230+
Model = modelName,
231+
WorkingDirectory = ResolvePlaygroundDirectory(sessionId),
232+
SandboxMode = GeminiSandboxMode.WorkspaceWrite,
233+
ApprovalPolicy = GeminiApprovalMode.Yolo,
234+
},
235+
});
236+
}
237+
213238
throw new InvalidOperationException(
214239
string.Format(
215240
CultureInfo.InvariantCulture,

DotPilot.Core/ChatSessions/Models/AgentSessionStates.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ public enum AgentProviderKind
66
Codex,
77
ClaudeCode,
88
GitHubCopilot,
9+
Gemini,
910
}
1011

1112
public enum AgentProviderStatus

DotPilot.Core/Providers/Configuration/AgentProviderKindExtensions.cs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
using ManagedCode.GeminiSharpSDK.Models;
2+
13
namespace DotPilot.Core.Providers;
24

35
internal static class AgentProviderKindExtensions
@@ -10,6 +12,7 @@ public static string GetCommandName(this AgentProviderKind kind)
1012
AgentProviderKind.Codex => "codex",
1113
AgentProviderKind.ClaudeCode => "claude",
1214
AgentProviderKind.GitHubCopilot => "copilot",
15+
AgentProviderKind.Gemini => "gemini",
1316
_ => throw new ArgumentOutOfRangeException(nameof(kind), kind, null),
1417
};
1518
}
@@ -22,6 +25,7 @@ public static string GetDefaultModelName(this AgentProviderKind kind)
2225
AgentProviderKind.Codex => "gpt-5",
2326
AgentProviderKind.ClaudeCode => "claude-sonnet-4-5",
2427
AgentProviderKind.GitHubCopilot => "gpt-5",
28+
AgentProviderKind.Gemini => GeminiModels.Gemini25Pro,
2529
_ => throw new ArgumentOutOfRangeException(nameof(kind), kind, null),
2630
};
2731
}
@@ -34,6 +38,7 @@ public static string GetDisplayName(this AgentProviderKind kind)
3438
AgentProviderKind.Codex => "Codex",
3539
AgentProviderKind.ClaudeCode => "Claude Code",
3640
AgentProviderKind.GitHubCopilot => "GitHub Copilot",
41+
AgentProviderKind.Gemini => "Gemini",
3742
_ => throw new ArgumentOutOfRangeException(nameof(kind), kind, null),
3843
};
3944
}
@@ -46,6 +51,7 @@ public static string GetInstallCommand(this AgentProviderKind kind)
4651
AgentProviderKind.Codex => "npm install -g @openai/codex",
4752
AgentProviderKind.ClaudeCode => "npm install -g @anthropic-ai/claude-code",
4853
AgentProviderKind.GitHubCopilot => "npm install -g @github/copilot",
54+
AgentProviderKind.Gemini => "npm install -g @google/gemini-cli",
4955
_ => throw new ArgumentOutOfRangeException(nameof(kind), kind, null),
5056
};
5157
}
@@ -58,6 +64,12 @@ public static IReadOnlyList<string> GetSupportedModelNames(this AgentProviderKin
5864
AgentProviderKind.Codex => [],
5965
AgentProviderKind.ClaudeCode => [],
6066
AgentProviderKind.GitHubCopilot => [],
67+
AgentProviderKind.Gemini =>
68+
[
69+
GeminiModels.Gemini25Pro,
70+
GeminiModels.Gemini25Flash,
71+
GeminiModels.Gemini25FlashLite,
72+
],
6173
_ => throw new ArgumentOutOfRangeException(nameof(kind), kind, null),
6274
};
6375
}

DotPilot.Core/Providers/Services/AgentProviderStatusSnapshotReader.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,7 @@ private static async ValueTask<ProviderCliMetadataSnapshot> ResolveMetadataAsync
173173
AgentProviderKind.GitHubCopilot => await CopilotCliMetadataReader.TryReadAsync(
174174
executablePath,
175175
cancellationToken).ConfigureAwait(false),
176+
AgentProviderKind.Gemini => GeminiCliMetadataReader.TryRead(executablePath),
176177
_ => new ProviderCliMetadataSnapshot(null, null, []),
177178
};
178179
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
using ManagedCode.GeminiSharpSDK.Client;
2+
using ManagedCode.GeminiSharpSDK.Configuration;
3+
4+
namespace DotPilot.Core.Providers;
5+
6+
internal static class GeminiCliMetadataReader
7+
{
8+
public static ProviderCliMetadataSnapshot TryRead(string executablePath)
9+
{
10+
ArgumentException.ThrowIfNullOrWhiteSpace(executablePath);
11+
12+
try
13+
{
14+
using var client = new GeminiClient(new GeminiOptions
15+
{
16+
GeminiExecutablePath = executablePath,
17+
});
18+
19+
var metadata = client.GetCliMetadata();
20+
var supportedModels = metadata.Models
21+
.Where(static model => model.IsListed)
22+
.Select(static model => model.Slug)
23+
.ToArray();
24+
25+
return new ProviderCliMetadataSnapshot(
26+
metadata.InstalledVersion,
27+
ResolveSuggestedModel(metadata.DefaultModel, supportedModels),
28+
supportedModels);
29+
}
30+
catch
31+
{
32+
var fallbackModel = AgentProviderKind.Gemini.GetDefaultModelName();
33+
return new ProviderCliMetadataSnapshot(
34+
InstalledVersion: null,
35+
fallbackModel,
36+
AgentProviderKind.Gemini.GetSupportedModelNames());
37+
}
38+
}
39+
40+
private static string ResolveSuggestedModel(string? configuredModel, IReadOnlyList<string> supportedModels)
41+
{
42+
if (!string.IsNullOrWhiteSpace(configuredModel))
43+
{
44+
return configuredModel;
45+
}
46+
47+
return supportedModels.FirstOrDefault(static model => !string.IsNullOrWhiteSpace(model)) ??
48+
AgentProviderKind.Gemini.GetDefaultModelName();
49+
}
50+
}

DotPilot.Tests/AgentBuilder/Services/AgentPromptDraftGeneratorTests.cs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,24 @@ public async Task CreateManualDraftAsyncUsesDefaultDebugFallback()
4545
draft.ModelName.Should().Be("debug-echo");
4646
}
4747

48+
[Test]
49+
public async Task CreateManualDraftAsyncUsesGeminiWhenItIsTheOnlyEnabledRealProvider()
50+
{
51+
using var commandScope = CodexCliTestScope.Create(nameof(AgentPromptDraftGeneratorTests));
52+
commandScope.WriteVersionCommand("gemini", "gemini-cli 0.34.0");
53+
commandScope.WriteGeminiMetadata("gemini-2.5-pro", "gemini-2.5-pro", "gemini-2.5-flash");
54+
55+
await using var fixture = CreateFixture();
56+
(await fixture.WorkspaceState.UpdateProviderAsync(
57+
new UpdateProviderPreferenceCommand(AgentProviderKind.Gemini, true),
58+
CancellationToken.None)).ShouldSucceed();
59+
60+
var draft = await fixture.Generator.CreateManualDraftAsync(CancellationToken.None);
61+
62+
draft.ProviderKind.Should().Be(AgentProviderKind.Gemini);
63+
draft.ModelName.Should().Be("gemini-2.5-pro");
64+
}
65+
4866
private static TestFixture CreateFixture()
4967
{
5068
var services = new ServiceCollection();

DotPilot.Tests/AgentBuilder/ViewModels/AgentBuilderModelTests.cs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,29 @@ public async Task HandleProviderSelectionChangedUsesProviderKindParameterWhenNoP
216216
(await model.SelectedProvider)!.Kind.Should().Be(AgentProviderKind.ClaudeCode);
217217
}
218218

219+
[Test]
220+
public async Task HandleProviderSelectionChangedUsesGeminiProviderKindParameterWhenNoProviderOptionIsProvided()
221+
{
222+
using var commandScope = CodexCliTestScope.Create(nameof(AgentBuilderModelTests));
223+
commandScope.WriteVersionCommand("gemini", "gemini-cli 0.34.0");
224+
commandScope.WriteGeminiMetadata("gemini-2.5-pro", "gemini-2.5-pro", "gemini-2.5-flash");
225+
226+
await using var fixture = await CreateFixtureAsync();
227+
(await fixture.WorkspaceState.UpdateProviderAsync(
228+
new UpdateProviderPreferenceCommand(AgentProviderKind.Gemini, true),
229+
CancellationToken.None)).ShouldSucceed();
230+
231+
var model = ActivatorUtilities.CreateInstance<AgentBuilderModel>(fixture.Provider);
232+
233+
await model.BuildManually(CancellationToken.None);
234+
await model.HandleProviderSelectionChanged(AgentProviderKind.Gemini, CancellationToken.None);
235+
236+
(await model.BuilderProviderDisplayName).Should().Be("Gemini");
237+
(await model.BuilderSuggestedModelName).Should().Be("gemini-2.5-pro");
238+
(await model.SelectedProvider).Should().NotBeNull();
239+
(await model.SelectedProvider)!.Kind.Should().Be(AgentProviderKind.Gemini);
240+
}
241+
219242
[Test]
220243
public async Task HandleProviderSelectionChangedUsesTheProvidedProviderWhenThePreviousProviderRemainsPopulated()
221244
{

DotPilot.Tests/ChatSessions/Execution/AgentSessionServiceTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ public async Task GetWorkspaceAsyncSeedsDefaultSystemAgentForANewStore()
2828
agent.Name == AgentSessionDefaults.SystemAgentName &&
2929
agent.ProviderKind == AgentProviderKind.Debug &&
3030
agent.ModelName == AgentSessionDefaults.GetDefaultModel(AgentProviderKind.Debug));
31-
workspace.Providers.Should().HaveCount(4);
31+
workspace.Providers.Should().HaveCount(5);
3232
workspace.Providers.Should().ContainSingle(provider => provider.Kind == AgentProviderKind.Debug);
3333
workspace.Providers.Should().ContainSingle(provider =>
3434
provider.Kind == AgentProviderKind.Debug &&

0 commit comments

Comments
 (0)