@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,264 @@
1
+ /**
2
+ * Search bridge - Provides search/chunkSearch with pagination support
3
+ *
4
+ * Returns result objects with:
5
+ * - .results - current page results
6
+ * - .hasMore - boolean
7
+ * - .nextPage() - returns next page (same shape)
8
+ * - async iterator - for await (const r of results)
9
+ */
10
+
11
+ import type { QuickJSHandle } from 'quickjs-emscripten-core';
12
+ import { isFail } from 'quickjs-emscripten-core';
13
+ import type { BridgeContext } from './types.js';
14
+ import type { SmartBucket } from '@liquidmetal-ai/raindrop-framework';
15
+ import { withTrackedPromiseResult, parseMountPath } from './shared/index.js';
16
+
17
+ export interface SearchMountInfo {
18
+ name: string;
19
+ smartbucket: SmartBucket;
20
+ }
21
+
22
+ /**
23
+ * Build a single search result item handle. Uses Scope for per-item cleanup.
24
+ */
25
+ function createSearchResultItem(
26
+ context: BridgeContext['context'],
27
+ r: any
28
+ ): QuickJSHandle {
29
+ const itemHandle = context.newObject();
30
+
31
+ if (r.text) {
32
+ using textHandle = context.newString(r.text);
33
+ context.setProp(itemHandle, 'text', textHandle);
34
+ }
35
+ if (r.source) {
36
+ using sourceHandle = context.newString(r.source);
37
+ context.setProp(itemHandle, 'source', sourceHandle);
38
+ }
39
+ if (r.score !== undefined) {
40
+ using scoreHandle = context.newNumber(r.score);
41
+ context.setProp(itemHandle, 'score', scoreHandle);
42
+ }
43
+ if (r.chunkSignature) {
44
+ using chunkHandle = context.newString(r.chunkSignature);
45
+ context.setProp(itemHandle, 'chunkSignature', chunkHandle);
46
+ }
47
+
48
+ return itemHandle;
49
+ }
50
+
51
+ /**
52
+ * Build a results array handle from search results.
53
+ */
54
+ function createResultsArray(
55
+ context: BridgeContext['context'],
56
+ results: any[]
57
+ ): QuickJSHandle {
58
+ const resultsArray = context.newArray();
59
+ results.forEach((r, index) => {
60
+ using itemHandle = createSearchResultItem(context, r);
61
+ context.setProp(resultsArray, index, itemHandle);
62
+ });
63
+ return resultsArray;
64
+ }
65
+
66
+ /**
67
+ * Install search API in the sandbox
68
+ */
69
+ export function installSearch(
70
+ ctx: BridgeContext,
71
+ searchMounts: Map<string, SearchMountInfo>
72
+ ): void {
73
+ const { context, logger } = ctx;
74
+
75
+ /**
76
+ * Create a search results object with pagination support
77
+ */
78
+ function createSearchResultsHandle(
79
+ results: any[],
80
+ hasMore: boolean,
81
+ pagination: { total: number; page: number; pageSize: number },
82
+ smartbucket: SmartBucket,
83
+ requestId: string
84
+ ): QuickJSHandle {
85
+ const resultHandle = context.newObject();
86
+
87
+ // .results array
88
+ {
89
+ using resultsArray = createResultsArray(context, results);
90
+ context.setProp(resultHandle, 'results', resultsArray);
91
+ }
92
+
93
+ // .hasMore boolean
94
+ context.setProp(resultHandle, 'hasMore', hasMore ? context.true : context.false);
95
+
96
+ // .total, .page, .pageSize for info
97
+ {
98
+ using totalHandle = context.newNumber(pagination.total);
99
+ context.setProp(resultHandle, 'total', totalHandle);
100
+ }
101
+ {
102
+ using pageHandle = context.newNumber(pagination.page);
103
+ context.setProp(resultHandle, 'page', pageHandle);
104
+ }
105
+ {
106
+ using pageSizeHandle = context.newNumber(pagination.pageSize);
107
+ context.setProp(resultHandle, 'pageSize', pageSizeHandle);
108
+ }
109
+
110
+ // .nextPage() method - captures requestId in closure
111
+ {
112
+ using nextPageMethod = context.newFunction('nextPage', () => {
113
+ if (!hasMore) {
114
+ // No more pages - return empty results
115
+ return withTrackedPromiseResult(ctx, async () => {
116
+ return createSearchResultsHandle(
117
+ [],
118
+ false,
119
+ { total: pagination.total, page: pagination.page + 1, pageSize: pagination.pageSize },
120
+ smartbucket,
121
+ requestId
122
+ );
123
+ });
124
+ }
125
+
126
+ return withTrackedPromiseResult(ctx, async () => {
127
+ const nextPage = pagination.page + 1;
128
+ const result = await smartbucket.getPaginatedResults({
129
+ requestId,
130
+ page: nextPage
131
+ });
132
+
133
+ return createSearchResultsHandle(
134
+ result.results,
135
+ result.pagination.hasMore,
136
+ {
137
+ total: result.pagination.total,
138
+ page: result.pagination.page,
139
+ pageSize: result.pagination.pageSize
140
+ },
141
+ smartbucket,
142
+ requestId
143
+ );
144
+ });
145
+ });
146
+ context.setProp(resultHandle, 'nextPage', nextPageMethod);
147
+ }
148
+
149
+ // Symbol.asyncIterator for "for await" support
150
+ // We define the iterator entirely in QuickJS so there are no captured host handles
151
+ // that could be disposed before the iterator is used.
152
+ const asyncIteratorSetupCode = `
153
+ (function(resultsObj) {
154
+ resultsObj[Symbol.asyncIterator] = function() {
155
+ return {
156
+ currentResults: resultsObj.results.slice(),
157
+ currentIndex: 0,
158
+ resultsObj: resultsObj,
159
+ async next() {
160
+ // If we have results in current page, yield them
161
+ if (this.currentIndex < this.currentResults.length) {
162
+ return { value: this.currentResults[this.currentIndex++], done: false };
163
+ }
164
+ // Try to get next page
165
+ if (this.resultsObj.hasMore) {
166
+ this.resultsObj = await this.resultsObj.nextPage();
167
+ this.currentResults = this.resultsObj.results.slice();
168
+ this.currentIndex = 0;
169
+ if (this.currentResults.length > 0) {
170
+ return { value: this.currentResults[this.currentIndex++], done: false };
171
+ }
172
+ }
173
+ // No more results
174
+ return { done: true };
175
+ }
176
+ };
177
+ };
178
+ })
179
+ `;
180
+
181
+ const setupFnResult = context.evalCode(asyncIteratorSetupCode);
182
+ if (isFail(setupFnResult)) {
183
+ logger?.warn?.('[Search Bridge] Failed to create async iterator setup');
184
+ setupFnResult.error.dispose();
185
+ } else {
186
+ using setupFn = setupFnResult.value;
187
+ const callResult = context.callFunction(setupFn, context.undefined, resultHandle);
188
+ if (isFail(callResult)) {
189
+ logger?.warn?.('[Search Bridge] Failed to apply async iterator');
190
+ callResult.error.dispose();
191
+ } else {
192
+ callResult.value.dispose();
193
+ }
194
+ }
195
+
196
+ return resultHandle;
197
+ }
198
+
199
+ // search(path, query) - Semantic search with pagination
200
+ {
201
+ using searchMethod = context.newFunction(
202
+ 'search',
203
+ (pathHandle: QuickJSHandle, queryHandle: QuickJSHandle) => {
204
+ const path = context.dump(pathHandle) as string;
205
+ const query = context.dump(queryHandle) as string;
206
+
207
+ return withTrackedPromiseResult(ctx, async () => {
208
+ const { mount } = parseMountPath(path, searchMounts, 'Search');
209
+ const requestId = `search-${Date.now()}-${Math.random().toString(36).substring(7)}`;
210
+
211
+ const result = await mount.smartbucket.search({
212
+ input: query,
213
+ requestId
214
+ });
215
+
216
+ return createSearchResultsHandle(
217
+ result.results,
218
+ result.pagination.hasMore,
219
+ {
220
+ total: result.pagination.total,
221
+ page: result.pagination.page,
222
+ pageSize: result.pagination.pageSize
223
+ },
224
+ mount.smartbucket,
225
+ requestId
226
+ );
227
+ });
228
+ }
229
+ );
230
+ context.setProp(context.global, 'search', searchMethod);
231
+ }
232
+
233
+ // chunkSearch(path, query) - RAG chunk search (no pagination)
234
+ {
235
+ using chunkSearchMethod = context.newFunction(
236
+ 'chunkSearch',
237
+ (pathHandle: QuickJSHandle, queryHandle: QuickJSHandle) => {
238
+ const path = context.dump(pathHandle) as string;
239
+ const query = context.dump(queryHandle) as string;
240
+
241
+ return withTrackedPromiseResult(ctx, async () => {
242
+ const { mount } = parseMountPath(path, searchMounts, 'Search');
243
+ const requestId = `chunk-${Date.now()}-${Math.random().toString(36).substring(7)}`;
244
+
245
+ const result = await mount.smartbucket.chunkSearch({
246
+ input: query,
247
+ requestId
248
+ });
249
+
250
+ // Build results using the shared helper
251
+ const resultHandle = context.newObject();
252
+ {
253
+ using resultsArray = createResultsArray(context, result.results);
254
+ context.setProp(resultHandle, 'results', resultsArray);
255
+ }
256
+ return resultHandle;
257
+ });
258
+ }
259
+ );
260
+ context.setProp(context.global, 'chunkSearch', chunkSearchMethod);
261
+ }
262
+
263
+ logger?.info?.(`[Search Bridge] Installed with ${searchMounts.size} SmartBucket mount(s)`);
264
+ }
@@ -0,0 +1,93 @@
1
+ /**
2
+ * Shared body consumption methods (json, text, arrayBuffer) for Response-like objects
3
+ */
4
+
5
+ import type { QuickJSHandle } from 'quickjs-emscripten-core';
6
+ import type { BridgeContext } from '../types.js';
7
+ import { withTrackedPromiseResult, withTrackedPromiseValue } from './promise-helper.js';
8
+ import { parseJsonInContext } from './json-helpers.js';
9
+
10
+ /**
11
+ * Create a body consumption method (json, text, or arrayBuffer)
12
+ *
13
+ * @param ctx - Bridge context
14
+ * @param methodName - 'json', 'text', or 'arrayBuffer'
15
+ * @param responseId - ID of the response in the registry
16
+ * @param responseRegistry - Map of response IDs to Response objects
17
+ */
18
+ export function createBodyMethod(
19
+ ctx: BridgeContext,
20
+ methodName: 'json' | 'text' | 'arrayBuffer',
21
+ responseId: number,
22
+ responseRegistry: Map<number, Response>
23
+ ): QuickJSHandle {
24
+ const { context, tracker } = ctx;
25
+
26
+ return context.newFunction(methodName, () => {
27
+ const response = responseRegistry.get(responseId);
28
+
29
+ if (!response) {
30
+ const deferred = context.newPromise();
31
+ tracker.deferredPromises.add(deferred);
32
+ using errHandle = context.newString('Response not found or already consumed');
33
+ deferred.reject(errHandle);
34
+ tracker.deferredPromises.delete(deferred);
35
+ return deferred.handle;
36
+ }
37
+
38
+ // Check if body was already used (host Response tracks this)
39
+ if (response.bodyUsed) {
40
+ const deferred = context.newPromise();
41
+ tracker.deferredPromises.add(deferred);
42
+ using errHandle = context.newString('Body has already been consumed');
43
+ deferred.reject(errHandle);
44
+ tracker.deferredPromises.delete(deferred);
45
+ return deferred.handle;
46
+ }
47
+
48
+ if (methodName === 'text') {
49
+ return withTrackedPromiseValue(ctx, async () => {
50
+ return await response.text();
51
+ });
52
+ } else if (methodName === 'arrayBuffer') {
53
+ return withTrackedPromiseResult(ctx, async () => {
54
+ const buffer = await response.arrayBuffer();
55
+ return ctx.context.newArrayBuffer(buffer);
56
+ });
57
+ } else {
58
+ // json
59
+ return withTrackedPromiseResult(ctx, async () => {
60
+ const data = await response.json();
61
+ const jsonStr = JSON.stringify(data);
62
+ return parseJsonInContext(context, jsonStr);
63
+ });
64
+ }
65
+ });
66
+ }
67
+
68
+ /**
69
+ * Attach all body methods (json, text, arrayBuffer) to a response handle
70
+ */
71
+ export function attachBodyMethods(
72
+ ctx: BridgeContext,
73
+ responseHandle: QuickJSHandle,
74
+ responseId: number,
75
+ responseRegistry: Map<number, Response>
76
+ ): void {
77
+ const { context } = ctx;
78
+
79
+ {
80
+ using jsonMethod = createBodyMethod(ctx, 'json', responseId, responseRegistry);
81
+ context.setProp(responseHandle, 'json', jsonMethod);
82
+ }
83
+
84
+ {
85
+ using textMethod = createBodyMethod(ctx, 'text', responseId, responseRegistry);
86
+ context.setProp(responseHandle, 'text', textMethod);
87
+ }
88
+
89
+ {
90
+ using arrayBufferMethod = createBodyMethod(ctx, 'arrayBuffer', responseId, responseRegistry);
91
+ context.setProp(responseHandle, 'arrayBuffer', arrayBufferMethod);
92
+ }
93
+ }
@@ -0,0 +1,112 @@
1
+ /**
2
+ * Shared cleanup utilities for stream-based bridges
3
+ */
4
+
5
+ import type { Logger } from '../../../types.js';
6
+
7
+ /**
8
+ * Registry of host objects that need cleanup
9
+ */
10
+ export interface StreamRegistry {
11
+ hostResponses?: Map<number, Response>;
12
+ hostStreams: Map<number, ReadableStream>;
13
+ hostReaders: Map<number, ReadableStreamDefaultReader>;
14
+ }
15
+
16
+ /**
17
+ * Create a cleanup handler for stream-based bridges.
18
+ *
19
+ * The cleanup order is important:
20
+ * 1. Release readers FIRST - streams can't be cancelled while locked
21
+ * 2. Cancel streams - this settles any pending read promises
22
+ * 3. Wait for cancellation to complete (with timeout)
23
+ * 4. Clear all maps
24
+ *
25
+ * @param registry - Registry containing Maps of host objects
26
+ * @param bridgeName - Name for logging (e.g., 'Fetch', 'Response', 'Bucket')
27
+ * @param logger - Optional logger
28
+ */
29
+ export function createStreamCleanupHandler(
30
+ registry: StreamRegistry,
31
+ bridgeName: string,
32
+ logger?: Logger,
33
+ cleanupTimeoutMs: number = 5000
34
+ ): () => Promise<void> {
35
+ return async () => {
36
+ logger?.info?.(`[${bridgeName}] Cleanup: aborting all active streams and readers`);
37
+
38
+ const pendingOperations: Promise<unknown>[] = [];
39
+
40
+ // STEP 1: Release readers FIRST before cancelling streams
41
+ // Streams cannot be cancelled while a reader has the lock
42
+ const readersToRelease = Array.from(registry.hostReaders.entries());
43
+ for (const [id, reader] of readersToRelease) {
44
+ try {
45
+ reader.releaseLock();
46
+ logger?.info?.(`[${bridgeName}] Released reader lock for reader ${id}`);
47
+ } catch (e) {
48
+ // Reader might already be released, ignore error
49
+ logger?.info?.(`[${bridgeName}] Reader ${id} already released or not locked: ${e}`);
50
+ }
51
+ }
52
+
53
+ // STEP 2: Cancel all streams (after readers are released)
54
+ const streamsToCancel = Array.from(registry.hostStreams.entries());
55
+ for (const [id, stream] of streamsToCancel) {
56
+ try {
57
+ const cancelPromise = stream.cancel('Sandbox cleanup').catch(() => {
58
+ // Ignore errors from cancellation
59
+ });
60
+ pendingOperations.push(cancelPromise);
61
+ logger?.info?.(`[${bridgeName}] Cancelled stream ${id}`);
62
+ } catch (e) {
63
+ // Stream might be cancelled or closed already
64
+ logger?.info?.(`[${bridgeName}] Stream ${id} already cancelled or closed: ${e}`);
65
+ }
66
+ }
67
+
68
+ // STEP 3: Wait for all stream cancellation promises to settle WITH TIMEOUT
69
+ // This prevents stream cleanup from blocking disposal indefinitely
70
+ if (pendingOperations.length > 0) {
71
+ logger?.info?.(
72
+ `[${bridgeName}] Waiting for ${pendingOperations.length} stream operations to settle (with timeout)`
73
+ );
74
+
75
+ let timer: ReturnType<typeof setTimeout> | undefined;
76
+ try {
77
+ const timeout = new Promise(resolve => {
78
+ timer = setTimeout(resolve, cleanupTimeoutMs);
79
+ });
80
+ await Promise.race([Promise.allSettled(pendingOperations), timeout]);
81
+ logger?.info?.(`[${bridgeName}] Stream cleanup completed`);
82
+ } catch (e) {
83
+ logger?.warn?.(`[${bridgeName}] Stream cleanup timeout or error: ${e}`);
84
+ } finally {
85
+ if (timer !== undefined) {
86
+ clearTimeout(timer);
87
+ }
88
+ }
89
+ }
90
+
91
+ // STEP 4: Clear all registries AFTER operations have settled
92
+ registry.hostReaders.clear();
93
+ registry.hostStreams.clear();
94
+ if (registry.hostResponses) {
95
+ registry.hostResponses.clear();
96
+ }
97
+ };
98
+ }
99
+
100
+ /**
101
+ * Counter factory for generating unique IDs
102
+ */
103
+ export function createIdCounter(): { value: number } {
104
+ return { value: 0 };
105
+ }
106
+
107
+ /**
108
+ * Get next ID from counter
109
+ */
110
+ export function nextId(counter: { value: number }): number {
111
+ return ++counter.value;
112
+ }
@@ -0,0 +1,76 @@
1
+ /**
2
+ * Shared value conversion utility for QuickJS bridges
3
+ *
4
+ * Single source of truth for converting JavaScript values to QuickJS handles.
5
+ * Used by both utils.ts (createSyncBridge/createAsyncBridge) and promise-helper.ts.
6
+ */
7
+
8
+ import type { QuickJSContext, QuickJSHandle } from 'quickjs-emscripten-core';
9
+ import { getJsonParse } from './json-helpers.js';
10
+
11
+ /**
12
+ * Safely extract the relevant portion of a Uint8Array's backing ArrayBuffer.
13
+ * Handles the case where a Uint8Array is a view into a larger ArrayBuffer
14
+ * (common with Node.js Buffer).
15
+ */
16
+ function safeArrayBuffer(uint8Array: Uint8Array): ArrayBuffer {
17
+ if (uint8Array.byteOffset === 0 && uint8Array.byteLength === uint8Array.buffer.byteLength) {
18
+ return uint8Array.buffer as ArrayBuffer;
19
+ }
20
+ return uint8Array.buffer.slice(
21
+ uint8Array.byteOffset,
22
+ uint8Array.byteOffset + uint8Array.byteLength
23
+ ) as ArrayBuffer;
24
+ }
25
+
26
+ /**
27
+ * Convert JavaScript values to QuickJS handles.
28
+ *
29
+ * IMPORTANT: Returns duplicated handles for singleton values (undefined, null, true, false)
30
+ * so that callers can always safely call .dispose() on the returned handle.
31
+ */
32
+ export function convertToHandle(context: QuickJSContext, value: any): QuickJSHandle {
33
+ if (value === undefined) {
34
+ return context.undefined.dup();
35
+ } else if (value === null) {
36
+ return context.null.dup();
37
+ } else if (typeof value === 'number') {
38
+ return context.newNumber(value);
39
+ } else if (typeof value === 'string') {
40
+ return context.newString(value);
41
+ } else if (typeof value === 'boolean') {
42
+ return value ? context.true.dup() : context.false.dup();
43
+ } else if (value instanceof Uint8Array || value instanceof ArrayBuffer) {
44
+ const uint8Array = value instanceof Uint8Array ? value : new Uint8Array(value);
45
+ return context.newArrayBuffer(safeArrayBuffer(uint8Array));
46
+ } else if (Array.isArray(value)) {
47
+ const arr = context.newArray();
48
+ for (let i = 0; i < value.length; i++) {
49
+ const elemHandle = convertToHandle(context, value[i]);
50
+ context.setProp(arr, i, elemHandle);
51
+ elemHandle.dispose();
52
+ }
53
+ return arr;
54
+ } else if (typeof value === 'object') {
55
+ try {
56
+ const jsonStr = JSON.stringify(value);
57
+ const jsonParse = getJsonParse(context);
58
+ const jsonStrHandle = context.newString(jsonStr);
59
+ const result = context.callFunction(jsonParse, context.undefined, jsonStrHandle);
60
+ jsonStrHandle.dispose();
61
+ jsonParse.dispose();
62
+
63
+ if (result.error) {
64
+ result.error.dispose();
65
+ throw new Error('Failed to convert object via JSON.parse');
66
+ }
67
+ return (result as { value: QuickJSHandle }).value;
68
+ } catch (e) {
69
+ throw new Error(
70
+ 'Failed to convert object: ' + (e instanceof Error ? e.message : String(e))
71
+ );
72
+ }
73
+ } else {
74
+ return context.newString(String(value));
75
+ }
76
+ }