@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/README.md ADDED
@@ -0,0 +1,310 @@
1
+ # @jambonz/tools
2
+
3
+ Pre-built, reusable tools for jambonz pipeline voice AI agents.
4
+
5
+ Instead of copy-pasting tool schemas and writing API handlers for every application, import a ready-made tool and wire it up in a few lines:
6
+
7
+ ```typescript
8
+ import { createTavilySearch, registerTools } from '@jambonz/tools';
9
+
10
+ const search = createTavilySearch({ apiKey: 'tvly-xxx' });
11
+
12
+ session.pipeline({
13
+ llm: {
14
+ vendor: 'openai',
15
+ model: 'gpt-4.1-mini',
16
+ llmOptions: {
17
+ messages: [{ role: 'system', content: 'You are a helpful assistant.' }],
18
+ tools: [search.schema],
19
+ },
20
+ },
21
+ toolHook: '/tool-call',
22
+ // ...stt, tts, etc.
23
+ }).send();
24
+
25
+ registerTools(session, '/tool-call', [search]);
26
+ ```
27
+
28
+ That's it. The schema tells the LLM what the tool does, `registerTools` handles dispatch and execution.
29
+
30
+ ## Install
31
+
32
+ ```bash
33
+ npm install @jambonz/tools
34
+ ```
35
+
36
+ ## Concepts
37
+
38
+ ### How tools work in jambonz pipeline
39
+
40
+ When you give an LLM tools in a jambonz pipeline, the flow is:
41
+
42
+ 1. The LLM decides to call a tool and sends a `tool_call` with a name and arguments
43
+ 2. The feature-server routes it to your application via the `toolHook` path
44
+ 3. Your application executes the tool and sends the result back via `session.sendToolOutput()`
45
+ 4. The LLM incorporates the result into its response
46
+
47
+ This package handles step 3 for you — each tool bundles a pre-built `execute()` function that calls the right API, parses the result, and returns a text string the LLM can use.
48
+
49
+ ### The JambonzTool interface
50
+
51
+ Every tool in this package implements:
52
+
53
+ ```typescript
54
+ interface JambonzTool {
55
+ schema: ToolSchema; // what the LLM sees
56
+ execute(args: Record<string, any>): Promise<string>; // what runs when the LLM calls it
57
+ }
58
+ ```
59
+
60
+ You can use these two pieces independently:
61
+ - Pass `tool.schema` to `llmOptions.tools` in your pipeline verb
62
+ - Call `tool.execute(args)` yourself in a custom toolHook handler
63
+ - Or use `registerTools()` to wire everything up automatically
64
+
65
+ ### registerTools()
66
+
67
+ `registerTools()` is a convenience that listens on a session's toolHook path and dispatches to the right tool:
68
+
69
+ ```typescript
70
+ registerTools(session, '/tool-call', [search, weather, calculator], {
71
+ logger: log, // optional pino logger — logs tool calls and errors
72
+ });
73
+ ```
74
+
75
+ It handles:
76
+ - Matching the tool name from the LLM's request to the right handler
77
+ - Calling `execute()` with the parsed arguments
78
+ - Sending the result back via `session.sendToolOutput()`
79
+ - Error handling — if a tool throws, the error message is sent back to the LLM
80
+ - Unknown tools — if the LLM calls a tool that isn't registered, an error is returned
81
+
82
+ ## Available tools
83
+
84
+ ### Web Search (Tavily)
85
+
86
+ Search the web for current information. Requires a [Tavily API key](https://tavily.com).
87
+
88
+ ```typescript
89
+ import { createTavilySearch } from '@jambonz/tools';
90
+
91
+ const search = createTavilySearch({
92
+ apiKey: 'tvly-xxx', // required
93
+ maxResults: 3, // default: 3
94
+ searchDepth: 'basic', // 'basic' | 'advanced' (default: 'basic')
95
+ topic: 'general', // 'general' | 'news' | 'finance' (default: 'general')
96
+ includeDomains: ['cnn.com'], // optional: only search these domains
97
+ excludeDomains: ['x.com'], // optional: never include these domains
98
+ });
99
+ ```
100
+
101
+ **Tool name:** `web_search`
102
+ **Parameters the LLM sends:** `{ query: "latest news about..." }`
103
+
104
+ ### Weather (Open-Meteo)
105
+
106
+ Current weather for any location worldwide. **No API key required.**
107
+
108
+ ```typescript
109
+ import { createWeather } from '@jambonz/tools';
110
+
111
+ const weather = createWeather({
112
+ scale: 'fahrenheit', // 'celsius' | 'fahrenheit' (default: 'celsius')
113
+ });
114
+ ```
115
+
116
+ **Tool name:** `get_weather`
117
+ **Parameters the LLM sends:** `{ location: "San Francisco" }`
118
+ **Returns:** Temperature, feels-like, wind speed, humidity, and conditions.
119
+
120
+ ### Wikipedia
121
+
122
+ Factual summaries from Wikipedia. **No API key required.**
123
+
124
+ ```typescript
125
+ import { createWikipedia } from '@jambonz/tools';
126
+
127
+ const wiki = createWikipedia({
128
+ maxSentences: 5, // default: 5
129
+ language: 'en', // Wikipedia language edition (default: 'en')
130
+ });
131
+ ```
132
+
133
+ **Tool name:** `wikipedia`
134
+ **Parameters the LLM sends:** `{ query: "Eiffel Tower" }`
135
+ **Returns:** Article title and summary text.
136
+
137
+ ### Calculator
138
+
139
+ Safe math expression evaluator. **No API key required.**
140
+
141
+ ```typescript
142
+ import { createCalculator } from '@jambonz/tools';
143
+
144
+ const calc = createCalculator();
145
+ ```
146
+
147
+ **Tool name:** `calculator`
148
+ **Parameters the LLM sends:** `{ expression: "87.50 * 0.15" }`
149
+ **Supports:** `+`, `-`, `*`, `/`, `^` (power), `%` (modulo), parentheses, and functions: `sqrt`, `abs`, `round`, `ceil`, `floor`, `sin`, `cos`, `tan`, `log`, `log10`, `exp`. Constants: `pi`, `e`.
150
+
151
+ Uses a safe recursive descent parser — no `eval()`.
152
+
153
+ ### Date & Time
154
+
155
+ Current date and time for any timezone. **No API key required.**
156
+
157
+ ```typescript
158
+ import { createDateTime } from '@jambonz/tools';
159
+
160
+ const datetime = createDateTime({
161
+ defaultTimezone: 'America/New_York', // default: 'UTC'
162
+ });
163
+ ```
164
+
165
+ **Tool name:** `get_datetime`
166
+ **Parameters the LLM sends:** `{ timezone: "Asia/Tokyo" }` (optional — uses default if omitted)
167
+ **Returns:** Formatted date, time, and timezone. Handles common city names ("London", "Tokyo") in addition to IANA zones.
168
+
169
+ ## Full example
170
+
171
+ A voice agent with web search, weather, and a calculator:
172
+
173
+ ```typescript
174
+ import * as http from 'node:http';
175
+ import pino from 'pino';
176
+ import { createEndpoint } from '@jambonz/sdk/websocket';
177
+ import {
178
+ createTavilySearch,
179
+ createWeather,
180
+ createCalculator,
181
+ registerTools,
182
+ } from '@jambonz/tools';
183
+
184
+ const logger = pino({ level: 'info' });
185
+ const port = parseInt(process.env.PORT || '3000', 10);
186
+ const server = http.createServer();
187
+ const makeService = createEndpoint({ server, port });
188
+
189
+ const search = createTavilySearch({ apiKey: process.env.TAVILY_API_KEY! });
190
+ const weather = createWeather({ scale: 'fahrenheit' });
191
+ const calc = createCalculator();
192
+ const tools = [search, weather, calc];
193
+
194
+ const svc = makeService({ path: '/' });
195
+ svc.on('session:new', (session) => {
196
+ const log = logger.child({ call_sid: session.callSid });
197
+
198
+ session.on('/pipeline-complete', () => {
199
+ session.hangup().reply();
200
+ });
201
+
202
+ registerTools(session, '/tool-call', tools, { logger: log });
203
+
204
+ session
205
+ .pipeline({
206
+ stt: { vendor: 'deepgram', language: 'multi' },
207
+ tts: { vendor: 'cartesia', voice: '9626c31c-bec5-4cca-baa8-f8ba9e84c8bc' },
208
+ llm: {
209
+ vendor: 'openai',
210
+ model: 'gpt-4.1-mini',
211
+ llmOptions: {
212
+ messages: [
213
+ {
214
+ role: 'system',
215
+ content: [
216
+ 'You are a helpful voice assistant with access to web search, weather, and a calculator.',
217
+ 'Use the appropriate tool when the user asks about current events, weather, or math.',
218
+ 'Keep responses concise and conversational.',
219
+ ].join(' '),
220
+ },
221
+ ],
222
+ tools: tools.map((t) => t.schema),
223
+ },
224
+ },
225
+ toolHook: '/tool-call',
226
+ bargeIn: { enable: true },
227
+ turnDetection: 'krisp',
228
+ actionHook: '/pipeline-complete',
229
+ })
230
+ .send();
231
+ });
232
+
233
+ logger.info({ port }, 'listening');
234
+ ```
235
+
236
+ ## Mixing with custom tools and MCP
237
+
238
+ `@jambonz/tools` works alongside your own custom tools and MCP servers. Just include whatever you need:
239
+
240
+ ```typescript
241
+ // your custom tool
242
+ const myTool = {
243
+ name: 'lookup_order',
244
+ description: 'Look up an order by ID',
245
+ parameters: { type: 'object', properties: { order_id: { type: 'string' } }, required: ['order_id'] },
246
+ };
247
+
248
+ session.pipeline({
249
+ llm: {
250
+ llmOptions: {
251
+ tools: [
252
+ search.schema, // from @jambonz/tools
253
+ weather.schema, // from @jambonz/tools
254
+ myTool, // your custom schema
255
+ ],
256
+ },
257
+ },
258
+ mcpServers: [ // MCP tools discovered automatically
259
+ { url: 'https://mcp.example.com/sse' },
260
+ ],
261
+ toolHook: '/tool-call',
262
+ });
263
+
264
+ // register the pre-built tools
265
+ registerTools(session, '/tool-call', [search, weather], { logger: log });
266
+
267
+ // handle your custom tool separately
268
+ session.on('/tool-call', async (evt) => {
269
+ if (evt.name === 'lookup_order') {
270
+ const result = await db.orders.find(evt.arguments.order_id);
271
+ session.sendToolOutput(evt.tool_call_id, JSON.stringify(result));
272
+ }
273
+ });
274
+ ```
275
+
276
+ > **Note:** MCP tools are handled by the feature-server directly — they don't arrive on your toolHook. Only inline tools (from `llmOptions.tools`) route to your application.
277
+
278
+ ## Using with dynamic tools
279
+
280
+ You can inject `@jambonz/tools` tools mid-conversation using `updatePipeline()`:
281
+
282
+ ```typescript
283
+ // start without tools, then add them after a few turns
284
+ session.updatePipeline({
285
+ type: 'update_tools',
286
+ tools: [search.schema, weather.schema],
287
+ });
288
+ ```
289
+
290
+ ## Contributing a new tool
291
+
292
+ Adding a tool to this package:
293
+
294
+ 1. Create `src/tools/your-tool.ts`
295
+ 2. Export a factory function: `createYourTool(options?): JambonzTool`
296
+ 3. The factory returns `{ schema, execute }`
297
+ 4. Add the export to `src/index.ts`
298
+ 5. Add documentation to this README
299
+
300
+ ### Guidelines
301
+
302
+ - **Keep it simple** — a tool is a schema + an async function that returns a string. No classes, no inheritance.
303
+ - **Prefer free APIs** — tools that work without API keys have a lower barrier to entry. If an API key is required, make it the only required option.
304
+ - **Return text, not JSON** — the LLM reads the result as part of a conversation. Format output as natural language, not raw JSON.
305
+ - **Handle errors gracefully** — return a helpful message rather than throwing when possible (e.g., "No results found" rather than crashing).
306
+ - **Think voice-first** — the result will be spoken aloud. Avoid markdown, URLs, or formatting that sounds awkward when read by TTS.
307
+
308
+ ## License
309
+
310
+ MIT