@far-world-labs/verblets 0.1.2 → 0.1.3

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.
@@ -93,6 +93,9 @@ jobs:
93
93
  runs-on: ubuntu-latest
94
94
  needs: build
95
95
  if: github.ref == 'refs/heads/main' && github.event_name == 'push'
96
+ permissions:
97
+ contents: write
98
+ pull-requests: write
96
99
  steps:
97
100
  - uses: actions/checkout@v4
98
101
  with:
package/README.md CHANGED
@@ -108,76 +108,75 @@ Codebase utilities analyze, test, and improve code quality using AI reasoning. T
108
108
  This example shows how verblets enable building systems that understand context, make nuanced decisions, and adapt to complex real-world scenarios - capabilities that would be nearly impossible with traditional programming approaches.
109
109
 
110
110
  ```javascript
111
+ import bulkMap from './src/chains/bulk-map/index.js';
112
+ import list from './src/chains/list/index.js';
111
113
  import {
112
- intent,
113
- sentiment,
114
+ anonymize,
114
115
  bool,
115
116
  enum,
116
- toObject,
117
- anonymize,
117
+ intent,
118
+ listFilter,
118
119
  questions,
119
120
  sort,
120
- listFilter,
121
- summaryMap,
121
+ toObject,
122
+ sentiment,
123
+ bulkScore
122
124
  } from 'verblets';
123
125
 
124
126
  // Intelligent customer support system that handles complex, contextual requests
125
- async function handleCustomerRequest(customerMessage, customerHistory, productCatalog) {
126
- // 1. Understand what the customer actually wants
127
+ async function handleCustomerRequest(message, history, catalog) {
127
128
  const customerIntent = await intent({
128
- text: customerMessage,
129
+ text: message,
129
130
  operations: [
130
131
  { name: 'refund-request', parameters: { reason: 'string', orderNumber: 'string?' } },
131
132
  { name: 'product-inquiry', parameters: { productType: 'string', feature: 'string?' } },
132
133
  { name: 'technical-support', parameters: { issue: 'string', urgency: 'string' } },
133
- { name: 'complaint', parameters: { category: 'string', severity: 'string' } },
134
- ],
134
+ { name: 'complaint', parameters: { category: 'string', severity: 'string' } }
135
+ ]
135
136
  });
136
137
 
137
- // 2. Assess emotional state and urgency
138
- const emotionalState = await sentiment(customerMessage);
139
- const isUrgent = await bool(`Is this customer request urgent? ${customerMessage}`);
138
+ const emotion = await sentiment(message);
139
+ const urgent = await bool(`Is this urgent? ${message}`);
140
140
 
141
- // 3. Determine appropriate response strategy
142
- const responseStrategy = await enum(customerMessage, {
141
+ const strategy = await enum(message, {
143
142
  immediate_escalation: 'Customer is very upset, escalate to human agent',
144
143
  detailed_help: 'Customer needs comprehensive assistance',
145
144
  quick_resolution: 'Simple issue that can be resolved quickly',
146
- educational: 'Customer needs to understand how something works',
145
+ educational: 'Customer needs to understand how something works'
147
146
  });
148
147
 
149
- // 4. Generate contextual follow-up questions
150
- const clarifyingQuestions = await questions(
151
- `Customer says: "${customerMessage}". What should we ask to help them better?`
148
+ const followUpQuestions = await questions(
149
+ `Customer says: "${message}". What should we ask to help them better?`
152
150
  );
153
151
 
154
- // 5. Find relevant products/solutions from catalog
155
- const relevantProducts = await listFilter(
156
- productCatalog,
152
+ const candidates = await listFilter(
153
+ catalog,
157
154
  `Products that might solve: ${
158
155
  customerIntent.parameters.issue || customerIntent.parameters.productType
159
156
  }`
160
157
  );
161
158
 
162
- // 6. Prioritize solutions by customer context
163
- const prioritizedSolutions = await sort(
164
- relevantProducts,
165
- `by relevance to ${emotionalState} customer with ${customerIntent.intent.operation} request`
159
+ const scores = await bulkScore(candidates, 'resolution likelihood');
160
+ const prioritized = await sort(candidates, `by likelihood score ${scores.join(', ')}`);
161
+
162
+ const bulletPoints = await bulkMap(
163
+ prioritized.map(p => p.name),
164
+ 'Write a friendly one-line apology referencing <list>.'
166
165
  );
167
166
 
168
- // 7. Create anonymized case summary for training
167
+ const sampleReplies = await list('3 reassuring follow-up messages for this customer');
168
+
169
169
  const caseSummary = await anonymize(
170
- `Customer ${customerIntent.intent.operation}: ${customerMessage}.
171
- History: ${customerHistory}. Resolution: ${prioritizedSolutions[0]}`
170
+ `Customer ${customerIntent.intent.operation}: ${message}.
171
+ History: ${history}. Resolution: ${prioritized[0]}`
172
172
  );
173
173
 
174
- // 8. Structure the complete response
175
174
  const response = await toObject(
176
175
  `
177
176
  Customer needs ${customerIntent.intent.operation} help.
178
- They are ${emotionalState} and ${isUrgent ? 'urgent' : 'not urgent'}.
179
- Best approach: ${responseStrategy}.
180
- Top solution: ${prioritizedSolutions[0]?.name}
177
+ They are ${emotion} and ${urgent ? 'urgent' : 'not urgent'}.
178
+ Best approach: ${strategy}.
179
+ Top solution: ${prioritized[0]?.name}
181
180
  `,
182
181
  {
183
182
  type: 'object',
@@ -187,15 +186,18 @@ async function handleCustomerRequest(customerMessage, customerHistory, productCa
187
186
  emotion: { type: 'string' },
188
187
  strategy: { type: 'string' },
189
188
  recommendedSolution: { type: 'string' },
190
- followUpQuestions: { type: 'array' },
191
- },
189
+ followUpQuestions: { type: 'array' }
190
+ }
192
191
  }
193
192
  );
194
193
 
194
+
195
195
  return {
196
196
  ...response,
197
- followUpQuestions: clarifyingQuestions.slice(0, 3),
197
+ followUpQuestions: followUpQuestions.slice(0, 3),
198
198
  anonymizedCase: caseSummary,
199
+ bulletPoints,
200
+ sampleReplies
199
201
  };
200
202
  }
201
203
 
@@ -207,26 +209,28 @@ const result = await handleCustomerRequest(
207
209
  {
208
210
  name: 'Premium Wireless Headphones',
209
211
  category: 'audio',
210
- features: ['noise-canceling', 'wireless'],
212
+ features: ['noise-canceling', 'wireless']
211
213
  },
212
214
  { name: 'Express Shipping Upgrade', category: 'service', features: ['priority', 'tracking'] },
213
- { name: 'Gift Card', category: 'compensation', features: ['flexible', 'immediate'] },
215
+ { name: 'Gift Card', category: 'compensation', features: ['flexible', 'immediate'] }
214
216
  ]
215
217
  );
216
218
 
217
219
  /* Returns something like:
218
220
  {
219
- intent: "refund-request",
221
+ intent: "refund-request",
220
222
  urgency: "high",
221
223
  emotion: "frustrated",
222
224
  strategy: "immediate_escalation",
223
225
  recommendedSolution: "Express Shipping Upgrade",
224
226
  followUpQuestions: [
225
227
  "What's your order number so I can track this immediately?",
226
- "Would you like us to expedite a replacement for tomorrow delivery?",
228
+ "Would you like us to expedite a replacement for tomorrow delivery?",
227
229
  "How can we make this right for your daughter's birthday?"
228
230
  ],
229
231
  anonymizedCase: "Customer refund-request: Customer frustrated about delayed premium headphones order for child's birthday..."
232
+ bulletPoints: ["Apologies for the delay on Premium Wireless Headphones", ...],
233
+ sampleReplies: ["We're monitoring shipping updates...", ...]
230
234
  }
231
235
  */
232
236
  ```
@@ -240,427 +244,6 @@ This system demonstrates capabilities that would require thousands of lines of t
240
244
  - **Privacy-aware data handling** for compliance
241
245
  - **Structured output** that integrates with existing systems
242
246
 
243
- <<<<<<< HEAD
244
- With verblets, complex AI-powered workflows become as simple as calling functions.
245
- =======
246
- - **list** - Generate contextual lists
247
- ```javascript
248
- await list("potential failure points in our microservices architecture");
249
- ```
250
-
251
- - **intent** - Understand user intentions and extract structured data
252
- ```javascript
253
- // Define what operations your system can handle
254
- const operations = [
255
- {
256
- name: "book-flight",
257
- parameters: {
258
- destination: "string",
259
- date: "string",
260
- class: "string"
261
- }
262
- },
263
- {
264
- name: "find-song",
265
- parameters: {
266
- lyrics: "string",
267
- artist: "string?"
268
- }
269
- }
270
- ];
271
-
272
- // It understands flight booking requests
273
- const flightRequest = await intent({
274
- text: "I need a business class flight to Tokyo next Friday",
275
- operations
276
- });
277
- /* Returns:
278
- {
279
- "queryText": "I need a business class flight to Tokyo next Friday",
280
- "intent": {
281
- "operation": "book-flight",
282
- "displayName": "Book Flight"
283
- },
284
- "parameters": {
285
- "destination": "Tokyo",
286
- "date": "next Friday",
287
- "class": "business"
288
- }
289
- }
290
- */
291
-
292
- - **auto** - Use function calling to select and prepare operations
293
- ```javascript
294
- // First, we have our available functions defined as schemas
295
- const availableFunctions = [
296
- {
297
- "name": "bool",
298
- "description": "Answer yes/no questions about facts or situations",
299
- "parameters": {
300
- "type": "object",
301
- "properties": {
302
- "text": {
303
- "type": "string",
304
- "description": "The question to answer"
305
- }
306
- }
307
- }
308
- },
309
- {
310
- "name": "list",
311
- "description": "Generate lists of relevant items",
312
- "parameters": {
313
- "type": "object",
314
- "properties": {
315
- "text": {
316
- "type": "string",
317
- "description": "What to list"
318
- }
319
- }
320
- }
321
- },
322
- // ... many more function definitions ...
323
- ];
324
-
325
- // When someone has an urgent question
326
- const result = await auto(
327
- "Help! My dog just ate an avocado! Is this dangerous?"
328
- );
329
- /* First, auto selects the appropriate function:
330
- {
331
- "name": "bool", // Recognizes this needs a yes/no answer
332
- "arguments": {
333
- "text": "Help! My dog just ate an avocado! Is this dangerous?"
334
- },
335
- "functionArgsAsArray": [
336
- {
337
- "text": "Help! My dog just ate an avocado! Is this dangerous?"
338
- }
339
- ]
340
- }
341
- */
342
-
343
- // Then it automatically runs the selected function internally, passing arguments it extracts
344
- const answer = await bool(result.arguments.text);
345
- // Returns: true (yes, it can be dangerous)
346
- ```
347
-
348
- - **questions** - Explore topics through intelligent question generation
349
- ```javascript
350
-
351
- const investigation = await questions(
352
- "Why isn't my houseplant thriving?",
353
- {
354
- searchBreadth: 0.7, // Explore more broadly (0-1)
355
- shouldStop: (q, all) => all.length > 20 // Custom stopping condition
356
- }
357
- );
358
- /* Returns targeted diagnostic questions:
359
- [
360
- "Which direction does the window face?",
361
- "Are the leaves turning yellow or brown?",
362
- "Is the soil staying wet for long periods?",
363
- "Are there any visible pests on the leaves?",
364
- "When was it last repotted?",
365
- "Is there good drainage in the pot?",
366
- "What's the humidity level in the room?",
367
- // ... continues until stopping condition ...
368
- ]
369
- */
370
- ```
371
-
372
- - **shorten-text** - Intelligently compress text while preserving meaning
373
- ```javascript
374
- // Shorten long content while keeping key information
375
- const story = `Once upon a time, in a bustling city called Metropolis,
376
- there lived a young programmer named Ada. She spent her days writing
377
- elegant code and her nights dreaming of artificial intelligence.
378
- One day, while debugging a particularly tricky neural network,
379
- she discovered something extraordinary...`;
380
-
381
- const shortened = await shortenText(story, {
382
- targetTokenCount: 20,
383
- minCharsToRemove: 15
384
- });
385
- /* Returns:
386
- "Once upon a time, in Metropolis, a programmer named Ada
387
- spent her days writing code... discovered something extraordinary"
388
- */
389
-
390
- // Preserve structure while reducing size
391
- const documentation = `# API Reference
392
- ## Authentication
393
- All requests must include an API key in the header.
394
- The key should be prefixed with 'Bearer '.
395
-
396
- ## Rate Limiting
397
- Requests are limited to 100 per minute.
398
- Exceeding this will result in a 429 response.
399
-
400
- ## Endpoints
401
- GET /users - Retrieve user list
402
- POST /users - Create new user
403
- DELETE /users/{id} - Remove user`;
404
-
405
- const compact = await shortenText(documentation, {
406
- targetTokenCount: 30,
407
- minCharsToRemove: 20
408
- });
409
- /* Returns:
410
- "# API Reference
411
- ## Authentication
412
- All requests need API key... 'Bearer '
413
-
414
- ## Rate Limiting
415
- 100 per minute... 429 response
416
-
417
- ## Endpoints
418
- GET /users...DELETE /users/{id}"
419
- */
420
- ```
421
-
422
- - **bulk-map** - Map over lists in retryable batches using the `listMap` verblet
423
- ```javascript
424
- import { bulkMap } from './src/index.js';
425
-
426
- const gadgets = [
427
- 'solar-powered flashlight',
428
- 'quantum laptop',
429
- 'smart refrigerator',
430
- // ...more items
431
- ];
432
- const results = await bulkMap(
433
- gadgets,
434
- 'Give each item a catchphrase worthy of a blockbuster commercial',
435
- { chunkSize: 5, maxAttempts: 2 }
436
- );
437
- // results[0] === 'Illuminate your world with the sun'
438
- // results[1] === 'Computing beyond limits'
439
- ```
440
-
441
- - **bulk-reduce** - Reduce long lists sequentially using `listReduce`
442
- ```javascript
443
- import bulkReduce from './src/chains/bulk-reduce/index.js';
444
-
445
- const logLines = [
446
- 'User logged in',
447
- 'User viewed dashboard',
448
- 'User logged out'
449
- ];
450
- const summary = await bulkReduce(logLines, 'Summarize the events');
451
- // e.g. 'User session: login, dashboard view and logout'
452
- ```
453
-
454
- <<<<<<< HEAD
455
- **bulk-partition** - Discover the best categories and group large lists consistently
456
- ```javascript
457
- import bulkPartition from './src/chains/bulk-partition/index.js';
458
-
459
- const feedback = [
460
- 'Great interface and onboarding',
461
- 'Price is a bit steep',
462
- 'Love the mobile app',
463
- 'Needs more integrations'
464
- ];
465
- const result = await bulkPartition(
466
- feedback,
467
- 'Is each line praise, criticism, or a feature request?',
468
- { chunkSize: 2, topN: 3 }
469
- );
470
- // {
471
- // praise: ['Great interface and onboarding', 'Love the mobile app'],
472
- // criticism: ['Price is a bit steep'],
473
- // 'feature request': ['Needs more integrations']
474
- // }
475
- ```
476
-
477
- - **themes** - Extract recurring themes from text
478
- ```javascript
479
- const mainThemes = await themes(longReport, { topN: 3 });
480
- // ['strategy', 'market challenges', 'team morale']
481
- ```
482
- =======
483
- - **bulk-find** - Search lists in batches using `listFind`
484
- ```javascript
485
- import bulkFind, { bulkFindRetry } from './src/lib/bulk-find/index.js';
486
-
487
- const diaryEntries = [
488
- 'Hiked up the mountains today and saw breathtaking views',
489
- 'Visited the local market and tried a spicy stew',
490
- 'Spotted penguins playing on the beach this morning'
491
- ];
492
- const match = await bulkFindRetry(diaryEntries, 'mentions penguins', {
493
- chunkSize: 2,
494
- maxAttempts: 2
495
- });
496
- // => 'Spotted penguins playing on the beach this morning'
497
- ```
498
-
499
- >>>>>>> f71abac (Restore eslint disable comment)
500
- - **search-best-first** - Intelligently explore solution spaces
501
- ```javascript
502
- // Find the best recipe adjustments when ingredients are missing
503
- const search = new BestFirstSearch({
504
- initialState: {
505
- recipe: "Classic Tiramisu",
506
- missing: ["mascarpone cheese", "ladyfingers"],
507
- available: ["cream cheese", "pound cake", "whipped cream"]
508
- },
509
- goalTest: state => state.substitutions.complete && state.flavor.preserved,
510
- heuristic: state => state.flavor.similarity
511
- });
512
-
513
- const path = await search.findPath();
514
- /* Returns sequence of steps:
515
- [
516
- {
517
- action: "substitute mascarpone",
518
- details: "Mix 8oz cream cheese with 1/4 cup whipped cream",
519
- confidence: 0.85
520
- },
521
- {
522
- action: "substitute ladyfingers",
523
- details: "Slice pound cake, toast until crisp, soak in coffee",
524
- confidence: 0.75
525
- },
526
- {
527
- action: "adjust ratios",
528
- details: "Increase coffee soak time to 45 seconds for pound cake",
529
- confidence: 0.9
530
- }
531
- ]
532
- */
533
-
534
- // Or find the optimal way to refactor complex code
535
- const refactorPath = await new BestFirstSearch({
536
- initialState: {
537
- file: "src/legacy-parser.js",
538
- metrics: {
539
- complexity: 85,
540
- coverage: 0.4,
541
- maintainability: "D"
542
- }
543
- },
544
- goalTest: state =>
545
- state.metrics.complexity < 30 &&
546
- state.metrics.coverage > 0.8 &&
547
- state.metrics.maintainability === "A",
548
- heuristic: state =>
549
- (1 / state.metrics.complexity) *
550
- state.metrics.coverage *
551
- (state.metrics.maintainability === "A" ? 1 : 0.5)
552
- }).findPath();
553
- /* Returns optimal refactoring sequence:
554
- [
555
- {
556
- action: "extract function",
557
- target: "parseNestedBlocks()",
558
- benefit: "Reduces complexity by 40%"
559
- },
560
- {
561
- action: "add unit tests",
562
- coverage: ["error handling", "edge cases"],
563
- benefit: "Coverage increases to 85%"
564
- },
565
- {
566
- action: "implement strategy pattern",
567
- components: ["BlockParser", "InlineParser"],
568
- benefit: "Maintainability improves to grade A"
569
- }
570
- ]
571
- */
572
- ```
573
-
574
- - **test-advice** - Get comprehensive testing and code quality insights
575
- ```javascript
576
- // Get deep analysis of your code's test coverage and quality
577
- const insights = await testAdvice("src/payment-processor.js");
578
- /* Returns array of findings across multiple dimensions:
579
- [
580
- {
581
- "name": "Boundary Testing",
582
- "expected": "Handle zero-amount transactions",
583
- "saw": "No validation for $0.00 payments in processPayment()",
584
- "isSuccess": false
585
- },
586
- {
587
- "name": "Success Scenarios",
588
- "expected": "Processes standard credit card payment",
589
- "saw": "Correctly handles Visa/MC format: line 47 validateCard()",
590
- "isSuccess": true
591
- },
592
- {
593
- "name": "Clean Code",
594
- "expected": "Single responsibility in transaction logging",
595
- "saw": "logPayment() mixing business logic with logging: line 92",
596
- "isSuccess": false
597
- },
598
- // ... analyzes across 8 dimensions:
599
- // - Boundary cases
600
- // - Success scenarios
601
- // - Failure modes
602
- // - Potential defects
603
- // - Best practices
604
- // - Clean code principles
605
- // - Code quality
606
- // - Refactoring opportunities
607
- ]
608
- */
609
- ```
610
-
611
- - **veiled-variants** - Mask sensitive queries with safer alternatives
612
- ```javascript
613
- const alternatives = await veiledVariants({
614
- prompt: "If pigeons are government spies, how can I ask for counter-surveillance tips without sounding paranoid?"
615
- });
616
- /* Returns 15 reframed queries */
617
- ```
618
-
619
- - **scan-js** - Analyze code for quality and maintainability
620
- ```javascript
621
- // Analyze your codebase for maintainability
622
- const analysis = await scanJs({
623
- entry: "src/app.js",
624
- features: "maintainability"
625
- });
626
- /* Returns analysis of each function:
627
- {
628
- "src/app.js:::handlePayment": {
629
- "complexity": "low",
630
- "documentation": "well-documented",
631
- "sideEffects": "isolated to database calls",
632
- "errorHandling": "comprehensive",
633
- "testability": "high"
634
- },
635
- "src/app.js:::validateInput": {
636
- "complexity": "medium",
637
- "documentation": "needs improvement",
638
- "sideEffects": "pure function",
639
- "errorHandling": "basic validation only",
640
- "testability": "high"
641
- }
642
- // ... continues for all functions ...
643
- }
644
- */
645
-
646
- // Focus on specific quality aspects
647
- const security = await scanJs({
648
- entry: "src/auth/",
649
- features: "security"
650
- });
651
- /* Returns security-focused analysis:
652
- {
653
- "src/auth/login.js:::hashPassword": {
654
- "inputValidation": "sanitizes all inputs",
655
- "cryptography": "uses current best practices",
656
- "dataExposure": "no sensitive data in logs",
657
- "authentication": "implements rate limiting"
658
- }
659
- // ... continues for all security-relevant functions ...
660
- }
661
- */
662
- ```
663
- >>>>>>> 01eb5cf (Add themes chain for dual reduce)
664
247
 
665
248
  ## Contributing
666
249
 
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "@far-world-labs/verblets",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "description": "OpenAI Client",
5
- "main": "index.js",
5
+ "main": "src/index.js",
6
6
  "type": "module",
7
7
  "repository": {
8
8
  "type": "git",
@@ -1,11 +1,16 @@
1
1
  import fs from 'node:fs/promises';
2
2
  import { describe, expect, it, vi } from 'vitest';
3
+ import { fileURLToPath } from 'url';
4
+ import { dirname, join } from 'path';
3
5
 
4
6
  import toObject from '../../verblets/to-object/index.js';
5
7
  import list from './index.js';
6
8
 
9
+ const __filename = fileURLToPath(import.meta.url);
10
+ const __dirname = dirname(__filename);
11
+
7
12
  const loadSchema = async () => {
8
- const file = (await fs.readFile('./src/json-schemas/cars-test.json')).toString();
13
+ const file = (await fs.readFile(join(__dirname, '../../json-schemas/cars-test.json'))).toString();
9
14
 
10
15
  return toObject(file);
11
16
  };
@@ -1,5 +1,5 @@
1
- import fs from 'node:fs/promises';
2
- import path from 'node:path';
1
+ import fs from 'fs/promises';
2
+ import path from 'path';
3
3
 
4
4
  import parseJSParts from '../parse-js-parts/index.js';
5
5
  import search from '../search-best-first/index.js';
@@ -10,12 +10,12 @@ export class Node {
10
10
  }
11
11
 
12
12
  toString() {
13
- return `${this.filename}:::${this.functionName ?? ''}`;
13
+ return `${this.filename}:${this.functionName}`;
14
14
  }
15
15
  }
16
16
 
17
17
  const processLocalImport = async (source) => {
18
- const importedFile = await fs.readFile(source, 'utf8');
18
+ const importedFile = await fs.readFile(source, 'utf-8');
19
19
  const parsedImport = parseJSParts(source, importedFile);
20
20
  return Object.entries(parsedImport.functionsMap).map(([importKey, importValue]) => ({
21
21
  filename: source,
@@ -28,18 +28,35 @@ const processNpmImport = async (source, includeNodeModules = false) => {
28
28
  if (!includeNodeModules) return [];
29
29
 
30
30
  try {
31
- const packageJson = JSON.parse(await fs.readFile('./package.json', 'utf8'));
31
+ // Find the project root by looking for package.json
32
+ let currentDir = process.cwd();
33
+ let packageJsonPath = path.join(currentDir, 'package.json');
34
+
35
+ // If not found in current directory, try to find it in parent directories
36
+ while (
37
+ !(await fs
38
+ .access(packageJsonPath)
39
+ .then(() => true)
40
+ .catch(() => false))
41
+ ) {
42
+ const parentDir = path.dirname(currentDir);
43
+ if (parentDir === currentDir) break; // Reached filesystem root
44
+ currentDir = parentDir;
45
+ packageJsonPath = path.join(currentDir, 'package.json');
46
+ }
47
+
48
+ const packageJson = JSON.parse(await fs.readFile(packageJsonPath, 'utf8'));
32
49
  if (packageJson.dependencies[source] || packageJson.devDependencies[source]) {
33
- const nodeModulePath = `./node_modules/${source}`;
50
+ const nodeModulePath = path.join(currentDir, 'node_modules', source);
34
51
  const npmPackageJson = JSON.parse(
35
- await fs.readFile(`${nodeModulePath}/package.json`, 'utf8')
52
+ await fs.readFile(path.join(nodeModulePath, 'package.json'), 'utf8')
36
53
  );
37
54
  const mainFilePath = npmPackageJson.main || 'index.js';
38
- const importedFile = await fs.readFile(`${nodeModulePath}/${mainFilePath}`, 'utf-8');
55
+ const importedFile = await fs.readFile(path.join(nodeModulePath, mainFilePath), 'utf-8');
39
56
  const parsedImport = parseJSParts(mainFilePath, importedFile);
40
57
 
41
58
  return Object.entries(parsedImport.functionsMap).map(([importKey, importValue]) => ({
42
- filename: `${nodeModulePath}/${mainFilePath}`,
59
+ filename: path.join(nodeModulePath, mainFilePath),
43
60
  functionName: importKey,
44
61
  functionData: importValue,
45
62
  }));
@@ -1,5 +1,20 @@
1
- import whisper from 'whisper-node';
2
- import record from 'node-record-lpcm16';
1
+ // Lazy import whisper to avoid initialization issues
2
+ let whisper;
3
+ let record;
4
+
5
+ async function getWhisper() {
6
+ if (!whisper) {
7
+ whisper = (await import('whisper-node')).default;
8
+ }
9
+ return whisper;
10
+ }
11
+
12
+ async function getRecord() {
13
+ if (!record) {
14
+ record = (await import('node-record-lpcm16')).default;
15
+ }
16
+ return record;
17
+ }
3
18
 
4
19
  export default class Transcriber {
5
20
  constructor(targetWord, silenceDuration = 5000, wordPauseDuration = 2000) {
@@ -11,8 +26,9 @@ export default class Transcriber {
11
26
  this.recording = null;
12
27
  }
13
28
 
14
- startRecording() {
15
- this.recording = record.record({
29
+ async startRecording() {
30
+ const recordModule = await getRecord();
31
+ this.recording = recordModule.record({
16
32
  sampleRateHertz: 16000,
17
33
  threshold: 0,
18
34
  recordProgram: 'rec',
@@ -29,8 +45,9 @@ export default class Transcriber {
29
45
  }
30
46
  }
31
47
 
32
- transcribe(stream) {
33
- whisper
48
+ async transcribe(stream) {
49
+ const whisperModule = await getWhisper();
50
+ whisperModule
34
51
  .transcribeStream(stream, { streaming: true })
35
52
  .then((transcription) => {
36
53
  this.handleTranscription(transcription);
@@ -1,21 +1,20 @@
1
- import fs from 'node:fs/promises';
2
-
3
- import {
4
- contentIsExample,
5
- contentIsSchema,
6
- onlyJSON,
7
- contentIsIntent,
8
- contentIsOperationOption,
9
- contentIsParametersOptions,
10
- } from './constants.js';
1
+ import fs from 'fs/promises';
2
+ import { fileURLToPath } from 'url';
3
+ import { dirname, join } from 'path';
11
4
  import wrapVariable from './wrap-variable.js';
5
+ import { onlyJSON } from './constants.js';
6
+
7
+ const __filename = fileURLToPath(import.meta.url);
8
+ const __dirname = dirname(__filename);
9
+
10
+ const contentIsIntent = 'The intent is:';
11
+ const contentIsSchema = 'The schema is:';
12
+ const contentIsExample = 'An example of the output is:';
13
+ const contentIsOperationOption = 'The possible operations are:';
14
+ const contentIsParametersOptions = 'The possible parameters are:';
12
15
 
13
16
  const exampleJSON = `{
14
- "queryText": "play some music",
15
- "intent": {
16
- "operation": "play-music",
17
- "displayName": "Play Music"
18
- },
17
+ "intent": "play_music",
19
18
  "parameters": {
20
19
  "genre": "rock"
21
20
  },
@@ -24,7 +23,7 @@ const exampleJSON = `{
24
23
  }
25
24
  }`;
26
25
 
27
- const intentSchema = JSON.parse(await fs.readFile('./src/json-schemas/intent.json'));
26
+ const intentSchema = JSON.parse(await fs.readFile(join(__dirname, '../json-schemas/intent.json')));
28
27
 
29
28
  /**
30
29
  * Approximates intent recognition like you might find with Wit.ai,
@@ -1,26 +1,31 @@
1
1
  import Ajv from 'ajv';
2
2
  import fs from 'node:fs/promises';
3
3
  import { describe, expect, it, beforeAll, afterAll } from 'vitest';
4
-
4
+ import { expect as aiExpect } from '../../chains/expect/index.js';
5
5
  import { longTestTimeout } from '../../constants/common.js';
6
- import aiExpect from '../expect/index.js';
6
+ import { fileURLToPath } from 'url';
7
+ import { dirname, join } from 'path';
8
+
7
9
  import intent from './index.js';
8
10
 
9
- const resultSchema = async () => {
10
- return JSON.parse(await fs.readFile('./src/json-schemas/intent.json'));
11
- };
11
+ const __filename = fileURLToPath(import.meta.url);
12
+ const __dirname = dirname(__filename);
13
+
14
+ async function getIntentSchema() {
15
+ return JSON.parse(await fs.readFile(join(__dirname, '../../json-schemas/intent.json')));
16
+ }
12
17
 
13
18
  const examples = [
14
19
  {
15
20
  inputs: { text: 'Give me a flight to Burgas' },
16
- want: { resultSchema },
21
+ want: { resultSchema: getIntentSchema },
17
22
  },
18
23
  {
19
24
  inputs: {
20
25
  text: 'Lookup a song by the quote \
21
26
  "I just gotta tell you how I\'m feeling"',
22
27
  },
23
- want: { resultSchema },
28
+ want: { resultSchema: getIntentSchema },
24
29
  },
25
30
  ];
26
31
 
@@ -85,7 +90,7 @@ describe('Intent verblet', () => {
85
90
  const result = await intent({ text: travelRequest });
86
91
 
87
92
  // Traditional schema validation
88
- const schema = await resultSchema();
93
+ const schema = await getIntentSchema();
89
94
  const ajv = new Ajv();
90
95
  const validate = ajv.compile(schema);
91
96
  expect(validate(result)).toBe(true);
@@ -112,7 +117,7 @@ describe('Intent verblet', () => {
112
117
  const result = await intent({ text: musicQuery });
113
118
 
114
119
  // Schema validation
115
- const schema = await resultSchema();
120
+ const schema = await getIntentSchema();
116
121
  const ajv = new Ajv();
117
122
  const validate = ajv.compile(schema);
118
123
  expect(validate(result)).toBe(true);