Skip to content

Commit 9e00c83

Browse files
committed
Add sample CLI using the Grok client
Showcases running x/web/code execution tools simultaneously.
1 parent edde6fd commit 9e00c83

File tree

5 files changed

+187
-1
lines changed

5 files changed

+187
-1
lines changed

src/Directory.targets

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
<Project InitialTargets="SetLocalVersion">
1+
<Project>
22

33
<Target Name="SetLocalVersion" Condition="!$(CI)">
44
<GetVersion>

src/Grok/Grok.csproj

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<OutputType>Exe</OutputType>
5+
<TargetFramework>net10.0</TargetFramework>
6+
<PackageId>grok</PackageId>
7+
<Description>Sample Grok CLI using xAI and xAI.Protocol packages</Description>
8+
</PropertyGroup>
9+
10+
<ItemGroup>
11+
<Compile Include="..\xAI.Tests\DotEnv.cs" Link="DotEnv.cs" />
12+
</ItemGroup>
13+
14+
<ItemGroup>
15+
<PackageReference Include="Microsoft.Extensions.Configuration" Version="10.0.1" />
16+
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="10.0.1" />
17+
<PackageReference Include="Microsoft.Extensions.Configuration.UserSecrets" Version="10.0.1" />
18+
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="10.0.1" />
19+
<PackageReference Include="Microsoft.Extensions.Hosting" Version="10.0.1" />
20+
<PackageReference Include="Microsoft.Extensions.Http" Version="10.0.1" />
21+
<PackageReference Include="Microsoft.Extensions.Http.Resilience" Version="10.1.0" />
22+
<PackageReference Include="Spectre.Console" Version="0.54.0" />
23+
<PackageReference Include="DotNetEnv" Version="3.1.1" />
24+
<PackageReference Include="DotNetConfig.Configuration" Version="1.2.0" />
25+
</ItemGroup>
26+
27+
<ItemGroup>
28+
<ProjectReference Include="..\xAI\xAI.csproj" />
29+
</ItemGroup>
30+
31+
</Project>

src/Grok/Interactive.cs

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
using System.Diagnostics;
2+
using System.Text;
3+
using DotNetConfig;
4+
using Microsoft.Extensions.AI;
5+
using Microsoft.Extensions.Configuration;
6+
using Microsoft.Extensions.Hosting;
7+
using Spectre.Console;
8+
using xAI;
9+
using xAI.Protocol;
10+
11+
namespace Grok;
12+
13+
partial class Interactive(IConfiguration configuration) : IHostedService
14+
{
15+
readonly CancellationTokenSource cts = new();
16+
string? apiKey = configuration["grok:apikey"];
17+
GrokClient? client;
18+
19+
public Task StartAsync(CancellationToken cancellationToken)
20+
{
21+
if (string.IsNullOrEmpty(apiKey))
22+
{
23+
apiKey = AnsiConsole.Ask<string>("Enter Grok API key:");
24+
Config.Build(ConfigLevel.Global).SetString("grok", "apikey", apiKey);
25+
}
26+
27+
client = new GrokClient(apiKey);
28+
29+
_ = Task.Run(InputListener, cancellationToken);
30+
31+
return Task.CompletedTask;
32+
}
33+
34+
public Task StopAsync(CancellationToken cancellationToken)
35+
{
36+
cts.Cancel();
37+
AnsiConsole.MarkupLine($":robot: Stopping");
38+
return Task.CompletedTask;
39+
}
40+
41+
async Task InputListener()
42+
{
43+
Debug.Assert(client != null);
44+
45+
var models = await client.GetModelsClient().ListLanguageModelsAsync();
46+
var modelId = configuration["grok:modelid"];
47+
var choices = models.Select(x => x.Aliases.OrderBy(a => a.Length).FirstOrDefault() ?? x.Name).ToList();
48+
if (modelId != null && choices.IndexOf(modelId) is var index && index > 0)
49+
{
50+
choices.RemoveAt(index);
51+
choices.Insert(0, modelId);
52+
}
53+
54+
modelId = AnsiConsole.Prompt(new SelectionPrompt<string>().Title("Select model").AddChoices(choices));
55+
Config.Build(ConfigLevel.Global).SetString("grok", "modelid", modelId);
56+
57+
var chat = client!.GetChatClient().AsIChatClient(modelId);
58+
var options = new ChatOptions
59+
{
60+
Tools = [new GrokXSearchTool(), new HostedWebSearchTool(), new HostedCodeInterpreterTool()]
61+
};
62+
var conversation = new List<ChatMessage>();
63+
64+
AnsiConsole.MarkupLine($":robot: Ready");
65+
AnsiConsole.Markup($":person_beard: ");
66+
67+
while (!cts.IsCancellationRequested)
68+
{
69+
var input = Console.ReadLine()?.Trim();
70+
if (!string.IsNullOrWhiteSpace(input))
71+
{
72+
try
73+
{
74+
if (input is "cls" or "clear")
75+
{
76+
Console.Clear();
77+
conversation.Clear();
78+
}
79+
else
80+
{
81+
conversation.Add(new ChatMessage(ChatRole.User, input));
82+
var contents = await AnsiConsole.Status().StartAsync("Sending...", async ctx =>
83+
{
84+
var contents = new List<TextContent>();
85+
await foreach (var update in chat.GetStreamingResponseAsync(conversation, options, cts.Token))
86+
{
87+
foreach (var tool in update.Contents.Select(x => x.RawRepresentation as ToolCall).Where(x => x != null))
88+
ctx.Status($"Calling: {tool!.Function.Name.EscapeMarkup()}");
89+
foreach (var thinking in update.Contents.OfType<TextReasoningContent>())
90+
ctx.Status($"Thinking: {thinking.Text.EscapeMarkup()}");
91+
92+
contents.AddRange(update.Contents.OfType<TextContent>());
93+
}
94+
return contents;
95+
});
96+
97+
foreach (var content in contents)
98+
Console.Write(content);
99+
100+
Console.WriteLine();
101+
}
102+
}
103+
catch (Exception e)
104+
{
105+
AnsiConsole.WriteException(e);
106+
}
107+
finally
108+
{
109+
AnsiConsole.Markup($":person_beard: ");
110+
}
111+
}
112+
else
113+
{
114+
AnsiConsole.Markup($":person_beard: ");
115+
}
116+
}
117+
}
118+
}

src/Grok/Program.cs

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
using System.Diagnostics;
2+
using System.Runtime.InteropServices;
3+
using System.Text;
4+
using Grok;
5+
using Microsoft.Extensions.Configuration;
6+
using Microsoft.Extensions.DependencyInjection;
7+
using Microsoft.Extensions.Hosting;
8+
using Microsoft.Extensions.Logging;
9+
10+
// Some users reported not getting emoji on Windows from F5 in VS so we force UTF-8 encoding.
11+
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
12+
Console.InputEncoding = Console.OutputEncoding = Encoding.UTF8;
13+
14+
var host = Host.CreateApplicationBuilder(args);
15+
host.Logging.ClearProviders();
16+
17+
host.Configuration.AddDotNetConfig();
18+
host.Configuration.AddUserSecrets<Program>();
19+
20+
host.Services.AddHttpClient();
21+
host.Services.ConfigureHttpClientDefaults(x =>
22+
{
23+
if (Debugger.IsAttached)
24+
x.ConfigureHttpClient(h => h.Timeout = TimeSpan.MaxValue);
25+
else
26+
x.AddStandardResilienceHandler();
27+
});
28+
29+
var cts = new CancellationTokenSource();
30+
Console.CancelKeyPress += (s, e) => cts.Cancel();
31+
host.Services.AddSingleton(cts);
32+
host.Services.AddSingleton<IHostedService, Interactive>();
33+
34+
var app = host.Build();
35+
36+
await app.RunAsync(cts.Token);

xAI.slnx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
<Solution>
2+
<Project Path="src/Grok/Grok.csproj" Id="eb2d1b6b-bcd3-4b4b-a57b-f34ac96e3710" />
23
<Project Path="src/xAI.Protocol/xAI.Protocol.csproj" />
34
<Project Path="src/xAI.Tests/xAI.Tests.csproj" Id="c22563af-5b36-4100-9f31-93629b7bb17c" />
45
<Project Path="src/xAI/xAI.csproj" />

0 commit comments

Comments
 (0)