@cloudflare/sandbox 0.0.0-aa00a75 → 0.0.0-aeba44f
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/CHANGELOG.md +158 -15
- package/Dockerfile +88 -71
- package/LICENSE +176 -0
- package/README.md +10 -5
- package/dist/index.d.ts +1953 -9
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +3278 -53
- package/dist/index.js.map +1 -1
- package/package.json +11 -9
- package/src/clients/base-client.ts +39 -24
- package/src/clients/command-client.ts +8 -8
- package/src/clients/file-client.ts +51 -20
- package/src/clients/git-client.ts +9 -3
- package/src/clients/index.ts +16 -15
- package/src/clients/interpreter-client.ts +51 -47
- package/src/clients/port-client.ts +10 -10
- package/src/clients/process-client.ts +11 -8
- package/src/clients/sandbox-client.ts +2 -4
- package/src/clients/types.ts +6 -2
- package/src/clients/utility-client.ts +67 -5
- package/src/errors/adapter.ts +90 -32
- package/src/errors/classes.ts +189 -64
- package/src/errors/index.ts +9 -5
- package/src/file-stream.ts +11 -6
- package/src/index.ts +28 -17
- package/src/interpreter.ts +50 -41
- package/src/request-handler.ts +34 -21
- package/src/sandbox.ts +502 -151
- package/src/security.ts +21 -6
- package/src/sse-parser.ts +4 -3
- package/src/version.ts +6 -0
- package/startup.sh +1 -1
- package/tests/base-client.test.ts +116 -80
- package/tests/command-client.test.ts +149 -112
- package/tests/file-client.test.ts +373 -185
- package/tests/file-stream.test.ts +24 -20
- package/tests/get-sandbox.test.ts +149 -0
- package/tests/git-client.test.ts +260 -101
- package/tests/port-client.test.ts +100 -108
- package/tests/process-client.test.ts +204 -179
- package/tests/request-handler.test.ts +292 -0
- package/tests/sandbox.test.ts +336 -62
- package/tests/sse-parser.test.ts +17 -16
- package/tests/utility-client.test.ts +129 -56
- package/tests/version.test.ts +16 -0
- package/tsdown.config.ts +12 -0
- package/vitest.config.ts +6 -6
- package/dist/chunk-BCJ7SF3Q.js +0 -117
- package/dist/chunk-BCJ7SF3Q.js.map +0 -1
- package/dist/chunk-BFVUNTP4.js +0 -104
- package/dist/chunk-BFVUNTP4.js.map +0 -1
- package/dist/chunk-EKSWCBCA.js +0 -86
- package/dist/chunk-EKSWCBCA.js.map +0 -1
- package/dist/chunk-HGF554LH.js +0 -2236
- package/dist/chunk-HGF554LH.js.map +0 -1
- package/dist/chunk-Z532A7QC.js +0 -78
- package/dist/chunk-Z532A7QC.js.map +0 -1
- package/dist/file-stream.d.ts +0 -43
- package/dist/file-stream.js +0 -9
- package/dist/file-stream.js.map +0 -1
- package/dist/interpreter.d.ts +0 -33
- package/dist/interpreter.js +0 -8
- package/dist/interpreter.js.map +0 -1
- package/dist/request-handler.d.ts +0 -18
- package/dist/request-handler.js +0 -12
- package/dist/request-handler.js.map +0 -1
- package/dist/sandbox-D9K2ypln.d.ts +0 -583
- package/dist/sandbox.d.ts +0 -4
- package/dist/sandbox.js +0 -12
- package/dist/sandbox.js.map +0 -1
- package/dist/security.d.ts +0 -31
- package/dist/security.js +0 -13
- package/dist/security.js.map +0 -1
- package/dist/sse-parser.d.ts +0 -28
- package/dist/sse-parser.js +0 -11
- package/dist/sse-parser.js.map +0 -1
package/tests/sse-parser.test.ts
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
asyncIterableToSSEStream,
|
|
4
|
+
parseSSEStream,
|
|
5
|
+
responseToAsyncIterable
|
|
6
|
+
} from '../src/sse-parser';
|
|
3
7
|
|
|
4
8
|
function createMockSSEStream(events: string[]): ReadableStream<Uint8Array> {
|
|
5
9
|
return new ReadableStream({
|
|
@@ -25,7 +29,6 @@ describe('SSE Parser', () => {
|
|
|
25
29
|
});
|
|
26
30
|
|
|
27
31
|
describe('parseSSEStream', () => {
|
|
28
|
-
|
|
29
32
|
it('should parse valid SSE events', async () => {
|
|
30
33
|
const stream = createMockSSEStream([
|
|
31
34
|
'data: {"type":"start","command":"echo test"}\n\n',
|
|
@@ -134,9 +137,7 @@ describe('SSE Parser', () => {
|
|
|
134
137
|
});
|
|
135
138
|
|
|
136
139
|
it('should handle remaining buffer data after stream ends', async () => {
|
|
137
|
-
const stream = createMockSSEStream([
|
|
138
|
-
'data: {"type":"complete"}'
|
|
139
|
-
]);
|
|
140
|
+
const stream = createMockSSEStream(['data: {"type":"complete"}']);
|
|
140
141
|
|
|
141
142
|
const events: any[] = [];
|
|
142
143
|
for await (const event of parseSSEStream(stream)) {
|
|
@@ -153,7 +154,8 @@ describe('SSE Parser', () => {
|
|
|
153
154
|
controller.abort();
|
|
154
155
|
|
|
155
156
|
await expect(async () => {
|
|
156
|
-
for await (const event of parseSSEStream(stream, controller.signal)) {
|
|
157
|
+
for await (const event of parseSSEStream(stream, controller.signal)) {
|
|
158
|
+
}
|
|
157
159
|
}).rejects.toThrow('Operation was aborted');
|
|
158
160
|
});
|
|
159
161
|
|
|
@@ -236,10 +238,10 @@ describe('SSE Parser', () => {
|
|
|
236
238
|
const stream = asyncIterableToSSEStream(mockEvents());
|
|
237
239
|
const reader = stream.getReader();
|
|
238
240
|
const decoder = new TextDecoder();
|
|
239
|
-
|
|
241
|
+
|
|
240
242
|
const chunks: string[] = [];
|
|
241
243
|
let done = false;
|
|
242
|
-
|
|
244
|
+
|
|
243
245
|
while (!done) {
|
|
244
246
|
const { value, done: readerDone } = await reader.read();
|
|
245
247
|
done = readerDone;
|
|
@@ -251,9 +253,9 @@ describe('SSE Parser', () => {
|
|
|
251
253
|
const fullOutput = chunks.join('');
|
|
252
254
|
expect(fullOutput).toBe(
|
|
253
255
|
'data: {"type":"start","command":"test"}\n\n' +
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
256
|
+
'data: {"type":"stdout","data":"output"}\n\n' +
|
|
257
|
+
'data: {"type":"complete","exitCode":0}\n\n' +
|
|
258
|
+
'data: [DONE]\n\n'
|
|
257
259
|
);
|
|
258
260
|
});
|
|
259
261
|
|
|
@@ -262,10 +264,9 @@ describe('SSE Parser', () => {
|
|
|
262
264
|
yield { name: 'test', value: 123 };
|
|
263
265
|
}
|
|
264
266
|
|
|
265
|
-
const stream = asyncIterableToSSEStream(
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
);
|
|
267
|
+
const stream = asyncIterableToSSEStream(mockEvents(), {
|
|
268
|
+
serialize: (event) => `custom:${event.name}=${event.value}`
|
|
269
|
+
});
|
|
269
270
|
|
|
270
271
|
const reader = stream.getReader();
|
|
271
272
|
const decoder = new TextDecoder();
|
|
@@ -287,4 +288,4 @@ describe('SSE Parser', () => {
|
|
|
287
288
|
await expect(reader.read()).rejects.toThrow('Async iterable error');
|
|
288
289
|
});
|
|
289
290
|
});
|
|
290
|
-
});
|
|
291
|
+
});
|
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
2
2
|
import type {
|
|
3
3
|
CommandsResponse,
|
|
4
|
-
PingResponse
|
|
4
|
+
PingResponse,
|
|
5
|
+
VersionResponse
|
|
5
6
|
} from '../src/clients';
|
|
6
7
|
import { UtilityClient } from '../src/clients/utility-client';
|
|
7
|
-
import {
|
|
8
|
-
SandboxError
|
|
9
|
-
} from '../src/errors';
|
|
8
|
+
import { SandboxError } from '../src/errors';
|
|
10
9
|
|
|
11
10
|
// Mock data factory for creating test responses
|
|
12
|
-
const mockPingResponse = (
|
|
11
|
+
const mockPingResponse = (
|
|
12
|
+
overrides: Partial<PingResponse> = {}
|
|
13
|
+
): PingResponse => ({
|
|
13
14
|
success: true,
|
|
14
15
|
message: 'pong',
|
|
15
16
|
uptime: 12345,
|
|
@@ -17,7 +18,10 @@ const mockPingResponse = (overrides: Partial<PingResponse> = {}): PingResponse =
|
|
|
17
18
|
...overrides
|
|
18
19
|
});
|
|
19
20
|
|
|
20
|
-
const mockCommandsResponse = (
|
|
21
|
+
const mockCommandsResponse = (
|
|
22
|
+
commands: string[],
|
|
23
|
+
overrides: Partial<CommandsResponse> = {}
|
|
24
|
+
): CommandsResponse => ({
|
|
21
25
|
success: true,
|
|
22
26
|
availableCommands: commands,
|
|
23
27
|
count: commands.length,
|
|
@@ -25,6 +29,16 @@ const mockCommandsResponse = (commands: string[], overrides: Partial<CommandsRes
|
|
|
25
29
|
...overrides
|
|
26
30
|
});
|
|
27
31
|
|
|
32
|
+
const mockVersionResponse = (
|
|
33
|
+
version: string = '0.4.5',
|
|
34
|
+
overrides: Partial<VersionResponse> = {}
|
|
35
|
+
): VersionResponse => ({
|
|
36
|
+
success: true,
|
|
37
|
+
version,
|
|
38
|
+
timestamp: '2023-01-01T00:00:00Z',
|
|
39
|
+
...overrides
|
|
40
|
+
});
|
|
41
|
+
|
|
28
42
|
describe('UtilityClient', () => {
|
|
29
43
|
let client: UtilityClient;
|
|
30
44
|
let mockFetch: ReturnType<typeof vi.fn>;
|
|
@@ -37,7 +51,7 @@ describe('UtilityClient', () => {
|
|
|
37
51
|
|
|
38
52
|
client = new UtilityClient({
|
|
39
53
|
baseUrl: 'http://test.com',
|
|
40
|
-
port: 3000
|
|
54
|
+
port: 3000
|
|
41
55
|
});
|
|
42
56
|
});
|
|
43
57
|
|
|
@@ -47,10 +61,9 @@ describe('UtilityClient', () => {
|
|
|
47
61
|
|
|
48
62
|
describe('health checking', () => {
|
|
49
63
|
it('should check sandbox health successfully', async () => {
|
|
50
|
-
mockFetch.mockResolvedValue(
|
|
51
|
-
JSON.stringify(mockPingResponse()),
|
|
52
|
-
|
|
53
|
-
));
|
|
64
|
+
mockFetch.mockResolvedValue(
|
|
65
|
+
new Response(JSON.stringify(mockPingResponse()), { status: 200 })
|
|
66
|
+
);
|
|
54
67
|
|
|
55
68
|
const result = await client.ping();
|
|
56
69
|
|
|
@@ -61,10 +74,11 @@ describe('UtilityClient', () => {
|
|
|
61
74
|
const messages = ['pong', 'alive', 'ok'];
|
|
62
75
|
|
|
63
76
|
for (const message of messages) {
|
|
64
|
-
mockFetch.mockResolvedValueOnce(
|
|
65
|
-
JSON.stringify(mockPingResponse({ message })),
|
|
66
|
-
|
|
67
|
-
|
|
77
|
+
mockFetch.mockResolvedValueOnce(
|
|
78
|
+
new Response(JSON.stringify(mockPingResponse({ message })), {
|
|
79
|
+
status: 200
|
|
80
|
+
})
|
|
81
|
+
);
|
|
68
82
|
|
|
69
83
|
const result = await client.ping();
|
|
70
84
|
expect(result).toBe(message);
|
|
@@ -79,11 +93,11 @@ describe('UtilityClient', () => {
|
|
|
79
93
|
const healthChecks = await Promise.all([
|
|
80
94
|
client.ping(),
|
|
81
95
|
client.ping(),
|
|
82
|
-
client.ping()
|
|
96
|
+
client.ping()
|
|
83
97
|
]);
|
|
84
98
|
|
|
85
99
|
expect(healthChecks).toHaveLength(3);
|
|
86
|
-
healthChecks.forEach(result => {
|
|
100
|
+
healthChecks.forEach((result) => {
|
|
87
101
|
expect(result).toBe('pong');
|
|
88
102
|
});
|
|
89
103
|
|
|
@@ -96,10 +110,9 @@ describe('UtilityClient', () => {
|
|
|
96
110
|
code: 'HEALTH_CHECK_FAILED'
|
|
97
111
|
};
|
|
98
112
|
|
|
99
|
-
mockFetch.mockResolvedValue(
|
|
100
|
-
JSON.stringify(errorResponse),
|
|
101
|
-
|
|
102
|
-
));
|
|
113
|
+
mockFetch.mockResolvedValue(
|
|
114
|
+
new Response(JSON.stringify(errorResponse), { status: 503 })
|
|
115
|
+
);
|
|
103
116
|
|
|
104
117
|
await expect(client.ping()).rejects.toThrow();
|
|
105
118
|
});
|
|
@@ -115,10 +128,11 @@ describe('UtilityClient', () => {
|
|
|
115
128
|
it('should discover available system commands', async () => {
|
|
116
129
|
const systemCommands = ['ls', 'cat', 'echo', 'grep', 'find'];
|
|
117
130
|
|
|
118
|
-
mockFetch.mockResolvedValue(
|
|
119
|
-
JSON.stringify(mockCommandsResponse(systemCommands)),
|
|
120
|
-
|
|
121
|
-
|
|
131
|
+
mockFetch.mockResolvedValue(
|
|
132
|
+
new Response(JSON.stringify(mockCommandsResponse(systemCommands)), {
|
|
133
|
+
status: 200
|
|
134
|
+
})
|
|
135
|
+
);
|
|
122
136
|
|
|
123
137
|
const result = await client.getCommands();
|
|
124
138
|
|
|
@@ -131,10 +145,11 @@ describe('UtilityClient', () => {
|
|
|
131
145
|
it('should handle minimal command environments', async () => {
|
|
132
146
|
const minimalCommands = ['sh', 'echo', 'cat'];
|
|
133
147
|
|
|
134
|
-
mockFetch.mockResolvedValue(
|
|
135
|
-
JSON.stringify(mockCommandsResponse(minimalCommands)),
|
|
136
|
-
|
|
137
|
-
|
|
148
|
+
mockFetch.mockResolvedValue(
|
|
149
|
+
new Response(JSON.stringify(mockCommandsResponse(minimalCommands)), {
|
|
150
|
+
status: 200
|
|
151
|
+
})
|
|
152
|
+
);
|
|
138
153
|
|
|
139
154
|
const result = await client.getCommands();
|
|
140
155
|
|
|
@@ -145,10 +160,11 @@ describe('UtilityClient', () => {
|
|
|
145
160
|
it('should handle large command environments', async () => {
|
|
146
161
|
const richCommands = Array.from({ length: 150 }, (_, i) => `cmd_${i}`);
|
|
147
162
|
|
|
148
|
-
mockFetch.mockResolvedValue(
|
|
149
|
-
JSON.stringify(mockCommandsResponse(richCommands)),
|
|
150
|
-
|
|
151
|
-
|
|
163
|
+
mockFetch.mockResolvedValue(
|
|
164
|
+
new Response(JSON.stringify(mockCommandsResponse(richCommands)), {
|
|
165
|
+
status: 200
|
|
166
|
+
})
|
|
167
|
+
);
|
|
152
168
|
|
|
153
169
|
const result = await client.getCommands();
|
|
154
170
|
|
|
@@ -157,10 +173,9 @@ describe('UtilityClient', () => {
|
|
|
157
173
|
});
|
|
158
174
|
|
|
159
175
|
it('should handle empty command environments', async () => {
|
|
160
|
-
mockFetch.mockResolvedValue(
|
|
161
|
-
JSON.stringify(mockCommandsResponse([])),
|
|
162
|
-
|
|
163
|
-
));
|
|
176
|
+
mockFetch.mockResolvedValue(
|
|
177
|
+
new Response(JSON.stringify(mockCommandsResponse([])), { status: 200 })
|
|
178
|
+
);
|
|
164
179
|
|
|
165
180
|
const result = await client.getCommands();
|
|
166
181
|
|
|
@@ -174,10 +189,9 @@ describe('UtilityClient', () => {
|
|
|
174
189
|
code: 'PERMISSION_DENIED'
|
|
175
190
|
};
|
|
176
191
|
|
|
177
|
-
mockFetch.mockResolvedValue(
|
|
178
|
-
JSON.stringify(errorResponse),
|
|
179
|
-
|
|
180
|
-
));
|
|
192
|
+
mockFetch.mockResolvedValue(
|
|
193
|
+
new Response(JSON.stringify(errorResponse), { status: 403 })
|
|
194
|
+
);
|
|
181
195
|
|
|
182
196
|
await expect(client.getCommands()).rejects.toThrow();
|
|
183
197
|
});
|
|
@@ -185,10 +199,9 @@ describe('UtilityClient', () => {
|
|
|
185
199
|
|
|
186
200
|
describe('error handling and resilience', () => {
|
|
187
201
|
it('should handle malformed server responses gracefully', async () => {
|
|
188
|
-
mockFetch.mockResolvedValue(
|
|
189
|
-
'invalid json {',
|
|
190
|
-
|
|
191
|
-
));
|
|
202
|
+
mockFetch.mockResolvedValue(
|
|
203
|
+
new Response('invalid json {', { status: 200 })
|
|
204
|
+
);
|
|
192
205
|
|
|
193
206
|
await expect(client.ping()).rejects.toThrow(SandboxError);
|
|
194
207
|
});
|
|
@@ -202,10 +215,9 @@ describe('UtilityClient', () => {
|
|
|
202
215
|
|
|
203
216
|
it('should handle partial service failures', async () => {
|
|
204
217
|
// First call (ping) succeeds
|
|
205
|
-
mockFetch.mockResolvedValueOnce(
|
|
206
|
-
JSON.stringify(mockPingResponse()),
|
|
207
|
-
|
|
208
|
-
));
|
|
218
|
+
mockFetch.mockResolvedValueOnce(
|
|
219
|
+
new Response(JSON.stringify(mockPingResponse()), { status: 200 })
|
|
220
|
+
);
|
|
209
221
|
|
|
210
222
|
// Second call (getCommands) fails
|
|
211
223
|
const errorResponse = {
|
|
@@ -213,10 +225,9 @@ describe('UtilityClient', () => {
|
|
|
213
225
|
code: 'SERVICE_UNAVAILABLE'
|
|
214
226
|
};
|
|
215
227
|
|
|
216
|
-
mockFetch.mockResolvedValueOnce(
|
|
217
|
-
JSON.stringify(errorResponse),
|
|
218
|
-
|
|
219
|
-
));
|
|
228
|
+
mockFetch.mockResolvedValueOnce(
|
|
229
|
+
new Response(JSON.stringify(errorResponse), { status: 503 })
|
|
230
|
+
);
|
|
220
231
|
|
|
221
232
|
const pingResult = await client.ping();
|
|
222
233
|
expect(pingResult).toBe('pong');
|
|
@@ -231,7 +242,9 @@ describe('UtilityClient', () => {
|
|
|
231
242
|
if (callCount % 2 === 0) {
|
|
232
243
|
return Promise.reject(new Error('Intermittent failure'));
|
|
233
244
|
} else {
|
|
234
|
-
return Promise.resolve(
|
|
245
|
+
return Promise.resolve(
|
|
246
|
+
new Response(JSON.stringify(mockPingResponse()))
|
|
247
|
+
);
|
|
235
248
|
}
|
|
236
249
|
});
|
|
237
250
|
|
|
@@ -239,7 +252,7 @@ describe('UtilityClient', () => {
|
|
|
239
252
|
client.ping(), // Should succeed (call 1)
|
|
240
253
|
client.ping(), // Should fail (call 2)
|
|
241
254
|
client.ping(), // Should succeed (call 3)
|
|
242
|
-
client.ping()
|
|
255
|
+
client.ping() // Should fail (call 4)
|
|
243
256
|
]);
|
|
244
257
|
|
|
245
258
|
expect(results[0].status).toBe('fulfilled');
|
|
@@ -249,6 +262,66 @@ describe('UtilityClient', () => {
|
|
|
249
262
|
});
|
|
250
263
|
});
|
|
251
264
|
|
|
265
|
+
describe('version checking', () => {
|
|
266
|
+
it('should get container version successfully', async () => {
|
|
267
|
+
mockFetch.mockResolvedValue(
|
|
268
|
+
new Response(JSON.stringify(mockVersionResponse('0.4.5')), {
|
|
269
|
+
status: 200
|
|
270
|
+
})
|
|
271
|
+
);
|
|
272
|
+
|
|
273
|
+
const result = await client.getVersion();
|
|
274
|
+
|
|
275
|
+
expect(result).toBe('0.4.5');
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
it('should handle different version formats', async () => {
|
|
279
|
+
const versions = ['1.0.0', '2.5.3-beta', '0.0.1', '10.20.30'];
|
|
280
|
+
|
|
281
|
+
for (const version of versions) {
|
|
282
|
+
mockFetch.mockResolvedValueOnce(
|
|
283
|
+
new Response(JSON.stringify(mockVersionResponse(version)), {
|
|
284
|
+
status: 200
|
|
285
|
+
})
|
|
286
|
+
);
|
|
287
|
+
|
|
288
|
+
const result = await client.getVersion();
|
|
289
|
+
expect(result).toBe(version);
|
|
290
|
+
}
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
it('should return "unknown" when version endpoint does not exist (backward compatibility)', async () => {
|
|
294
|
+
// Simulate 404 or other error for old containers
|
|
295
|
+
mockFetch.mockResolvedValue(
|
|
296
|
+
new Response(JSON.stringify({ error: 'Not Found' }), { status: 404 })
|
|
297
|
+
);
|
|
298
|
+
|
|
299
|
+
const result = await client.getVersion();
|
|
300
|
+
|
|
301
|
+
expect(result).toBe('unknown');
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
it('should return "unknown" on network failure (backward compatibility)', async () => {
|
|
305
|
+
mockFetch.mockRejectedValue(new Error('Network connection failed'));
|
|
306
|
+
|
|
307
|
+
const result = await client.getVersion();
|
|
308
|
+
|
|
309
|
+
expect(result).toBe('unknown');
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
it('should handle version response with unknown value', async () => {
|
|
313
|
+
mockFetch.mockResolvedValue(
|
|
314
|
+
new Response(JSON.stringify(mockVersionResponse('unknown')), {
|
|
315
|
+
status: 200
|
|
316
|
+
})
|
|
317
|
+
);
|
|
318
|
+
|
|
319
|
+
const result = await client.getVersion();
|
|
320
|
+
|
|
321
|
+
expect(result).toBe('unknown');
|
|
322
|
+
});
|
|
323
|
+
});
|
|
324
|
+
|
|
252
325
|
describe('constructor options', () => {
|
|
253
326
|
it('should initialize with minimal options', () => {
|
|
254
327
|
const minimalClient = new UtilityClient();
|
|
@@ -258,7 +331,7 @@ describe('UtilityClient', () => {
|
|
|
258
331
|
it('should initialize with full options', () => {
|
|
259
332
|
const fullOptionsClient = new UtilityClient({
|
|
260
333
|
baseUrl: 'http://custom.com',
|
|
261
|
-
port: 8080
|
|
334
|
+
port: 8080
|
|
262
335
|
});
|
|
263
336
|
expect(fullOptionsClient).toBeInstanceOf(UtilityClient);
|
|
264
337
|
});
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { describe, expect, test } from 'vitest';
|
|
2
|
+
import packageJson from '../package.json';
|
|
3
|
+
import { SDK_VERSION } from '../src/version';
|
|
4
|
+
|
|
5
|
+
describe('Version Sync', () => {
|
|
6
|
+
test('SDK_VERSION matches package.json version', () => {
|
|
7
|
+
// Verify versions match
|
|
8
|
+
expect(SDK_VERSION).toBe(packageJson.version);
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
test('SDK_VERSION is a valid semver version', () => {
|
|
12
|
+
// Check if version matches semver pattern (major.minor.patch)
|
|
13
|
+
const semverPattern = /^\d+\.\d+\.\d+(-[\w.]+)?$/;
|
|
14
|
+
expect(SDK_VERSION).toMatch(semverPattern);
|
|
15
|
+
});
|
|
16
|
+
});
|
package/tsdown.config.ts
ADDED
package/vitest.config.ts
CHANGED
|
@@ -18,14 +18,14 @@ export default defineWorkersConfig({
|
|
|
18
18
|
poolOptions: {
|
|
19
19
|
workers: {
|
|
20
20
|
wrangler: {
|
|
21
|
-
configPath: './tests/wrangler.jsonc'
|
|
21
|
+
configPath: './tests/wrangler.jsonc'
|
|
22
22
|
},
|
|
23
23
|
singleWorker: true,
|
|
24
|
-
isolatedStorage: false
|
|
25
|
-
}
|
|
26
|
-
}
|
|
24
|
+
isolatedStorage: false
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
27
|
},
|
|
28
28
|
esbuild: {
|
|
29
|
-
target: 'esnext'
|
|
30
|
-
}
|
|
29
|
+
target: 'esnext'
|
|
30
|
+
}
|
|
31
31
|
});
|
package/dist/chunk-BCJ7SF3Q.js
DELETED
|
@@ -1,117 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
validateLanguage
|
|
3
|
-
} from "./chunk-Z532A7QC.js";
|
|
4
|
-
|
|
5
|
-
// src/interpreter.ts
|
|
6
|
-
import {
|
|
7
|
-
Execution,
|
|
8
|
-
ResultImpl
|
|
9
|
-
} from "@repo/shared";
|
|
10
|
-
var CodeInterpreter = class {
|
|
11
|
-
interpreterClient;
|
|
12
|
-
contexts = /* @__PURE__ */ new Map();
|
|
13
|
-
constructor(sandbox) {
|
|
14
|
-
this.interpreterClient = sandbox.client.interpreter;
|
|
15
|
-
}
|
|
16
|
-
/**
|
|
17
|
-
* Create a new code execution context
|
|
18
|
-
*/
|
|
19
|
-
async createCodeContext(options = {}) {
|
|
20
|
-
validateLanguage(options.language);
|
|
21
|
-
const context = await this.interpreterClient.createCodeContext(options);
|
|
22
|
-
this.contexts.set(context.id, context);
|
|
23
|
-
return context;
|
|
24
|
-
}
|
|
25
|
-
/**
|
|
26
|
-
* Run code with optional context
|
|
27
|
-
*/
|
|
28
|
-
async runCode(code, options = {}) {
|
|
29
|
-
let context = options.context;
|
|
30
|
-
if (!context) {
|
|
31
|
-
const language = options.language || "python";
|
|
32
|
-
context = await this.getOrCreateDefaultContext(language);
|
|
33
|
-
}
|
|
34
|
-
const execution = new Execution(code, context);
|
|
35
|
-
await this.interpreterClient.runCodeStream(context.id, code, options.language, {
|
|
36
|
-
onStdout: (output) => {
|
|
37
|
-
execution.logs.stdout.push(output.text);
|
|
38
|
-
if (options.onStdout) return options.onStdout(output);
|
|
39
|
-
},
|
|
40
|
-
onStderr: (output) => {
|
|
41
|
-
execution.logs.stderr.push(output.text);
|
|
42
|
-
if (options.onStderr) return options.onStderr(output);
|
|
43
|
-
},
|
|
44
|
-
onResult: async (result) => {
|
|
45
|
-
execution.results.push(new ResultImpl(result));
|
|
46
|
-
if (options.onResult) return options.onResult(result);
|
|
47
|
-
},
|
|
48
|
-
onError: (error) => {
|
|
49
|
-
execution.error = error;
|
|
50
|
-
if (options.onError) return options.onError(error);
|
|
51
|
-
}
|
|
52
|
-
});
|
|
53
|
-
return execution;
|
|
54
|
-
}
|
|
55
|
-
/**
|
|
56
|
-
* Run code and return a streaming response
|
|
57
|
-
*/
|
|
58
|
-
async runCodeStream(code, options = {}) {
|
|
59
|
-
let context = options.context;
|
|
60
|
-
if (!context) {
|
|
61
|
-
const language = options.language || "python";
|
|
62
|
-
context = await this.getOrCreateDefaultContext(language);
|
|
63
|
-
}
|
|
64
|
-
const response = await this.interpreterClient.doFetch("/api/execute/code", {
|
|
65
|
-
method: "POST",
|
|
66
|
-
headers: {
|
|
67
|
-
"Content-Type": "application/json",
|
|
68
|
-
Accept: "text/event-stream"
|
|
69
|
-
},
|
|
70
|
-
body: JSON.stringify({
|
|
71
|
-
context_id: context.id,
|
|
72
|
-
code,
|
|
73
|
-
language: options.language
|
|
74
|
-
})
|
|
75
|
-
});
|
|
76
|
-
if (!response.ok) {
|
|
77
|
-
const errorData = await response.json().catch(() => ({ error: "Unknown error" }));
|
|
78
|
-
throw new Error(
|
|
79
|
-
errorData.error || `Failed to execute code: ${response.status}`
|
|
80
|
-
);
|
|
81
|
-
}
|
|
82
|
-
if (!response.body) {
|
|
83
|
-
throw new Error("No response body for streaming execution");
|
|
84
|
-
}
|
|
85
|
-
return response.body;
|
|
86
|
-
}
|
|
87
|
-
/**
|
|
88
|
-
* List all code contexts
|
|
89
|
-
*/
|
|
90
|
-
async listCodeContexts() {
|
|
91
|
-
const contexts = await this.interpreterClient.listCodeContexts();
|
|
92
|
-
for (const context of contexts) {
|
|
93
|
-
this.contexts.set(context.id, context);
|
|
94
|
-
}
|
|
95
|
-
return contexts;
|
|
96
|
-
}
|
|
97
|
-
/**
|
|
98
|
-
* Delete a code context
|
|
99
|
-
*/
|
|
100
|
-
async deleteCodeContext(contextId) {
|
|
101
|
-
await this.interpreterClient.deleteCodeContext(contextId);
|
|
102
|
-
this.contexts.delete(contextId);
|
|
103
|
-
}
|
|
104
|
-
async getOrCreateDefaultContext(language) {
|
|
105
|
-
for (const context of this.contexts.values()) {
|
|
106
|
-
if (context.language === language) {
|
|
107
|
-
return context;
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
return this.createCodeContext({ language });
|
|
111
|
-
}
|
|
112
|
-
};
|
|
113
|
-
|
|
114
|
-
export {
|
|
115
|
-
CodeInterpreter
|
|
116
|
-
};
|
|
117
|
-
//# sourceMappingURL=chunk-BCJ7SF3Q.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/interpreter.ts"],"sourcesContent":["import {\n type CodeContext,\n type CreateContextOptions,\n Execution,\n type ExecutionError,\n type OutputMessage,\n type Result,\n ResultImpl,\n type RunCodeOptions,\n} from \"@repo/shared\";\nimport type { InterpreterClient } from \"./clients/interpreter-client.js\";\nimport type { Sandbox } from \"./sandbox.js\";\nimport { validateLanguage } from \"./security.js\";\n\nexport class CodeInterpreter {\n private interpreterClient: InterpreterClient;\n private contexts = new Map<string, CodeContext>();\n\n constructor(sandbox: Sandbox) {\n // In init-testing architecture, client is a SandboxClient with an interpreter property\n this.interpreterClient = (sandbox.client as any).interpreter as InterpreterClient;\n }\n\n /**\n * Create a new code execution context\n */\n async createCodeContext(\n options: CreateContextOptions = {}\n ): Promise<CodeContext> {\n // Validate language before sending to container\n validateLanguage(options.language);\n\n const context = await this.interpreterClient.createCodeContext(options);\n this.contexts.set(context.id, context);\n return context;\n }\n\n /**\n * Run code with optional context\n */\n async runCode(\n code: string,\n options: RunCodeOptions = {}\n ): Promise<Execution> {\n // Get or create context\n let context = options.context;\n if (!context) {\n // Try to find or create a default context for the language\n const language = options.language || \"python\";\n context = await this.getOrCreateDefaultContext(language);\n }\n\n // Create execution object to collect results\n const execution = new Execution(code, context);\n\n // Stream execution\n await this.interpreterClient.runCodeStream(context.id, code, options.language, {\n onStdout: (output: OutputMessage) => {\n execution.logs.stdout.push(output.text);\n if (options.onStdout) return options.onStdout(output);\n },\n onStderr: (output: OutputMessage) => {\n execution.logs.stderr.push(output.text);\n if (options.onStderr) return options.onStderr(output);\n },\n onResult: async (result: Result) => {\n execution.results.push(new ResultImpl(result) as any);\n if (options.onResult) return options.onResult(result);\n },\n onError: (error: ExecutionError) => {\n execution.error = error;\n if (options.onError) return options.onError(error);\n },\n });\n\n return execution;\n }\n\n /**\n * Run code and return a streaming response\n */\n async runCodeStream(\n code: string,\n options: RunCodeOptions = {}\n ): Promise<ReadableStream> {\n // Get or create context\n let context = options.context;\n if (!context) {\n const language = options.language || \"python\";\n context = await this.getOrCreateDefaultContext(language);\n }\n\n // Create streaming response\n // Note: doFetch is protected but we need direct access for raw stream response\n const response = await (this.interpreterClient as any).doFetch(\"/api/execute/code\", {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Accept: \"text/event-stream\",\n },\n body: JSON.stringify({\n context_id: context.id,\n code,\n language: options.language,\n }),\n });\n\n if (!response.ok) {\n const errorData = (await response\n .json()\n .catch(() => ({ error: \"Unknown error\" }))) as { error?: string };\n throw new Error(\n errorData.error || `Failed to execute code: ${response.status}`\n );\n }\n\n if (!response.body) {\n throw new Error(\"No response body for streaming execution\");\n }\n\n return response.body;\n }\n\n /**\n * List all code contexts\n */\n async listCodeContexts(): Promise<CodeContext[]> {\n const contexts = await this.interpreterClient.listCodeContexts();\n\n // Update local cache\n for (const context of contexts) {\n this.contexts.set(context.id, context);\n }\n\n return contexts;\n }\n\n /**\n * Delete a code context\n */\n async deleteCodeContext(contextId: string): Promise<void> {\n await this.interpreterClient.deleteCodeContext(contextId);\n this.contexts.delete(contextId);\n }\n\n private async getOrCreateDefaultContext(\n language: \"python\" | \"javascript\" | \"typescript\"\n ): Promise<CodeContext> {\n // Check if we have a cached context for this language\n for (const context of this.contexts.values()) {\n if (context.language === language) {\n return context;\n }\n }\n\n // Create new default context\n return this.createCodeContext({ language });\n }\n}\n"],"mappings":"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","names":[]}
|