package tools
import (
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
"time"
"assistant/config"
)
const weatherToolName = "get_current_weather"
var weatherParams = json.RawMessage(`{
"type": "object",
"properties": {},
"additionalProperties": false
}`)
func newWeatherTool(cfg config.WeatherToolConfig) Tool {
lat := cfg.Lat
lon := cfg.Lon
return Tool{
Name: weatherToolName,
Description: "Get current weather for the single preconfigured location only (no location argument supported).",
Parameters: weatherParams,
Run: func(ctx context.Context, _ json.RawMessage) (string, error) {
return fetchOpenMeteo(ctx, lat, lon)
},
}
}
func fetchOpenMeteo(ctx context.Context, lat, lon float64) (string, error) {
q := url.Values{}
q.Set("latitude", fmt.Sprintf("%.4f", lat))
q.Set("longitude", fmt.Sprintf("%.4f", lon))
q.Set("current", "temperature_2m,weather_code,wind_speed_10m")
uri := "https://api.open-meteo.com/v1/forecast?" + q.Encode()
req, err := http.NewRequestWithContext(ctx, http.MethodGet, uri, nil)
if err != nil {
return "", err
}
c := &http.Client{Timeout: 20 * time.Second}
res, err := c.Do(req)
if err != nil {
return "", err
}
defer res.Body.Close()
b, err := io.ReadAll(io.LimitReader(res.Body, 1<<20))
if err != nil {
return "", err
}
if res.StatusCode < 200 || res.StatusCode >= 300 {
return "", fmt.Errorf("open-meteo: %s: %s", res.Status, string(b[:min(len(b), 200)]))
}
var payload struct {
Current struct {
Temperature float64 `json:"temperature_2m"`
WeatherCode int `json:"weather_code"`
WindSpeed float64 `json:"wind_speed_10m"`
} `json:"current"`
}
if err := json.Unmarshal(b, &payload); err != nil {
return "", err
}
desc := weatherCodeDescription(payload.Current.WeatherCode)
return fmt.Sprintf("Temperature: %.1f°C (%s). Wind: %.0f km/h. (WMO weather code %d)",
payload.Current.Temperature, desc, payload.Current.WindSpeed, payload.Current.WeatherCode), nil
}
func min(a, b int) int {
if a < b {
return a
}
return b
}
func weatherCodeDescription(code int) string {
// WMO Weather interpretation codes ( WW ).
switch {
case code == 0:
return "clear"
case code <= 3:
return "partly cloudy"
case code <= 48:
return "fog"
case code <= 57:
return "drizzle"
case code <= 67:
return "rain"
case code <= 77:
return "snow"
case code <= 82:
return "rain showers"
case code <= 86:
return "snow showers"
case code == 95:
return "thunderstorm"
default:
return "conditions vary"
}
}