@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.
- package/.prettierrc +9 -0
- package/CHANGELOG.md +8 -0
- package/eslint.config.mjs +28 -0
- package/package.json +53 -0
- package/src/engine/agent.ts +478 -0
- package/src/engine/llm-provider.test.ts +275 -0
- package/src/engine/llm-provider.ts +330 -0
- package/src/engine/stream-parser.ts +170 -0
- package/src/index.ts +142 -0
- package/src/mounts/mount-manager.test.ts +516 -0
- package/src/mounts/mount-manager.ts +327 -0
- package/src/mounts/mount-registry.ts +196 -0
- package/src/mounts/zod-to-string.test.ts +154 -0
- package/src/mounts/zod-to-string.ts +213 -0
- package/src/presets/agent-tools.ts +57 -0
- package/src/presets/index.ts +5 -0
- package/src/sandbox/README.md +1321 -0
- package/src/sandbox/bridges/README.md +571 -0
- package/src/sandbox/bridges/actor.test.ts +229 -0
- package/src/sandbox/bridges/actor.ts +195 -0
- package/src/sandbox/bridges/bridge-fixes.test.ts +614 -0
- package/src/sandbox/bridges/bucket.test.ts +300 -0
- package/src/sandbox/bridges/cleanup-reproduction.test.ts +225 -0
- package/src/sandbox/bridges/console-multiple.test.ts +187 -0
- package/src/sandbox/bridges/console.test.ts +157 -0
- package/src/sandbox/bridges/console.ts +122 -0
- package/src/sandbox/bridges/fetch.ts +93 -0
- package/src/sandbox/bridges/index.ts +78 -0
- package/src/sandbox/bridges/readable-stream.ts +323 -0
- package/src/sandbox/bridges/response.test.ts +154 -0
- package/src/sandbox/bridges/response.ts +123 -0
- package/src/sandbox/bridges/review-fixes.test.ts +331 -0
- package/src/sandbox/bridges/search.test.ts +475 -0
- package/src/sandbox/bridges/search.ts +264 -0
- package/src/sandbox/bridges/shared/body-methods.ts +93 -0
- package/src/sandbox/bridges/shared/cleanup.ts +112 -0
- package/src/sandbox/bridges/shared/convert.ts +76 -0
- package/src/sandbox/bridges/shared/headers.ts +181 -0
- package/src/sandbox/bridges/shared/index.ts +36 -0
- package/src/sandbox/bridges/shared/json-helpers.ts +77 -0
- package/src/sandbox/bridges/shared/path-parser.ts +109 -0
- package/src/sandbox/bridges/shared/promise-helper.ts +108 -0
- package/src/sandbox/bridges/shared/registry-setup.ts +84 -0
- package/src/sandbox/bridges/shared/response-object.ts +280 -0
- package/src/sandbox/bridges/shared/result-builder.ts +130 -0
- package/src/sandbox/bridges/shared/scope-helpers.ts +44 -0
- package/src/sandbox/bridges/shared/stream-reader.ts +90 -0
- package/src/sandbox/bridges/storage-bridge.test.ts +893 -0
- package/src/sandbox/bridges/storage.ts +421 -0
- package/src/sandbox/bridges/text-decoder.ts +190 -0
- package/src/sandbox/bridges/text-encoder.ts +102 -0
- package/src/sandbox/bridges/types.ts +39 -0
- package/src/sandbox/bridges/utils.ts +123 -0
- package/src/sandbox/index.ts +6 -0
- package/src/sandbox/quickjs-wasm.d.ts +9 -0
- package/src/sandbox/sandbox.test.ts +191 -0
- package/src/sandbox/sandbox.ts +831 -0
- package/src/sandbox/test-helper.ts +43 -0
- package/src/sandbox/test-mocks.ts +154 -0
- package/src/sandbox/user-stream.test.ts +77 -0
- package/src/skills/frontmatter.test.ts +305 -0
- package/src/skills/frontmatter.ts +200 -0
- package/src/skills/index.ts +9 -0
- package/src/skills/skills-loader.test.ts +237 -0
- package/src/skills/skills-loader.ts +200 -0
- package/src/tools/actor-storage-tools.ts +250 -0
- package/src/tools/code-tools.test.ts +199 -0
- package/src/tools/code-tools.ts +444 -0
- package/src/tools/file-tools.ts +206 -0
- package/src/tools/registry.ts +125 -0
- package/src/tools/script-tools.ts +145 -0
- package/src/tools/smartbucket-tools.ts +203 -0
- package/src/tools/sql-tools.ts +213 -0
- package/src/tools/tool-factory.ts +119 -0
- package/src/types.ts +512 -0
- package/tsconfig.eslint.json +5 -0
- package/tsconfig.json +15 -0
- package/vitest.config.ts +33 -0
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for Actor Bridge
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { describe, it, expect, vi } from 'vitest';
|
|
6
|
+
import { z } from 'zod';
|
|
7
|
+
import { executeWithAsyncHost } from '../test-helper.js';
|
|
8
|
+
import { installActor } from './actor.js';
|
|
9
|
+
import type { ActorMountInfo } from '../../types.js';
|
|
10
|
+
|
|
11
|
+
describe('Actor Bridge', () => {
|
|
12
|
+
// Mock actor stub
|
|
13
|
+
const createMockActorStub = () => ({
|
|
14
|
+
getState: vi.fn().mockResolvedValue({ loggedIn: true, userId: 'user-123' }),
|
|
15
|
+
updatePrefs: vi.fn().mockResolvedValue({ success: true }),
|
|
16
|
+
clearSession: vi.fn().mockResolvedValue({ success: true })
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
// Mock actor namespace
|
|
20
|
+
const createMockActorNamespace = (stub: ReturnType<typeof createMockActorStub>) => ({
|
|
21
|
+
idFromName: vi.fn().mockImplementation((name: string) => ({
|
|
22
|
+
toString: () => name,
|
|
23
|
+
equals: () => false,
|
|
24
|
+
name
|
|
25
|
+
})),
|
|
26
|
+
get: vi.fn().mockReturnValue(stub)
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
const createActorMountInfo = (stub: ReturnType<typeof createMockActorStub>): ActorMountInfo => ({
|
|
30
|
+
name: 'session',
|
|
31
|
+
type: 'actor',
|
|
32
|
+
resource: createMockActorNamespace(stub) as any,
|
|
33
|
+
description: 'Per-user session state',
|
|
34
|
+
methods: {
|
|
35
|
+
getState: {
|
|
36
|
+
description: 'Get current session state',
|
|
37
|
+
params: z.tuple([]),
|
|
38
|
+
returns: z.object({
|
|
39
|
+
loggedIn: z.boolean(),
|
|
40
|
+
userId: z.string().optional()
|
|
41
|
+
})
|
|
42
|
+
},
|
|
43
|
+
updatePrefs: {
|
|
44
|
+
description: 'Update user preferences',
|
|
45
|
+
params: z.tuple([z.object({
|
|
46
|
+
theme: z.enum(['light', 'dark']).optional(),
|
|
47
|
+
notifications: z.boolean().optional()
|
|
48
|
+
}).describe('prefs')]),
|
|
49
|
+
returns: z.object({ success: z.boolean() })
|
|
50
|
+
},
|
|
51
|
+
clearSession: {
|
|
52
|
+
description: 'Clear session data',
|
|
53
|
+
params: z.tuple([]),
|
|
54
|
+
returns: z.object({ success: z.boolean() })
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('should call actor method with correct arguments', async () => {
|
|
60
|
+
const mockStub = createMockActorStub();
|
|
61
|
+
const mountInfo = createActorMountInfo(mockStub);
|
|
62
|
+
const actorMounts = new Map<string, ActorMountInfo>([['session', mountInfo]]);
|
|
63
|
+
|
|
64
|
+
const code = `
|
|
65
|
+
const session = await actor('/session', 'user-123');
|
|
66
|
+
const state = await session.getState();
|
|
67
|
+
return state;
|
|
68
|
+
`;
|
|
69
|
+
|
|
70
|
+
const result = await executeWithAsyncHost(
|
|
71
|
+
code,
|
|
72
|
+
{},
|
|
73
|
+
{
|
|
74
|
+
timeoutMs: 5000,
|
|
75
|
+
bridgeInstallers: [ctx => installActor(ctx, actorMounts)]
|
|
76
|
+
}
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
expect(result.success).toBe(true);
|
|
80
|
+
expect(result.result).toEqual({ loggedIn: true, userId: 'user-123' });
|
|
81
|
+
expect(mockStub.getState).toHaveBeenCalledTimes(1);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it('should pass parameters to actor method', async () => {
|
|
85
|
+
const mockStub = createMockActorStub();
|
|
86
|
+
const mountInfo = createActorMountInfo(mockStub);
|
|
87
|
+
const actorMounts = new Map<string, ActorMountInfo>([['session', mountInfo]]);
|
|
88
|
+
|
|
89
|
+
const code = `
|
|
90
|
+
const session = await actor('/session', 'user-456');
|
|
91
|
+
const result = await session.updatePrefs({ theme: 'dark' });
|
|
92
|
+
return result;
|
|
93
|
+
`;
|
|
94
|
+
|
|
95
|
+
const result = await executeWithAsyncHost(
|
|
96
|
+
code,
|
|
97
|
+
{},
|
|
98
|
+
{
|
|
99
|
+
timeoutMs: 5000,
|
|
100
|
+
bridgeInstallers: [ctx => installActor(ctx, actorMounts)]
|
|
101
|
+
}
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
expect(result.success).toBe(true);
|
|
105
|
+
expect(result.result).toEqual({ success: true });
|
|
106
|
+
expect(mockStub.updatePrefs).toHaveBeenCalledWith({ theme: 'dark' });
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it('should validate params against schema', async () => {
|
|
110
|
+
const mockStub = createMockActorStub();
|
|
111
|
+
const mountInfo = createActorMountInfo(mockStub);
|
|
112
|
+
const actorMounts = new Map<string, ActorMountInfo>([['session', mountInfo]]);
|
|
113
|
+
|
|
114
|
+
const code = `
|
|
115
|
+
const session = await actor('/session', 'user-789');
|
|
116
|
+
try {
|
|
117
|
+
// Pass invalid theme value
|
|
118
|
+
const result = await session.updatePrefs({ theme: 'invalid-theme' });
|
|
119
|
+
return { error: false, result };
|
|
120
|
+
} catch (e) {
|
|
121
|
+
return { error: true, message: e.message || String(e) };
|
|
122
|
+
}
|
|
123
|
+
`;
|
|
124
|
+
|
|
125
|
+
const result = await executeWithAsyncHost(
|
|
126
|
+
code,
|
|
127
|
+
{},
|
|
128
|
+
{
|
|
129
|
+
timeoutMs: 5000,
|
|
130
|
+
bridgeInstallers: [ctx => installActor(ctx, actorMounts)]
|
|
131
|
+
}
|
|
132
|
+
);
|
|
133
|
+
|
|
134
|
+
expect(result.success).toBe(true);
|
|
135
|
+
expect(result.result.error).toBe(true);
|
|
136
|
+
expect(result.result.message).toContain('Invalid');
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
it('should return error for unknown mount', async () => {
|
|
140
|
+
const mockStub = createMockActorStub();
|
|
141
|
+
const mountInfo = createActorMountInfo(mockStub);
|
|
142
|
+
const actorMounts = new Map<string, ActorMountInfo>([['session', mountInfo]]);
|
|
143
|
+
|
|
144
|
+
const code = `
|
|
145
|
+
try {
|
|
146
|
+
const unknown = await actor('/nonexistent', 'test');
|
|
147
|
+
return { error: false };
|
|
148
|
+
} catch (e) {
|
|
149
|
+
return { error: true, message: e.message || String(e) };
|
|
150
|
+
}
|
|
151
|
+
`;
|
|
152
|
+
|
|
153
|
+
const result = await executeWithAsyncHost(
|
|
154
|
+
code,
|
|
155
|
+
{},
|
|
156
|
+
{
|
|
157
|
+
timeoutMs: 5000,
|
|
158
|
+
bridgeInstallers: [ctx => installActor(ctx, actorMounts)]
|
|
159
|
+
}
|
|
160
|
+
);
|
|
161
|
+
|
|
162
|
+
expect(result.success).toBe(true);
|
|
163
|
+
expect(result.result.error).toBe(true);
|
|
164
|
+
expect(result.result.message).toContain('not found');
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
it('should cache actor stubs', async () => {
|
|
168
|
+
const mockStub = createMockActorStub();
|
|
169
|
+
const mountInfo = createActorMountInfo(mockStub);
|
|
170
|
+
const actorMounts = new Map<string, ActorMountInfo>([['session', mountInfo]]);
|
|
171
|
+
|
|
172
|
+
const code = `
|
|
173
|
+
// Get the same actor twice
|
|
174
|
+
const session1 = await actor('/session', 'user-same');
|
|
175
|
+
const session2 = await actor('/session', 'user-same');
|
|
176
|
+
|
|
177
|
+
// Call methods on both
|
|
178
|
+
await session1.getState();
|
|
179
|
+
await session2.getState();
|
|
180
|
+
|
|
181
|
+
return { called: true };
|
|
182
|
+
`;
|
|
183
|
+
|
|
184
|
+
const result = await executeWithAsyncHost(
|
|
185
|
+
code,
|
|
186
|
+
{},
|
|
187
|
+
{
|
|
188
|
+
timeoutMs: 5000,
|
|
189
|
+
bridgeInstallers: [ctx => installActor(ctx, actorMounts)]
|
|
190
|
+
}
|
|
191
|
+
);
|
|
192
|
+
|
|
193
|
+
expect(result.success).toBe(true);
|
|
194
|
+
// The namespace.get should only be called once due to caching
|
|
195
|
+
// Note: Due to how the current implementation works, this may be called twice
|
|
196
|
+
// since we're creating new stub handles. This test documents the current behavior.
|
|
197
|
+
expect(mockStub.getState).toHaveBeenCalledTimes(2);
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
it('should handle actor method errors', async () => {
|
|
201
|
+
const mockStub = createMockActorStub();
|
|
202
|
+
mockStub.clearSession.mockRejectedValue(new Error('Session already cleared'));
|
|
203
|
+
const mountInfo = createActorMountInfo(mockStub);
|
|
204
|
+
const actorMounts = new Map<string, ActorMountInfo>([['session', mountInfo]]);
|
|
205
|
+
|
|
206
|
+
const code = `
|
|
207
|
+
const session = await actor('/session', 'user-error');
|
|
208
|
+
try {
|
|
209
|
+
await session.clearSession();
|
|
210
|
+
return { error: false };
|
|
211
|
+
} catch (e) {
|
|
212
|
+
return { error: true, message: e.message || String(e) };
|
|
213
|
+
}
|
|
214
|
+
`;
|
|
215
|
+
|
|
216
|
+
const result = await executeWithAsyncHost(
|
|
217
|
+
code,
|
|
218
|
+
{},
|
|
219
|
+
{
|
|
220
|
+
timeoutMs: 5000,
|
|
221
|
+
bridgeInstallers: [ctx => installActor(ctx, actorMounts)]
|
|
222
|
+
}
|
|
223
|
+
);
|
|
224
|
+
|
|
225
|
+
expect(result.success).toBe(true);
|
|
226
|
+
expect(result.result.error).toBe(true);
|
|
227
|
+
expect(result.result.message).toContain('already cleared');
|
|
228
|
+
});
|
|
229
|
+
});
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Actor Bridge - Provides actor(path, instanceId) with schema-based method stubs
|
|
3
|
+
*
|
|
4
|
+
* Usage in sandbox:
|
|
5
|
+
* const session = await actor('/sessions', 'user-123');
|
|
6
|
+
* const state = await session.getState();
|
|
7
|
+
* await session.updatePrefs({ theme: 'dark' });
|
|
8
|
+
*
|
|
9
|
+
* Features:
|
|
10
|
+
* - Explicit methods generated from Zod schemas
|
|
11
|
+
* - Stub caching per actor instance
|
|
12
|
+
* - Parameter validation against schema
|
|
13
|
+
* - ReadableStream return handling
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import type { QuickJSHandle } from 'quickjs-emscripten-core';
|
|
17
|
+
import type { BridgeContext } from './types.js';
|
|
18
|
+
import type { ActorMountInfo, ActorMethodSchema } from '../../types.js';
|
|
19
|
+
import type { Actor, ActorStub } from '@liquidmetal-ai/raindrop-framework';
|
|
20
|
+
import { convertToHandle } from './utils.js';
|
|
21
|
+
import {
|
|
22
|
+
createStreamCleanupHandler,
|
|
23
|
+
createIdCounter,
|
|
24
|
+
nextId,
|
|
25
|
+
type StreamRegistry,
|
|
26
|
+
withTrackedPromise,
|
|
27
|
+
createReaderHandle,
|
|
28
|
+
parseMountPath
|
|
29
|
+
} from './shared/index.js';
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Install actor API in the sandbox
|
|
33
|
+
*/
|
|
34
|
+
export function installActor(ctx: BridgeContext, actorMounts: Map<string, ActorMountInfo>): void {
|
|
35
|
+
const { context, logger, cleanupHandlers } = ctx;
|
|
36
|
+
|
|
37
|
+
// Cache for host actor stubs: "mountName:instanceName" → hostStub
|
|
38
|
+
const hostStubCache = new Map<string, ActorStub<Actor<unknown>>>();
|
|
39
|
+
|
|
40
|
+
// Stream registry for any streaming returns
|
|
41
|
+
const registry: StreamRegistry = {
|
|
42
|
+
hostStreams: new Map<number, ReadableStream>(),
|
|
43
|
+
hostReaders: new Map<number, ReadableStreamDefaultReader>()
|
|
44
|
+
};
|
|
45
|
+
const streamIdCounter = createIdCounter();
|
|
46
|
+
const readerIdCounter = createIdCounter();
|
|
47
|
+
|
|
48
|
+
// Register cleanup handlers
|
|
49
|
+
cleanupHandlers.push(() => {
|
|
50
|
+
hostStubCache.clear();
|
|
51
|
+
logger?.info?.('[Actor Bridge] Cleaned up host stub cache');
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
cleanupHandlers.push(createStreamCleanupHandler(registry, 'Actor', logger));
|
|
55
|
+
|
|
56
|
+
// actor(path, instanceId) → stub with methods
|
|
57
|
+
{
|
|
58
|
+
using actorFunction = context.newFunction(
|
|
59
|
+
'actor',
|
|
60
|
+
(pathHandle: QuickJSHandle, nameHandle: QuickJSHandle) => {
|
|
61
|
+
// Validate inputs — QuickJS converts host exceptions thrown in
|
|
62
|
+
// newFunction callbacks into sandbox-visible thrown errors
|
|
63
|
+
const path = context.dump(pathHandle);
|
|
64
|
+
if (typeof path !== 'string') {
|
|
65
|
+
throw new Error('actor() first argument must be a string path');
|
|
66
|
+
}
|
|
67
|
+
const instanceName = context.dump(nameHandle);
|
|
68
|
+
if (typeof instanceName !== 'string') {
|
|
69
|
+
throw new Error('actor() second argument must be a string instance name');
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Parse the path to get mount
|
|
73
|
+
const { mount } = parseMountPath(path, actorMounts, 'Actor');
|
|
74
|
+
|
|
75
|
+
const cacheKey = `${mount.name}:${instanceName}`;
|
|
76
|
+
|
|
77
|
+
// Get or create the host stub (cached)
|
|
78
|
+
let hostStub = hostStubCache.get(cacheKey);
|
|
79
|
+
if (!hostStub) {
|
|
80
|
+
const id = mount.resource.idFromName(instanceName);
|
|
81
|
+
hostStub = mount.resource.get(id);
|
|
82
|
+
hostStubCache.set(cacheKey, hostStub);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Always create a new sandbox stub handle
|
|
86
|
+
// (QuickJS handles can't be reused after being returned)
|
|
87
|
+
return createActorStub(ctx, hostStub, mount.methods, registry, {
|
|
88
|
+
streamIdCounter,
|
|
89
|
+
readerIdCounter
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
context.setProp(context.global, 'actor', actorFunction);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
logger?.info?.(`[Actor Bridge] Installed with ${actorMounts.size} mount(s)`);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Create a sandbox stub object with methods defined by the schema
|
|
102
|
+
*/
|
|
103
|
+
function createActorStub(
|
|
104
|
+
ctx: BridgeContext,
|
|
105
|
+
hostStub: ActorStub<Actor<unknown>>,
|
|
106
|
+
methods: Record<string, ActorMethodSchema>,
|
|
107
|
+
registry: StreamRegistry,
|
|
108
|
+
counters: { streamIdCounter: { value: number }; readerIdCounter: { value: number } }
|
|
109
|
+
): QuickJSHandle {
|
|
110
|
+
const { context, logger } = ctx;
|
|
111
|
+
|
|
112
|
+
const stubHandle = context.newObject();
|
|
113
|
+
|
|
114
|
+
// Create a method for each schema-defined method
|
|
115
|
+
for (const [methodName, schema] of Object.entries(methods)) {
|
|
116
|
+
using methodHandle = context.newFunction(methodName, (...args: QuickJSHandle[]) => {
|
|
117
|
+
// Dump all arguments from the sandbox
|
|
118
|
+
const jsArgs = args.map(arg => context.dump(arg));
|
|
119
|
+
|
|
120
|
+
// Validate the argument tuple against the schema
|
|
121
|
+
const parseResult = schema.params.safeParse(jsArgs);
|
|
122
|
+
|
|
123
|
+
// If validation fails, reject immediately
|
|
124
|
+
if (!parseResult.success) {
|
|
125
|
+
const errorMessage = `Invalid params for ${methodName}: ${parseResult.error.message}`;
|
|
126
|
+
return withTrackedPromise(ctx, async (deferred) => {
|
|
127
|
+
using errHandle = context.newString(errorMessage);
|
|
128
|
+
deferred.reject(errHandle);
|
|
129
|
+
ctx.runtime.executePendingJobs();
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return withTrackedPromise(ctx, async (deferred) => {
|
|
134
|
+
// Call the actual actor method
|
|
135
|
+
const result = await (hostStub as any)[methodName](...parseResult.data);
|
|
136
|
+
|
|
137
|
+
// Handle ReadableStream specially
|
|
138
|
+
if (result instanceof ReadableStream) {
|
|
139
|
+
using streamHandle = createStreamHandle(ctx, result, registry, counters);
|
|
140
|
+
deferred.resolve(streamHandle);
|
|
141
|
+
} else {
|
|
142
|
+
// Validate return type (optional, just for safety)
|
|
143
|
+
const returnResult = schema.returns.safeParse(result);
|
|
144
|
+
if (!returnResult.success) {
|
|
145
|
+
logger?.warn?.(
|
|
146
|
+
`[Actor Bridge] Return type mismatch for ${methodName}: ${returnResult.error.message}`
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
using resultHandle = convertToHandle(context, result);
|
|
151
|
+
deferred.resolve(resultHandle);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
ctx.runtime.executePendingJobs();
|
|
155
|
+
});
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
context.setProp(stubHandle, methodName, methodHandle);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
return stubHandle;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Create a ReadableStream handle for streaming returns
|
|
166
|
+
*/
|
|
167
|
+
function createStreamHandle(
|
|
168
|
+
ctx: BridgeContext,
|
|
169
|
+
stream: ReadableStream,
|
|
170
|
+
registry: StreamRegistry,
|
|
171
|
+
counters: { streamIdCounter: { value: number }; readerIdCounter: { value: number } }
|
|
172
|
+
): QuickJSHandle {
|
|
173
|
+
const { context } = ctx;
|
|
174
|
+
|
|
175
|
+
const streamId = nextId(counters.streamIdCounter);
|
|
176
|
+
registry.hostStreams.set(streamId, stream);
|
|
177
|
+
|
|
178
|
+
const streamHandle = context.newObject();
|
|
179
|
+
|
|
180
|
+
// Add getReader() method
|
|
181
|
+
{
|
|
182
|
+
using getReaderMethod = context.newFunction('getReader', () => {
|
|
183
|
+
const reader = stream.getReader();
|
|
184
|
+
const readerId = nextId(counters.readerIdCounter);
|
|
185
|
+
registry.hostReaders.set(readerId, reader);
|
|
186
|
+
|
|
187
|
+
return createReaderHandle(ctx, reader, () => {
|
|
188
|
+
registry.hostReaders.delete(readerId);
|
|
189
|
+
});
|
|
190
|
+
});
|
|
191
|
+
context.setProp(streamHandle, 'getReader', getReaderMethod);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
return streamHandle;
|
|
195
|
+
}
|