Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add OpenWeather plugin #1880

Merged
merged 1 commit into from
Jan 6, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -402,3 +402,5 @@ STARGAZE_ENDPOINT=
# GenLayer
GENLAYER_PRIVATE_KEY=0x0000000000000000000000000000000000000000000000000000000000000000 # Private key of the GenLayer account to use for the agent

# OpenWeather
OPEN_WEATHER_API_KEY= # OpenWeather API key
1 change: 1 addition & 0 deletions agent/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@
"@elizaos/plugin-avalanche": "workspace:*",
"@elizaos/plugin-web-search": "workspace:*",
"@elizaos/plugin-genlayer": "workspace:*",
"@elizaos/plugin-open-weather": "workspace:*",
"readline": "1.3.0",
"ws": "8.18.0",
"yargs": "17.7.2"
Expand Down
4 changes: 4 additions & 0 deletions agent/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ import { webSearchPlugin } from "@elizaos/plugin-web-search";
import { stargazePlugin } from "@elizaos/plugin-stargaze";
import { zksyncEraPlugin } from "@elizaos/plugin-zksync-era";
import { availPlugin } from "@elizaos/plugin-avail";
import { openWeatherPlugin } from "@elizaos/plugin-open-weather";
import Database from "better-sqlite3";
import fs from "fs";
import net from "net";
Expand Down Expand Up @@ -624,6 +625,9 @@ export async function createAgent(
: null,
getSecret(character, "AVAIL_SEED") ? availPlugin : null,
getSecret(character, "AVAIL_APP_ID") ? availPlugin : null,
getSecret(character, "OPEN_WEATHER_API_KEY")
? openWeatherPlugin
: null,
].filter(Boolean),
providers: [],
actions: [],
Expand Down
6 changes: 6 additions & 0 deletions packages/plugin-open-weather/.npmignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
*

!dist/**
!package.json
!readme.md
!tsup.config.ts
208 changes: 208 additions & 0 deletions packages/plugin-open-weather/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
# @elizaos/plugin-open-weather

A plugin for Eliza that enables weather checking using the OpenWeather API.

## Features

- Weather & temperature check for any specified city
- Supports temperatures, weather descriptions, wind speed, with possible add-ons for full API response

## Installation

```bash
npm install @elizaos/plugin-open-weather
```

## Configuration

1. Get your API key from [OpenWeather](https://openweathermap.org/api)

2. Set up your environment variables:

```bash
OPEN_WEATHER_API_KEY=your_api_key
```

3. Register the plugin in your Eliza configuration:

```typescript
import { openWeatherPlugin } from "@elizaos/plugin-open-weather";

// In your Eliza configuration
plugins: [
new openWeatherPlugin(),
// ... other plugins
];
```

## Usage

The plugin responds to natural language queries about weather in a specified city. Here are some examples:

```plaintext
"What's the current weather in London?"
"Show me weather in New York"
"Get the weather in Tokyo"
"What's the weather like?"
```

### Available Actions

#### GET_CURRENT_WEATHER

Fetches the current weather for a specified city. If no city is specified it will prompt the user for a city.

```typescript
// Example response format
{
"coord": {
"lon": 7.367,
"lat": 45.133
},
"weather": [
{
"id": 501,
"main": "Rain",
"description": "moderate rain",
"icon": "10d"
}
],
"base": "stations",
"main": {
"temp": 284.2,
"feels_like": 282.93,
"temp_min": 283.06,
"temp_max": 286.82,
"pressure": 1021,
"humidity": 60,
"sea_level": 1021,
"grnd_level": 910
},
"visibility": 10000,
"wind": {
"speed": 4.09,
"deg": 121,
"gust": 3.47
},
"rain": {
"1h": 2.73
},
"clouds": {
"all": 83
},
"dt": 1726660758,
"sys": {
"type": 1,
"id": 6736,
"country": "IT",
"sunrise": 1726636384,
"sunset": 1726680975
},
"timezone": 7200,
"id": 3165523,
"name": "Province of Turin",
"cod": 200
}
```

## API Reference

### Environment Variables

| Variable | Description | Required |
| -------------------- | ------------------------ | -------- |
| OPEN_WEATHER_API_KEY | Your OpenWeather API key | Yes |

### Types

```typescript
export interface WeatherResponse {
coord: Coordinates;
weather: Weather[];
base: string;
main: MainWeather;
visibility: number;
wind: Wind;
rain?: Precipitation;
snow?: Precipitation;
clouds: Clouds;
dt: number;
sys: System;
timezone: number;
id: number;
name: string;
cod: number;
}

interface Coordinates {
lon: number;
lat: number;
}

interface Weather {
id: number;
main: string;
description: string;
icon: string;
}

interface MainWeather {
temp: number;
feels_like: number;
temp_min: number;
temp_max: number;
pressure: number;
humidity: number;
sea_level?: number;
grnd_level?: number;
}

interface Wind {
speed: number;
deg: number;
gust?: number;
}

interface Precipitation {
"1h"?: number;
"3h"?: number;
}

interface Clouds {
all: number;
}

interface System {
type: number;
id: number;
country: string;
sunrise: number;
sunset: number;
}
```

## Error Handling

The plugin includes error handling for:

- Invalid API keys
- Rate limiting
- Network timeouts
- Invalid cities/locations

## Rate Limits

1,000 API calls per day for free.

OpenWeather API has different rate limits based on your subscription plan. Please refer to [OpenWeather's pricing page](https://openweathermap.org/api) for detailed information.

## Support

For support, please open an issue in the repository or reach out to the maintainers:

- Discord: kylebuildsstuff
- X/twitter: [kylebuildsstuff](https://x.com/kylebuildsstuff)

## Links

- [OpenWeather weather API Documentation](https://openweathermap.org/current)
3 changes: 3 additions & 0 deletions packages/plugin-open-weather/eslint.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import eslintGlobalConfig from "../../eslint.config.mjs";

export default [...eslintGlobalConfig];
34 changes: 34 additions & 0 deletions packages/plugin-open-weather/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
{
"name": "@elizaos/plugin-open-weather",
"version": "0.1.7",
"type": "module",
"main": "dist/index.js",
"module": "dist/index.js",
"types": "dist/index.d.ts",
"exports": {
"./package.json": "./package.json",
".": {
"import": {
"@elizaos/source": "./src/index.ts",
"types": "./dist/index.d.ts",
"default": "./dist/index.js"
}
}
},
"files": [
"dist"
],
"dependencies": {
"@elizaos/core": "workspace:*",
"tsup": "8.3.5",
"zod": "^3.22.4"
},
"scripts": {
"build": "tsup --format esm --dts",
"dev": "tsup --format esm --dts --watch",
"lint": "eslint --fix --cache ."
},
"peerDependencies": {
"whatwg-url": "7.1.0"
}
}
107 changes: 107 additions & 0 deletions packages/plugin-open-weather/src/actions/getCurrentWeather.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import { composeContext, elizaLogger } from "@elizaos/core";
import { generateMessageResponse } from "@elizaos/core";
import {
Action,
ActionExample,
HandlerCallback,
IAgentRuntime,
Memory,
ModelClass,
State,
} from "@elizaos/core";
import { validateOpenWeatherConfig } from "../environment";
import { getCurrentWeatherTemplate } from "../templates";
import { getCurrentWeatherExamples } from "../examples";
import { createWeatherService } from "../services";

export const getCurrentWeatherAction: Action = {
name: "GET_CURRENT_WEATHER",
similes: [
"WEATHER",
"TEMPERATURE",
"FORECAST",
"WEATHER_REPORT",
"WEATHER_UPDATE",
"CHECK_WEATHER",
"WEATHER_CHECK",
"CHECK_TEMPERATURE",
"WEATHER_OUTSIDE",
],
description: "Get the current weather for a given location",
validate: async (runtime: IAgentRuntime) => {
await validateOpenWeatherConfig(runtime);
return true;
},
handler: async (
runtime: IAgentRuntime,
message: Memory,
state: State,
_options: { [key: string]: unknown },
callback: HandlerCallback
) => {
// Initialize/update state
if (!state) {
state = (await runtime.composeState(message)) as State;
}
state = await runtime.updateRecentMessageState(state);

// state -> context
const weatherContext = composeContext({
state,
template: getCurrentWeatherTemplate,
});

// context -> content
const content = await generateMessageResponse({
runtime,
context: weatherContext,
modelClass: ModelClass.SMALL,
});

// parse content
const hasLocation =
content?.city && content?.country && !content?.error;

if (!hasLocation) {
return;
}

// Instantiate API service
const config = await validateOpenWeatherConfig(runtime);
const weatherService = createWeatherService(
config.OPEN_WEATHER_API_KEY
);

// Fetch weather & respond
try {
const weatherData = await weatherService.getWeather(
String(content?.city || ""),
content?.country ? String(content?.country) : undefined
);
elizaLogger.success(
`Successfully fetched weather for ${content.city}, ${content.country}`
);

if (callback) {
callback({
text: `The current weather in ${content.city}, ${content.country} is ${weatherData.main.temp}°C, feels like ${weatherData.main.feels_like}°C, and is ${weatherData.weather[0].description} with a wind speed of ${weatherData.wind.speed} km/h.`,
content: weatherData,
});

return true;
}
} catch (error) {
elizaLogger.error("Error in GET_CURRENT_WEATHER handler:", error);

callback({
text: `Error fetching weather: ${error.message}`,
content: { error: error.message },
});

return false;
}

return;
},
examples: getCurrentWeatherExamples as ActionExample[][],
} as Action;
1 change: 1 addition & 0 deletions packages/plugin-open-weather/src/actions/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./getCurrentWeather.ts";
Loading
Loading