Universal.MoonshotAI.Client 1.0.0

dotnet add package Universal.MoonshotAI.Client --version 1.0.0
                    
NuGet\Install-Package Universal.MoonshotAI.Client -Version 1.0.0
                    
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="Universal.MoonshotAI.Client" Version="1.0.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Universal.MoonshotAI.Client" Version="1.0.0" />
                    
Directory.Packages.props
<PackageReference Include="Universal.MoonshotAI.Client" />
                    
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 Universal.MoonshotAI.Client --version 1.0.0
                    
#r "nuget: Universal.MoonshotAI.Client, 1.0.0"
                    
#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 Universal.MoonshotAI.Client@1.0.0
                    
#: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=Universal.MoonshotAI.Client&version=1.0.0
                    
Install as a Cake Addin
#tool nuget:?package=Universal.MoonshotAI.Client&version=1.0.0
                    
Install as a Cake Tool

Universal.MoonshotAI.Client

A simple .NET client library for Moonshot AI's Chat Completions API (Kimi models), supporting streaming, tools, and structured outputs.

Quick Start

using Universal.MoonshotAI.Client;
using Universal.MoonshotAI.Client.V1.Chat;

using var client = new MoonshotAIClient("your-api-key");
var response = await client.V1.Chat.CreateCompletionAsync(new CreateCompletionRequest
{
    Model = Models.KimiK3,
    Messages = [
        new SystemMessage("You are a helpful assistant."),
        new UserMessage("Hello, how are you?")
    ]
});

Console.WriteLine(response.Choices[0].Message.Content);

Calling api.moonshot.cn (mainland China) instead of the default api.moonshot.ai:

using var client = new MoonshotAIClient("https", "api.moonshot.cn", "your-api-key");

Streaming Responses

await foreach (var chunk in client.V1.Chat.CreateCompletionStreamingAsync(new CreateCompletionRequest
{
    Model = Models.KimiK3,
    Messages = [new UserMessage("Write a short story.")],
    Stream = true
}))
{
    if (chunk.Choices?[0]?.Delta?.Content != null)
    {
        Console.Write(chunk.Choices[0].Delta.Content);
    }
}

Function Calling & Tools

var response = await client.V1.Chat.CreateCompletionAsync(new CreateCompletionRequest
{
    Model = Models.KimiK3,
    Messages = [new UserMessage("What's the weather like in San Francisco?")],
    Tools = [
        new Tool
        {
            Type = ToolTypes.Function,
            Function = new Function
            {
                Name = "get_weather",
                Description = "Get current weather for a location",
                Parameters = new JsonSchema
                {
                    Type = "object",
                    Properties = new Dictionary<string, JsonSchema>
                    {
                        ["location"] = new JsonSchema { Type = "string", Description = "The city and state" }
                    },
                    Required = new[] { "location" }
                }
            }
        }
    ]
});

if (response.Choices[0].Message is AssistantMessage assistantMsg && assistantMsg.ToolCalls?.Length > 0)
{
    var toolCall = assistantMsg.ToolCalls[0];
    var weatherData = GetWeather(toolCall.Function.Arguments); // Your implementation

    var followUp = await client.V1.Chat.CreateCompletionAsync(new CreateCompletionRequest
    {
        Model = Models.KimiK3,
        Messages = [
            new UserMessage("What's the weather like in San Francisco?"),
            assistantMsg,
            new ToolMessage(weatherData, toolCall.Id)
        ]
    });
}

Prefix Continuation (Partial Mode)

Moonshot-specific: append an assistant message with Partial = true to have the model continue from that content rather than starting a fresh turn (e.g. to force a response to keep going after being cut off, or to seed the start of a code block):

var response = await client.V1.Chat.CreateCompletionAsync(new CreateCompletionRequest
{
    Model = Models.KimiK3,
    Messages = [
        new UserMessage("Write a Python function that reverses a string."),
        new AssistantMessage("```python\n") { Partial = true }
    ]
});

When continuing a reasoning model's response this way (kimi-k3, or kimi-k2.6/kimi-k2.5 with thinking enabled), echo the prior ReasoningContent back on the partial message too, and set MaxCompletionTokens generously — truncating mid-thought can make the model discard its progress and restart from scratch instead of continuing:

var first = await client.V1.Chat.CreateCompletionAsync(new CreateCompletionRequest
{
    Model = Models.KimiK3,
    Messages = [new UserMessage("Write a Python function that reverses a string.")],
    MaxCompletionTokens = 200
});

var assistantMsg = (AssistantMessage)first.Choices[0].Message;
var continued = await client.V1.Chat.CreateCompletionAsync(new CreateCompletionRequest
{
    Model = Models.KimiK3,
    Messages = [
        new UserMessage("Write a Python function that reverses a string."),
        new AssistantMessage(assistantMsg.Content)
        {
            Partial = true,
            ReasoningContent = assistantMsg.ReasoningContent
        }
    ],
    MaxCompletionTokens = 2000
});

Extended Thinking

kimi-k2.6 and kimi-k2.5 support toggling extended thinking via Thinking; kimi-k3 uses ReasoningEffort instead; kimi-k2.7-code always reasons and exposes no control for it. When enabled, the chain-of-thought is returned on AssistantMessage.ReasoningContent (and streamed incrementally via CompletionChunkDelta.ReasoningContent) — separate from the final answer in Content:

var response = await client.V1.Chat.CreateCompletionAsync(new CreateCompletionRequest
{
    Model = Models.KimiK26,
    Messages = [new UserMessage("Prove that sqrt(2) is irrational.")],
    Thinking = new ThinkingConfiguration { Type = ThinkingTypes.Enabled }
});

var assistantMsg = (AssistantMessage)response.Choices[0].Message;
Console.WriteLine(assistantMsg.ReasoningContent); // the chain of thought
Console.WriteLine(assistantMsg.Content);           // the final answer

Web Search (Built-in Tool)

Moonshot-specific: declare Tool.WebSearch and, when the model calls it, echo the arguments straight back — content and function name — in a ToolMessage rather than executing a search yourself; Moonshot runs the search server-side and continues from there. Only meaningfully supported on kimi-k3 and kimi-k2.6; each search is billed separately from token usage and can add substantially to context length:

var response = await client.V1.Chat.CreateCompletionAsync(new CreateCompletionRequest
{
    Model = Models.KimiK3,
    Messages = [new UserMessage("What's the latest news about Moonshot AI?")],
    Tools = [Tool.WebSearch]
});

if (response.Choices[0].Message is AssistantMessage assistantMsg && assistantMsg.ToolCalls?.Length > 0)
{
    var toolCall = assistantMsg.ToolCalls[0]; // toolCall.Function.Name == BuiltinFunctionNames.WebSearch
    var followUp = await client.V1.Chat.CreateCompletionAsync(new CreateCompletionRequest
    {
        Model = Models.KimiK3,
        Messages = [
            new UserMessage("What's the latest news about Moonshot AI?"),
            assistantMsg,
            // name is required here — omitting it fails with an opaque "tokenization failed" error
            new ToolMessage(toolCall.Function.Arguments, toolCall.Id, toolCall.Function.Name)
        ],
        Tools = [Tool.WebSearch]
    });
}

Caveat: as of 2026-07-29, this follow-up turn reliably 400s with "tokenization failed" against the live API. This was chased down to the byte: a raw curl request built to match Moonshot's own cookbook example field-for-field — bypassing this library entirely — reproduces the identical error, and the test account's balance/tier (enterprise-tier-3, active, funded) rules out a billing or account-gating explanation. This is an upstream issue in the $web_search feature itself, not in this library or your request. The shape implemented here is correct and should work once Moonshot's side is fixed. Universal.MoonshotAI.Client.Tests reflects this by marking its live web-search test inconclusive (not failing) on this specific error.

Context Caching

Automatic — there is no cache ID or TTL to manage. Any request whose prompt exceeds 256 tokens is eligible to have its stable leading prefix (shared system instructions, documents, tool definitions) matched against a prior request's; keep that prefix byte-for-byte identical and put large fixed context first in Messages to maximize hit rate. Check Usage.CachedTokens on the response to confirm a hit:

var response = await client.V1.Chat.CreateCompletionAsync(request);
Console.WriteLine($"{response.Usage.CachedTokens} of {response.Usage.PromptTokens} prompt tokens were cached");

Structured JSON Output

The model only ever produces a JSON object — describe the desired shape (field names, an example) in the prompt itself, since ResponseFormat alone doesn't communicate a schema to the model. Set MaxCompletionTokens generously; a truncated response (Choice.FinishReason == "length") is invalid JSON.

var response = await client.V1.Chat.CreateCompletionAsync(new CreateCompletionRequest
{
    Model = Models.KimiK3,
    Messages = [new UserMessage("Analyze this text for sentiment. Respond as {\"sentiment\": string, \"confidence\": number}.")],
    ResponseFormat = ResponseFormat.JsonObject
});

Error Handling

The library throws HttpException for API errors:

try
{
    var response = await client.V1.Chat.CreateCompletionAsync(request);
}
catch (HttpException ex)
{
    Console.WriteLine($"API Error: {ex.StatusCode} - {ex.Message}");
}

Disposal

The client implements IDisposable:

using var client = new MoonshotAIClient("your-api-key");
// Client will be automatically disposed

Advanced Usage

Raw Streaming Response

using var httpResponse = await client.V1.Chat.CreateCompletionStreamingRawAsync(request);
// Forward the raw HTTP response stream

Specific Clients

using var chatClient = new ChatClient(apiKey);

Known Gaps

This library only covers what Moonshot AI's Chat Completions API documents today — there is no Moonshot equivalent of OpenAI's Embeddings, Images, Responses, or Realtime APIs, and Moonshot's own Files API (used for uploading documents rather than for context caching, which is automatic — see above) isn't covered either, so all of those are intentionally absent rather than stubbed out. Vision input (*-vision-preview models) is reachable by passing a raw content-block array/object as Message.Content, since this library doesn't yet model typed content blocks (image_url, etc.) the way Universal.OpenAI.Client does.

A handful of request-shape rules are documented by Moonshot but enforced only server-side, not by this library (matching how the rest of this client defers to the API's own validation): Temperature must match the active Thinking mode on kimi-k2.6/kimi-k2.5, Number > 1 conflicts with Temperature near 0, and ToolChoiceOptions.Required isn't accepted by kimi-k2.7-code/kimi-k2.6. See the XML doc comments on the relevant properties for specifics — passing an unsupported combination surfaces as an HttpException from the API, same as any other invalid request.

Product Compatible and additional computed target framework versions.
.NET net5.0 was computed.  net5.0-windows was computed.  net6.0 was computed.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 was computed.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 was computed.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  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. 
.NET Core netcoreapp2.0 was computed.  netcoreapp2.1 was computed.  netcoreapp2.2 was computed.  netcoreapp3.0 was computed.  netcoreapp3.1 was computed. 
.NET Standard netstandard2.0 is compatible.  netstandard2.1 was computed. 
.NET Framework net461 was computed.  net462 was computed.  net463 was computed.  net47 was computed.  net471 was computed.  net472 was computed.  net48 was computed.  net481 was computed. 
MonoAndroid monoandroid was computed. 
MonoMac monomac was computed. 
MonoTouch monotouch was computed. 
Tizen tizen40 was computed.  tizen60 was computed. 
Xamarin.iOS xamarinios was computed. 
Xamarin.Mac xamarinmac was computed. 
Xamarin.TVOS xamarintvos was computed. 
Xamarin.WatchOS xamarinwatchos was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages (1)

Showing the top 1 NuGet packages that depend on Universal.MoonshotAI.Client:

Package Downloads
Universal.Operative.Sdk.Discrete.MoonshotAI

Moonshot AI (Kimi) extensions for the Universal.Operative.Sdk.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.0.0 149 7/29/2026

Initial release.