weather.ts 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126
  1. import { tool } from "ai";
  2. import { z } from "zod";
  3. /**
  4. * Get current weather and 7-day forecast for a city using Open-Meteo API.
  5. * Free, no API key required.
  6. * https://open-meteo.com/
  7. */
  8. export const getWeather = tool({
  9. description:
  10. "Get current weather conditions and a 7-day forecast for a given city. Returns temperature, humidity, wind speed, weather conditions, and daily forecasts.",
  11. inputSchema: z.object({
  12. city: z
  13. .string()
  14. .describe("City name (e.g., 'New York', 'London', 'Tokyo')"),
  15. }),
  16. execute: async ({ city }) => {
  17. // Step 1: Geocode the city name to coordinates
  18. const geocodeUrl = `https://geocoding-api.open-meteo.com/v1/search?name=${encodeURIComponent(city)}&count=1&language=en&format=json`;
  19. const geocodeRes = await fetch(geocodeUrl);
  20. if (!geocodeRes.ok) {
  21. return { error: `Failed to geocode city: ${city}` };
  22. }
  23. const geocodeData = (await geocodeRes.json()) as {
  24. results?: Array<{
  25. name: string;
  26. country: string;
  27. latitude: number;
  28. longitude: number;
  29. timezone: string;
  30. }>;
  31. };
  32. if (!geocodeData.results || geocodeData.results.length === 0) {
  33. return { error: `City not found: ${city}` };
  34. }
  35. const location = geocodeData.results[0]!;
  36. // Step 2: Get weather data
  37. const weatherUrl = `https://api.open-meteo.com/v1/forecast?latitude=${location.latitude}&longitude=${location.longitude}&current=temperature_2m,relative_humidity_2m,apparent_temperature,weather_code,wind_speed_10m&daily=weather_code,temperature_2m_max,temperature_2m_min,precipitation_sum&temperature_unit=fahrenheit&wind_speed_unit=mph&precipitation_unit=inch&timezone=${encodeURIComponent(location.timezone)}&forecast_days=7`;
  38. const weatherRes = await fetch(weatherUrl);
  39. if (!weatherRes.ok) {
  40. return { error: "Failed to fetch weather data" };
  41. }
  42. const weather = (await weatherRes.json()) as {
  43. current: {
  44. temperature_2m: number;
  45. relative_humidity_2m: number;
  46. apparent_temperature: number;
  47. weather_code: number;
  48. wind_speed_10m: number;
  49. };
  50. daily: {
  51. time: string[];
  52. weather_code: number[];
  53. temperature_2m_max: number[];
  54. temperature_2m_min: number[];
  55. precipitation_sum: number[];
  56. };
  57. };
  58. const weatherDescription = describeWeatherCode(
  59. weather.current.weather_code,
  60. );
  61. const forecast = weather.daily.time.map((date, i) => ({
  62. date,
  63. day: new Date(date + "T12:00:00").toLocaleDateString("en-US", {
  64. weekday: "short",
  65. }),
  66. high: Math.round(weather.daily.temperature_2m_max[i]!),
  67. low: Math.round(weather.daily.temperature_2m_min[i]!),
  68. condition: describeWeatherCode(weather.daily.weather_code[i]!),
  69. precipitation: weather.daily.precipitation_sum[i]!,
  70. }));
  71. return {
  72. city: location.name,
  73. country: location.country,
  74. current: {
  75. temperature: Math.round(weather.current.temperature_2m),
  76. feelsLike: Math.round(weather.current.apparent_temperature),
  77. humidity: weather.current.relative_humidity_2m,
  78. windSpeed: Math.round(weather.current.wind_speed_10m),
  79. condition: weatherDescription,
  80. },
  81. forecast,
  82. };
  83. },
  84. });
  85. function describeWeatherCode(code: number): string {
  86. const descriptions: Record<number, string> = {
  87. 0: "Clear sky",
  88. 1: "Mainly clear",
  89. 2: "Partly cloudy",
  90. 3: "Overcast",
  91. 45: "Foggy",
  92. 48: "Depositing rime fog",
  93. 51: "Light drizzle",
  94. 53: "Moderate drizzle",
  95. 55: "Dense drizzle",
  96. 61: "Slight rain",
  97. 63: "Moderate rain",
  98. 65: "Heavy rain",
  99. 71: "Slight snow",
  100. 73: "Moderate snow",
  101. 75: "Heavy snow",
  102. 77: "Snow grains",
  103. 80: "Slight rain showers",
  104. 81: "Moderate rain showers",
  105. 82: "Violent rain showers",
  106. 85: "Slight snow showers",
  107. 86: "Heavy snow showers",
  108. 95: "Thunderstorm",
  109. 96: "Thunderstorm with slight hail",
  110. 99: "Thunderstorm with heavy hail",
  111. };
  112. return descriptions[code] ?? "Unknown";
  113. }