@liquidmetal-ai/precip 1.0.0

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.
Files changed (78) hide show
  1. package/.prettierrc +9 -0
  2. package/CHANGELOG.md +8 -0
  3. package/eslint.config.mjs +28 -0
  4. package/package.json +53 -0
  5. package/src/engine/agent.ts +478 -0
  6. package/src/engine/llm-provider.test.ts +275 -0
  7. package/src/engine/llm-provider.ts +330 -0
  8. package/src/engine/stream-parser.ts +170 -0
  9. package/src/index.ts +142 -0
  10. package/src/mounts/mount-manager.test.ts +516 -0
  11. package/src/mounts/mount-manager.ts +327 -0
  12. package/src/mounts/mount-registry.ts +196 -0
  13. package/src/mounts/zod-to-string.test.ts +154 -0
  14. package/src/mounts/zod-to-string.ts +213 -0
  15. package/src/presets/agent-tools.ts +57 -0
  16. package/src/presets/index.ts +5 -0
  17. package/src/sandbox/README.md +1321 -0
  18. package/src/sandbox/bridges/README.md +571 -0
  19. package/src/sandbox/bridges/actor.test.ts +229 -0
  20. package/src/sandbox/bridges/actor.ts +195 -0
  21. package/src/sandbox/bridges/bridge-fixes.test.ts +614 -0
  22. package/src/sandbox/bridges/bucket.test.ts +300 -0
  23. package/src/sandbox/bridges/cleanup-reproduction.test.ts +225 -0
  24. package/src/sandbox/bridges/console-multiple.test.ts +187 -0
  25. package/src/sandbox/bridges/console.test.ts +157 -0
  26. package/src/sandbox/bridges/console.ts +122 -0
  27. package/src/sandbox/bridges/fetch.ts +93 -0
  28. package/src/sandbox/bridges/index.ts +78 -0
  29. package/src/sandbox/bridges/readable-stream.ts +323 -0
  30. package/src/sandbox/bridges/response.test.ts +154 -0
  31. package/src/sandbox/bridges/response.ts +123 -0
  32. package/src/sandbox/bridges/review-fixes.test.ts +331 -0
  33. package/src/sandbox/bridges/search.test.ts +475 -0
  34. package/src/sandbox/bridges/search.ts +264 -0
  35. package/src/sandbox/bridges/shared/body-methods.ts +93 -0
  36. package/src/sandbox/bridges/shared/cleanup.ts +112 -0
  37. package/src/sandbox/bridges/shared/convert.ts +76 -0
  38. package/src/sandbox/bridges/shared/headers.ts +181 -0
  39. package/src/sandbox/bridges/shared/index.ts +36 -0
  40. package/src/sandbox/bridges/shared/json-helpers.ts +77 -0
  41. package/src/sandbox/bridges/shared/path-parser.ts +109 -0
  42. package/src/sandbox/bridges/shared/promise-helper.ts +108 -0
  43. package/src/sandbox/bridges/shared/registry-setup.ts +84 -0
  44. package/src/sandbox/bridges/shared/response-object.ts +280 -0
  45. package/src/sandbox/bridges/shared/result-builder.ts +130 -0
  46. package/src/sandbox/bridges/shared/scope-helpers.ts +44 -0
  47. package/src/sandbox/bridges/shared/stream-reader.ts +90 -0
  48. package/src/sandbox/bridges/storage-bridge.test.ts +893 -0
  49. package/src/sandbox/bridges/storage.ts +421 -0
  50. package/src/sandbox/bridges/text-decoder.ts +190 -0
  51. package/src/sandbox/bridges/text-encoder.ts +102 -0
  52. package/src/sandbox/bridges/types.ts +39 -0
  53. package/src/sandbox/bridges/utils.ts +123 -0
  54. package/src/sandbox/index.ts +6 -0
  55. package/src/sandbox/quickjs-wasm.d.ts +9 -0
  56. package/src/sandbox/sandbox.test.ts +191 -0
  57. package/src/sandbox/sandbox.ts +831 -0
  58. package/src/sandbox/test-helper.ts +43 -0
  59. package/src/sandbox/test-mocks.ts +154 -0
  60. package/src/sandbox/user-stream.test.ts +77 -0
  61. package/src/skills/frontmatter.test.ts +305 -0
  62. package/src/skills/frontmatter.ts +200 -0
  63. package/src/skills/index.ts +9 -0
  64. package/src/skills/skills-loader.test.ts +237 -0
  65. package/src/skills/skills-loader.ts +200 -0
  66. package/src/tools/actor-storage-tools.ts +250 -0
  67. package/src/tools/code-tools.test.ts +199 -0
  68. package/src/tools/code-tools.ts +444 -0
  69. package/src/tools/file-tools.ts +206 -0
  70. package/src/tools/registry.ts +125 -0
  71. package/src/tools/script-tools.ts +145 -0
  72. package/src/tools/smartbucket-tools.ts +203 -0
  73. package/src/tools/sql-tools.ts +213 -0
  74. package/src/tools/tool-factory.ts +119 -0
  75. package/src/types.ts +512 -0
  76. package/tsconfig.eslint.json +5 -0
  77. package/tsconfig.json +15 -0
  78. package/vitest.config.ts +33 -0
@@ -0,0 +1,571 @@
1
+ # QuickJS Bridge System
2
+
3
+ A modular system for bridging runtime APIs into QuickJS sandbox environments.
4
+
5
+ ## Overview
6
+
7
+ The bridge system provides a clean, reusable architecture for exposing host runtime APIs to sandboxed code. Each bridge is self-contained and follows consistent patterns for:
8
+
9
+ - Creating async functions that return promises
10
+ - Managing state across calls
11
+ - Converting between JavaScript and QuickJS types
12
+ - Tracking promises for proper cleanup
13
+
14
+ ## Architecture
15
+
16
+ ```
17
+ src/sandbox/bridges/
18
+ ├── types.ts # TypeScript types for bridge system
19
+ ├── utils.ts # Reusable bridge utilities
20
+ ├── index.ts # Main entry point, exports all bridges
21
+ ├── fetch.ts # Fetch API bridge (Response, Headers)
22
+ ├── response.ts # Response constructor bridge
23
+ ├── console.ts # Console API bridge (console.log, warn, error)
24
+ ├── text-encoder.ts # TextEncoder bridge
25
+ ├── text-decoder.ts # TextDecoder bridge (with stateful instances)
26
+ └── readable-stream.ts # ReadableStream and Reader bridges
27
+ ```
28
+
29
+ ## Core Utilities
30
+
31
+ ### BridgeContext
32
+
33
+ All bridges receive a `BridgeContext` object:
34
+
35
+ ```typescript
36
+ interface BridgeContext {
37
+ context: QuickJSContext; // QuickJS context
38
+ runtime: any; // QuickJS runtime
39
+ tracker: PromiseTracker; // Promise tracker for async ops
40
+ logger?: Logger; // Optional logger
41
+ }
42
+ ```
43
+
44
+ ### Utility Functions
45
+
46
+ #### `convertToHandle(context, value)`
47
+
48
+ Converts JavaScript values to QuickJS handles:
49
+
50
+ ```typescript
51
+ const handle = convertToHandle(context, { foo: 'bar' });
52
+ context.setProp(objectHandle, 'data', handle);
53
+ handle.dispose();
54
+ ```
55
+
56
+ #### `createAsyncBridge(ctx, name, hostFn)`
57
+
58
+ Creates an async function that bridges to a host function:
59
+
60
+ ```typescript
61
+ const readFileHandle = createAsyncBridge(ctx, 'readFile', async path => {
62
+ return await fs.readFile(path, 'utf-8');
63
+ });
64
+
65
+ context.setProp(context.global, 'readFile', readFileHandle);
66
+ readFileHandle.dispose();
67
+ ```
68
+
69
+ #### `createSyncBridge(ctx, name, hostFn)`
70
+
71
+ Creates a synchronous function bridge:
72
+
73
+ ```typescript
74
+ const mathAbsHandle = createSyncBridge(ctx, 'abs', x => Math.abs(x));
75
+ context.setProp(mathObj, 'abs', mathAbsHandle);
76
+ mathAbsHandle.dispose();
77
+ ```
78
+
79
+ #### `defineClass(ctx, classCode, className)`
80
+
81
+ Defines an ES6 class in the sandbox using `evalCode`:
82
+
83
+ ```typescript
84
+ const classHandle = defineClass(
85
+ ctx,
86
+ `
87
+ class MyClass {
88
+ constructor(value) {
89
+ this.value = value;
90
+ }
91
+ getValue() {
92
+ return this.value;
93
+ }
94
+ }
95
+ `,
96
+ 'MyClass'
97
+ );
98
+
99
+ if (classHandle) {
100
+ context.setProp(context.global, 'MyClass', classHandle);
101
+ classHandle.dispose();
102
+ }
103
+ ```
104
+
105
+ #### `setProperties(context, handle, props)`
106
+
107
+ Sets multiple properties at once:
108
+
109
+ ```typescript
110
+ setProperties(context, responseHandle, {
111
+ status: 200,
112
+ ok: true,
113
+ statusText: 'OK'
114
+ });
115
+ ```
116
+
117
+ #### `addMethod(context, objHandle, methodName, methodHandle)`
118
+
119
+ Adds a method to an object and disposes the method handle:
120
+
121
+ ```typescript
122
+ const getMethod = context.newFunction('get', nameHandle => {
123
+ const name = context.dump(nameHandle);
124
+ return context.newString(getValue(name));
125
+ });
126
+
127
+ addMethod(context, headersHandle, 'get', getMethod);
128
+ ```
129
+
130
+ ## Creating a New Bridge
131
+
132
+ ### Example: Date API Bridge
133
+
134
+ ```typescript
135
+ // src/sandbox/bridges/date.ts
136
+ import type { BridgeContext } from './types.js';
137
+ import { createSyncBridge, createAsyncBridge } from './utils.js';
138
+
139
+ export function installDate(ctx: BridgeContext): void {
140
+ const { context, logger } = ctx;
141
+
142
+ // Simple sync function
143
+ const nowHandle = createSyncBridge(ctx, 'now', () => Date.now());
144
+ context.setProp(context.global, 'dateNow', nowHandle);
145
+ nowHandle.dispose();
146
+
147
+ // Async function with delay
148
+ const delayHandle = createAsyncBridge(ctx, 'delay', async (ms: number) => {
149
+ await new Promise(resolve => setTimeout(resolve, ms));
150
+ return `Delayed ${ms}ms`;
151
+ });
152
+ context.setProp(context.global, 'delay', delayHandle);
153
+ delayHandle.dispose();
154
+
155
+ logger?.info?.('[Bridge] Date API installed');
156
+ }
157
+ ```
158
+
159
+ ### Example: ES6 Class Bridge
160
+
161
+ ```typescript
162
+ // src/sandbox/bridges/timer.ts
163
+ import type { BridgeContext } from './types.js';
164
+ import { defineClass } from './utils.js';
165
+
166
+ export function installTimer(ctx: BridgeContext): void {
167
+ const { context, logger } = ctx;
168
+
169
+ // Track timer instances
170
+ const timers = new Map<number, any>();
171
+ let timerIdCounter = 0;
172
+
173
+ // Host function to start timer
174
+ const startTimerHandle = context.newFunction('__hostStartTimer', msHandle => {
175
+ const ms = context.dump(msHandle);
176
+ const timerId = ++timerIdCounter;
177
+
178
+ const handle = setTimeout(() => {
179
+ timers.delete(timerId);
180
+ }, ms);
181
+
182
+ timers.set(timerId, handle);
183
+ return context.newNumber(timerId);
184
+ });
185
+
186
+ context.setProp(context.global, '__hostStartTimer', startTimerHandle);
187
+ startTimerHandle.dispose();
188
+
189
+ // Define Timer class
190
+ const classHandle = defineClass(
191
+ ctx,
192
+ `
193
+ class Timer {
194
+ constructor(ms) {
195
+ this.id = __hostStartTimer(ms);
196
+ this.ms = ms;
197
+ }
198
+
199
+ getId() {
200
+ return this.id;
201
+ }
202
+ }
203
+ `,
204
+ 'Timer'
205
+ );
206
+
207
+ if (classHandle) {
208
+ context.setProp(context.global, 'Timer', classHandle);
209
+ classHandle.dispose();
210
+ logger?.info?.('[Bridge] Timer installed');
211
+ }
212
+ }
213
+ ```
214
+
215
+ ### Register in `bridges/index.ts`
216
+
217
+ ```typescript
218
+ export { installDate } from './date.js';
219
+ export { installTimer } from './timer.js';
220
+
221
+ // Add to installAllBridges
222
+ export function installAllBridges(ctx: BridgeContext): void {
223
+ installFetch(ctx);
224
+ installResponse(ctx);
225
+ installTextEncoder(ctx);
226
+ installTextDecoder(ctx);
227
+ installDate(ctx); // Add your bridge
228
+ installTimer(ctx); // Add your bridge
229
+
230
+ ctx.logger?.info?.('[Bridge] All bridges installed');
231
+ }
232
+ ```
233
+
234
+ ## Existing Bridges
235
+
236
+ ### fetch (fetch.ts)
237
+
238
+ Bridges runtime's native `fetch` API with:
239
+
240
+ - Full Response object (status, headers, body)
241
+ - Headers object with `get()`, `has()`, `entries()` methods
242
+ - Body methods: `json()`, `text()`, `arrayBuffer()`
243
+ - ReadableStream support for response.body
244
+
245
+ **Usage in sandbox:**
246
+
247
+ ```javascript
248
+ const res = await fetch('https://api.example.com/data');
249
+ const data = await res.json();
250
+ const contentType = res.headers.get('content-type');
251
+ ```
252
+
253
+ ### Response (response.ts)
254
+
255
+ Provides `Response` constructor for creating Response objects manually with:
256
+
257
+ - Same interface as browser Response API
258
+ - Accepts body (string, ReadableStream, Uint8Array) and init options
259
+ - Convenience methods for consuming ReadableStreams: `text()`, `json()`, `arrayBuffer()`
260
+ - Useful for wrapping streams, testing, and creating mock responses
261
+
262
+ **Usage in sandbox:**
263
+
264
+ ```javascript
265
+ // Wrap a ReadableStream for easy consumption
266
+ const stream = someAsyncFunction(); // Returns a ReadableStream
267
+ const response = new Response(stream);
268
+ const text = await response.text();
269
+
270
+ // Create mock response for testing
271
+ const mockResponse = new Response('Hello World', {
272
+ status: 200,
273
+ headers: { 'Content-Type': 'text/plain' }
274
+ });
275
+ const data = await mockResponse.text();
276
+
277
+ // Create error response
278
+ const errorResponse = new Response('Not found', {
279
+ status: 404,
280
+ statusText: 'Not Found'
281
+ });
282
+ ```
283
+
284
+ ### Console (console.ts)
285
+
286
+ Bridges runtime's `console` API with:
287
+
288
+ - `console.log()` - Standard logging (shown in stdout)
289
+ - `console.warn()` - Warning messages (prefixed with [WARN])
290
+ - `console.error()` - Error messages (prefixed with [ERROR])
291
+ - Output is captured and returned with sandbox execution result
292
+ - Multiple arguments are formatted and joined
293
+
294
+ **Usage in sandbox:**
295
+
296
+ ```javascript
297
+ console.log('Processing data...');
298
+ const data = await fetch('https://api.example.com/data');
299
+ console.log('Fetched items:', data.length);
300
+ console.warn('High memory usage');
301
+ console.error('Failed to parse:', error);
302
+ ```
303
+
304
+ **Output format:**
305
+ When using the `run_code` tool, console output is formatted with return value:
306
+
307
+ ```
308
+ [stdout]
309
+ Processing data...
310
+ Fetched items: 42
311
+
312
+ [result]
313
+ { success: true, ... }
314
+ ```
315
+
316
+ ### TextEncoder (text-encoder.ts)
317
+
318
+ Bridges runtime's `TextEncoder` for encoding strings to Uint8Array.
319
+
320
+ **Usage in sandbox:**
321
+
322
+ ```javascript
323
+ const encoder = new TextEncoder();
324
+ const bytes = encoder.encode('Hello, World!');
325
+ ```
326
+
327
+ ### TextDecoder (text-decoder.ts)
328
+
329
+ Bridges runtime's `TextDecoder` with stateful instance tracking for streaming decode.
330
+
331
+ **Usage in sandbox:**
332
+
333
+ ```javascript
334
+ const decoder = new TextDecoder();
335
+
336
+ // Streaming decode
337
+ const text1 = decoder.decode(chunk1, { stream: true });
338
+ const text2 = decoder.decode(chunk2, { stream: true });
339
+ const text3 = decoder.decode(); // Final flush
340
+ ```
341
+
342
+ ### ReadableStream (readable-stream.ts)
343
+
344
+ Bridges runtime's `ReadableStream` for response.body streaming.
345
+
346
+ **Usage in sandbox:**
347
+
348
+ ```javascript
349
+ const reader = response.body.getReader();
350
+ while (true) {
351
+ const { done, value } = await reader.read();
352
+ if (done) break;
353
+ // Process chunk (Uint8Array)
354
+ }
355
+ reader.releaseLock();
356
+ ```
357
+
358
+ ## Benefits
359
+
360
+ ### 1. Modularity
361
+
362
+ Each bridge is self-contained in its own file, making it easy to:
363
+
364
+ - Understand what it does
365
+ - Test in isolation
366
+ - Modify without affecting others
367
+ - Remove if not needed
368
+
369
+ ### 2. Reusability
370
+
371
+ Common patterns are extracted to `utils.ts`:
372
+
373
+ - `createAsyncBridge` - Standard async function pattern
374
+ - `createSyncBridge` - Standard sync function pattern
375
+ - `defineClass` - ES6 class definition pattern
376
+ - `convertToHandle` - Type conversion pattern
377
+
378
+ ### 3. Consistency
379
+
380
+ All bridges follow the same structure:
381
+
382
+ 1. Receive `BridgeContext`
383
+ 2. Create host functions if needed
384
+ 3. Define sandbox API
385
+ 4. Log installation
386
+
387
+ ### 4. Extensibility
388
+
389
+ Adding new bridges is straightforward:
390
+
391
+ 1. Create new file in `bridges/`
392
+ 2. Export installation function
393
+ 3. Register in `bridges/index.ts`
394
+ 4. Document in this file
395
+
396
+ ### 5. Maintainability
397
+
398
+ Before: 885 lines in one function
399
+ After: 450 lines in sandbox.ts + focused bridge modules
400
+
401
+ ## Common Patterns
402
+
403
+ ### Pattern 1: Simple Sync Function
404
+
405
+ ```typescript
406
+ const fnHandle = createSyncBridge(ctx, 'funcName', (arg1, arg2) => {
407
+ return result;
408
+ });
409
+ context.setProp(context.global, 'funcName', fnHandle);
410
+ fnHandle.dispose();
411
+ ```
412
+
413
+ ### Pattern 2: Async Function with Promise Tracking
414
+
415
+ ```typescript
416
+ const fnHandle = createAsyncBridge(ctx, 'asyncFunc', async arg => {
417
+ const result = await someAsyncOperation(arg);
418
+ return result;
419
+ });
420
+ context.setProp(context.global, 'asyncFunc', fnHandle);
421
+ fnHandle.dispose();
422
+ ```
423
+
424
+ ### Pattern 3: Stateful Instance Management
425
+
426
+ ```typescript
427
+ const instances = new Map<number, InstanceType>();
428
+ let idCounter = 0;
429
+
430
+ const createHandle = context.newFunction('__hostCreate', () => {
431
+ const instance = new InstanceType();
432
+ const id = ++idCounter;
433
+ instances.set(id, instance);
434
+ return context.newNumber(id);
435
+ });
436
+
437
+ const useHandle = context.newFunction('__hostUse', idHandle => {
438
+ const id = context.dump(idHandle);
439
+ const instance = instances.get(id);
440
+ return instance.doSomething();
441
+ });
442
+ ```
443
+
444
+ ### Pattern 4: ES6 Class with Host Methods
445
+
446
+ ```typescript
447
+ // Register host methods
448
+ context.setProp(context.global, '__hostCreate', createHandle);
449
+ context.setProp(context.global, '__hostMethod', methodHandle);
450
+
451
+ // Define class that uses host methods
452
+ const classHandle = defineClass(
453
+ ctx,
454
+ `
455
+ class MyClass {
456
+ constructor(arg) {
457
+ this.id = __hostCreate(arg);
458
+ }
459
+ method() {
460
+ return __hostMethod(this.id);
461
+ }
462
+ }
463
+ `,
464
+ 'MyClass'
465
+ );
466
+
467
+ context.setProp(context.global, 'MyClass', classHandle);
468
+ classHandle.dispose();
469
+ ```
470
+
471
+ ## Promise Tracking
472
+
473
+ All async operations are automatically tracked via `BridgeContext.tracker`:
474
+
475
+ ```typescript
476
+ interface PromiseTracker {
477
+ pendingPromises: Set<Promise<void>>; // Active promises
478
+ notifyNewPromise: () => void; // Signal new promise
479
+ newPromiseSignal: Promise<void>; // Wait for new promise
480
+ deferredPromises: Set<any>; // Cleanup tracking
481
+ }
482
+ ```
483
+
484
+ The `createAsyncBridge` utility handles tracking automatically. Manual tracking:
485
+
486
+ ```typescript
487
+ const deferred = context.newPromise();
488
+ tracker.deferredPromises.add(deferred);
489
+
490
+ const hostPromise = someAsyncOp()
491
+ .then(result => {
492
+ deferred.resolve(convertToHandle(context, result));
493
+ runtime.executePendingJobs();
494
+ })
495
+ .finally(() => {
496
+ tracker.pendingPromises.delete(hostPromise);
497
+ });
498
+
499
+ tracker.pendingPromises.add(hostPromise);
500
+ tracker.notifyNewPromise();
501
+ ```
502
+
503
+ ## Testing Bridges
504
+
505
+ Create focused tests for each bridge:
506
+
507
+ ```typescript
508
+ import { describe, it, expect } from 'vitest';
509
+ import { createPromiseTracker, installDate } from '../src/sandbox/bridges/index.js';
510
+
511
+ describe('Date bridge', () => {
512
+ it('should provide dateNow function', async () => {
513
+ // Setup context and tracker
514
+ const tracker = createPromiseTracker();
515
+ const ctx = { context, runtime, tracker };
516
+
517
+ // Install bridge
518
+ installDate(ctx);
519
+
520
+ // Test functionality
521
+ const result = context.evalCode('dateNow()');
522
+ expect(result.value).toBeGreaterThan(0);
523
+ });
524
+ });
525
+ ```
526
+
527
+ ## Performance Considerations
528
+
529
+ ### Minimize Handle Creation
530
+
531
+ ```typescript
532
+ // Bad - creates many handles
533
+ for (const item of items) {
534
+ const handle = context.newString(item);
535
+ // ... use handle
536
+ handle.dispose();
537
+ }
538
+
539
+ // Good - batch operations
540
+ const arrayHandle = context.newArray();
541
+ for (let i = 0; i < items.length; i++) {
542
+ const itemHandle = context.newString(items[i]);
543
+ context.setProp(arrayHandle, i, itemHandle);
544
+ itemHandle.dispose();
545
+ }
546
+ ```
547
+
548
+ ### Dispose Immediately
549
+
550
+ ```typescript
551
+ // Dispose handles as soon as they're no longer needed
552
+ const handle = context.newString('value');
553
+ context.setProp(obj, 'key', handle);
554
+ handle.dispose(); // Dispose immediately after setting
555
+ ```
556
+
557
+ ### Use Batch Utilities
558
+
559
+ ```typescript
560
+ // Use setProperties for multiple values
561
+ setProperties(context, handle, {
562
+ prop1: value1,
563
+ prop2: value2,
564
+ prop3: value3
565
+ });
566
+ ```
567
+
568
+ ## See Also
569
+
570
+ - [Fetch API Documentation](../docs/FETCH_API.md)
571
+ - [Sandbox Architecture](../docs/SANDBOX_FETCH_ARCHITECTURE.md)