kopia lustrzana https://github.com/transitive-bullshit/chatgpt-api
feat: add @agentic/open-meteo weather api client
rodzic
34631ddf66
commit
91ff8d645c
|
@ -0,0 +1,42 @@
|
||||||
|
---
|
||||||
|
title: Open Meteo
|
||||||
|
description: Open-Meteo weather API client.
|
||||||
|
---
|
||||||
|
|
||||||
|
The [Open-Meteo weather API](https://open-meteo.com) provides a free weather forecast API for open-source developers and non-commercial use.
|
||||||
|
|
||||||
|
- package: `@agentic/open-meteo`
|
||||||
|
- exports: `class OpenMeteoClient`, `namespace openmeteo`
|
||||||
|
- env vars: `OPEN_METEO_API_KEY`
|
||||||
|
- [source](https://github.com/transitive-bullshit/agentic/blob/main/packages/open-meteo/src/open-meteo-client.ts)
|
||||||
|
- [open-meteo api docs](https://open-meteo.com/en/docs)
|
||||||
|
|
||||||
|
## Install
|
||||||
|
|
||||||
|
<CodeGroup>
|
||||||
|
```bash npm
|
||||||
|
npm install @agentic/open-meteo
|
||||||
|
```
|
||||||
|
|
||||||
|
```bash yarn
|
||||||
|
yarn add @agentic/open-meteo
|
||||||
|
```
|
||||||
|
|
||||||
|
```bash pnpm
|
||||||
|
pnpm add @agentic/open-meteo
|
||||||
|
```
|
||||||
|
|
||||||
|
</CodeGroup>
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { OpenMeteoClient } from '@agentic/open-meteo'
|
||||||
|
|
||||||
|
const openMeteo = new OpenMeteoClient()
|
||||||
|
const res = await openMeteo.getForecast({
|
||||||
|
location: {
|
||||||
|
name: 'San Francisco'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
|
@ -0,0 +1,47 @@
|
||||||
|
{
|
||||||
|
"name": "@agentic/open-meteo",
|
||||||
|
"version": "7.6.1",
|
||||||
|
"description": "Agentic SDK for the Open-Meteo weather API.",
|
||||||
|
"author": "Travis Fischer <travis@transitivebullsh.it>",
|
||||||
|
"license": "MIT",
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "git+https://github.com/transitive-bullshit/agentic.git"
|
||||||
|
},
|
||||||
|
"type": "module",
|
||||||
|
"source": "./src/index.ts",
|
||||||
|
"module": "./dist/index.js",
|
||||||
|
"types": "./dist/index.d.ts",
|
||||||
|
"sideEffects": false,
|
||||||
|
"exports": {
|
||||||
|
".": {
|
||||||
|
"types": "./dist/index.d.ts",
|
||||||
|
"import": "./dist/index.js",
|
||||||
|
"default": "./dist/index.js"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"dist"
|
||||||
|
],
|
||||||
|
"scripts": {
|
||||||
|
"build": "tsup",
|
||||||
|
"dev": "tsup --watch",
|
||||||
|
"clean": "del dist",
|
||||||
|
"test": "run-s test:*",
|
||||||
|
"test:lint": "eslint .",
|
||||||
|
"test:typecheck": "tsc --noEmit"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@agentic/core": "workspace:*",
|
||||||
|
"ky": "catalog:"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"zod": "catalog:"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@agentic/tsconfig": "workspace:*"
|
||||||
|
},
|
||||||
|
"publishConfig": {
|
||||||
|
"access": "public"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,24 @@
|
||||||
|
<p align="center">
|
||||||
|
<a href="https://agentic.so">
|
||||||
|
<img alt="Agentic" src="https://raw.githubusercontent.com/transitive-bullshit/agentic/main/docs/media/agentic-header.jpg" width="308">
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<em>AI agent stdlib that works with any LLM and TypeScript AI SDK.</em>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<a href="https://github.com/transitive-bullshit/agentic/actions/workflows/main.yml"><img alt="Build Status" src="https://github.com/transitive-bullshit/agentic/actions/workflows/main.yml/badge.svg" /></a>
|
||||||
|
<a href="https://www.npmjs.com/package/@agentic/stdlib"><img alt="NPM" src="https://img.shields.io/npm/v/@agentic/stdlib.svg" /></a>
|
||||||
|
<a href="https://github.com/transitive-bullshit/agentic/blob/main/license"><img alt="MIT License" src="https://img.shields.io/badge/license-MIT-blue" /></a>
|
||||||
|
<a href="https://prettier.io"><img alt="Prettier Code Formatting" src="https://img.shields.io/badge/code_style-prettier-brightgreen.svg" /></a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
# Agentic
|
||||||
|
|
||||||
|
**See the [github repo](https://github.com/transitive-bullshit/agentic) or [docs](https://agentic.so) for more info.**
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
MIT © [Travis Fischer](https://x.com/transitive_bs)
|
|
@ -0,0 +1,2 @@
|
||||||
|
export * from './open-meteo'
|
||||||
|
export * from './open-meteo-client'
|
|
@ -0,0 +1,144 @@
|
||||||
|
import {
|
||||||
|
aiFunction,
|
||||||
|
AIFunctionsProvider,
|
||||||
|
getEnv,
|
||||||
|
pick,
|
||||||
|
sanitizeSearchParams
|
||||||
|
} from '@agentic/core'
|
||||||
|
import defaultKy, { type KyInstance } from 'ky'
|
||||||
|
|
||||||
|
import { openmeteo } from './open-meteo'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Agentic OpenMeteo weather client.
|
||||||
|
*
|
||||||
|
* Open-Meteo offers free weather forecast APIs for open-source developers
|
||||||
|
* and non-commercial use.
|
||||||
|
*
|
||||||
|
* @note The API key is optional.
|
||||||
|
*
|
||||||
|
* @see https://open-meteo.com/en/docs
|
||||||
|
*/
|
||||||
|
export class OpenMeteoClient extends AIFunctionsProvider {
|
||||||
|
protected readonly ky: KyInstance
|
||||||
|
protected readonly apiKey: string | undefined
|
||||||
|
protected readonly apiBaseUrl: string
|
||||||
|
|
||||||
|
constructor({
|
||||||
|
apiKey = getEnv('OPEN_METEO_API_KEY'),
|
||||||
|
apiBaseUrl = openmeteo.apiBaseUrl,
|
||||||
|
ky = defaultKy
|
||||||
|
}: {
|
||||||
|
apiKey?: string
|
||||||
|
apiBaseUrl?: string
|
||||||
|
ky?: KyInstance
|
||||||
|
} = {}) {
|
||||||
|
super()
|
||||||
|
|
||||||
|
this.apiKey = apiKey
|
||||||
|
this.apiBaseUrl = apiBaseUrl
|
||||||
|
|
||||||
|
this.ky = ky.extend({
|
||||||
|
prefixUrl: apiBaseUrl,
|
||||||
|
...(apiKey
|
||||||
|
? {
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${apiKey}`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
: {})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the 7-day weather variables in hourly and daily resolution for given WGS84 latitude and longitude coordinates. Available worldwide.
|
||||||
|
*/
|
||||||
|
@aiFunction({
|
||||||
|
name: 'open_meteo_get_forecast',
|
||||||
|
description: `Gets the 7-day weather forecast in hourly and daily resolution for given location. Available worldwide.`,
|
||||||
|
inputSchema: openmeteo.GetV1ForecastParamsSchema
|
||||||
|
})
|
||||||
|
async getForecast(
|
||||||
|
params: openmeteo.GetV1ForecastParams
|
||||||
|
): Promise<openmeteo.GetV1ForecastResponse> {
|
||||||
|
const extractLocation = async (): Promise<openmeteo.Location> => {
|
||||||
|
if ('name' in params.location) {
|
||||||
|
const response = await this._geocode(params.location)
|
||||||
|
return pick(response, 'latitude', 'longitude')
|
||||||
|
}
|
||||||
|
|
||||||
|
return params.location
|
||||||
|
}
|
||||||
|
|
||||||
|
const { start, end } = validateAndSetDates(params.startDate, params.endDate)
|
||||||
|
|
||||||
|
return this.ky
|
||||||
|
.get('forecast', {
|
||||||
|
searchParams: sanitizeSearchParams({
|
||||||
|
...(await extractLocation()),
|
||||||
|
temperature_unit: params.temperatureUnit,
|
||||||
|
start_date: toDateString(start),
|
||||||
|
end_date: toDateString(end),
|
||||||
|
current: [
|
||||||
|
'temperature_2m',
|
||||||
|
'rain',
|
||||||
|
'relative_humidity_2m',
|
||||||
|
'wind_speed_10m'
|
||||||
|
],
|
||||||
|
daily: ['temperature_2m_max', 'temperature_2m_min', 'rain_sum'],
|
||||||
|
hourly: ['temperature_2m', 'relative_humidity_2m', 'rain'],
|
||||||
|
timezone: 'UTC'
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.json<openmeteo.GetV1ForecastResponse>()
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async _geocode(
|
||||||
|
location: openmeteo.LocationSearch
|
||||||
|
): Promise<openmeteo.Location> {
|
||||||
|
const { results } = await this.ky
|
||||||
|
.get('search', {
|
||||||
|
searchParams: sanitizeSearchParams({
|
||||||
|
name: location.name,
|
||||||
|
language: location.language,
|
||||||
|
country: location.country,
|
||||||
|
format: 'json',
|
||||||
|
count: 1
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.json<any>()
|
||||||
|
|
||||||
|
if (!results?.length) {
|
||||||
|
throw new Error(`No results found for location "${location.name}"`)
|
||||||
|
}
|
||||||
|
|
||||||
|
return results[0]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function toDateString(date: Date): string {
|
||||||
|
return date.toISOString().split('T')[0]!
|
||||||
|
}
|
||||||
|
|
||||||
|
function validateAndSetDates(
|
||||||
|
startDateStr: string,
|
||||||
|
endDateStr?: string
|
||||||
|
): { start: Date; end: Date } {
|
||||||
|
const now = new Date()
|
||||||
|
const start = startDateStr
|
||||||
|
? new Date(startDateStr)
|
||||||
|
: new Date(Date.UTC(now.getFullYear(), now.getMonth(), now.getDate()))
|
||||||
|
|
||||||
|
if (endDateStr) {
|
||||||
|
const end = new Date(endDateStr)
|
||||||
|
if (end < start) {
|
||||||
|
throw new Error(
|
||||||
|
`The 'end_date' (${endDateStr}) has to occur on or after the 'start_date' (${startDateStr}).`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return { start, end }
|
||||||
|
} else {
|
||||||
|
// If endDate is undefined, set it to the start date
|
||||||
|
return { start, end: new Date(start) }
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,201 @@
|
||||||
|
import { z } from 'zod'
|
||||||
|
|
||||||
|
export namespace openmeteo {
|
||||||
|
export const apiBaseUrl = 'https://api.open-meteo.com/v1'
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
// Component schemas
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/** For each selected weather variable, data will be returned as a floating point array. Additionally a `time` array will be returned with ISO8601 timestamps. */
|
||||||
|
export const HourlyResponseSchema = z
|
||||||
|
.object({
|
||||||
|
time: z.array(z.string()),
|
||||||
|
temperature_2m: z.array(z.number()).optional(),
|
||||||
|
relative_humidity_2m: z.array(z.number()).optional(),
|
||||||
|
dew_point_2m: z.array(z.number()).optional(),
|
||||||
|
apparent_temperature: z.array(z.number()).optional(),
|
||||||
|
pressure_msl: z.array(z.number()).optional(),
|
||||||
|
cloud_cover: z.array(z.number()).optional(),
|
||||||
|
cloud_cover_low: z.array(z.number()).optional(),
|
||||||
|
cloud_cover_mid: z.array(z.number()).optional(),
|
||||||
|
cloud_cover_high: z.array(z.number()).optional(),
|
||||||
|
wind_speed_10m: z.array(z.number()).optional(),
|
||||||
|
wind_speed_80m: z.array(z.number()).optional(),
|
||||||
|
wind_speed_120m: z.array(z.number()).optional(),
|
||||||
|
wind_speed_180m: z.array(z.number()).optional(),
|
||||||
|
wind_direction_10m: z.array(z.number()).optional(),
|
||||||
|
wind_direction_80m: z.array(z.number()).optional(),
|
||||||
|
wind_direction_120m: z.array(z.number()).optional(),
|
||||||
|
wind_direction_180m: z.array(z.number()).optional(),
|
||||||
|
wind_gusts_10m: z.array(z.number()).optional(),
|
||||||
|
shortwave_radiation: z.array(z.number()).optional(),
|
||||||
|
direct_radiation: z.array(z.number()).optional(),
|
||||||
|
direct_normal_irradiance: z.array(z.number()).optional(),
|
||||||
|
diffuse_radiation: z.array(z.number()).optional(),
|
||||||
|
vapour_pressure_deficit: z.array(z.number()).optional(),
|
||||||
|
evapotranspiration: z.array(z.number()).optional(),
|
||||||
|
precipitation: z.array(z.number()).optional(),
|
||||||
|
weather_code: z.array(z.number()).optional(),
|
||||||
|
snow_height: z.array(z.number()).optional(),
|
||||||
|
freezing_level_height: z.array(z.number()).optional(),
|
||||||
|
soil_temperature_0cm: z.array(z.number()).optional(),
|
||||||
|
soil_temperature_6cm: z.array(z.number()).optional(),
|
||||||
|
soil_temperature_18cm: z.array(z.number()).optional(),
|
||||||
|
soil_temperature_54cm: z.array(z.number()).optional(),
|
||||||
|
soil_moisture_0_1cm: z.array(z.number()).optional(),
|
||||||
|
soil_moisture_1_3cm: z.array(z.number()).optional(),
|
||||||
|
soil_moisture_3_9cm: z.array(z.number()).optional(),
|
||||||
|
soil_moisture_9_27cm: z.array(z.number()).optional(),
|
||||||
|
soil_moisture_27_81cm: z.array(z.number()).optional()
|
||||||
|
})
|
||||||
|
.describe(
|
||||||
|
'For each selected weather variable, data will be returned as a floating point array. Additionally a `time` array will be returned with ISO8601 timestamps.'
|
||||||
|
)
|
||||||
|
export type HourlyResponse = z.infer<typeof HourlyResponseSchema>
|
||||||
|
|
||||||
|
/** For each selected daily weather variable, data will be returned as a floating point array. Additionally a `time` array will be returned with ISO8601 timestamps. */
|
||||||
|
export const DailyResponseSchema = z
|
||||||
|
.object({
|
||||||
|
time: z.array(z.string()),
|
||||||
|
temperature_2m_max: z.array(z.number()).optional(),
|
||||||
|
temperature_2m_min: z.array(z.number()).optional(),
|
||||||
|
apparent_temperature_max: z.array(z.number()).optional(),
|
||||||
|
apparent_temperature_min: z.array(z.number()).optional(),
|
||||||
|
precipitation_sum: z.array(z.number()).optional(),
|
||||||
|
precipitation_hours: z.array(z.number()).optional(),
|
||||||
|
weather_code: z.array(z.number()).optional(),
|
||||||
|
sunrise: z.array(z.number()).optional(),
|
||||||
|
sunset: z.array(z.number()).optional(),
|
||||||
|
wind_speed_10m_max: z.array(z.number()).optional(),
|
||||||
|
wind_gusts_10m_max: z.array(z.number()).optional(),
|
||||||
|
wind_direction_10m_dominant: z.array(z.number()).optional(),
|
||||||
|
shortwave_radiation_sum: z.array(z.number()).optional(),
|
||||||
|
uv_index_max: z.array(z.number()).optional(),
|
||||||
|
uv_index_clear_sky_max: z.array(z.number()).optional(),
|
||||||
|
et0_fao_evapotranspiration: z.array(z.number()).optional()
|
||||||
|
})
|
||||||
|
.describe(
|
||||||
|
'For each selected daily weather variable, data will be returned as a floating point array. Additionally a `time` array will be returned with ISO8601 timestamps.'
|
||||||
|
)
|
||||||
|
export type DailyResponse = z.infer<typeof DailyResponseSchema>
|
||||||
|
|
||||||
|
/** Current weather conditions with the attributes: time, temperature, wind_speed, wind_direction and weather_code */
|
||||||
|
export const CurrentWeatherSchema = z
|
||||||
|
.object({
|
||||||
|
time: z.string(),
|
||||||
|
temperature: z.number(),
|
||||||
|
wind_speed: z.number(),
|
||||||
|
wind_direction: z.number(),
|
||||||
|
weather_code: z.number().int()
|
||||||
|
})
|
||||||
|
.describe(
|
||||||
|
'Current weather conditions with the attributes: time, temperature, wind_speed, wind_direction and weather_code'
|
||||||
|
)
|
||||||
|
export type CurrentWeather = z.infer<typeof CurrentWeatherSchema>
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
// Operation schemas
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
export const GetV1ForecastParamsSchema = z.object({
|
||||||
|
location: z.union([
|
||||||
|
z
|
||||||
|
.object({
|
||||||
|
name: z.string().min(1),
|
||||||
|
country: z.string().optional(),
|
||||||
|
language: z.string().default('English')
|
||||||
|
})
|
||||||
|
.strip(),
|
||||||
|
z
|
||||||
|
.object({
|
||||||
|
latitude: z.coerce.number(),
|
||||||
|
longitude: z.coerce.number()
|
||||||
|
})
|
||||||
|
.strip()
|
||||||
|
]),
|
||||||
|
startDate: z
|
||||||
|
.string()
|
||||||
|
.date()
|
||||||
|
.describe(
|
||||||
|
'Start date for the weather forecast in the format YYYY-MM-DD (UTC)'
|
||||||
|
),
|
||||||
|
endDate: z
|
||||||
|
.string()
|
||||||
|
.date()
|
||||||
|
.describe(
|
||||||
|
'End date for the weather forecast in the format YYYY-MM-DD (UTC)'
|
||||||
|
)
|
||||||
|
.optional(),
|
||||||
|
temperatureUnit: z.enum(['celsius', 'fahrenheit']).default('fahrenheit')
|
||||||
|
})
|
||||||
|
export type GetV1ForecastParams = z.infer<typeof GetV1ForecastParamsSchema>
|
||||||
|
|
||||||
|
export const GetV1ForecastResponseSchema = z.object({
|
||||||
|
/** WGS84 of the center of the weather grid-cell which was used to generate this forecast. This coordinate might be up to 5 km away. */
|
||||||
|
latitude: z
|
||||||
|
.number()
|
||||||
|
.describe(
|
||||||
|
'WGS84 of the center of the weather grid-cell which was used to generate this forecast. This coordinate might be up to 5 km away.'
|
||||||
|
)
|
||||||
|
.optional(),
|
||||||
|
/** WGS84 of the center of the weather grid-cell which was used to generate this forecast. This coordinate might be up to 5 km away. */
|
||||||
|
longitude: z
|
||||||
|
.number()
|
||||||
|
.describe(
|
||||||
|
'WGS84 of the center of the weather grid-cell which was used to generate this forecast. This coordinate might be up to 5 km away.'
|
||||||
|
)
|
||||||
|
.optional(),
|
||||||
|
/** The elevation in meters of the selected weather grid-cell. In mountain terrain it might differ from the location you would expect. */
|
||||||
|
elevation: z
|
||||||
|
.number()
|
||||||
|
.describe(
|
||||||
|
'The elevation in meters of the selected weather grid-cell. In mountain terrain it might differ from the location you would expect.'
|
||||||
|
)
|
||||||
|
.optional(),
|
||||||
|
/** Generation time of the weather forecast in milli seconds. This is mainly used for performance monitoring and improvements. */
|
||||||
|
generationtime_ms: z
|
||||||
|
.number()
|
||||||
|
.describe(
|
||||||
|
'Generation time of the weather forecast in milli seconds. This is mainly used for performance monitoring and improvements.'
|
||||||
|
)
|
||||||
|
.optional(),
|
||||||
|
/** Applied timezone offset from the &timezone= parameter. */
|
||||||
|
utc_offset_seconds: z
|
||||||
|
.number()
|
||||||
|
.int()
|
||||||
|
.describe('Applied timezone offset from the &timezone= parameter.')
|
||||||
|
.optional(),
|
||||||
|
hourly: HourlyResponseSchema.optional(),
|
||||||
|
/** For each selected weather variable, the unit will be listed here. */
|
||||||
|
hourly_units: z
|
||||||
|
.record(z.string())
|
||||||
|
.describe(
|
||||||
|
'For each selected weather variable, the unit will be listed here.'
|
||||||
|
)
|
||||||
|
.optional(),
|
||||||
|
daily: DailyResponseSchema.optional(),
|
||||||
|
/** For each selected daily weather variable, the unit will be listed here. */
|
||||||
|
daily_units: z
|
||||||
|
.record(z.string())
|
||||||
|
.describe(
|
||||||
|
'For each selected daily weather variable, the unit will be listed here.'
|
||||||
|
)
|
||||||
|
.optional(),
|
||||||
|
current_weather: CurrentWeatherSchema.optional()
|
||||||
|
})
|
||||||
|
export type GetV1ForecastResponse = z.infer<
|
||||||
|
typeof GetV1ForecastResponseSchema
|
||||||
|
>
|
||||||
|
|
||||||
|
export interface Location {
|
||||||
|
latitude: number
|
||||||
|
longitude: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface LocationSearch {
|
||||||
|
name: string
|
||||||
|
country?: string
|
||||||
|
language?: string
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,5 @@
|
||||||
|
{
|
||||||
|
"extends": "@agentic/tsconfig/base.json",
|
||||||
|
"include": ["src"],
|
||||||
|
"exclude": ["node_modules", "dist"]
|
||||||
|
}
|
Ładowanie…
Reference in New Issue