Rystem.PlayFramework
10.0.6
dotnet add package Rystem.PlayFramework --version 10.0.6
NuGet\Install-Package Rystem.PlayFramework -Version 10.0.6
<PackageReference Include="Rystem.PlayFramework" Version="10.0.6" />
<PackageVersion Include="Rystem.PlayFramework" Version="10.0.6" />
<PackageReference Include="Rystem.PlayFramework" />
paket add Rystem.PlayFramework --version 10.0.6
#r "nuget: Rystem.PlayFramework, 10.0.6"
#:package Rystem.PlayFramework@10.0.6
#addin nuget:?package=Rystem.PlayFramework&version=10.0.6
#tool nuget:?package=Rystem.PlayFramework&version=10.0.6
What is Rystem?
Play Framework
Docs
Introduction
The Rystem.PlayFramework library enables developers to integrate multi-agent systems and OpenAI into their .NET applications. It provides tools to define and orchestrate complex agent-based scenarios using OpenAI's API. In this guide, we'll cover how to install, configure, and use the UseAiEndpoints middleware and PlayFramework scenes within a .NET application.
Prerequisites
To use this library, you need:
- .NET 9.0 SDK or later
- An OpenAI API key and endpoint
- Access to an HTTP API (if you're using scenes with HTTP clients)
- A database or service to handle identity management (optional)
Installation
Follow these steps to set up Rystem.PlayFramework:
- Clone or download the repository from GitHub.
- Add the package to your project using the NuGet package manager:
dotnet add package Rystem.PlayFramework
Project Setup
The .csproj file is already configured for building the package, including symbol generation and source embedding for debugging purposes. The most important parts are:
- TargetFramework: net9.0
- Package Information: Contains metadata like authorship, versioning, repository URL, and licensing.
Ensure that your csproj file contains the necessary framework and package references.
Configuration
In your .NET application, you will need to configure the services and middlewares for Rystem.PlayFramework. This is done primarily within two extension methods:
AddServices(IServiceCollection services, IConfiguration configuration): This method sets up the necessary services, such as OpenAI configuration, HTTP client, identity management, and PlayFramework scenes.Example setup:
//setup OpenAi client services.AddOpenAi(x => { x.ApiKey = configuration["OpenAi2:ApiKey"]!; x.Azure.ResourceName = configuration["OpenAi2:ResourceName"]!; x.Version = "2024-08-01-preview"; x.DefaultRequestConfiguration.Chat = chatClient => { chatClient.WithModel(configuration["OpenAi2:ModelName"]!); }; x.PriceBuilder .AddModel(ChatModelName.Gpt4_o, new OpenAiCost { Units = 0.0000025m, Kind = KindOfCost.Input, UnitOfMeasure = UnitOfMeasure.Tokens }, new OpenAiCost { Kind = KindOfCost.CachedInput, UnitOfMeasure = UnitOfMeasure.Tokens, Units = 0.00000125m }, new OpenAiCost { Kind = KindOfCost.Output, UnitOfMeasure = UnitOfMeasure.Tokens, Units = 0.00001m }); }, "playframework"); //setup http client to use during play framework integration to call external services services.AddHttpClient("apiDomain", x => { x.BaseAddress = new Uri(configuration["Api:Uri"]!); }); //setup for Play Framework services.AddPlayFramework(scenes => { scenes.Configure(settings => { settings.OpenAi.Name = "playframework"; }) .AddMainActor((context) => $"Oggi � {DateTime.UtcNow}.", true) .AddScene(scene => { scene .WithName("Weather") .WithDescription("Get information about the weather") .WithHttpClient("apiDomain") .WithOpenAi("playframework") .WithApi(pathBuilder => { pathBuilder .Map(new Regex("Country/*")) .Map(new Regex("City/*")) .Map("Weather/"); }) .WithActors(actors => { actors .AddActor("Nel caso non esistesse la citt� richiesta potresti aggiungerla con il numero dei suoi abitanti.") .AddActor("Ricordati che va sempre aggiunta anche la nazione, quindi se non c'� la nazione aggiungi anche quella.") .AddActor("Non chiamare alcun meteo prima di assicurarti che tutto sia stato popolato correttamente.") .AddActor<ActorWithDbRequest>(); }); }) .AddScene(scene => { scene .WithName("Identity") .WithDescription("Get information about the user") .WithOpenAi("openai") .WithService<IdentityManager>(builder => { builder.WithMethod(x => x.GetNameAsync); }); }); });UseMiddlewares(IApplicationBuilder app): This method enables middleware components for routing, authorization, HTTPS redirection, and AI endpoints. It also maps OpenAPI and Scalar API routes.Example middleware setup:
app.UseRouting(); app.UseEndpoints(endpoints => { endpoints.MapOpenApi(); endpoints.MapScalarApiReference(); endpoints.MapControllers(); }); app.UseHttpsRedirection(); app.UseAuthorization(); app.UseAiEndpoints(); // Enables AI endpoints for your application
Configuration Options
- OpenAI Configuration: The OpenAI settings are fetched from the
appsettings.jsonfile using configuration keys such asOpenAi:ApiKey,OpenAi:Endpoint, andOpenAi:ModelName. - HTTP Client: A named HTTP client (
apiDomain) is used for interacting with external APIs. - Scenes: You can define multiple scenes within PlayFramework, each tied to specific functionality (e.g.,
WeatherandIdentityin this example).
Example appsettings.json
{
"OpenAi": {
"ApiKey": "your-openai-api-key",
"Endpoint": "https://api.openai.com",
"ModelName": "gpt-4"
},
"Api": {
"Uri": "https://api.example.com/"
}
}
Usage
Defining a Scene
A scene in PlayFramework represents a unit of interaction. Each scene can have:
- Name: A descriptive name of the scene.
- Description: A short description of the scene's purpose.
- HttpClient: The HTTP client to use when making API requests within the scene.
- Actors: Define behaviors or actions that should be executed as part of the scene.
Example Scene: Weather
scene.WithName("Weather")
.WithDescription("Get information about the weather")
.WithHttpClient("apiDomain")
.WithOpenAi("openai")
.WithApi(pathBuilder =>
{
pathBuilder.Map(new Regex("Country/*"))
.Map(new Regex("City/*"))
.Map("Weather/");
})
.WithActors(actors =>
{
actors.AddActor("Ensure that the requested city exists, or add it with its population.")
.AddActor<ActorWithDbRequest>();
});
Example Scene: Identity Management
The IdentityManager service is used to manage user identities. It can be configured and used as follows:
scene.WithName("Identity")
.WithDescription("Get information about the user")
.WithOpenAi("openai")
.WithService<IdentityManager>(builder =>
{
builder.WithMethod(x => x.GetNameAsync);
});
This setup allows you to fetch information about a user, possibly using OpenAI in the process.
Middleware
The UseAiEndpoints() middleware is essential for enabling AI-powered interactions in your API. Once registered, it allows your application to respond to AI-driven requests via OpenAI or custom scenes.
How AI Endpoints Work
Endpoint Definition
The primary endpoint that gets mapped by UseAiEndpoints is api/ai/message. This endpoint receives a query string parameter m (which represents the user's message or request) and uses the ISceneManager to handle the message. The AI service or scene logic processes the message and returns a result.
Example of a GET Request:
GET https://yourdomain.com/api/ai/message?m=What%20is%20the%20weather%20today?
This will trigger the scene manager to process the message (e.g., fetching weather information using an AI service or scene logic).
Endpoint Logic
- Input: The endpoint expects a query string parameter
m(message), which will be processed by the scene manager. - Service Dependency: The
ISceneManagerservice is injected and handles the business logic of processing the message. - Response: The result of the scene or AI interaction is returned as a response to the client.
Authorization
The endpoints can be configured to require authorization either by default (using the isAuthorized parameter) or by specifying custom authorization policies.
Example of enforcing authorization:
app.UseAiEndpoints("AdminPolicy", "UserPolicy"); // Only users matching these policies can access AI endpoints
AI Endpoint Response (JSON Format)
When a request is made to the AI endpoint (api/ai/message), the response is returned in JSON format. The structure of the response is defined by the AiSceneResponse class.
JSON Response Structure
The JSON response will contain the following fields:
- Id: A unique identifier for the request, generated as a
GUID. - Name: The name of the scene that handled the request (optional).
- FunctionName: The name of the specific function or action executed by the scene (optional).
- Message: The original message or query that was sent to the AI endpoint.
- Arguments: Any arguments that were passed to the function (optional).
- Response: The result or output generated by the AI or scene.
Example JSON Response
{
"Id": "123e4567-e89b-12d3-a456-426614174000",
"Name": "Weather",
"FunctionName": "GetWeatherInfo",
"Message": "What is the weather in New York?",
"Arguments": "City: New York, Country: USA",
"Response": "The weather in New York is sunny with a temperature of 25�C."
}
Field Descriptions
- Id: A unique request identifier.
- Name: Name of the scene that handled the request, e.g., "Weather".
- FunctionName: The name of the specific function invoked by the scene, e.g., "GetWeatherInfo".
- Message: The original query made by the user, e.g., "What is the weather in New York?".
- Arguments: Any relevant parameters that were processed as part of the function, e.g., "City: New York, Country: USA".
- Response: The result of the AI's or scene's execution, e.g., "The weather in New York is sunny with a temperature of 25�C.".
This structured response allows clients to interpret and use the output of the AI or multi-agent system in a consistent manner.
MCP Server Integration
PlayFramework now supports integration with Model Context Protocol (MCP) servers, enabling your scenes to access tools, resources, and prompts exposed by MCP servers. This allows for seamless integration with external services and capabilities.
What is MCP?
The Model Context Protocol (MCP) is a standardized protocol for exposing capabilities to AI models. MCP servers can expose:
- Tools: Functions that the AI can call to perform actions
- Resources: Static or dynamic data that the AI can access and read
- Prompts: Pre-configured prompts or templates that provide context to the AI
Setting Up an MCP Server
MCP servers can be registered globally during application startup and then selectively used by individual scenes.
1. Register an MCP Server
In your service configuration, use the AddMcpServer method to register an MCP server:
services.AddPlayFramework(scenes =>
{
scenes.Configure(settings =>
{
settings.OpenAi.Name = "playframework";
})
.AddMcpServer("myMcpServer", mcp =>
{
// Configure HTTP-based MCP server
mcp.WithHttpServer("http://localhost:3000");
// Optionally set custom timeout (default is 30 seconds)
mcp.WithTimeout(TimeSpan.FromSeconds(60));
})
.AddScene(scene =>
{
// Configure scene to use MCP server tools
scene.UseMcpServer("myMcpServer");
});
});
2. Configuring MCP Elements in a Scene
Each scene can independently decide which MCP elements to use via the UseMcpServer() method:
.AddScene(scene =>
{
scene
.WithName("DataProcessing")
.WithDescription("Process data using MCP tools")
.WithOpenAi("playframework")
// Use all MCP elements (tools, resources, prompts)
.UseMcpServer("myMcpServer")
// Or configure with filters
.UseMcpServer("myMcpServer", filterBuilder =>
{
filterBuilder.WithTools(toolConfig =>
{
toolConfig.Whitelist("get_data", "process_*");
});
});
})
Filtering MCP Elements
You can fine-tune which MCP elements are available in each scene using fluent builder methods:
Using All Elements
// Enable tools, resources, and prompts
.UseMcpServer("myMcpServer")
Using Only Specific Element Types
// Use only tools, disable resources and prompts
.UseMcpServer("myMcpServer", filterBuilder =>
{
filterBuilder.OnlyTools();
})
// Use only resources, disable tools and prompts
.UseMcpServer("myMcpServer", filterBuilder =>
{
filterBuilder.OnlyResources();
})
// Use only prompts, disable tools and resources
.UseMcpServer("myMcpServer", filterBuilder =>
{
filterBuilder.OnlyPrompts();
})
Filtering by Name
Each element type supports filtering by name patterns:
.UseMcpServer("myMcpServer", filterBuilder =>
{
filterBuilder.WithTools(toolConfig =>
{
// Whitelist specific tools
toolConfig.Whitelist("get_user", "get_profile");
// Or use patterns
toolConfig.Whitelist("get_*", "list_*");
// Or match by regex
toolConfig.Regex("^(get|list)_.*");
// Or use startsWith
toolConfig.StartsWith("process_");
// Or use a custom predicate
toolConfig.Predicate(toolName => !toolName.StartsWith("admin_"));
// Or exclude specific tools
toolConfig.Exclude("dangerous_operation");
});
})
The same filtering options are available for resources and prompts:
.UseMcpServer("myMcpServer", filterBuilder =>
{
filterBuilder
.WithTools(toolConfig => toolConfig.Whitelist("get_*"))
.WithResources(resourceConfig => resourceConfig.Whitelist("data_*"))
.WithPrompts(promptConfig => promptConfig.Exclude("internal_*"));
})
Complete Example: Multi-Service Scene with MCP
services.AddPlayFramework(scenes =>
{
scenes.Configure(settings =>
{
settings.OpenAi.Name = "playframework";
})
// Register MCP server
.AddMcpServer("dataServer", mcp =>
{
mcp.WithHttpServer("http://localhost:3000");
})
// Add HTTP client for API calls
.AddHttpClient("apiDomain", x =>
{
x.BaseAddress = new Uri("https://api.example.com/");
})
// Create a scene that uses both MCP tools and HTTP APIs
.AddScene(scene =>
{
scene
.WithName("DataProcessing")
.WithDescription("Process and retrieve data using MCP and HTTP APIs")
.WithOpenAi("playframework")
.WithHttpClient("apiDomain")
// Use MCP tools with filtering
.UseMcpServer("dataServer", filterBuilder =>
{
filterBuilder.WithTools(toolConfig =>
{
toolConfig.Whitelist("get_*", "process_*");
});
})
// Register additional service methods
.WithService<DataManager>(builder =>
{
builder.WithMethod(x => x.GetDataAsync);
});
});
});
MCP Server Types
PlayFramework supports different MCP server communication methods:
HTTP Server
Connect to an MCP server via HTTP:
.AddMcpServer("httpMcp", mcp =>
{
mcp.WithHttpServer("http://localhost:3000");
})
Stdio Command
Connect to an MCP server via stdio (useful for local executables):
.AddMcpServer("localMcp", mcp =>
{
mcp.WithCommand("node", "path/to/mcp/server.js");
mcp.WithTimeout(TimeSpan.FromSeconds(30));
})
How MCP Elements Are Injected
- Tools: Available as callable functions that the AI can invoke to perform actions
- Resources: Injected as system messages with their content
- Prompts: Injected as system messages providing context and guidance
The AI automatically learns what tools, resources, and prompts are available and uses them appropriately to solve the given task.
Exposing PlayFramework as an MCP Server
In addition to consuming MCP servers, PlayFramework can also expose itself as an MCP server, allowing external MCP clients (like Claude Desktop, other AI agents, or custom applications) to consume your PlayFramework as a tool.
Why Expose as MCP Server?
- Interoperability: Allow any MCP-compatible client to use your PlayFramework
- Claude Desktop Integration: Users can add your PlayFramework directly to Claude Desktop
- Multi-Agent Scenarios: Other AI agents can call your PlayFramework as a tool
- Standardized API: Uses the standard MCP JSON-RPC protocol
Configuration
1. Enable MCP Server Exposure
When configuring your PlayFramework, use ExposeAsMcpServer():
services.AddPlayFramework(builder =>
{
// Expose this PlayFramework as an MCP server
builder.ExposeAsMcpServer(config =>
{
config.Description = "AI Assistant for customer support and order management";
config.Prompt = "You are a helpful assistant..."; // Optional
config.EnableResources = true; // Default: true - generates scene documentation
config.AuthorizationPolicy = "ApiKeyPolicy"; // Optional - null = public access
});
// Add your scenes as usual
builder.AddScene(scene =>
{
scene.WithName("CustomerSupport")
.WithDescription("Handles customer inquiries and complaints");
});
builder.AddScene(scene =>
{
scene.WithName("OrderManagement")
.WithDescription("Manages orders, returns, and shipping");
});
}, name: "MyAssistant");
2. Map MCP Endpoints
In your Program.cs, map the MCP endpoints:
var app = builder.Build();
// Map MCP endpoints for all exposed PlayFrameworks
app.MapPlayFrameworkMcpEndpoints("/mcp");
app.Run();
This creates a JSON-RPC endpoint at:
POST /mcp/MyAssistant
Authorization
Authorization is configured at the PlayFramework level using standard .NET authorization policies:
// Configure authorization policy
builder.Services.AddAuthorization(options =>
{
options.AddPolicy("ApiKeyPolicy", policy =>
policy.RequireAssertion(ctx =>
{
var httpContext = ctx.Resource as HttpContext;
return httpContext?.Request.Headers["X-Api-Key"] == "your-secret-key";
}));
});
// Apply to PlayFramework
builder.ExposeAsMcpServer(config =>
{
config.AuthorizationPolicy = "ApiKeyPolicy";
});
If AuthorizationPolicy is null or not set, the endpoint allows anonymous access.
Supported MCP Methods
The exposed MCP server supports all standard MCP methods:
| Method | Description |
|---|---|
tools/list |
Returns the PlayFramework as a single tool |
tools/call |
Executes the PlayFramework with the provided message |
resources/list |
Returns documentation for each scene |
resources/read |
Returns the markdown documentation content |
prompts/list |
Returns configured prompts (if set) |
prompts/get |
Returns the prompt content |
Example: Using with Claude Desktop
Add to your Claude Desktop configuration (claude_desktop_config.json):
{
"mcpServers": {
"my-assistant": {
"url": "http://localhost:5000/mcp/MyAssistant"
}
}
}
Example JSON-RPC Request/Response
Request (tools/call)
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "MyAssistant",
"arguments": {
"message": "What's the status of order #12345?"
}
}
}
Response
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"content": [
{
"type": "text",
"text": "Order #12345 is currently being shipped and will arrive by Friday."
}
],
"isError": false
}
}
Complete Example: Full MCP Integration
Here's an example that both consumes an external MCP server AND exposes the PlayFramework as an MCP server:
services.AddPlayFramework(builder =>
{
// Configure settings
builder.Configure(settings =>
{
settings.OpenAi.Name = "playframework";
});
// Consume external MCP server
builder.AddMcpServer("externalTools", mcp =>
{
mcp.WithHttpServer("http://external-mcp-server:3000");
});
// Expose THIS PlayFramework as an MCP server
builder.ExposeAsMcpServer(config =>
{
config.Description = "Multi-scene AI assistant with data processing";
config.EnableResources = true;
});
// Scene that uses external MCP tools
builder.AddScene(scene =>
{
scene.WithName("DataProcessing")
.WithDescription("Processes data using external tools")
.UseMcpServer("externalTools", filter =>
{
filter.WithTools(t => t.Whitelist("process_*", "transform_*"));
});
});
}, name: "DataAssistant");
// In Program.cs
app.MapPlayFrameworkMcpEndpoints("/mcp");
// Now accessible at: POST /mcp/DataAssistant
Example of a weather scene
[
{
"id": "18b0628a-5202-47cc-813d-099702be3153",
"name": "Weather",
"functionName": null,
"message": "Starting",
"arguments": null,
"response": null
},
{
"id": "55f4754f-f372-40a2-a805-ce50c5d1c88d",
"name": "Weather",
"functionName": "Country_CityExists",
"message": null,
"arguments": "\"{\n \u0022city\u0022: \u0022milan\u0022\n}\"",
"response": "false"
},
{
"id": "455e0384-a52e-4ee3-81d1-e0f9478a8484",
"name": "Weather",
"functionName": "Country_AddCity",
"message": null,
"arguments": "\"{\n \u0022city\u0022: {\n \u0022Name\u0022: \u0022milan\u0022,\n \u0022Country\u0022: \u0022Italy\u0022,\n \u0022Population\u0022: 1366180\n }\n}\"",
"response": "\"true\""
},
{
"id": "aca48a7a-bb8d-4352-9d79-eb0535a2f6ed",
"name": "Weather",
"functionName": "Country_Exists",
"message": null,
"arguments": "\"{\n \u0022country\u0022: \u0022Italia\u0022\n}\"",
"response": "false"
},
{
"id": "cdaf0ae2-3663-4287-94cf-edd1062e6bd8",
"name": "Weather",
"functionName": "Country_AddCountry",
"message": null,
"arguments": "\"{\n \u0022country\u0022: {\n \u0022Name\u0022: \u0022Italy\u0022,\n \u0022Population\u0022: 60461826\n }\n}\"",
"response": "\"true\""
},
{
"id": "0d369878-cc5f-4563-86a9-0714d909b85f",
"name": "Weather",
"functionName": "WeatherForecast_Get",
"message": null,
"arguments": "\"{\n \u0022city\u0022: \u0022milan\u0022\n}\"",
"response": "[{\"date\":\"2024-10-19\",\"temperatureC\":20,\"temperatureF\":67,\"summary\":\"Cool\"},{\"date\":\"2024-10-20\",\"temperatureC\":20,\"temperatureF\":67,\"summary\":\"Hot\"},{\"date\":\"2024-10-21\",\"temperatureC\":20,\"temperatureF\":67,\"summary\":\"Cool\"},{\"date\":\"2024-10-22\",\"temperatureC\":20,\"temperatureF\":67,\"summary\":\"Freezing\"},{\"date\":\"2024-10-23\",\"temperatureC\":20,\"temperatureF\":67,\"summary\":\"Hot\"}]"
},
{
"id": "13192a15-e4c6-44d4-aec9-d38253c2dfe4",
"name": "Weather",
"functionName": null,
"message": "Il tempo oggi a Milano � fresco con una temperatura di 20 gradi Celsius (67 gradi Fahrenheit).",
"arguments": null,
"response": null
}
]
Building and Testing
To build the project, run:
dotnet build
To run tests, add a test project or directly execute requests against your API to validate the scene and actor configurations.
Conclusion
By following this guide, you can successfully install, configure, and use Rystem.PlayFramework with OpenAI integration. The key steps are setting up the services, defining scenes, and utilizing the AI middleware to handle interactions in your .NET application.
| Product | Versions 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. |
-
net10.0
- Rystem.OpenAi (>= 10.0.6)
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 |
|---|---|---|
| 10.0.6 | 35 | 1/23/2026 |
| 10.0.5 | 132 | 12/12/2025 |
| 10.0.4 | 138,167 | 12/1/2025 |
| 10.0.3 | 175 | 11/26/2025 |
| 10.0.2 | 191 | 11/25/2025 |
| 10.0.1 | 327 | 11/21/2025 |
| 10.0.0 | 291 | 11/13/2025 |
| 9.0.41 | 254 | 8/4/2025 |
| 9.0.40 | 147 | 7/31/2025 |
| 9.0.39 | 160 | 7/31/2025 |
| 9.0.38 | 488,864 | 7/28/2025 |
| 9.0.37 | 373,399 | 5/13/2025 |
| 9.0.36 | 309 | 5/12/2025 |
| 9.0.35 | 92,868 | 4/28/2025 |
| 9.0.34 | 44,474 | 4/20/2025 |
| 9.0.33 | 49,749 | 4/15/2025 |
| 9.0.32 | 256 | 4/15/2025 |
| 9.0.31 | 5,793 | 4/3/2025 |
| 9.0.30 | 88,860 | 3/24/2025 |
| 9.0.29 | 8,986 | 3/18/2025 |
| 9.0.28 | 206 | 3/17/2025 |
| 9.0.26 | 226 | 3/13/2025 |
| 9.0.25 | 52,123 | 3/9/2025 |
| 9.0.20 | 190 | 3/9/2025 |
| 9.0.19 | 287 | 3/7/2025 |
| 9.0.18 | 260 | 3/6/2025 |
| 9.0.17 | 19,529 | 3/6/2025 |
| 9.0.16 | 269 | 3/4/2025 |
| 9.0.15 | 302,535 | 1/16/2025 |
| 9.0.14 | 288 | 1/16/2025 |
| 9.0.13 | 126 | 1/13/2025 |
| 9.0.12 | 12,837 | 1/13/2025 |
| 9.0.11 | 120 | 1/13/2025 |
| 9.0.10 | 23,996 | 1/9/2025 |
| 9.0.9 | 119 | 1/9/2025 |
| 9.0.8 | 144 | 1/9/2025 |
| 9.0.7 | 3,986 | 1/8/2025 |
| 9.0.6 | 130 | 1/7/2025 |
| 9.0.5 | 12,512 | 1/6/2025 |
| 9.0.4 | 19,157 | 1/3/2025 |
| 9.0.3 | 73,343 | 12/22/2024 |
| 9.0.2 | 11,780 | 12/14/2024 |
| 9.0.1 | 68,696 | 12/10/2024 |
| 9.0.1-pre.4 | 102 | 12/9/2024 |
| 9.0.1-pre.3 | 101 | 12/8/2024 |
| 9.0.1-pre.2 | 107 | 12/7/2024 |
| 9.0.1-pre.1 | 99 | 12/7/2024 |
| 9.0.0 | 104,470 | 11/16/2024 |
| 9.0.0-rc.9 | 115 | 10/21/2024 |
| 9.0.0-rc.8 | 123 | 10/20/2024 |
| 9.0.0-rc.7 | 120 | 10/20/2024 |
| 9.0.0-rc.6 | 101 | 10/20/2024 |
| 9.0.0-rc.5 | 106 | 10/20/2024 |
| 9.0.0-rc.4 | 101 | 10/20/2024 |
| 9.0.0-rc.3 | 96 | 10/20/2024 |
| 9.0.0-rc.2 | 106 | 10/20/2024 |
| 9.0.0-rc.1 | 156 | 10/18/2024 |