@voicethere/agent 0.5.3 → 0.5.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/templates/game-sync/agent.js +11313 -143
- package/dist/templates/registry.d.ts.map +1 -1
- package/dist/templates/registry.js +21 -1
- package/dist/templates/registry.js.map +1 -1
- package/dist/templates/voice-showcase/agent.js +1242 -0
- package/package.json +11 -11
- package/templates/README.md +19 -11
- package/templates/game-sync-protocol.ts +52 -0
- package/templates/game-sync-redis.ts +121 -0
- package/templates/game-sync-sim.ts +122 -0
- package/templates/game-sync-world-layout.ts +167 -0
- package/templates/game-sync.ts +431 -303
- package/templates/voice-showcase/agent.ts +119 -0
- package/templates/voice-showcase/conversation.ts +520 -0
- package/templates/voice-showcase/fun-facts.ts +24 -0
- package/templates/voice-showcase/recipes.ts +57 -0
- package/templates/voice-showcase/weather.ts +177 -0
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
/** Open-Meteo geocoding + current weather (no API key). */
|
|
2
|
+
|
|
3
|
+
export const GEOCODE_URL = "https://geocoding-api.open-meteo.com/v1/search";
|
|
4
|
+
export const FORECAST_URL = "https://api.open-meteo.com/v1/forecast";
|
|
5
|
+
export const FETCH_TIMEOUT_MS = 8000;
|
|
6
|
+
|
|
7
|
+
export interface GeocodeResult {
|
|
8
|
+
name: string;
|
|
9
|
+
country: string;
|
|
10
|
+
latitude: number;
|
|
11
|
+
longitude: number;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface WeatherResult {
|
|
15
|
+
place: string;
|
|
16
|
+
temperatureC: number;
|
|
17
|
+
condition: string;
|
|
18
|
+
windKmh: number;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export type FetchFn = typeof fetch;
|
|
22
|
+
|
|
23
|
+
/** Map WMO weather_code to a short English phrase. */
|
|
24
|
+
export function wmoCodeToPhrase(code: number): string {
|
|
25
|
+
if (code === 0) return "clear sky";
|
|
26
|
+
if (code <= 3) return "partly cloudy";
|
|
27
|
+
if (code <= 48) return "foggy";
|
|
28
|
+
if (code <= 57) return "drizzle";
|
|
29
|
+
if (code <= 67) return "rain";
|
|
30
|
+
if (code <= 77) return "snow";
|
|
31
|
+
if (code <= 82) return "rain showers";
|
|
32
|
+
if (code <= 86) return "snow showers";
|
|
33
|
+
if (code <= 99) return "thunderstorm";
|
|
34
|
+
return "variable conditions";
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function formatWeatherSpeech(result: WeatherResult): string {
|
|
38
|
+
const temp = Math.round(result.temperatureC);
|
|
39
|
+
const wind = Math.round(result.windKmh);
|
|
40
|
+
return `In ${result.place}, it is ${temp} degrees Celsius with ${result.condition} and winds around ${wind} kilometers per hour.`;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** Parse city/zip and country from a single utterance when possible. */
|
|
44
|
+
export function parseLocationUtterance(
|
|
45
|
+
utterance: string,
|
|
46
|
+
): { city: string; country?: string } | null {
|
|
47
|
+
const text = utterance.trim();
|
|
48
|
+
if (!text) return null;
|
|
49
|
+
|
|
50
|
+
const inMatch = text.match(
|
|
51
|
+
/^(?:in\s+)?(.+?)\s+in\s+([a-zA-Z][\w\s.-]{1,40})$/i,
|
|
52
|
+
);
|
|
53
|
+
if (inMatch) {
|
|
54
|
+
return { city: inMatch[1]!.trim(), country: inMatch[2]!.trim() };
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const commaMatch = text.match(/^(.+?),\s*([a-zA-Z][\w\s.-]{1,40})$/);
|
|
58
|
+
if (commaMatch) {
|
|
59
|
+
return { city: commaMatch[1]!.trim(), country: commaMatch[2]!.trim() };
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const countryMatch = text.match(
|
|
63
|
+
/^(.+?)\s+(?:country\s+)?([a-zA-Z][\w\s.-]{2,40})$/i,
|
|
64
|
+
);
|
|
65
|
+
if (countryMatch && countryMatch[2]!.split(/\s+/).length <= 3) {
|
|
66
|
+
const city = countryMatch[1]!.trim();
|
|
67
|
+
const country = countryMatch[2]!.trim();
|
|
68
|
+
if (city.length >= 2 && country.length >= 2) {
|
|
69
|
+
return { city, country };
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (/^\d{5}(-\d{4})?$/.test(text)) {
|
|
74
|
+
return { city: text };
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (text.length >= 2 && text.length <= 60) {
|
|
78
|
+
return { city: text };
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export async function geocodeLocation(
|
|
85
|
+
city: string,
|
|
86
|
+
country: string | undefined,
|
|
87
|
+
fetchFn: FetchFn = fetch,
|
|
88
|
+
): Promise<GeocodeResult | null> {
|
|
89
|
+
const query = country ? `${city}, ${country}` : city;
|
|
90
|
+
const url = new URL(GEOCODE_URL);
|
|
91
|
+
url.searchParams.set("name", query);
|
|
92
|
+
url.searchParams.set("count", "1");
|
|
93
|
+
url.searchParams.set("language", "en");
|
|
94
|
+
url.searchParams.set("format", "json");
|
|
95
|
+
|
|
96
|
+
const response = await fetchFn(url.toString(), {
|
|
97
|
+
signal: AbortSignal.timeout(FETCH_TIMEOUT_MS),
|
|
98
|
+
});
|
|
99
|
+
if (!response.ok) return null;
|
|
100
|
+
|
|
101
|
+
const data = (await response.json()) as {
|
|
102
|
+
results?: Array<{
|
|
103
|
+
name?: string;
|
|
104
|
+
country?: string;
|
|
105
|
+
latitude?: number;
|
|
106
|
+
longitude?: number;
|
|
107
|
+
}>;
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
const hit = data.results?.[0];
|
|
111
|
+
if (
|
|
112
|
+
!hit ||
|
|
113
|
+
typeof hit.latitude !== "number" ||
|
|
114
|
+
typeof hit.longitude !== "number"
|
|
115
|
+
) {
|
|
116
|
+
return null;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return {
|
|
120
|
+
name: hit.name ?? city,
|
|
121
|
+
country: hit.country ?? country ?? "",
|
|
122
|
+
latitude: hit.latitude,
|
|
123
|
+
longitude: hit.longitude,
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export async function fetchCurrentWeather(
|
|
128
|
+
geo: GeocodeResult,
|
|
129
|
+
fetchFn: FetchFn = fetch,
|
|
130
|
+
): Promise<WeatherResult | null> {
|
|
131
|
+
const url = new URL(FORECAST_URL);
|
|
132
|
+
url.searchParams.set("latitude", String(geo.latitude));
|
|
133
|
+
url.searchParams.set("longitude", String(geo.longitude));
|
|
134
|
+
url.searchParams.set("current", "temperature_2m,weather_code,wind_speed_10m");
|
|
135
|
+
url.searchParams.set("wind_speed_unit", "kmh");
|
|
136
|
+
|
|
137
|
+
const response = await fetchFn(url.toString(), {
|
|
138
|
+
signal: AbortSignal.timeout(FETCH_TIMEOUT_MS),
|
|
139
|
+
});
|
|
140
|
+
if (!response.ok) return null;
|
|
141
|
+
|
|
142
|
+
const data = (await response.json()) as {
|
|
143
|
+
current?: {
|
|
144
|
+
temperature_2m?: number;
|
|
145
|
+
weather_code?: number;
|
|
146
|
+
wind_speed_10m?: number;
|
|
147
|
+
};
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
const current = data.current;
|
|
151
|
+
if (
|
|
152
|
+
!current ||
|
|
153
|
+
typeof current.temperature_2m !== "number" ||
|
|
154
|
+
typeof current.weather_code !== "number"
|
|
155
|
+
) {
|
|
156
|
+
return null;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const place = geo.country ? `${geo.name}, ${geo.country}` : geo.name;
|
|
160
|
+
|
|
161
|
+
return {
|
|
162
|
+
place,
|
|
163
|
+
temperatureC: current.temperature_2m,
|
|
164
|
+
condition: wmoCodeToPhrase(current.weather_code),
|
|
165
|
+
windKmh: current.wind_speed_10m ?? 0,
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
export async function lookupWeather(
|
|
170
|
+
city: string,
|
|
171
|
+
country: string | undefined,
|
|
172
|
+
fetchFn: FetchFn = fetch,
|
|
173
|
+
): Promise<WeatherResult | null> {
|
|
174
|
+
const geo = await geocodeLocation(city, country, fetchFn);
|
|
175
|
+
if (!geo) return null;
|
|
176
|
+
return fetchCurrentWeather(geo, fetchFn);
|
|
177
|
+
}
|