@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.
Files changed (76) hide show
  1. package/CHANGELOG.md +158 -15
  2. package/Dockerfile +88 -71
  3. package/LICENSE +176 -0
  4. package/README.md +10 -5
  5. package/dist/index.d.ts +1953 -9
  6. package/dist/index.d.ts.map +1 -0
  7. package/dist/index.js +3278 -53
  8. package/dist/index.js.map +1 -1
  9. package/package.json +11 -9
  10. package/src/clients/base-client.ts +39 -24
  11. package/src/clients/command-client.ts +8 -8
  12. package/src/clients/file-client.ts +51 -20
  13. package/src/clients/git-client.ts +9 -3
  14. package/src/clients/index.ts +16 -15
  15. package/src/clients/interpreter-client.ts +51 -47
  16. package/src/clients/port-client.ts +10 -10
  17. package/src/clients/process-client.ts +11 -8
  18. package/src/clients/sandbox-client.ts +2 -4
  19. package/src/clients/types.ts +6 -2
  20. package/src/clients/utility-client.ts +67 -5
  21. package/src/errors/adapter.ts +90 -32
  22. package/src/errors/classes.ts +189 -64
  23. package/src/errors/index.ts +9 -5
  24. package/src/file-stream.ts +11 -6
  25. package/src/index.ts +28 -17
  26. package/src/interpreter.ts +50 -41
  27. package/src/request-handler.ts +34 -21
  28. package/src/sandbox.ts +502 -151
  29. package/src/security.ts +21 -6
  30. package/src/sse-parser.ts +4 -3
  31. package/src/version.ts +6 -0
  32. package/startup.sh +1 -1
  33. package/tests/base-client.test.ts +116 -80
  34. package/tests/command-client.test.ts +149 -112
  35. package/tests/file-client.test.ts +373 -185
  36. package/tests/file-stream.test.ts +24 -20
  37. package/tests/get-sandbox.test.ts +149 -0
  38. package/tests/git-client.test.ts +260 -101
  39. package/tests/port-client.test.ts +100 -108
  40. package/tests/process-client.test.ts +204 -179
  41. package/tests/request-handler.test.ts +292 -0
  42. package/tests/sandbox.test.ts +336 -62
  43. package/tests/sse-parser.test.ts +17 -16
  44. package/tests/utility-client.test.ts +129 -56
  45. package/tests/version.test.ts +16 -0
  46. package/tsdown.config.ts +12 -0
  47. package/vitest.config.ts +6 -6
  48. package/dist/chunk-BCJ7SF3Q.js +0 -117
  49. package/dist/chunk-BCJ7SF3Q.js.map +0 -1
  50. package/dist/chunk-BFVUNTP4.js +0 -104
  51. package/dist/chunk-BFVUNTP4.js.map +0 -1
  52. package/dist/chunk-EKSWCBCA.js +0 -86
  53. package/dist/chunk-EKSWCBCA.js.map +0 -1
  54. package/dist/chunk-HGF554LH.js +0 -2236
  55. package/dist/chunk-HGF554LH.js.map +0 -1
  56. package/dist/chunk-Z532A7QC.js +0 -78
  57. package/dist/chunk-Z532A7QC.js.map +0 -1
  58. package/dist/file-stream.d.ts +0 -43
  59. package/dist/file-stream.js +0 -9
  60. package/dist/file-stream.js.map +0 -1
  61. package/dist/interpreter.d.ts +0 -33
  62. package/dist/interpreter.js +0 -8
  63. package/dist/interpreter.js.map +0 -1
  64. package/dist/request-handler.d.ts +0 -18
  65. package/dist/request-handler.js +0 -12
  66. package/dist/request-handler.js.map +0 -1
  67. package/dist/sandbox-D9K2ypln.d.ts +0 -583
  68. package/dist/sandbox.d.ts +0 -4
  69. package/dist/sandbox.js +0 -12
  70. package/dist/sandbox.js.map +0 -1
  71. package/dist/security.d.ts +0 -31
  72. package/dist/security.js +0 -13
  73. package/dist/security.js.map +0 -1
  74. package/dist/sse-parser.d.ts +0 -28
  75. package/dist/sse-parser.js +0 -11
  76. package/dist/sse-parser.js.map +0 -1
@@ -1,5 +1,9 @@
1
1
  import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
2
- import { asyncIterableToSSEStream, parseSSEStream, responseToAsyncIterable } from '../src/sse-parser';
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
- 'data: {"type":"stdout","data":"output"}\n\n' +
255
- 'data: {"type":"complete","exitCode":0}\n\n' +
256
- 'data: [DONE]\n\n'
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
- mockEvents(),
267
- { serialize: (event) => `custom:${event.name}=${event.value}` }
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 = (overrides: Partial<PingResponse> = {}): PingResponse => ({
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 = (commands: string[], overrides: Partial<CommandsResponse> = {}): CommandsResponse => ({
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(new Response(
51
- JSON.stringify(mockPingResponse()),
52
- { status: 200 }
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(new Response(
65
- JSON.stringify(mockPingResponse({ message })),
66
- { status: 200 }
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(new Response(
100
- JSON.stringify(errorResponse),
101
- { status: 503 }
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(new Response(
119
- JSON.stringify(mockCommandsResponse(systemCommands)),
120
- { status: 200 }
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(new Response(
135
- JSON.stringify(mockCommandsResponse(minimalCommands)),
136
- { status: 200 }
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(new Response(
149
- JSON.stringify(mockCommandsResponse(richCommands)),
150
- { status: 200 }
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(new Response(
161
- JSON.stringify(mockCommandsResponse([])),
162
- { status: 200 }
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(new Response(
178
- JSON.stringify(errorResponse),
179
- { status: 403 }
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(new Response(
189
- 'invalid json {',
190
- { status: 200 }
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(new Response(
206
- JSON.stringify(mockPingResponse()),
207
- { status: 200 }
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(new Response(
217
- JSON.stringify(errorResponse),
218
- { status: 503 }
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(new Response(JSON.stringify(mockPingResponse())));
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(), // Should fail (call 4)
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
+ });
@@ -0,0 +1,12 @@
1
+ import { defineConfig } from 'tsdown';
2
+
3
+ export default defineConfig({
4
+ entry: 'src/index.ts',
5
+ outDir: 'dist',
6
+ dts: {
7
+ sourcemap: true,
8
+ resolve: ['@repo/shared']
9
+ },
10
+ sourcemap: true,
11
+ format: 'esm'
12
+ });
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
  });
@@ -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":";;;;;AAAA;AAAA,EAGE;AAAA,EAIA;AAAA,OAEK;AAKA,IAAM,kBAAN,MAAsB;AAAA,EACnB;AAAA,EACA,WAAW,oBAAI,IAAyB;AAAA,EAEhD,YAAY,SAAkB;AAE5B,SAAK,oBAAqB,QAAQ,OAAe;AAAA,EACnD;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,kBACJ,UAAgC,CAAC,GACX;AAEtB,qBAAiB,QAAQ,QAAQ;AAEjC,UAAM,UAAU,MAAM,KAAK,kBAAkB,kBAAkB,OAAO;AACtE,SAAK,SAAS,IAAI,QAAQ,IAAI,OAAO;AACrC,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,QACJ,MACA,UAA0B,CAAC,GACP;AAEpB,QAAI,UAAU,QAAQ;AACtB,QAAI,CAAC,SAAS;AAEZ,YAAM,WAAW,QAAQ,YAAY;AACrC,gBAAU,MAAM,KAAK,0BAA0B,QAAQ;AAAA,IACzD;AAGA,UAAM,YAAY,IAAI,UAAU,MAAM,OAAO;AAG7C,UAAM,KAAK,kBAAkB,cAAc,QAAQ,IAAI,MAAM,QAAQ,UAAU;AAAA,MAC7E,UAAU,CAAC,WAA0B;AACnC,kBAAU,KAAK,OAAO,KAAK,OAAO,IAAI;AACtC,YAAI,QAAQ,SAAU,QAAO,QAAQ,SAAS,MAAM;AAAA,MACtD;AAAA,MACA,UAAU,CAAC,WAA0B;AACnC,kBAAU,KAAK,OAAO,KAAK,OAAO,IAAI;AACtC,YAAI,QAAQ,SAAU,QAAO,QAAQ,SAAS,MAAM;AAAA,MACtD;AAAA,MACA,UAAU,OAAO,WAAmB;AAClC,kBAAU,QAAQ,KAAK,IAAI,WAAW,MAAM,CAAQ;AACpD,YAAI,QAAQ,SAAU,QAAO,QAAQ,SAAS,MAAM;AAAA,MACtD;AAAA,MACA,SAAS,CAAC,UAA0B;AAClC,kBAAU,QAAQ;AAClB,YAAI,QAAQ,QAAS,QAAO,QAAQ,QAAQ,KAAK;AAAA,MACnD;AAAA,IACF,CAAC;AAED,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,cACJ,MACA,UAA0B,CAAC,GACF;AAEzB,QAAI,UAAU,QAAQ;AACtB,QAAI,CAAC,SAAS;AACZ,YAAM,WAAW,QAAQ,YAAY;AACrC,gBAAU,MAAM,KAAK,0BAA0B,QAAQ;AAAA,IACzD;AAIA,UAAM,WAAW,MAAO,KAAK,kBAA0B,QAAQ,qBAAqB;AAAA,MAClF,QAAQ;AAAA,MACR,SAAS;AAAA,QACP,gBAAgB;AAAA,QAChB,QAAQ;AAAA,MACV;AAAA,MACA,MAAM,KAAK,UAAU;AAAA,QACnB,YAAY,QAAQ;AAAA,QACpB;AAAA,QACA,UAAU,QAAQ;AAAA,MACpB,CAAC;AAAA,IACH,CAAC;AAED,QAAI,CAAC,SAAS,IAAI;AAChB,YAAM,YAAa,MAAM,SACtB,KAAK,EACL,MAAM,OAAO,EAAE,OAAO,gBAAgB,EAAE;AAC3C,YAAM,IAAI;AAAA,QACR,UAAU,SAAS,2BAA2B,SAAS,MAAM;AAAA,MAC/D;AAAA,IACF;AAEA,QAAI,CAAC,SAAS,MAAM;AAClB,YAAM,IAAI,MAAM,0CAA0C;AAAA,IAC5D;AAEA,WAAO,SAAS;AAAA,EAClB;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,mBAA2C;AAC/C,UAAM,WAAW,MAAM,KAAK,kBAAkB,iBAAiB;AAG/D,eAAW,WAAW,UAAU;AAC9B,WAAK,SAAS,IAAI,QAAQ,IAAI,OAAO;AAAA,IACvC;AAEA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,kBAAkB,WAAkC;AACxD,UAAM,KAAK,kBAAkB,kBAAkB,SAAS;AACxD,SAAK,SAAS,OAAO,SAAS;AAAA,EAChC;AAAA,EAEA,MAAc,0BACZ,UACsB;AAEtB,eAAW,WAAW,KAAK,SAAS,OAAO,GAAG;AAC5C,UAAI,QAAQ,aAAa,UAAU;AACjC,eAAO;AAAA,MACT;AAAA,IACF;AAGA,WAAO,KAAK,kBAAkB,EAAE,SAAS,CAAC;AAAA,EAC5C;AACF;","names":[]}