abox-code-interpreter 0.1.0__py3-none-any.whl

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.
@@ -0,0 +1,523 @@
1
+ import inspect
2
+ import json
3
+ import logging
4
+
5
+ from agentbox import TimeoutException, SandboxException
6
+ from dataclasses import dataclass, field
7
+ from typing import (
8
+ List,
9
+ Literal,
10
+ Optional,
11
+ Iterable,
12
+ Dict,
13
+ TypeVar,
14
+ Callable,
15
+ Awaitable,
16
+ Any,
17
+ Union,
18
+ )
19
+
20
+ from httpx import Response
21
+
22
+ from .charts import Chart, _deserialize_chart
23
+
24
+ RunCodeLanguage = Union[
25
+ Literal["python", "javascript", "typescript", "r", "java", "bash"],
26
+ str,
27
+ ]
28
+
29
+ T = TypeVar("T")
30
+ OutputHandler = Union[Callable[[T], Any],]
31
+
32
+ OutputHandlerWithAsync = Union[
33
+ OutputHandler[T],
34
+ Callable[[T], Awaitable[Any]],
35
+ ]
36
+
37
+ logger = logging.getLogger(__name__)
38
+
39
+
40
+ @dataclass
41
+ class OutputMessage:
42
+ """
43
+ Represents an output message from the sandbox code execution.
44
+ """
45
+
46
+ line: str
47
+ """
48
+ The output line.
49
+ """
50
+ timestamp: int
51
+ """
52
+ Unix epoch in nanoseconds
53
+ """
54
+ error: bool = False
55
+ """
56
+ Whether the output is an error.
57
+ """
58
+
59
+ def __str__(self):
60
+ return self.line
61
+
62
+
63
+ @dataclass
64
+ class ExecutionError:
65
+ """
66
+ Represents an error that occurred during the execution of a cell.
67
+ The error contains the name of the error, the value of the error, and the traceback.
68
+ """
69
+
70
+ name: str
71
+ """
72
+ Name of the error.
73
+ """
74
+ value: str
75
+ """
76
+ Value of the error.
77
+ """
78
+ traceback: str
79
+ """
80
+ The raw traceback of the error.
81
+ """
82
+
83
+ def __init__(self, name: str, value: str, traceback: str, **kwargs):
84
+ self.name = name
85
+ self.value = value
86
+ self.traceback = traceback
87
+
88
+ def to_json(self) -> str:
89
+ """
90
+ Returns the JSON representation of the Error object.
91
+ """
92
+ data = {"name": self.name, "value": self.value, "traceback": self.traceback}
93
+ return json.dumps(data)
94
+
95
+
96
+ class MIMEType(str):
97
+ """
98
+ Represents a MIME type.
99
+ """
100
+
101
+
102
+ @dataclass
103
+ class Result:
104
+ """
105
+ Represents the data to be displayed as a result of executing a cell in a Jupyter notebook.
106
+ The result is similar to the structure returned by ipython kernel: https://ipython.readthedocs.io/en/stable/development/execution.html#execution-semantics
107
+
108
+ The result can contain multiple types of data, such as text, images, plots, etc. Each type of data is represented
109
+ as a string, and the result can contain multiple types of data. The display calls don't have to have text representation,
110
+ for the actual result the representation is always present for the result, the other representations are always optional.
111
+ """
112
+
113
+ def __getitem__(self, item):
114
+ return getattr(self, item)
115
+
116
+ text: Optional[str] = None
117
+ html: Optional[str] = None
118
+ markdown: Optional[str] = None
119
+ svg: Optional[str] = None
120
+ png: Optional[str] = None
121
+ jpeg: Optional[str] = None
122
+ pdf: Optional[str] = None
123
+ latex: Optional[str] = None
124
+ json: Optional[dict] = None
125
+ javascript: Optional[str] = None
126
+ data: Optional[dict] = None
127
+ chart: Optional[Chart] = None
128
+ is_main_result: bool = False
129
+ """Whether this data is the result of the cell. Data can be produced by display calls of which can be multiple in a cell."""
130
+ extra: Optional[dict] = None
131
+ """Extra data that can be included. Not part of the standard types."""
132
+
133
+ def __init__(
134
+ self,
135
+ text: Optional[str] = None,
136
+ html: Optional[str] = None,
137
+ markdown: Optional[str] = None,
138
+ svg: Optional[str] = None,
139
+ png: Optional[str] = None,
140
+ jpeg: Optional[str] = None,
141
+ pdf: Optional[str] = None,
142
+ latex: Optional[str] = None,
143
+ json: Optional[dict] = None,
144
+ javascript: Optional[str] = None,
145
+ data: Optional[dict] = None,
146
+ chart: Optional[dict] = None,
147
+ is_main_result: bool = False,
148
+ extra: Optional[dict] = None,
149
+ **kwargs, # Allows for future expansion
150
+ ):
151
+ self.text = text
152
+ self.html = html
153
+ self.markdown = markdown
154
+ self.svg = svg
155
+ self.png = png
156
+ self.jpeg = jpeg
157
+ self.pdf = pdf
158
+ self.latex = latex
159
+ self.json = json
160
+ self.javascript = javascript
161
+ self.data = data
162
+ if chart:
163
+ try:
164
+ self.chart = _deserialize_chart(chart)
165
+ except Exception as e:
166
+ logger.error(
167
+ f"Error deserializing chart, check if you are using the latest version of the library: {e}"
168
+ )
169
+ self.is_main_result = is_main_result
170
+ self.extra = extra
171
+
172
+ def formats(self) -> Iterable[str]:
173
+ """
174
+ Returns all available formats of the result.
175
+
176
+ :return: All available formats of the result in MIME types.
177
+ """
178
+ formats = []
179
+ if self.text:
180
+ formats.append("text")
181
+ if self.html:
182
+ formats.append("html")
183
+ if self.markdown:
184
+ formats.append("markdown")
185
+ if self.svg:
186
+ formats.append("svg")
187
+ if self.png:
188
+ formats.append("png")
189
+ if self.jpeg:
190
+ formats.append("jpeg")
191
+ if self.pdf:
192
+ formats.append("pdf")
193
+ if self.latex:
194
+ formats.append("latex")
195
+ if self.json:
196
+ formats.append("json")
197
+ if self.javascript:
198
+ formats.append("javascript")
199
+ if self.data:
200
+ formats.append("data")
201
+ if self.chart:
202
+ formats.append("chart")
203
+
204
+ if self.extra:
205
+ for key in self.extra:
206
+ formats.append(key)
207
+
208
+ return formats
209
+
210
+ def __str__(self) -> str:
211
+ """
212
+ Returns the text representation of the data.
213
+
214
+ :return: The text representation of the data.
215
+ """
216
+ return self.__repr__()
217
+
218
+ def __repr__(self) -> str:
219
+ if self.text:
220
+ return f"Result({self.text})"
221
+ else:
222
+ return "Result(Formats: " + ", ".join(self.formats()) + ")"
223
+
224
+ def _repr_html_(self) -> Optional[str]:
225
+ """
226
+ Returns the HTML representation of the data.
227
+
228
+ :return: The HTML representation of the data.
229
+ """
230
+ return self.html
231
+
232
+ def _repr_markdown_(self) -> Optional[str]:
233
+ """
234
+ Returns the Markdown representation of the data.
235
+
236
+ :return: The Markdown representation of the data.
237
+ """
238
+ return self.markdown
239
+
240
+ def _repr_svg_(self) -> Optional[str]:
241
+ """
242
+ Returns the SVG representation of the data.
243
+
244
+ :return: The SVG representation of the data.
245
+ """
246
+ return self.svg
247
+
248
+ def _repr_png_(self) -> Optional[str]:
249
+ """
250
+ Returns the base64 representation of the PNG data.
251
+
252
+ :return: The base64 representation of the PNG data.
253
+ """
254
+ return self.png
255
+
256
+ def _repr_jpeg_(self) -> Optional[str]:
257
+ """
258
+ Returns the base64 representation of the JPEG data.
259
+
260
+ :return: The base64 representation of the JPEG data.
261
+ """
262
+ return self.jpeg
263
+
264
+ def _repr_pdf_(self) -> Optional[str]:
265
+ """
266
+ Returns the PDF representation of the data.
267
+
268
+ :return: The PDF representation of the data.
269
+ """
270
+ return self.pdf
271
+
272
+ def _repr_latex_(self) -> Optional[str]:
273
+ """
274
+ Returns the LaTeX representation of the data.
275
+
276
+ :return: The LaTeX representation of the data.
277
+ """
278
+ return self.latex
279
+
280
+ def _repr_json_(self) -> Optional[dict]:
281
+ """
282
+ Returns the JSON representation of the data.
283
+
284
+ :return: The JSON representation of the data.
285
+ """
286
+ return self.json
287
+
288
+ def _repr_javascript_(self) -> Optional[str]:
289
+ """
290
+ Returns the JavaScript representation of the data.
291
+
292
+ :return: The JavaScript representation of the data.
293
+ """
294
+ return self.javascript
295
+
296
+
297
+ @dataclass(repr=False)
298
+ class Logs:
299
+ """
300
+ Data printed to stdout and stderr during execution, usually by print statements, logs, warnings, subprocesses, etc.
301
+ """
302
+
303
+ stdout: List[str] = field(default_factory=list)
304
+ """List of strings printed to stdout by prints, subprocesses, etc."""
305
+ stderr: List[str] = field(default_factory=list)
306
+ """List of strings printed to stderr by prints, subprocesses, etc."""
307
+
308
+ def __init__(
309
+ self,
310
+ stdout: Optional[List[str]] = None,
311
+ stderr: Optional[List[str]] = None,
312
+ **kwargs,
313
+ ):
314
+ self.stdout = stdout or []
315
+ self.stderr = stderr or []
316
+
317
+ def __repr__(self):
318
+ return f"Logs(stdout: {self.stdout}, stderr: {self.stderr})"
319
+
320
+ def to_json(self) -> str:
321
+ """
322
+ Returns the JSON representation of the Logs object.
323
+ """
324
+ data = {"stdout": self.stdout, "stderr": self.stderr}
325
+ return json.dumps(data)
326
+
327
+
328
+ def serialize_results(results: List[Result]) -> List[Dict[str, str]]:
329
+ """
330
+ Serializes the results to JSON.
331
+ """
332
+ serialized = []
333
+ for result in results:
334
+ serialized_dict = {}
335
+ for key in result.formats():
336
+ if key == "chart":
337
+ chart = result.chart
338
+ if chart is not None:
339
+ serialized_dict[key] = chart.to_dict()
340
+ else:
341
+ serialized_dict[key] = result[key]
342
+
343
+ serialized_dict["text"] = result.text
344
+ serialized.append(serialized_dict)
345
+
346
+ return serialized
347
+
348
+
349
+ @dataclass(repr=False)
350
+ class Execution:
351
+ """
352
+ Represents the result of a cell execution.
353
+ """
354
+
355
+ results: List[Result] = field(default_factory=list)
356
+ """List of the result of the cell (interactively interpreted last line), display calls (e.g. matplotlib plots)."""
357
+ logs: Logs = field(default_factory=Logs)
358
+ """Logs printed to stdout and stderr during execution."""
359
+ error: Optional[ExecutionError] = None
360
+ """Error object if an error occurred, None otherwise."""
361
+ execution_count: Optional[int] = None
362
+ """Execution count of the cell."""
363
+
364
+ def __init__(
365
+ self,
366
+ results: Optional[List[Result]] = None,
367
+ logs: Optional[Logs] = None,
368
+ error: Optional[ExecutionError] = None,
369
+ execution_count: Optional[int] = None,
370
+ **kwargs,
371
+ ):
372
+ self.results = results or []
373
+ self.logs = logs or Logs()
374
+ self.error = error
375
+ self.execution_count = execution_count
376
+
377
+ def __repr__(self):
378
+ return f"Execution(Results: {self.results}, Logs: {self.logs}, Error: {self.error})"
379
+
380
+ @property
381
+ def text(self) -> Optional[str]:
382
+ """
383
+ Returns the text representation of the result.
384
+
385
+ :return: The text representation of the result.
386
+ """
387
+ for d in self.results:
388
+ if d.is_main_result:
389
+ return d.text
390
+
391
+ def to_json(self) -> str:
392
+ """
393
+ Returns the JSON representation of the Execution object.
394
+ """
395
+ data = {
396
+ "results": serialize_results(self.results),
397
+ "logs": self.logs.to_json(),
398
+ "error": self.error.to_json() if self.error else None,
399
+ }
400
+ return json.dumps(data)
401
+
402
+
403
+ async def aextract_exception(res: Response):
404
+ if res.is_success:
405
+ return None
406
+
407
+ await res.aread()
408
+ return extract_exception(res)
409
+
410
+
411
+ def extract_exception(res: Response):
412
+ if res.is_success:
413
+ return None
414
+
415
+ res.read()
416
+ return format_exception(res)
417
+
418
+
419
+ def format_exception(res: Response):
420
+ if res.is_success:
421
+ return None
422
+
423
+ if res.status_code == 404:
424
+ return SandboxException(res.text)
425
+ elif res.status_code == 502:
426
+ return TimeoutException(
427
+ f"{res.text}: This error is likely due to sandbox timeout. You can modify the sandbox timeout by passing 'timeout' when starting the sandbox or calling '.set_timeout' on the sandbox with the desired timeout."
428
+ )
429
+ else:
430
+ return SandboxException(f"{res.status_code}: {res.text}")
431
+
432
+
433
+ def parse_output(
434
+ execution: Execution,
435
+ output: str,
436
+ on_stdout: Optional[OutputHandler[OutputMessage]] = None,
437
+ on_stderr: Optional[OutputHandler[OutputMessage]] = None,
438
+ on_result: Optional[OutputHandler[Result]] = None,
439
+ on_error: Optional[OutputHandler[ExecutionError]] = None,
440
+ ):
441
+ _parse_output(execution, output, on_stdout, on_stderr, on_result, on_error)
442
+
443
+
444
+ async def async_parse_output(
445
+ execution: Execution,
446
+ output: str,
447
+ on_stdout: Optional[OutputHandlerWithAsync[OutputMessage]] = None,
448
+ on_stderr: Optional[OutputHandlerWithAsync[OutputMessage]] = None,
449
+ on_result: Optional[OutputHandlerWithAsync[Result]] = None,
450
+ on_error: Optional[OutputHandlerWithAsync[ExecutionError]] = None,
451
+ ):
452
+ none_or_awaitable = _parse_output(
453
+ execution, output, on_stdout, on_stderr, on_result, on_error
454
+ )
455
+ if inspect.isawaitable(none_or_awaitable):
456
+ await none_or_awaitable
457
+
458
+
459
+ def _parse_output(
460
+ execution: Execution,
461
+ output: str,
462
+ on_stdout: Optional[OutputHandler[OutputMessage]] = None,
463
+ on_stderr: Optional[OutputHandler[OutputMessage]] = None,
464
+ on_result: Optional[OutputHandler[Result]] = None,
465
+ on_error: Optional[OutputHandler[ExecutionError]] = None,
466
+ ) -> Union[None, Awaitable[Any]]:
467
+ data = json.loads(output)
468
+ data_type = data.pop("type")
469
+
470
+ if data_type == "result":
471
+ result = Result(**data)
472
+ execution.results.append(result)
473
+ if on_result:
474
+ return on_result(result)
475
+ elif data_type == "stdout":
476
+ execution.logs.stdout.append(data["text"])
477
+ if on_stdout:
478
+ return on_stdout(OutputMessage(data["text"], data["timestamp"], False))
479
+ elif data_type == "stderr":
480
+ execution.logs.stderr.append(data["text"])
481
+ if on_stderr:
482
+ return on_stderr(OutputMessage(data["text"], data["timestamp"], True))
483
+ elif data_type == "error":
484
+ execution.error = ExecutionError(data["name"], data["value"], data["traceback"])
485
+ if on_error:
486
+ return on_error(execution.error)
487
+ elif data_type == "number_of_executions":
488
+ execution.execution_count = data["execution_count"]
489
+
490
+ return None
491
+
492
+
493
+ @dataclass
494
+ class Context:
495
+ """
496
+ Represents a context for code execution.
497
+ """
498
+
499
+ id: str
500
+ """
501
+ The ID of the context.
502
+ """
503
+ language: str
504
+ """
505
+ The language of the context.
506
+ """
507
+ cwd: str
508
+ """
509
+ The working directory of the context.
510
+ """
511
+
512
+ def __init__(self, context_id: str, language: str, cwd: str, **kwargs):
513
+ self.id = context_id
514
+ self.language = language
515
+ self.cwd = cwd
516
+
517
+ @classmethod
518
+ def from_json(cls, data: Dict[str, str]):
519
+ return cls(
520
+ context_id=data["id"],
521
+ language=data["language"],
522
+ cwd=data["cwd"],
523
+ )