@jambonz/tools 0.1.2

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/index.js ADDED
@@ -0,0 +1,450 @@
1
+ // src/register.ts
2
+ function registerTools(session, hookPath, tools, options) {
3
+ const toolMap = new Map(tools.map((t) => [t.schema.name, t]));
4
+ const log = options?.logger;
5
+ session.on(hookPath, async (evt) => {
6
+ const { tool_call_id, name, arguments: args } = evt;
7
+ const tool = toolMap.get(name);
8
+ if (!tool) {
9
+ log?.error({ name, args }, `unknown tool: ${name}`);
10
+ if (options?.onUnknownTool) {
11
+ options.onUnknownTool(session, evt);
12
+ } else {
13
+ session.sendToolOutput(tool_call_id, `Unknown tool: ${name}`);
14
+ }
15
+ return;
16
+ }
17
+ log?.info({ name, args }, "tool call");
18
+ try {
19
+ const result = await tool.execute(args);
20
+ session.sendToolOutput(tool_call_id, result);
21
+ } catch (err) {
22
+ log?.error({ err, name, args }, `tool execution failed: ${name}`);
23
+ session.sendToolOutput(tool_call_id, `Error executing ${name}: ${err}`);
24
+ }
25
+ });
26
+ }
27
+
28
+ // src/tools/tavily-search.ts
29
+ var schema = {
30
+ name: "web_search",
31
+ description: "Search the web for current information on a given topic or question.",
32
+ parameters: {
33
+ type: "object",
34
+ properties: {
35
+ query: {
36
+ type: "string",
37
+ description: "The search query"
38
+ }
39
+ },
40
+ required: ["query"]
41
+ }
42
+ };
43
+ function createTavilySearch(options) {
44
+ const {
45
+ apiKey,
46
+ maxResults = 3,
47
+ searchDepth = "basic",
48
+ topic = "general",
49
+ includeDomains,
50
+ excludeDomains
51
+ } = options;
52
+ return {
53
+ schema,
54
+ async execute(args) {
55
+ const res = await fetch("https://api.tavily.com/search", {
56
+ method: "POST",
57
+ headers: { "Content-Type": "application/json" },
58
+ body: JSON.stringify({
59
+ api_key: apiKey,
60
+ query: args.query,
61
+ max_results: maxResults,
62
+ search_depth: searchDepth,
63
+ topic,
64
+ ...includeDomains && { include_domains: includeDomains },
65
+ ...excludeDomains && { exclude_domains: excludeDomains }
66
+ })
67
+ });
68
+ if (!res.ok) {
69
+ throw new Error(`Tavily API error: ${res.status} ${res.statusText}`);
70
+ }
71
+ const data = await res.json();
72
+ const results = (data.results || []).map((r) => `${r.title}: ${r.content}`).join("\n");
73
+ return results || "No results found.";
74
+ }
75
+ };
76
+ }
77
+
78
+ // src/tools/weather.ts
79
+ var schema2 = {
80
+ name: "get_weather",
81
+ description: "Get the current temperature, wind speed, and conditions for a given location.",
82
+ parameters: {
83
+ type: "object",
84
+ properties: {
85
+ location: {
86
+ type: "string",
87
+ description: 'City name or location, e.g. "San Francisco" or "Paris, France"'
88
+ }
89
+ },
90
+ required: ["location"]
91
+ }
92
+ };
93
+ function createWeather(options) {
94
+ const scale = options?.scale ?? "celsius";
95
+ return {
96
+ schema: schema2,
97
+ async execute(args) {
98
+ const location = args.location;
99
+ const geoRes = await fetch(
100
+ `https://geocoding-api.open-meteo.com/v1/search?name=${encodeURIComponent(location)}&count=1&language=en&format=json`
101
+ );
102
+ if (!geoRes.ok) {
103
+ throw new Error(`Geocoding API error: ${geoRes.status}`);
104
+ }
105
+ const geoData = await geoRes.json();
106
+ if (!geoData.results?.length) {
107
+ return `Sorry, I could not find weather data for "${location}".`;
108
+ }
109
+ const { latitude, longitude, name, country } = geoData.results[0];
110
+ const wxRes = await fetch(
111
+ `https://api.open-meteo.com/v1/forecast?latitude=${latitude}&longitude=${longitude}&current=temperature_2m,relative_humidity_2m,apparent_temperature,weather_code,wind_speed_10m&temperature_unit=${scale}`
112
+ );
113
+ if (!wxRes.ok) {
114
+ throw new Error(`Weather API error: ${wxRes.status}`);
115
+ }
116
+ const wx = await wxRes.json();
117
+ const unit = scale === "fahrenheit" ? "\xB0F" : "\xB0C";
118
+ const { temperature_2m, apparent_temperature, relative_humidity_2m, weather_code, wind_speed_10m } = wx.current;
119
+ const condition = weatherCodeToText(weather_code);
120
+ return [
121
+ `Current weather in ${name}, ${country}:`,
122
+ `${condition}, ${temperature_2m}${unit} (feels like ${apparent_temperature}${unit}).`,
123
+ `Wind: ${wind_speed_10m} km/h. Humidity: ${relative_humidity_2m}%.`
124
+ ].join(" ");
125
+ }
126
+ };
127
+ }
128
+ function weatherCodeToText(code) {
129
+ const descriptions = {
130
+ 0: "Clear sky",
131
+ 1: "Mainly clear",
132
+ 2: "Partly cloudy",
133
+ 3: "Overcast",
134
+ 45: "Foggy",
135
+ 48: "Depositing rime fog",
136
+ 51: "Light drizzle",
137
+ 53: "Moderate drizzle",
138
+ 55: "Dense drizzle",
139
+ 61: "Slight rain",
140
+ 63: "Moderate rain",
141
+ 65: "Heavy rain",
142
+ 71: "Slight snow",
143
+ 73: "Moderate snow",
144
+ 75: "Heavy snow",
145
+ 77: "Snow grains",
146
+ 80: "Slight rain showers",
147
+ 81: "Moderate rain showers",
148
+ 82: "Violent rain showers",
149
+ 85: "Slight snow showers",
150
+ 86: "Heavy snow showers",
151
+ 95: "Thunderstorm",
152
+ 96: "Thunderstorm with slight hail",
153
+ 99: "Thunderstorm with heavy hail"
154
+ };
155
+ return descriptions[code] ?? "Unknown conditions";
156
+ }
157
+
158
+ // src/tools/wikipedia.ts
159
+ var schema3 = {
160
+ name: "wikipedia",
161
+ description: "Look up a topic on Wikipedia to get a factual summary. Useful for answering questions about people, places, history, science, and general knowledge.",
162
+ parameters: {
163
+ type: "object",
164
+ properties: {
165
+ query: {
166
+ type: "string",
167
+ description: "The topic to look up on Wikipedia"
168
+ }
169
+ },
170
+ required: ["query"]
171
+ }
172
+ };
173
+ function createWikipedia(options) {
174
+ const maxSentences = options?.maxSentences ?? 5;
175
+ const lang = options?.language ?? "en";
176
+ return {
177
+ schema: schema3,
178
+ async execute(args) {
179
+ const query = args.query;
180
+ const searchRes = await fetch(
181
+ `https://${lang}.wikipedia.org/w/api.php?action=query&list=search&srsearch=${encodeURIComponent(query)}&srlimit=1&format=json&origin=*`
182
+ );
183
+ if (!searchRes.ok) {
184
+ throw new Error(`Wikipedia search error: ${searchRes.status}`);
185
+ }
186
+ const searchData = await searchRes.json();
187
+ if (!searchData.query.search.length) {
188
+ return `No Wikipedia article found for "${query}".`;
189
+ }
190
+ const { title } = searchData.query.search[0];
191
+ const summaryRes = await fetch(
192
+ `https://${lang}.wikipedia.org/api/rest_v1/page/summary/${encodeURIComponent(title)}`
193
+ );
194
+ if (!summaryRes.ok) {
195
+ throw new Error(`Wikipedia summary error: ${summaryRes.status}`);
196
+ }
197
+ const summary = await summaryRes.json();
198
+ const sentences = summary.extract.split(/(?<=\.)\s+/);
199
+ const trimmed = sentences.slice(0, maxSentences).join(" ");
200
+ return `${summary.title}: ${trimmed}`;
201
+ }
202
+ };
203
+ }
204
+
205
+ // src/tools/calculator.ts
206
+ var schema4 = {
207
+ name: "calculator",
208
+ description: "Evaluate a mathematical expression and return the numeric result. Supports arithmetic (+, -, *, /, ^, %), parentheses, and functions (sqrt, abs, round, ceil, floor, sin, cos, tan, log, log10, exp). Also supports constants pi and e.",
209
+ parameters: {
210
+ type: "object",
211
+ properties: {
212
+ expression: {
213
+ type: "string",
214
+ description: 'The math expression to evaluate, e.g. "15% of 87.50" should be sent as "87.50 * 0.15"'
215
+ }
216
+ },
217
+ required: ["expression"]
218
+ }
219
+ };
220
+ function createCalculator() {
221
+ return {
222
+ schema: schema4,
223
+ async execute(args) {
224
+ const expression = args.expression.trim();
225
+ try {
226
+ const result = evaluate(expression);
227
+ const formatted = Number.isInteger(result) ? result.toString() : parseFloat(result.toFixed(10)).toString();
228
+ return `${expression} = ${formatted}`;
229
+ } catch (err) {
230
+ return `Could not evaluate "${expression}": ${err.message}`;
231
+ }
232
+ }
233
+ };
234
+ }
235
+ var FUNCTIONS = {
236
+ sqrt: Math.sqrt,
237
+ abs: Math.abs,
238
+ round: Math.round,
239
+ ceil: Math.ceil,
240
+ floor: Math.floor,
241
+ sin: Math.sin,
242
+ cos: Math.cos,
243
+ tan: Math.tan,
244
+ log: Math.log,
245
+ log10: Math.log10,
246
+ exp: Math.exp
247
+ };
248
+ var CONSTANTS = {
249
+ pi: Math.PI,
250
+ e: Math.E
251
+ };
252
+ function evaluate(expr) {
253
+ const parser = { expr, pos: 0 };
254
+ const result = parseExpression(parser);
255
+ skipWhitespace(parser);
256
+ if (parser.pos < parser.expr.length) {
257
+ throw new Error(`Unexpected character: '${parser.expr[parser.pos]}'`);
258
+ }
259
+ return result;
260
+ }
261
+ function skipWhitespace(p) {
262
+ while (p.pos < p.expr.length && p.expr[p.pos] === " ") p.pos++;
263
+ }
264
+ function parseExpression(p) {
265
+ let left = parseTerm(p);
266
+ skipWhitespace(p);
267
+ while (p.pos < p.expr.length && (p.expr[p.pos] === "+" || p.expr[p.pos] === "-")) {
268
+ const op = p.expr[p.pos++];
269
+ const right = parseTerm(p);
270
+ left = op === "+" ? left + right : left - right;
271
+ skipWhitespace(p);
272
+ }
273
+ return left;
274
+ }
275
+ function parseTerm(p) {
276
+ let left = parsePower(p);
277
+ skipWhitespace(p);
278
+ while (p.pos < p.expr.length && (p.expr[p.pos] === "*" || p.expr[p.pos] === "/" || p.expr[p.pos] === "%")) {
279
+ const op = p.expr[p.pos++];
280
+ const right = parsePower(p);
281
+ if (op === "*") left *= right;
282
+ else if (op === "/") left /= right;
283
+ else left %= right;
284
+ skipWhitespace(p);
285
+ }
286
+ return left;
287
+ }
288
+ function parsePower(p) {
289
+ const base = parseUnary(p);
290
+ skipWhitespace(p);
291
+ if (p.pos < p.expr.length && (p.expr[p.pos] === "^" || p.expr[p.pos] === "*" && p.expr[p.pos + 1] === "*")) {
292
+ if (p.expr[p.pos] === "*") p.pos += 2;
293
+ else p.pos++;
294
+ const exp = parsePower(p);
295
+ return Math.pow(base, exp);
296
+ }
297
+ return base;
298
+ }
299
+ function parseUnary(p) {
300
+ skipWhitespace(p);
301
+ if (p.pos < p.expr.length && p.expr[p.pos] === "-") {
302
+ p.pos++;
303
+ return -parseUnary(p);
304
+ }
305
+ if (p.pos < p.expr.length && p.expr[p.pos] === "+") {
306
+ p.pos++;
307
+ return parseUnary(p);
308
+ }
309
+ return parseAtom(p);
310
+ }
311
+ function parseAtom(p) {
312
+ skipWhitespace(p);
313
+ if (p.pos < p.expr.length && p.expr[p.pos] === "(") {
314
+ p.pos++;
315
+ const val = parseExpression(p);
316
+ skipWhitespace(p);
317
+ if (p.pos >= p.expr.length || p.expr[p.pos] !== ")") {
318
+ throw new Error("Missing closing parenthesis");
319
+ }
320
+ p.pos++;
321
+ return val;
322
+ }
323
+ if (p.pos < p.expr.length && (isDigit(p.expr[p.pos]) || p.expr[p.pos] === ".")) {
324
+ return parseNumber(p);
325
+ }
326
+ if (p.pos < p.expr.length && isAlpha(p.expr[p.pos])) {
327
+ const name = parseIdentifier(p);
328
+ const lower = name.toLowerCase();
329
+ skipWhitespace(p);
330
+ if (p.pos < p.expr.length && p.expr[p.pos] === "(") {
331
+ const fn = FUNCTIONS[lower];
332
+ if (!fn) throw new Error(`Unknown function: ${name}`);
333
+ p.pos++;
334
+ const arg = parseExpression(p);
335
+ skipWhitespace(p);
336
+ if (p.pos >= p.expr.length || p.expr[p.pos] !== ")") {
337
+ throw new Error(`Missing closing parenthesis for ${name}()`);
338
+ }
339
+ p.pos++;
340
+ return fn(arg);
341
+ }
342
+ if (lower in CONSTANTS) return CONSTANTS[lower];
343
+ throw new Error(`Unknown identifier: ${name}`);
344
+ }
345
+ throw new Error(
346
+ p.pos < p.expr.length ? `Unexpected character: '${p.expr[p.pos]}'` : "Unexpected end of expression"
347
+ );
348
+ }
349
+ function parseNumber(p) {
350
+ const start = p.pos;
351
+ while (p.pos < p.expr.length && (isDigit(p.expr[p.pos]) || p.expr[p.pos] === ".")) p.pos++;
352
+ const num = parseFloat(p.expr.slice(start, p.pos));
353
+ if (isNaN(num)) throw new Error(`Invalid number at position ${start}`);
354
+ return num;
355
+ }
356
+ function parseIdentifier(p) {
357
+ const start = p.pos;
358
+ while (p.pos < p.expr.length && (isAlpha(p.expr[p.pos]) || isDigit(p.expr[p.pos]))) p.pos++;
359
+ return p.expr.slice(start, p.pos);
360
+ }
361
+ function isDigit(ch) {
362
+ return ch >= "0" && ch <= "9";
363
+ }
364
+ function isAlpha(ch) {
365
+ return ch >= "a" && ch <= "z" || ch >= "A" && ch <= "Z" || ch === "_";
366
+ }
367
+
368
+ // src/tools/datetime.ts
369
+ var schema5 = {
370
+ name: "get_datetime",
371
+ description: 'Get the current date and time, optionally for a specific timezone. Useful for answering "what time is it?" or "what is the date in Tokyo?"',
372
+ parameters: {
373
+ type: "object",
374
+ properties: {
375
+ timezone: {
376
+ type: "string",
377
+ description: 'IANA timezone name, e.g. "America/New_York", "Europe/London", "Asia/Tokyo". Omit for the default timezone.'
378
+ }
379
+ },
380
+ required: []
381
+ }
382
+ };
383
+ function createDateTime(options) {
384
+ const defaultTz = options?.defaultTimezone ?? "UTC";
385
+ return {
386
+ schema: schema5,
387
+ async execute(args) {
388
+ const input = args.timezone ?? defaultTz;
389
+ const tz = resolveTimezone(input);
390
+ try {
391
+ const now = /* @__PURE__ */ new Date();
392
+ const formatted = new Intl.DateTimeFormat("en-US", {
393
+ timeZone: tz,
394
+ weekday: "long",
395
+ year: "numeric",
396
+ month: "long",
397
+ day: "numeric",
398
+ hour: "numeric",
399
+ minute: "2-digit",
400
+ second: "2-digit",
401
+ timeZoneName: "short"
402
+ }).format(now);
403
+ return `Current date and time in ${tz}: ${formatted}`;
404
+ } catch {
405
+ return `Unknown timezone: "${input}". Please use an IANA timezone like "America/New_York" or "Asia/Tokyo".`;
406
+ }
407
+ }
408
+ };
409
+ }
410
+ var CITY_MAP = {
411
+ "new york": "America/New_York",
412
+ "nyc": "America/New_York",
413
+ "los angeles": "America/Los_Angeles",
414
+ "la": "America/Los_Angeles",
415
+ "chicago": "America/Chicago",
416
+ "denver": "America/Denver",
417
+ "london": "Europe/London",
418
+ "paris": "Europe/Paris",
419
+ "berlin": "Europe/Berlin",
420
+ "tokyo": "Asia/Tokyo",
421
+ "shanghai": "Asia/Shanghai",
422
+ "beijing": "Asia/Shanghai",
423
+ "mumbai": "Asia/Kolkata",
424
+ "delhi": "Asia/Kolkata",
425
+ "sydney": "Australia/Sydney",
426
+ "dubai": "Asia/Dubai",
427
+ "singapore": "Asia/Singapore",
428
+ "hong kong": "Asia/Hong_Kong",
429
+ "seoul": "Asia/Seoul",
430
+ "moscow": "Europe/Moscow",
431
+ "sao paulo": "America/Sao_Paulo",
432
+ "toronto": "America/Toronto",
433
+ "vancouver": "America/Vancouver",
434
+ "honolulu": "Pacific/Honolulu",
435
+ "anchorage": "America/Anchorage"
436
+ };
437
+ function resolveTimezone(input) {
438
+ if (input.includes("/")) return input;
439
+ const lower = input.toLowerCase().trim();
440
+ return CITY_MAP[lower] ?? input;
441
+ }
442
+ export {
443
+ createCalculator,
444
+ createDateTime,
445
+ createTavilySearch,
446
+ createWeather,
447
+ createWikipedia,
448
+ registerTools
449
+ };
450
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/register.ts","../src/tools/tavily-search.ts","../src/tools/weather.ts","../src/tools/wikipedia.ts","../src/tools/calculator.ts","../src/tools/datetime.ts"],"sourcesContent":["import type { JambonzTool, SessionLike } from './types.js';\n\ninterface ToolCallEvent {\n tool_call_id: string;\n name: string;\n arguments: Record<string, any>;\n}\n\n/**\n * Register one or more tools on a jambonz session.\n *\n * Listens on the given hook path for tool-call events, dispatches to the\n * matching tool's execute() method, and sends the result back via\n * session.sendToolOutput(). Unknown tool names are reported as errors.\n *\n * @param session - A jambonz WebSocket session (or any object with .on() and .sendToolOutput())\n * @param hookPath - The toolHook path used in the pipeline verb (e.g. '/tool-call')\n * @param tools - Array of JambonzTool instances to register\n * @param options - Optional configuration\n * @param options.logger - A pino-compatible logger for debug/error output\n * @param options.onUnknownTool - Custom handler for unrecognized tool names\n */\nexport function registerTools(\n session: SessionLike,\n hookPath: string,\n tools: JambonzTool[],\n options?: {\n logger?: { info: (...args: any[]) => void; error: (...args: any[]) => void };\n onUnknownTool?: (session: SessionLike, evt: ToolCallEvent) => void;\n }\n): void {\n const toolMap = new Map(tools.map((t) => [t.schema.name, t]));\n const log = options?.logger;\n\n session.on(hookPath, async(evt: ToolCallEvent) => {\n const { tool_call_id, name, arguments: args } = evt;\n const tool = toolMap.get(name);\n\n if (!tool) {\n log?.error({ name, args }, `unknown tool: ${name}`);\n if (options?.onUnknownTool) {\n options.onUnknownTool(session, evt);\n } else {\n session.sendToolOutput(tool_call_id, `Unknown tool: ${name}`);\n }\n return;\n }\n\n log?.info({ name, args }, 'tool call');\n try {\n const result = await tool.execute(args);\n session.sendToolOutput(tool_call_id, result);\n } catch (err) {\n log?.error({ err, name, args }, `tool execution failed: ${name}`);\n session.sendToolOutput(tool_call_id, `Error executing ${name}: ${err}`);\n }\n });\n}\n","import type { JambonzTool, ToolSchema } from '../types.js';\n\nexport interface TavilySearchOptions {\n /** Tavily API key (required) */\n apiKey: string;\n /** Maximum number of results to return (default: 3) */\n maxResults?: number;\n /** Search depth: 'basic' is faster, 'advanced' is more thorough (default: 'basic') */\n searchDepth?: 'basic' | 'advanced';\n /** Topic category (default: 'general') */\n topic?: 'general' | 'news' | 'finance';\n /** Only include results from these domains */\n includeDomains?: string[];\n /** Exclude results from these domains */\n excludeDomains?: string[];\n}\n\nconst schema: ToolSchema = {\n name: 'web_search',\n description: 'Search the web for current information on a given topic or question.',\n parameters: {\n type: 'object',\n properties: {\n query: {\n type: 'string',\n description: 'The search query',\n },\n },\n required: ['query'],\n },\n};\n\n/**\n * Create a web search tool powered by the Tavily Search API.\n *\n * Useful for answering questions about current events, recent news,\n * or any topic that requires up-to-date information.\n *\n * Requires a Tavily API key — sign up at https://tavily.com\n */\nexport function createTavilySearch(options: TavilySearchOptions): JambonzTool {\n const {\n apiKey,\n maxResults = 3,\n searchDepth = 'basic',\n topic = 'general',\n includeDomains,\n excludeDomains,\n } = options;\n\n return {\n schema,\n async execute(args: Record<string, any>): Promise<string> {\n const res = await fetch('https://api.tavily.com/search', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n api_key: apiKey,\n query: args.query,\n max_results: maxResults,\n search_depth: searchDepth,\n topic,\n ...(includeDomains && { include_domains: includeDomains }),\n ...(excludeDomains && { exclude_domains: excludeDomains }),\n }),\n });\n\n if (!res.ok) {\n throw new Error(`Tavily API error: ${res.status} ${res.statusText}`);\n }\n\n const data = await res.json() as {\n results?: { title: string; content: string; url: string }[];\n };\n\n const results = (data.results || [])\n .map((r) => `${r.title}: ${r.content}`)\n .join('\\n');\n\n return results || 'No results found.';\n },\n };\n}\n","import type { JambonzTool, ToolSchema } from '../types.js';\n\nexport interface WeatherOptions {\n /** Temperature scale (default: 'celsius') */\n scale?: 'celsius' | 'fahrenheit';\n}\n\nconst schema: ToolSchema = {\n name: 'get_weather',\n description: 'Get the current temperature, wind speed, and conditions for a given location.',\n parameters: {\n type: 'object',\n properties: {\n location: {\n type: 'string',\n description: 'City name or location, e.g. \"San Francisco\" or \"Paris, France\"',\n },\n },\n required: ['location'],\n },\n};\n\n/**\n * Create a weather lookup tool using the free Open-Meteo API.\n *\n * No API key required. Provides current temperature, wind speed,\n * and weather conditions for any location worldwide.\n */\nexport function createWeather(options?: WeatherOptions): JambonzTool {\n const scale = options?.scale ?? 'celsius';\n\n return {\n schema,\n async execute(args: Record<string, any>): Promise<string> {\n const location = args.location as string;\n\n /* geocode the location name */\n const geoRes = await fetch(\n 'https://geocoding-api.open-meteo.com/v1/search'\n + `?name=${encodeURIComponent(location)}&count=1&language=en&format=json`\n );\n if (!geoRes.ok) {\n throw new Error(`Geocoding API error: ${geoRes.status}`);\n }\n\n const geoData = await geoRes.json() as {\n results?: { latitude: number; longitude: number; name: string; country: string }[];\n };\n if (!geoData.results?.length) {\n return `Sorry, I could not find weather data for \"${location}\".`;\n }\n\n const { latitude, longitude, name, country } = geoData.results[0];\n\n /* fetch current weather */\n const wxRes = await fetch(\n 'https://api.open-meteo.com/v1/forecast'\n + `?latitude=${latitude}&longitude=${longitude}`\n + '&current=temperature_2m,relative_humidity_2m,apparent_temperature,weather_code,wind_speed_10m'\n + `&temperature_unit=${scale}`\n );\n if (!wxRes.ok) {\n throw new Error(`Weather API error: ${wxRes.status}`);\n }\n\n const wx = await wxRes.json() as {\n current: {\n temperature_2m: number;\n relative_humidity_2m: number;\n apparent_temperature: number;\n weather_code: number;\n wind_speed_10m: number;\n };\n };\n\n const unit = scale === 'fahrenheit' ? '°F' : '°C';\n const { temperature_2m, apparent_temperature, relative_humidity_2m, weather_code, wind_speed_10m } = wx.current;\n const condition = weatherCodeToText(weather_code);\n\n return [\n `Current weather in ${name}, ${country}:`,\n `${condition}, ${temperature_2m}${unit} (feels like ${apparent_temperature}${unit}).`,\n `Wind: ${wind_speed_10m} km/h. Humidity: ${relative_humidity_2m}%.`,\n ].join(' ');\n },\n };\n}\n\n/** Map WMO weather codes to human-readable descriptions */\nfunction weatherCodeToText(code: number): string {\n const descriptions: Record<number, string> = {\n 0: 'Clear sky',\n 1: 'Mainly clear', 2: 'Partly cloudy', 3: 'Overcast',\n 45: 'Foggy', 48: 'Depositing rime fog',\n 51: 'Light drizzle', 53: 'Moderate drizzle', 55: 'Dense drizzle',\n 61: 'Slight rain', 63: 'Moderate rain', 65: 'Heavy rain',\n 71: 'Slight snow', 73: 'Moderate snow', 75: 'Heavy snow',\n 77: 'Snow grains',\n 80: 'Slight rain showers', 81: 'Moderate rain showers', 82: 'Violent rain showers',\n 85: 'Slight snow showers', 86: 'Heavy snow showers',\n 95: 'Thunderstorm', 96: 'Thunderstorm with slight hail', 99: 'Thunderstorm with heavy hail',\n };\n return descriptions[code] ?? 'Unknown conditions';\n}\n","import type { JambonzTool, ToolSchema } from '../types.js';\n\nexport interface WikipediaOptions {\n /** Maximum number of sentences to return from the article summary (default: 5) */\n maxSentences?: number;\n /** Wikipedia language edition (default: 'en') */\n language?: string;\n}\n\nconst schema: ToolSchema = {\n name: 'wikipedia',\n description: 'Look up a topic on Wikipedia to get a factual summary. '\n + 'Useful for answering questions about people, places, history, science, and general knowledge.',\n parameters: {\n type: 'object',\n properties: {\n query: {\n type: 'string',\n description: 'The topic to look up on Wikipedia',\n },\n },\n required: ['query'],\n },\n};\n\n/**\n * Create a Wikipedia lookup tool using the free Wikipedia REST API.\n *\n * No API key required. Searches Wikipedia and returns article summaries,\n * ideal for answering general knowledge questions in voice conversations.\n */\nexport function createWikipedia(options?: WikipediaOptions): JambonzTool {\n const maxSentences = options?.maxSentences ?? 5;\n const lang = options?.language ?? 'en';\n\n return {\n schema,\n async execute(args: Record<string, any>): Promise<string> {\n const query = args.query as string;\n\n /* search for matching articles */\n const searchRes = await fetch(\n `https://${lang}.wikipedia.org/w/api.php`\n + `?action=query&list=search&srsearch=${encodeURIComponent(query)}`\n + '&srlimit=1&format=json&origin=*'\n );\n if (!searchRes.ok) {\n throw new Error(`Wikipedia search error: ${searchRes.status}`);\n }\n\n const searchData = await searchRes.json() as {\n query: { search: { title: string; pageid: number }[] };\n };\n if (!searchData.query.search.length) {\n return `No Wikipedia article found for \"${query}\".`;\n }\n\n const { title } = searchData.query.search[0];\n\n /* fetch the article summary */\n const summaryRes = await fetch(\n `https://${lang}.wikipedia.org/api/rest_v1/page/summary/${encodeURIComponent(title)}`\n );\n if (!summaryRes.ok) {\n throw new Error(`Wikipedia summary error: ${summaryRes.status}`);\n }\n\n const summary = await summaryRes.json() as {\n title: string;\n extract: string;\n };\n\n /* trim to maxSentences */\n const sentences = summary.extract.split(/(?<=\\.)\\s+/);\n const trimmed = sentences.slice(0, maxSentences).join(' ');\n\n return `${summary.title}: ${trimmed}`;\n },\n };\n}\n","import type { JambonzTool, ToolSchema } from '../types.js';\n\nconst schema: ToolSchema = {\n name: 'calculator',\n description: 'Evaluate a mathematical expression and return the numeric result. '\n + 'Supports arithmetic (+, -, *, /, ^, %), parentheses, and functions '\n + '(sqrt, abs, round, ceil, floor, sin, cos, tan, log, log10, exp). '\n + 'Also supports constants pi and e.',\n parameters: {\n type: 'object',\n properties: {\n expression: {\n type: 'string',\n description: 'The math expression to evaluate, e.g. \"15% of 87.50\" should be sent as \"87.50 * 0.15\"',\n },\n },\n required: ['expression'],\n },\n};\n\n/**\n * Create a safe math calculator tool.\n *\n * Uses a recursive descent parser — no eval() — to safely evaluate\n * arithmetic expressions. Ideal for voice agents handling calculations\n * like tips, conversions, or simple math.\n */\nexport function createCalculator(): JambonzTool {\n return {\n schema,\n async execute(args: Record<string, any>): Promise<string> {\n const expression = (args.expression as string).trim();\n try {\n const result = evaluate(expression);\n /* format nicely: avoid excessive decimals */\n const formatted = Number.isInteger(result) ? result.toString() : parseFloat(result.toFixed(10)).toString();\n return `${expression} = ${formatted}`;\n } catch (err) {\n return `Could not evaluate \"${expression}\": ${(err as Error).message}`;\n }\n },\n };\n}\n\n/* ---------- Safe expression evaluator (recursive descent parser) ---------- */\n\nconst FUNCTIONS: Record<string, (x: number) => number> = {\n sqrt: Math.sqrt,\n abs: Math.abs,\n round: Math.round,\n ceil: Math.ceil,\n floor: Math.floor,\n sin: Math.sin,\n cos: Math.cos,\n tan: Math.tan,\n log: Math.log,\n log10: Math.log10,\n exp: Math.exp,\n};\n\nconst CONSTANTS: Record<string, number> = {\n pi: Math.PI,\n e: Math.E,\n};\n\ninterface Parser {\n expr: string;\n pos: number;\n}\n\nfunction evaluate(expr: string): number {\n const parser: Parser = { expr, pos: 0 };\n const result = parseExpression(parser);\n skipWhitespace(parser);\n if (parser.pos < parser.expr.length) {\n throw new Error(`Unexpected character: '${parser.expr[parser.pos]}'`);\n }\n return result;\n}\n\nfunction skipWhitespace(p: Parser): void {\n while (p.pos < p.expr.length && p.expr[p.pos] === ' ') p.pos++;\n}\n\nfunction parseExpression(p: Parser): number {\n let left = parseTerm(p);\n skipWhitespace(p);\n while (p.pos < p.expr.length && (p.expr[p.pos] === '+' || p.expr[p.pos] === '-')) {\n const op = p.expr[p.pos++];\n const right = parseTerm(p);\n left = op === '+' ? left + right : left - right;\n skipWhitespace(p);\n }\n return left;\n}\n\nfunction parseTerm(p: Parser): number {\n let left = parsePower(p);\n skipWhitespace(p);\n while (p.pos < p.expr.length && (p.expr[p.pos] === '*' || p.expr[p.pos] === '/' || p.expr[p.pos] === '%')) {\n const op = p.expr[p.pos++];\n const right = parsePower(p);\n if (op === '*') left *= right;\n else if (op === '/') left /= right;\n else left %= right;\n skipWhitespace(p);\n }\n return left;\n}\n\nfunction parsePower(p: Parser): number {\n const base = parseUnary(p);\n skipWhitespace(p);\n if (p.pos < p.expr.length && (p.expr[p.pos] === '^' || (p.expr[p.pos] === '*' && p.expr[p.pos + 1] === '*'))) {\n if (p.expr[p.pos] === '*') p.pos += 2; else p.pos++;\n const exp = parsePower(p); /* right-associative */\n return Math.pow(base, exp);\n }\n return base;\n}\n\nfunction parseUnary(p: Parser): number {\n skipWhitespace(p);\n if (p.pos < p.expr.length && p.expr[p.pos] === '-') {\n p.pos++;\n return -parseUnary(p);\n }\n if (p.pos < p.expr.length && p.expr[p.pos] === '+') {\n p.pos++;\n return parseUnary(p);\n }\n return parseAtom(p);\n}\n\nfunction parseAtom(p: Parser): number {\n skipWhitespace(p);\n\n /* parenthesized expression */\n if (p.pos < p.expr.length && p.expr[p.pos] === '(') {\n p.pos++;\n const val = parseExpression(p);\n skipWhitespace(p);\n if (p.pos >= p.expr.length || p.expr[p.pos] !== ')') {\n throw new Error('Missing closing parenthesis');\n }\n p.pos++;\n return val;\n }\n\n /* number */\n if (p.pos < p.expr.length && (isDigit(p.expr[p.pos]) || p.expr[p.pos] === '.')) {\n return parseNumber(p);\n }\n\n /* identifier: function or constant */\n if (p.pos < p.expr.length && isAlpha(p.expr[p.pos])) {\n const name = parseIdentifier(p);\n const lower = name.toLowerCase();\n skipWhitespace(p);\n\n /* function call */\n if (p.pos < p.expr.length && p.expr[p.pos] === '(') {\n const fn = FUNCTIONS[lower];\n if (!fn) throw new Error(`Unknown function: ${name}`);\n p.pos++;\n const arg = parseExpression(p);\n skipWhitespace(p);\n if (p.pos >= p.expr.length || p.expr[p.pos] !== ')') {\n throw new Error(`Missing closing parenthesis for ${name}()`);\n }\n p.pos++;\n return fn(arg);\n }\n\n /* constant */\n if (lower in CONSTANTS) return CONSTANTS[lower];\n throw new Error(`Unknown identifier: ${name}`);\n }\n\n throw new Error(\n p.pos < p.expr.length\n ? `Unexpected character: '${p.expr[p.pos]}'`\n : 'Unexpected end of expression'\n );\n}\n\nfunction parseNumber(p: Parser): number {\n const start = p.pos;\n while (p.pos < p.expr.length && (isDigit(p.expr[p.pos]) || p.expr[p.pos] === '.')) p.pos++;\n const num = parseFloat(p.expr.slice(start, p.pos));\n if (isNaN(num)) throw new Error(`Invalid number at position ${start}`);\n return num;\n}\n\nfunction parseIdentifier(p: Parser): string {\n const start = p.pos;\n while (p.pos < p.expr.length && (isAlpha(p.expr[p.pos]) || isDigit(p.expr[p.pos]))) p.pos++;\n return p.expr.slice(start, p.pos);\n}\n\nfunction isDigit(ch: string): boolean {\n return ch >= '0' && ch <= '9';\n}\n\nfunction isAlpha(ch: string): boolean {\n return (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || ch === '_';\n}\n","import type { JambonzTool, ToolSchema } from '../types.js';\n\nexport interface DateTimeOptions {\n /** Default timezone when none is specified (default: 'UTC') */\n defaultTimezone?: string;\n}\n\nconst schema: ToolSchema = {\n name: 'get_datetime',\n description: 'Get the current date and time, optionally for a specific timezone. '\n + 'Useful for answering \"what time is it?\" or \"what is the date in Tokyo?\"',\n parameters: {\n type: 'object',\n properties: {\n timezone: {\n type: 'string',\n description:\n 'IANA timezone name, e.g. \"America/New_York\", \"Europe/London\", \"Asia/Tokyo\". '\n + 'Omit for the default timezone.',\n },\n },\n required: [],\n },\n};\n\n/**\n * Create a date/time tool using the built-in Intl API.\n *\n * No API key required. Returns the current date, time, and timezone\n * for any IANA timezone. Handles common city-to-timezone mapping for\n * cases where the LLM sends a city name instead of an IANA zone.\n */\nexport function createDateTime(options?: DateTimeOptions): JambonzTool {\n const defaultTz = options?.defaultTimezone ?? 'UTC';\n\n return {\n schema,\n async execute(args: Record<string, any>): Promise<string> {\n const input = (args.timezone as string | undefined) ?? defaultTz;\n const tz = resolveTimezone(input);\n\n try {\n const now = new Date();\n const formatted = new Intl.DateTimeFormat('en-US', {\n timeZone: tz,\n weekday: 'long',\n year: 'numeric',\n month: 'long',\n day: 'numeric',\n hour: 'numeric',\n minute: '2-digit',\n second: '2-digit',\n timeZoneName: 'short',\n }).format(now);\n\n return `Current date and time in ${tz}: ${formatted}`;\n } catch {\n return `Unknown timezone: \"${input}\". Please use an IANA timezone like \"America/New_York\" or \"Asia/Tokyo\".`;\n }\n },\n };\n}\n\n/**\n * Best-effort mapping from common city names to IANA timezones.\n * The LLM will usually send proper IANA names, but voice users\n * often say \"what time is it in London?\" and the LLM may forward that.\n */\nconst CITY_MAP: Record<string, string> = {\n 'new york': 'America/New_York',\n 'nyc': 'America/New_York',\n 'los angeles': 'America/Los_Angeles',\n 'la': 'America/Los_Angeles',\n 'chicago': 'America/Chicago',\n 'denver': 'America/Denver',\n 'london': 'Europe/London',\n 'paris': 'Europe/Paris',\n 'berlin': 'Europe/Berlin',\n 'tokyo': 'Asia/Tokyo',\n 'shanghai': 'Asia/Shanghai',\n 'beijing': 'Asia/Shanghai',\n 'mumbai': 'Asia/Kolkata',\n 'delhi': 'Asia/Kolkata',\n 'sydney': 'Australia/Sydney',\n 'dubai': 'Asia/Dubai',\n 'singapore': 'Asia/Singapore',\n 'hong kong': 'Asia/Hong_Kong',\n 'seoul': 'Asia/Seoul',\n 'moscow': 'Europe/Moscow',\n 'sao paulo': 'America/Sao_Paulo',\n 'toronto': 'America/Toronto',\n 'vancouver': 'America/Vancouver',\n 'honolulu': 'Pacific/Honolulu',\n 'anchorage': 'America/Anchorage',\n};\n\nfunction resolveTimezone(input: string): string {\n /* already looks like an IANA zone? */\n if (input.includes('/')) return input;\n /* check the city map */\n const lower = input.toLowerCase().trim();\n return CITY_MAP[lower] ?? input;\n}\n"],"mappings":";AAsBO,SAAS,cACd,SACA,UACA,OACA,SAIM;AACN,QAAM,UAAU,IAAI,IAAI,MAAM,IAAI,CAAC,MAAM,CAAC,EAAE,OAAO,MAAM,CAAC,CAAC,CAAC;AAC5D,QAAM,MAAM,SAAS;AAErB,UAAQ,GAAG,UAAU,OAAM,QAAuB;AAChD,UAAM,EAAE,cAAc,MAAM,WAAW,KAAK,IAAI;AAChD,UAAM,OAAO,QAAQ,IAAI,IAAI;AAE7B,QAAI,CAAC,MAAM;AACT,WAAK,MAAM,EAAE,MAAM,KAAK,GAAG,iBAAiB,IAAI,EAAE;AAClD,UAAI,SAAS,eAAe;AAC1B,gBAAQ,cAAc,SAAS,GAAG;AAAA,MACpC,OAAO;AACL,gBAAQ,eAAe,cAAc,iBAAiB,IAAI,EAAE;AAAA,MAC9D;AACA;AAAA,IACF;AAEA,SAAK,KAAK,EAAE,MAAM,KAAK,GAAG,WAAW;AACrC,QAAI;AACF,YAAM,SAAS,MAAM,KAAK,QAAQ,IAAI;AACtC,cAAQ,eAAe,cAAc,MAAM;AAAA,IAC7C,SAAS,KAAK;AACZ,WAAK,MAAM,EAAE,KAAK,MAAM,KAAK,GAAG,0BAA0B,IAAI,EAAE;AAChE,cAAQ,eAAe,cAAc,mBAAmB,IAAI,KAAK,GAAG,EAAE;AAAA,IACxE;AAAA,EACF,CAAC;AACH;;;ACxCA,IAAM,SAAqB;AAAA,EACzB,MAAM;AAAA,EACN,aAAa;AAAA,EACb,YAAY;AAAA,IACV,MAAM;AAAA,IACN,YAAY;AAAA,MACV,OAAO;AAAA,QACL,MAAM;AAAA,QACN,aAAa;AAAA,MACf;AAAA,IACF;AAAA,IACA,UAAU,CAAC,OAAO;AAAA,EACpB;AACF;AAUO,SAAS,mBAAmB,SAA2C;AAC5E,QAAM;AAAA,IACJ;AAAA,IACA,aAAa;AAAA,IACb,cAAc;AAAA,IACd,QAAQ;AAAA,IACR;AAAA,IACA;AAAA,EACF,IAAI;AAEJ,SAAO;AAAA,IACL;AAAA,IACA,MAAM,QAAQ,MAA4C;AACxD,YAAM,MAAM,MAAM,MAAM,iCAAiC;AAAA,QACvD,QAAQ;AAAA,QACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,QAC9C,MAAM,KAAK,UAAU;AAAA,UACnB,SAAS;AAAA,UACT,OAAO,KAAK;AAAA,UACZ,aAAa;AAAA,UACb,cAAc;AAAA,UACd;AAAA,UACA,GAAI,kBAAkB,EAAE,iBAAiB,eAAe;AAAA,UACxD,GAAI,kBAAkB,EAAE,iBAAiB,eAAe;AAAA,QAC1D,CAAC;AAAA,MACH,CAAC;AAED,UAAI,CAAC,IAAI,IAAI;AACX,cAAM,IAAI,MAAM,qBAAqB,IAAI,MAAM,IAAI,IAAI,UAAU,EAAE;AAAA,MACrE;AAEA,YAAM,OAAO,MAAM,IAAI,KAAK;AAI5B,YAAM,WAAW,KAAK,WAAW,CAAC,GAC/B,IAAI,CAAC,MAAM,GAAG,EAAE,KAAK,KAAK,EAAE,OAAO,EAAE,EACrC,KAAK,IAAI;AAEZ,aAAO,WAAW;AAAA,IACpB;AAAA,EACF;AACF;;;AC3EA,IAAMA,UAAqB;AAAA,EACzB,MAAM;AAAA,EACN,aAAa;AAAA,EACb,YAAY;AAAA,IACV,MAAM;AAAA,IACN,YAAY;AAAA,MACV,UAAU;AAAA,QACR,MAAM;AAAA,QACN,aAAa;AAAA,MACf;AAAA,IACF;AAAA,IACA,UAAU,CAAC,UAAU;AAAA,EACvB;AACF;AAQO,SAAS,cAAc,SAAuC;AACnE,QAAM,QAAQ,SAAS,SAAS;AAEhC,SAAO;AAAA,IACL,QAAAA;AAAA,IACA,MAAM,QAAQ,MAA4C;AACxD,YAAM,WAAW,KAAK;AAGtB,YAAM,SAAS,MAAM;AAAA,QACnB,uDACW,mBAAmB,QAAQ,CAAC;AAAA,MACzC;AACA,UAAI,CAAC,OAAO,IAAI;AACd,cAAM,IAAI,MAAM,wBAAwB,OAAO,MAAM,EAAE;AAAA,MACzD;AAEA,YAAM,UAAU,MAAM,OAAO,KAAK;AAGlC,UAAI,CAAC,QAAQ,SAAS,QAAQ;AAC5B,eAAO,6CAA6C,QAAQ;AAAA,MAC9D;AAEA,YAAM,EAAE,UAAU,WAAW,MAAM,QAAQ,IAAI,QAAQ,QAAQ,CAAC;AAGhE,YAAM,QAAQ,MAAM;AAAA,QAClB,mDACe,QAAQ,cAAc,SAAS,kHAEvB,KAAK;AAAA,MAC9B;AACA,UAAI,CAAC,MAAM,IAAI;AACb,cAAM,IAAI,MAAM,sBAAsB,MAAM,MAAM,EAAE;AAAA,MACtD;AAEA,YAAM,KAAK,MAAM,MAAM,KAAK;AAU5B,YAAM,OAAO,UAAU,eAAe,UAAO;AAC7C,YAAM,EAAE,gBAAgB,sBAAsB,sBAAsB,cAAc,eAAe,IAAI,GAAG;AACxG,YAAM,YAAY,kBAAkB,YAAY;AAEhD,aAAO;AAAA,QACL,sBAAsB,IAAI,KAAK,OAAO;AAAA,QACtC,GAAG,SAAS,KAAK,cAAc,GAAG,IAAI,gBAAgB,oBAAoB,GAAG,IAAI;AAAA,QACjF,SAAS,cAAc,oBAAoB,oBAAoB;AAAA,MACjE,EAAE,KAAK,GAAG;AAAA,IACZ;AAAA,EACF;AACF;AAGA,SAAS,kBAAkB,MAAsB;AAC/C,QAAM,eAAuC;AAAA,IAC3C,GAAG;AAAA,IACH,GAAG;AAAA,IAAgB,GAAG;AAAA,IAAiB,GAAG;AAAA,IAC1C,IAAI;AAAA,IAAS,IAAI;AAAA,IACjB,IAAI;AAAA,IAAiB,IAAI;AAAA,IAAoB,IAAI;AAAA,IACjD,IAAI;AAAA,IAAe,IAAI;AAAA,IAAiB,IAAI;AAAA,IAC5C,IAAI;AAAA,IAAe,IAAI;AAAA,IAAiB,IAAI;AAAA,IAC5C,IAAI;AAAA,IACJ,IAAI;AAAA,IAAuB,IAAI;AAAA,IAAyB,IAAI;AAAA,IAC5D,IAAI;AAAA,IAAuB,IAAI;AAAA,IAC/B,IAAI;AAAA,IAAgB,IAAI;AAAA,IAAiC,IAAI;AAAA,EAC/D;AACA,SAAO,aAAa,IAAI,KAAK;AAC/B;;;AC9FA,IAAMC,UAAqB;AAAA,EACzB,MAAM;AAAA,EACN,aAAa;AAAA,EAEb,YAAY;AAAA,IACV,MAAM;AAAA,IACN,YAAY;AAAA,MACV,OAAO;AAAA,QACL,MAAM;AAAA,QACN,aAAa;AAAA,MACf;AAAA,IACF;AAAA,IACA,UAAU,CAAC,OAAO;AAAA,EACpB;AACF;AAQO,SAAS,gBAAgB,SAAyC;AACvE,QAAM,eAAe,SAAS,gBAAgB;AAC9C,QAAM,OAAO,SAAS,YAAY;AAElC,SAAO;AAAA,IACL,QAAAA;AAAA,IACA,MAAM,QAAQ,MAA4C;AACxD,YAAM,QAAQ,KAAK;AAGnB,YAAM,YAAY,MAAM;AAAA,QACtB,WAAW,IAAI,8DACyB,mBAAmB,KAAK,CAAC;AAAA,MAEnE;AACA,UAAI,CAAC,UAAU,IAAI;AACjB,cAAM,IAAI,MAAM,2BAA2B,UAAU,MAAM,EAAE;AAAA,MAC/D;AAEA,YAAM,aAAa,MAAM,UAAU,KAAK;AAGxC,UAAI,CAAC,WAAW,MAAM,OAAO,QAAQ;AACnC,eAAO,mCAAmC,KAAK;AAAA,MACjD;AAEA,YAAM,EAAE,MAAM,IAAI,WAAW,MAAM,OAAO,CAAC;AAG3C,YAAM,aAAa,MAAM;AAAA,QACvB,WAAW,IAAI,2CAA2C,mBAAmB,KAAK,CAAC;AAAA,MACrF;AACA,UAAI,CAAC,WAAW,IAAI;AAClB,cAAM,IAAI,MAAM,4BAA4B,WAAW,MAAM,EAAE;AAAA,MACjE;AAEA,YAAM,UAAU,MAAM,WAAW,KAAK;AAMtC,YAAM,YAAY,QAAQ,QAAQ,MAAM,YAAY;AACpD,YAAM,UAAU,UAAU,MAAM,GAAG,YAAY,EAAE,KAAK,GAAG;AAEzD,aAAO,GAAG,QAAQ,KAAK,KAAK,OAAO;AAAA,IACrC;AAAA,EACF;AACF;;;AC7EA,IAAMC,UAAqB;AAAA,EACzB,MAAM;AAAA,EACN,aAAa;AAAA,EAIb,YAAY;AAAA,IACV,MAAM;AAAA,IACN,YAAY;AAAA,MACV,YAAY;AAAA,QACV,MAAM;AAAA,QACN,aAAa;AAAA,MACf;AAAA,IACF;AAAA,IACA,UAAU,CAAC,YAAY;AAAA,EACzB;AACF;AASO,SAAS,mBAAgC;AAC9C,SAAO;AAAA,IACL,QAAAA;AAAA,IACA,MAAM,QAAQ,MAA4C;AACxD,YAAM,aAAc,KAAK,WAAsB,KAAK;AACpD,UAAI;AACF,cAAM,SAAS,SAAS,UAAU;AAElC,cAAM,YAAY,OAAO,UAAU,MAAM,IAAI,OAAO,SAAS,IAAI,WAAW,OAAO,QAAQ,EAAE,CAAC,EAAE,SAAS;AACzG,eAAO,GAAG,UAAU,MAAM,SAAS;AAAA,MACrC,SAAS,KAAK;AACZ,eAAO,uBAAuB,UAAU,MAAO,IAAc,OAAO;AAAA,MACtE;AAAA,IACF;AAAA,EACF;AACF;AAIA,IAAM,YAAmD;AAAA,EACvD,MAAM,KAAK;AAAA,EACX,KAAK,KAAK;AAAA,EACV,OAAO,KAAK;AAAA,EACZ,MAAM,KAAK;AAAA,EACX,OAAO,KAAK;AAAA,EACZ,KAAK,KAAK;AAAA,EACV,KAAK,KAAK;AAAA,EACV,KAAK,KAAK;AAAA,EACV,KAAK,KAAK;AAAA,EACV,OAAO,KAAK;AAAA,EACZ,KAAK,KAAK;AACZ;AAEA,IAAM,YAAoC;AAAA,EACxC,IAAI,KAAK;AAAA,EACT,GAAG,KAAK;AACV;AAOA,SAAS,SAAS,MAAsB;AACtC,QAAM,SAAiB,EAAE,MAAM,KAAK,EAAE;AACtC,QAAM,SAAS,gBAAgB,MAAM;AACrC,iBAAe,MAAM;AACrB,MAAI,OAAO,MAAM,OAAO,KAAK,QAAQ;AACnC,UAAM,IAAI,MAAM,0BAA0B,OAAO,KAAK,OAAO,GAAG,CAAC,GAAG;AAAA,EACtE;AACA,SAAO;AACT;AAEA,SAAS,eAAe,GAAiB;AACvC,SAAO,EAAE,MAAM,EAAE,KAAK,UAAU,EAAE,KAAK,EAAE,GAAG,MAAM,IAAK,GAAE;AAC3D;AAEA,SAAS,gBAAgB,GAAmB;AAC1C,MAAI,OAAO,UAAU,CAAC;AACtB,iBAAe,CAAC;AAChB,SAAO,EAAE,MAAM,EAAE,KAAK,WAAW,EAAE,KAAK,EAAE,GAAG,MAAM,OAAO,EAAE,KAAK,EAAE,GAAG,MAAM,MAAM;AAChF,UAAM,KAAK,EAAE,KAAK,EAAE,KAAK;AACzB,UAAM,QAAQ,UAAU,CAAC;AACzB,WAAO,OAAO,MAAM,OAAO,QAAQ,OAAO;AAC1C,mBAAe,CAAC;AAAA,EAClB;AACA,SAAO;AACT;AAEA,SAAS,UAAU,GAAmB;AACpC,MAAI,OAAO,WAAW,CAAC;AACvB,iBAAe,CAAC;AAChB,SAAO,EAAE,MAAM,EAAE,KAAK,WAAW,EAAE,KAAK,EAAE,GAAG,MAAM,OAAO,EAAE,KAAK,EAAE,GAAG,MAAM,OAAO,EAAE,KAAK,EAAE,GAAG,MAAM,MAAM;AACzG,UAAM,KAAK,EAAE,KAAK,EAAE,KAAK;AACzB,UAAM,QAAQ,WAAW,CAAC;AAC1B,QAAI,OAAO,IAAK,SAAQ;AAAA,aACf,OAAO,IAAK,SAAQ;AAAA,QACxB,SAAQ;AACb,mBAAe,CAAC;AAAA,EAClB;AACA,SAAO;AACT;AAEA,SAAS,WAAW,GAAmB;AACrC,QAAM,OAAO,WAAW,CAAC;AACzB,iBAAe,CAAC;AAChB,MAAI,EAAE,MAAM,EAAE,KAAK,WAAW,EAAE,KAAK,EAAE,GAAG,MAAM,OAAQ,EAAE,KAAK,EAAE,GAAG,MAAM,OAAO,EAAE,KAAK,EAAE,MAAM,CAAC,MAAM,MAAO;AAC5G,QAAI,EAAE,KAAK,EAAE,GAAG,MAAM,IAAK,GAAE,OAAO;AAAA,QAAQ,GAAE;AAC9C,UAAM,MAAM,WAAW,CAAC;AACxB,WAAO,KAAK,IAAI,MAAM,GAAG;AAAA,EAC3B;AACA,SAAO;AACT;AAEA,SAAS,WAAW,GAAmB;AACrC,iBAAe,CAAC;AAChB,MAAI,EAAE,MAAM,EAAE,KAAK,UAAU,EAAE,KAAK,EAAE,GAAG,MAAM,KAAK;AAClD,MAAE;AACF,WAAO,CAAC,WAAW,CAAC;AAAA,EACtB;AACA,MAAI,EAAE,MAAM,EAAE,KAAK,UAAU,EAAE,KAAK,EAAE,GAAG,MAAM,KAAK;AAClD,MAAE;AACF,WAAO,WAAW,CAAC;AAAA,EACrB;AACA,SAAO,UAAU,CAAC;AACpB;AAEA,SAAS,UAAU,GAAmB;AACpC,iBAAe,CAAC;AAGhB,MAAI,EAAE,MAAM,EAAE,KAAK,UAAU,EAAE,KAAK,EAAE,GAAG,MAAM,KAAK;AAClD,MAAE;AACF,UAAM,MAAM,gBAAgB,CAAC;AAC7B,mBAAe,CAAC;AAChB,QAAI,EAAE,OAAO,EAAE,KAAK,UAAU,EAAE,KAAK,EAAE,GAAG,MAAM,KAAK;AACnD,YAAM,IAAI,MAAM,6BAA6B;AAAA,IAC/C;AACA,MAAE;AACF,WAAO;AAAA,EACT;AAGA,MAAI,EAAE,MAAM,EAAE,KAAK,WAAW,QAAQ,EAAE,KAAK,EAAE,GAAG,CAAC,KAAK,EAAE,KAAK,EAAE,GAAG,MAAM,MAAM;AAC9E,WAAO,YAAY,CAAC;AAAA,EACtB;AAGA,MAAI,EAAE,MAAM,EAAE,KAAK,UAAU,QAAQ,EAAE,KAAK,EAAE,GAAG,CAAC,GAAG;AACnD,UAAM,OAAO,gBAAgB,CAAC;AAC9B,UAAM,QAAQ,KAAK,YAAY;AAC/B,mBAAe,CAAC;AAGhB,QAAI,EAAE,MAAM,EAAE,KAAK,UAAU,EAAE,KAAK,EAAE,GAAG,MAAM,KAAK;AAClD,YAAM,KAAK,UAAU,KAAK;AAC1B,UAAI,CAAC,GAAI,OAAM,IAAI,MAAM,qBAAqB,IAAI,EAAE;AACpD,QAAE;AACF,YAAM,MAAM,gBAAgB,CAAC;AAC7B,qBAAe,CAAC;AAChB,UAAI,EAAE,OAAO,EAAE,KAAK,UAAU,EAAE,KAAK,EAAE,GAAG,MAAM,KAAK;AACnD,cAAM,IAAI,MAAM,mCAAmC,IAAI,IAAI;AAAA,MAC7D;AACA,QAAE;AACF,aAAO,GAAG,GAAG;AAAA,IACf;AAGA,QAAI,SAAS,UAAW,QAAO,UAAU,KAAK;AAC9C,UAAM,IAAI,MAAM,uBAAuB,IAAI,EAAE;AAAA,EAC/C;AAEA,QAAM,IAAI;AAAA,IACR,EAAE,MAAM,EAAE,KAAK,SACX,0BAA0B,EAAE,KAAK,EAAE,GAAG,CAAC,MACvC;AAAA,EACN;AACF;AAEA,SAAS,YAAY,GAAmB;AACtC,QAAM,QAAQ,EAAE;AAChB,SAAO,EAAE,MAAM,EAAE,KAAK,WAAW,QAAQ,EAAE,KAAK,EAAE,GAAG,CAAC,KAAK,EAAE,KAAK,EAAE,GAAG,MAAM,KAAM,GAAE;AACrF,QAAM,MAAM,WAAW,EAAE,KAAK,MAAM,OAAO,EAAE,GAAG,CAAC;AACjD,MAAI,MAAM,GAAG,EAAG,OAAM,IAAI,MAAM,8BAA8B,KAAK,EAAE;AACrE,SAAO;AACT;AAEA,SAAS,gBAAgB,GAAmB;AAC1C,QAAM,QAAQ,EAAE;AAChB,SAAO,EAAE,MAAM,EAAE,KAAK,WAAW,QAAQ,EAAE,KAAK,EAAE,GAAG,CAAC,KAAK,QAAQ,EAAE,KAAK,EAAE,GAAG,CAAC,GAAI,GAAE;AACtF,SAAO,EAAE,KAAK,MAAM,OAAO,EAAE,GAAG;AAClC;AAEA,SAAS,QAAQ,IAAqB;AACpC,SAAO,MAAM,OAAO,MAAM;AAC5B;AAEA,SAAS,QAAQ,IAAqB;AACpC,SAAQ,MAAM,OAAO,MAAM,OAAS,MAAM,OAAO,MAAM,OAAQ,OAAO;AACxE;;;ACvMA,IAAMC,UAAqB;AAAA,EACzB,MAAM;AAAA,EACN,aAAa;AAAA,EAEb,YAAY;AAAA,IACV,MAAM;AAAA,IACN,YAAY;AAAA,MACV,UAAU;AAAA,QACR,MAAM;AAAA,QACN,aACE;AAAA,MAEJ;AAAA,IACF;AAAA,IACA,UAAU,CAAC;AAAA,EACb;AACF;AASO,SAAS,eAAe,SAAwC;AACrE,QAAM,YAAY,SAAS,mBAAmB;AAE9C,SAAO;AAAA,IACL,QAAAA;AAAA,IACA,MAAM,QAAQ,MAA4C;AACxD,YAAM,QAAS,KAAK,YAAmC;AACvD,YAAM,KAAK,gBAAgB,KAAK;AAEhC,UAAI;AACF,cAAM,MAAM,oBAAI,KAAK;AACrB,cAAM,YAAY,IAAI,KAAK,eAAe,SAAS;AAAA,UACjD,UAAU;AAAA,UACV,SAAS;AAAA,UACT,MAAM;AAAA,UACN,OAAO;AAAA,UACP,KAAK;AAAA,UACL,MAAM;AAAA,UACN,QAAQ;AAAA,UACR,QAAQ;AAAA,UACR,cAAc;AAAA,QAChB,CAAC,EAAE,OAAO,GAAG;AAEb,eAAO,4BAA4B,EAAE,KAAK,SAAS;AAAA,MACrD,QAAQ;AACN,eAAO,sBAAsB,KAAK;AAAA,MACpC;AAAA,IACF;AAAA,EACF;AACF;AAOA,IAAM,WAAmC;AAAA,EACvC,YAAY;AAAA,EACZ,OAAO;AAAA,EACP,eAAe;AAAA,EACf,MAAM;AAAA,EACN,WAAW;AAAA,EACX,UAAU;AAAA,EACV,UAAU;AAAA,EACV,SAAS;AAAA,EACT,UAAU;AAAA,EACV,SAAS;AAAA,EACT,YAAY;AAAA,EACZ,WAAW;AAAA,EACX,UAAU;AAAA,EACV,SAAS;AAAA,EACT,UAAU;AAAA,EACV,SAAS;AAAA,EACT,aAAa;AAAA,EACb,aAAa;AAAA,EACb,SAAS;AAAA,EACT,UAAU;AAAA,EACV,aAAa;AAAA,EACb,WAAW;AAAA,EACX,aAAa;AAAA,EACb,YAAY;AAAA,EACZ,aAAa;AACf;AAEA,SAAS,gBAAgB,OAAuB;AAE9C,MAAI,MAAM,SAAS,GAAG,EAAG,QAAO;AAEhC,QAAM,QAAQ,MAAM,YAAY,EAAE,KAAK;AACvC,SAAO,SAAS,KAAK,KAAK;AAC5B;","names":["schema","schema","schema","schema"]}
package/package.json ADDED
@@ -0,0 +1,64 @@
1
+ {
2
+ "name": "@jambonz/tools",
3
+ "version": "0.1.2",
4
+ "description": "Pre-built, reusable tools for jambonz pipeline voice AI agents",
5
+ "type": "module",
6
+ "main": "./dist/index.cjs",
7
+ "module": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "import": {
12
+ "types": "./dist/index.d.ts",
13
+ "default": "./dist/index.js"
14
+ },
15
+ "require": {
16
+ "types": "./dist/index.d.cts",
17
+ "default": "./dist/index.cjs"
18
+ }
19
+ }
20
+ },
21
+ "files": [
22
+ "dist"
23
+ ],
24
+ "scripts": {
25
+ "build": "tsup",
26
+ "lint": "eslint src/",
27
+ "lint:fix": "eslint --fix src/",
28
+ "prepublishOnly": "npm run build",
29
+ "prepare": "husky"
30
+ },
31
+ "keywords": [
32
+ "jambonz",
33
+ "voice",
34
+ "ai",
35
+ "tools",
36
+ "pipeline",
37
+ "llm",
38
+ "function-calling"
39
+ ],
40
+ "repository": {
41
+ "type": "git",
42
+ "url": "https://github.com/jambonz/tools"
43
+ },
44
+ "license": "MIT",
45
+ "engines": {
46
+ "node": ">=22.0.0"
47
+ },
48
+ "devDependencies": {
49
+ "@eslint/js": "^9.27.0",
50
+ "@typescript-eslint/eslint-plugin": "^8.33.0",
51
+ "@typescript-eslint/parser": "^8.33.0",
52
+ "eslint": "^9.27.0",
53
+ "eslint-plugin-promise": "^7.2.1",
54
+ "globals": "^16.1.0",
55
+ "husky": "^9.1.7",
56
+ "lint-staged": "^16.4.0",
57
+ "tsup": "^8.4.0",
58
+ "typescript": "^5.8.3",
59
+ "typescript-eslint": "^8.33.0"
60
+ },
61
+ "lint-staged": {
62
+ "*.ts": "eslint --max-warnings 0"
63
+ }
64
+ }