diff --git a/legacy/src/tools/index.ts b/legacy/src/tools/index.ts index d349762f..f77d1edc 100644 --- a/legacy/src/tools/index.ts +++ b/legacy/src/tools/index.ts @@ -1,3 +1,4 @@ export * from './calculator' export * from './metaphor' export * from './novu' +export * from './weather' diff --git a/legacy/src/tools/weather.ts b/legacy/src/tools/weather.ts new file mode 100644 index 00000000..f84c45f9 --- /dev/null +++ b/legacy/src/tools/weather.ts @@ -0,0 +1,103 @@ +import { z } from 'zod' + +import * as types from '@/types' +import { WeatherClient } from '@/services/weather' +import { BaseTask } from '@/task' + +export const WeatherInputSchema = z.object({ + query: z + .string() + .describe( + 'Location to get the weather for. Can be a city name like "Paris", a zipcode like "53121", an international postal code like "SW1", or a latitude and longitude like "48.8567,2.3508"' + ) +}) +export type WeatherInput = z.infer + +const LocationSchema = z.object({ + name: z.string(), + region: z.string(), + country: z.string(), + lat: z.number(), + lon: z.number(), + tz_id: z.string(), + localtime_epoch: z.number(), + localtime: z.string() +}) + +const ConditionSchema = z.object({ + text: z.string(), + icon: z.string(), + code: z.number() +}) + +const CurrentSchema = z.object({ + last_updated_epoch: z.number(), + last_updated: z.string(), + temp_c: z.number(), + temp_f: z.number(), + is_day: z.number(), + condition: ConditionSchema, + wind_mph: z.number(), + wind_kph: z.number(), + wind_degree: z.number(), + wind_dir: z.string(), + pressure_mb: z.number(), + pressure_in: z.number(), + precip_mm: z.number(), + precip_in: z.number(), + humidity: z.number(), + cloud: z.number(), + feelslike_c: z.number(), + feelslike_f: z.number(), + vis_km: z.number(), + vis_miles: z.number(), + uv: z.number(), + gust_mph: z.number(), + gust_kph: z.number() +}) + +export const WeatherOutputSchema = z.object({ + location: LocationSchema, + current: CurrentSchema +}) +export type WeatherOutput = z.infer + +export class WeatherTool extends BaseTask { + client: WeatherClient + + constructor( + opts: { + weather: WeatherClient + } & types.BaseTaskOptions + ) { + super(opts) + + this.client = opts.weather + } + + public override get inputSchema() { + return WeatherInputSchema + } + + public override get outputSchema() { + return WeatherOutputSchema + } + + public override get nameForModel(): string { + return 'getCurrentWeather' + } + + public override get nameForHuman(): string { + return 'Weather' + } + + public override get descForModel(): string { + return 'Useful for getting the current weather at a location' + } + + protected override async _call( + ctx: types.TaskCallContext + ): Promise { + return this.client.getCurrentWeather(ctx.input!.query) + } +} diff --git a/legacy/test/services/weather.test.ts b/legacy/test/services/weather.test.ts index b80cb4d9..b192facd 100644 --- a/legacy/test/services/weather.test.ts +++ b/legacy/test/services/weather.test.ts @@ -1,8 +1,9 @@ import test from 'ava' -import { WeatherClient } from '@/services' +import { WeatherClient } from '@/services/weather' +import { WeatherTool } from '@/tools/weather' -import { ky } from '../_utils' +import { createTestAgenticRuntime, ky } from '../_utils' test('WeatherClient.getCurrentWeather', async (t) => { if (!process.env.WEATHER_API_KEY) { @@ -32,3 +33,22 @@ test('WeatherClient.ipInfo', async (t) => { t.truthy(res.lat) t.truthy(res.lon) }) + +test('WeatherTool.call', async (t) => { + if (!process.env.WEATHER_API_KEY) { + return t.pass() + } + + t.timeout(2 * 60 * 1000) + const agentic = createTestAgenticRuntime() + const weatherClient = new WeatherClient({ ky }) + const tool = new WeatherTool({ agentic, weather: weatherClient }) + + const result = await tool.call({ query: '11201' }) + // console.log(result) + t.truthy(result.current) + t.truthy(result.location) + t.is(result.location.name, 'Brooklyn') + t.is(result.location.region, 'New York') + t.is(result.location.country, 'USA') +})