@hanzo/runtime 0.0.0-dev

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/src/Process.ts ADDED
@@ -0,0 +1,357 @@
1
+ /*
2
+ * Copyright 2025 Daytona Platforms Inc.
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */
5
+
6
+ import { Command, Session, SessionExecuteRequest, SessionExecuteResponse, ToolboxApi } from '@daytonaio/api-client'
7
+ import { SandboxCodeToolbox } from './Sandbox'
8
+ import { ExecuteResponse } from './types/ExecuteResponse'
9
+ import { ArtifactParser } from './utils/ArtifactParser'
10
+ import { processStreamingResponse } from './utils/Stream'
11
+
12
+ /**
13
+ * Parameters for code execution.
14
+ */
15
+ export class CodeRunParams {
16
+ /**
17
+ * Command line arguments
18
+ */
19
+ argv?: string[]
20
+ /**
21
+ * Environment variables
22
+ */
23
+ env?: Record<string, string>
24
+ }
25
+
26
+ /**
27
+ * Handles process and code execution within a Sandbox.
28
+ *
29
+ * @class
30
+ */
31
+ export class Process {
32
+ constructor(
33
+ private readonly sandboxId: string,
34
+ private readonly codeToolbox: SandboxCodeToolbox,
35
+ private readonly toolboxApi: ToolboxApi,
36
+ private readonly getRootDir: () => Promise<string>,
37
+ ) {}
38
+
39
+ /**
40
+ * Executes a shell command in the Sandbox.
41
+ *
42
+ * @param {string} command - Shell command to execute
43
+ * @param {string} [cwd] - Working directory for command execution. If not specified, uses the Sandbox root directory.
44
+ * Default is the user's root directory.
45
+ * @param {Record<string, string>} [env] - Environment variables to set for the command
46
+ * @param {number} [timeout] - Maximum time in seconds to wait for the command to complete. 0 means wait indefinitely.
47
+ * @returns {Promise<ExecuteResponse>} Command execution results containing:
48
+ * - exitCode: The command's exit status
49
+ * - result: Standard output from the command
50
+ * - artifacts: ExecutionArtifacts object containing `stdout` (same as result) and `charts` (matplotlib charts metadata)
51
+ *
52
+ * @example
53
+ * // Simple command
54
+ * const response = await process.executeCommand('echo "Hello"');
55
+ * console.log(response.artifacts.stdout); // Prints: Hello
56
+ *
57
+ * @example
58
+ * // Command with working directory
59
+ * const result = await process.executeCommand('ls', 'workspace/src');
60
+ *
61
+ * @example
62
+ * // Command with timeout
63
+ * const result = await process.executeCommand('sleep 10', undefined, 5);
64
+ */
65
+ public async executeCommand(
66
+ command: string,
67
+ cwd?: string,
68
+ env?: Record<string, string>,
69
+ timeout?: number,
70
+ ): Promise<ExecuteResponse> {
71
+ const base64UserCmd = Buffer.from(command).toString('base64')
72
+ command = `echo '${base64UserCmd}' | base64 -d | sh`
73
+
74
+ if (env && Object.keys(env).length > 0) {
75
+ const safeEnvExports =
76
+ Object.entries(env)
77
+ .map(([key, value]) => {
78
+ const encodedValue = Buffer.from(value).toString('base64')
79
+ return `export ${key}=$(echo '${encodedValue}' | base64 -d)`
80
+ })
81
+ .join(';') + ';'
82
+ command = `${safeEnvExports} ${command}`
83
+ }
84
+
85
+ command = `sh -c "${command}"`
86
+
87
+ const response = await this.toolboxApi.executeCommand(this.sandboxId, {
88
+ command,
89
+ timeout,
90
+ cwd: cwd ?? (await this.getRootDir()),
91
+ })
92
+
93
+ // Parse artifacts from the output
94
+ const artifacts = ArtifactParser.parseArtifacts(response.data.result)
95
+
96
+ // Return enhanced response with parsed artifacts
97
+ return {
98
+ ...response.data,
99
+ result: artifacts.stdout,
100
+ artifacts,
101
+ }
102
+ }
103
+
104
+ /**
105
+ * Executes code in the Sandbox using the appropriate language runtime.
106
+ *
107
+ * @param {string} code - Code to execute
108
+ * @param {CodeRunParams} params - Parameters for code execution
109
+ * @param {number} [timeout] - Maximum time in seconds to wait for execution to complete
110
+ * @returns {Promise<ExecuteResponse>} Code execution results containing:
111
+ * - exitCode: The execution's exit status
112
+ * - result: Standard output from the code
113
+ * - artifacts: ExecutionArtifacts object containing `stdout` (same as result) and `charts` (matplotlib charts metadata)
114
+ *
115
+ * @example
116
+ * // Run TypeScript code
117
+ * const response = await process.codeRun(`
118
+ * const x = 10;
119
+ * const y = 20;
120
+ * console.log(\`Sum: \${x + y}\`);
121
+ * `);
122
+ * console.log(response.artifacts.stdout); // Prints: Sum: 30
123
+ *
124
+ * @example
125
+ * // Run Python code with matplotlib
126
+ * const response = await process.codeRun(`
127
+ * import matplotlib.pyplot as plt
128
+ * import numpy as np
129
+ *
130
+ * x = np.linspace(0, 10, 30)
131
+ * y = np.sin(x)
132
+ *
133
+ * plt.figure(figsize=(8, 5))
134
+ * plt.plot(x, y, 'b-', linewidth=2)
135
+ * plt.title('Line Chart')
136
+ * plt.xlabel('X-axis (seconds)')
137
+ * plt.ylabel('Y-axis (amplitude)')
138
+ * plt.grid(True)
139
+ * plt.show()
140
+ * `);
141
+ *
142
+ * if (response.artifacts?.charts) {
143
+ * const chart = response.artifacts.charts[0];
144
+ *
145
+ * console.log(`Type: ${chart.type}`);
146
+ * console.log(`Title: ${chart.title}`);
147
+ * if (chart.type === ChartType.LINE) {
148
+ * const lineChart = chart as LineChart
149
+ * console.log('X Label:', lineChart.x_label)
150
+ * console.log('Y Label:', lineChart.y_label)
151
+ * console.log('X Ticks:', lineChart.x_ticks)
152
+ * console.log('Y Ticks:', lineChart.y_ticks)
153
+ * console.log('X Tick Labels:', lineChart.x_tick_labels)
154
+ * console.log('Y Tick Labels:', lineChart.y_tick_labels)
155
+ * console.log('X Scale:', lineChart.x_scale)
156
+ * console.log('Y Scale:', lineChart.y_scale)
157
+ * console.log('Elements:')
158
+ * console.dir(lineChart.elements, { depth: null })
159
+ * }
160
+ * }
161
+ */
162
+ public async codeRun(code: string, params?: CodeRunParams, timeout?: number): Promise<ExecuteResponse> {
163
+ const runCommand = this.codeToolbox.getRunCommand(code, params)
164
+ return this.executeCommand(runCommand, undefined, params?.env, timeout)
165
+ }
166
+
167
+ /**
168
+ * Creates a new long-running background session in the Sandbox.
169
+ *
170
+ * Sessions are background processes that maintain state between commands, making them ideal for
171
+ * scenarios requiring multiple related commands or persistent environment setup. You can run
172
+ * long-running commands and monitor process status.
173
+ *
174
+ * @param {string} sessionId - Unique identifier for the new session
175
+ * @returns {Promise<void>}
176
+ *
177
+ * @example
178
+ * // Create a new session
179
+ * const sessionId = 'my-session';
180
+ * await process.createSession(sessionId);
181
+ * const session = await process.getSession(sessionId);
182
+ * // Do work...
183
+ * await process.deleteSession(sessionId);
184
+ */
185
+ public async createSession(sessionId: string): Promise<void> {
186
+ await this.toolboxApi.createSession(this.sandboxId, {
187
+ sessionId,
188
+ })
189
+ }
190
+
191
+ /**
192
+ * Get a session in the sandbox.
193
+ *
194
+ * @param {string} sessionId - Unique identifier of the session to retrieve
195
+ * @returns {Promise<Session>} Session information including:
196
+ * - sessionId: The session's unique identifier
197
+ * - commands: List of commands executed in the session
198
+ *
199
+ * @example
200
+ * const session = await process.getSession('my-session');
201
+ * session.commands.forEach(cmd => {
202
+ * console.log(`Command: ${cmd.command}`);
203
+ * });
204
+ */
205
+ public async getSession(sessionId: string): Promise<Session> {
206
+ const response = await this.toolboxApi.getSession(this.sandboxId, sessionId)
207
+ return response.data
208
+ }
209
+
210
+ /**
211
+ * Gets information about a specific command executed in a session.
212
+ *
213
+ * @param {string} sessionId - Unique identifier of the session
214
+ * @param {string} commandId - Unique identifier of the command
215
+ * @returns {Promise<Command>} Command information including:
216
+ * - id: The command's unique identifier
217
+ * - command: The executed command string
218
+ * - exitCode: Command's exit status (if completed)
219
+ *
220
+ * @example
221
+ * const cmd = await process.getSessionCommand('my-session', 'cmd-123');
222
+ * if (cmd.exitCode === 0) {
223
+ * console.log(`Command ${cmd.command} completed successfully`);
224
+ * }
225
+ */
226
+ public async getSessionCommand(sessionId: string, commandId: string): Promise<Command> {
227
+ const response = await this.toolboxApi.getSessionCommand(this.sandboxId, sessionId, commandId)
228
+ return response.data
229
+ }
230
+
231
+ /**
232
+ * Executes a command in an existing session.
233
+ *
234
+ * @param {string} sessionId - Unique identifier of the session to use
235
+ * @param {SessionExecuteRequest} req - Command execution request containing:
236
+ * - command: The command to execute
237
+ * - runAsync: Whether to execute asynchronously
238
+ * @param {number} timeout - Timeout in seconds
239
+ * @returns {Promise<SessionExecuteResponse>} Command execution results containing:
240
+ * - cmdId: Unique identifier for the executed command
241
+ * - output: Command output (if synchronous execution)
242
+ * - exitCode: Command exit status (if synchronous execution)
243
+ *
244
+ * @example
245
+ * // Execute commands in sequence, maintaining state
246
+ * const sessionId = 'my-session';
247
+ *
248
+ * // Change directory
249
+ * await process.executeSessionCommand(sessionId, {
250
+ * command: 'cd /home/daytona'
251
+ * });
252
+ *
253
+ * // Run command in new directory
254
+ * const result = await process.executeSessionCommand(sessionId, {
255
+ * command: 'pwd'
256
+ * });
257
+ * console.log(result.output); // Prints: /home/daytona
258
+ */
259
+ public async executeSessionCommand(
260
+ sessionId: string,
261
+ req: SessionExecuteRequest,
262
+ timeout?: number,
263
+ ): Promise<SessionExecuteResponse> {
264
+ const response = await this.toolboxApi.executeSessionCommand(
265
+ this.sandboxId,
266
+ sessionId,
267
+ req,
268
+ undefined,
269
+ timeout ? { timeout: timeout * 1000 } : {},
270
+ )
271
+ return response.data
272
+ }
273
+
274
+ /**
275
+ * Get the logs for a command executed in a session.
276
+ *
277
+ * @param {string} sessionId - Unique identifier of the session
278
+ * @param {string} commandId - Unique identifier of the command
279
+ * @returns {Promise<string>} Command logs
280
+ *
281
+ * @example
282
+ * const logs = await process.getSessionCommandLogs('my-session', 'cmd-123');
283
+ * console.log('Command output:', logs);
284
+ */
285
+ public async getSessionCommandLogs(sessionId: string, commandId: string): Promise<string>
286
+ /**
287
+ * Asynchronously retrieve and process the logs for a command executed in a session as they become available.
288
+ *
289
+ * @param {string} sessionId - Unique identifier of the session
290
+ * @param {string} commandId - Unique identifier of the command
291
+ * @param {function} onLogs - Callback function to handle each log chunk
292
+ * @returns {Promise<void>}
293
+ *
294
+ * @example
295
+ * const logs = await process.getSessionCommandLogs('my-session', 'cmd-123', (chunk) => {
296
+ * console.log('Log chunk:', chunk);
297
+ * });
298
+ */
299
+ public async getSessionCommandLogs(
300
+ sessionId: string,
301
+ commandId: string,
302
+ onLogs: (chunk: string) => void,
303
+ ): Promise<void>
304
+ public async getSessionCommandLogs(
305
+ sessionId: string,
306
+ commandId: string,
307
+ onLogs?: (chunk: string) => void,
308
+ ): Promise<string | void> {
309
+ if (!onLogs) {
310
+ const response = await this.toolboxApi.getSessionCommandLogs(this.sandboxId, sessionId, commandId)
311
+ return response.data
312
+ }
313
+
314
+ await processStreamingResponse(
315
+ () =>
316
+ this.toolboxApi.getSessionCommandLogs(this.sandboxId, sessionId, commandId, undefined, true, {
317
+ responseType: 'stream',
318
+ }),
319
+ onLogs,
320
+ () =>
321
+ this.getSessionCommand(sessionId, commandId).then((res) => res.exitCode !== null && res.exitCode !== undefined),
322
+ )
323
+ }
324
+
325
+ /**
326
+ * Lists all active sessions in the Sandbox.
327
+ *
328
+ * @returns {Promise<Session[]>} Array of active sessions
329
+ *
330
+ * @example
331
+ * const sessions = await process.listSessions();
332
+ * sessions.forEach(session => {
333
+ * console.log(`Session ${session.sessionId}:`);
334
+ * session.commands.forEach(cmd => {
335
+ * console.log(`- ${cmd.command} (${cmd.exitCode})`);
336
+ * });
337
+ * });
338
+ */
339
+ public async listSessions(): Promise<Session[]> {
340
+ const response = await this.toolboxApi.listSessions(this.sandboxId)
341
+ return response.data
342
+ }
343
+
344
+ /**
345
+ * Delete a session from the Sandbox.
346
+ *
347
+ * @param {string} sessionId - Unique identifier of the session to delete
348
+ * @returns {Promise<void>}
349
+ *
350
+ * @example
351
+ * // Clean up a completed session
352
+ * await process.deleteSession('my-session');
353
+ */
354
+ public async deleteSession(sessionId: string): Promise<void> {
355
+ await this.toolboxApi.deleteSession(this.sandboxId, sessionId)
356
+ }
357
+ }