What it does
Read the exact source snapshot below for its parameters, behavior, and registration.
Read the implementation
This page describes the selected release. The source link points to the same exact commit; it cannot silently open a newer implementation.
Lilith/Core/Tools/CurrentWeatherTool.cs ↗Implementation code
Read the implementation from this version's exact source snapshot. Helper links below stay on the same release.
Lilith/Core/Tools/CurrentWeatherTool.cs
C#
using System.Text;
using System.Text.Json;
using System.Text.RegularExpressions;
namespace Lilith.Core.Tools;
/// <summary>Bounded, explicit-city weather lookup. Never infers a location from IP.</summary>
public sealed class CurrentWeatherTool(HttpClient? client = null) : ILilithTool
{
private static readonly HttpClient Shared = new(new HttpClientHandler { AllowAutoRedirect = false, UseCookies = false }) { Timeout = TimeSpan.FromSeconds(15) };
public string Name => "get_current_weather";
public string Description => "Read current weather for an explicitly supplied city from wttr.in, in Celsius. Requires internet. Use the actual returned weather; report unavailable honestly. Never infer a city from the computer's IP or timezone.";
public JsonElement Parameters => JsonSerializer.SerializeToElement(new {
type = "object", properties = new { city = new { type = "string", description = "City, optionally with country/region, e.g. London, UK or New York, USA." } },
required = new[] { "city" }, additionalProperties = false
});
public async Task<string> ExecuteAsync(JsonElement arguments, CancellationToken cancellationToken = default)
{
if (arguments.ValueKind != JsonValueKind.Object || arguments.EnumerateObject().Any(p => p.Name != "city") ||
!arguments.TryGetProperty("city", out var value) || value.ValueKind != JsonValueKind.String) throw new ArgumentException("get_current_weather requires a city string only.");
string city = value.GetString()!.Trim();
if (city.Length is < 1 or > 100 || !city.Any(char.IsLetter) || !Regex.IsMatch(city, "^[\\p{L}\\p{M}\\p{N} .,'’()-]+$", RegexOptions.CultureInvariant, TimeSpan.FromSeconds(1)))
throw new ArgumentException("Supply a city name, optionally including its region/country.");
cancellationToken.ThrowIfCancellationRequested();
using var deadline = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
deadline.CancelAfter(TimeSpan.FromSeconds(12));
try
{
string url = "https://wttr.in/" + Uri.EscapeDataString(city) + "?format=3&m";
using var request = new HttpRequestMessage(HttpMethod.Get, url);
request.Headers.UserAgent.ParseAdd("Lilith/1.0");
using var response = await (client ?? Shared).SendAsync(request, HttpCompletionOption.ResponseHeadersRead, deadline.Token);
if (!response.IsSuccessStatusCode) return Unavailable(city, "Weather service returned HTTP " + (int)response.StatusCode + ".");
await using var stream = await response.Content.ReadAsStreamAsync(deadline.Token);
byte[] buffer = new byte[4097]; int length = 0;
while (length < buffer.Length)
{
int read = await stream.ReadAsync(buffer.AsMemory(length), deadline.Token);
if (read == 0) break;
length += read;
}
if (length > 4096) return Unavailable(city, "Weather response exceeded its limit.");
string weather = new UTF8Encoding(false, true).GetString(buffer, 0, length).Trim();
if (weather.Length is < 1 or > 500 || weather.Any(char.IsControl) || weather.Contains('<') ||
!Regex.IsMatch(weather, @"[-+]?\d+(?:\.\d+)?\s*°C", RegexOptions.CultureInvariant, TimeSpan.FromSeconds(1)))
return Unavailable(city, "Weather service did not return a usable current-weather line.");
return JsonSerializer.Serialize(new { status = "completed", city, weather, units = "Celsius", source = "https://wttr.in", retrieved_at = DateTimeOffset.UtcNow.ToString("O") });
}
catch (OperationCanceledException) when (!cancellationToken.IsCancellationRequested) { return Unavailable(city, "Weather request timed out."); }
catch (Exception ex) when (ex is HttpRequestException or IOException or DecoderFallbackException) { return Unavailable(city, "Weather service could not be reached or read."); }
}
private static string Unavailable(string city, string error) => JsonSerializer.Serialize(new { status = "unavailable", city, source = "https://wttr.in", error });
}
Scenarios using this tool
- Current weather for a city — bundled
- Weather for the saved user city — bundled