diff --git a/.changeset/lazy-impalas-accept.md b/.changeset/lazy-impalas-accept.md
new file mode 100644
index 0000000..cf0eb9f
--- /dev/null
+++ b/.changeset/lazy-impalas-accept.md
@@ -0,0 +1,5 @@
+---
+'@e2b/code-interpreter-template': patch
+---
+
+Add [Deno kernel](https://docs.deno.com/runtime/reference/cli/jupyter/)
diff --git a/js/tests/languages/deno.test.ts b/js/tests/languages/deno.test.ts
new file mode 100644
index 0000000..91e4b3a
--- /dev/null
+++ b/js/tests/languages/deno.test.ts
@@ -0,0 +1,80 @@
+import { expect } from 'vitest'
+
+import { sandboxTest } from '../setup'
+
+sandboxTest('js simple', async ({ sandbox }) => {
+ const result = await sandbox.runCode('console.log("Hello, World!")', {language: "deno"})
+
+ expect(result.logs.stdout.join().trim()).toEqual('Hello, World!')
+})
+
+sandboxTest('js import', async ({ sandbox }) => {
+ const result = await sandbox.runCode('import isOdd from "npm:is-odd"\nisOdd(3)', {language: "deno"})
+
+ expect(result.results[0].text).toEqual('true')
+})
+
+sandboxTest('js top level await', async ({ sandbox }) => {
+ const result = await sandbox.runCode(`
+ async function main() {
+ return 'Hello, World!'
+ }
+
+ await main()
+ `, {language: "deno"})
+ expect(result.results[0].text).toEqual('Hello, World!')
+})
+
+sandboxTest('js es6', async ({ sandbox }) => {
+ const result = await sandbox.runCode(`
+ const add = (x, y) => x + y;
+ add(1, 2)`, {language: "deno"})
+ expect(result.results[0].text).toEqual('3')
+})
+
+
+sandboxTest('js context', async ({ sandbox }) => {
+ await sandbox.runCode('const z = 1', {language: "deno"})
+ const result = await sandbox.runCode('z', {language: "deno"})
+ expect(result.results[0].text).toEqual('1')
+})
+
+sandboxTest('js cwd', async ({ sandbox }) => {
+ const result = await sandbox.runCode('process.cwd()', {language: "deno"})
+ expect(result.results[0].text).toEqual('/home/user')
+
+ const ctx = await sandbox.createCodeContext( {cwd: '/home', language: "deno"})
+ const result2 = await sandbox.runCode('process.cwd()', {context: ctx})
+ expect(result2.results[0].text).toEqual('/home')
+})
+
+sandboxTest('ts simple', async ({ sandbox }) => {
+ const result = await sandbox.runCode(`
+function subtract(x: number, y: number): number {
+ return x - y;
+}
+
+subtract(1, 2)
+`, {language: "deno"})
+
+ expect(result.results[0].text).toEqual('-1')
+})
+
+sandboxTest('test display', async ({ sandbox }) => {
+ const result = await sandbox.runCode(`
+ {
+ [Symbol.for("Jupyter.display")]() {
+ return {
+ // Plain text content
+ "text/plain": "Hello world!",
+
+ // HTML output
+ "text/html": "
Hello world!
",
+ }
+ }
+}
+`, {language: "deno"})
+
+ expect(result.results[0].html).toBe('Hello world!
')
+ expect(result.results[0].text).toBe('Hello world!')
+})
diff --git a/python/tests/languages/test_deno.py b/python/tests/languages/test_deno.py
new file mode 100644
index 0000000..0a0e58e
--- /dev/null
+++ b/python/tests/languages/test_deno.py
@@ -0,0 +1,87 @@
+from e2b_code_interpreter import AsyncSandbox
+
+
+async def test_javascript(async_sandbox: AsyncSandbox):
+ code = """
+ console.log('Hello, World!')
+ """
+ execution = await async_sandbox.run_code(code, language="deno")
+ assert execution.logs.stdout == ["Hello, World!\n"]
+
+
+async def test_import(async_sandbox: AsyncSandbox):
+ code = """
+ import isOdd from 'npm:is-odd'
+ isOdd(3)
+ """
+ execution = await async_sandbox.run_code(code, language="deno")
+ assert execution.results[0].text == "true"
+
+
+async def test_toplevel_await(async_sandbox: AsyncSandbox):
+ code = """
+ async function main() {
+ return 'Hello, World!'
+ }
+
+ await main()
+ """
+ execution = await async_sandbox.run_code(code, language="deno")
+ assert execution.results[0].text == "Hello, World!"
+
+
+async def test_es6(async_sandbox: AsyncSandbox):
+ code = """
+const add = (x, y) => x + y;
+add(1, 2);
+ """
+ execution = await async_sandbox.run_code(code, language="deno")
+ assert execution.results[0].text == "3"
+
+
+async def test_context(async_sandbox: AsyncSandbox):
+ await async_sandbox.run_code("const x = 1", language="deno")
+ execution = await async_sandbox.run_code("x", language="deno")
+ assert execution.results[0].text == "1"
+
+
+async def test_cwd(async_sandbox: AsyncSandbox):
+ execution = await async_sandbox.run_code("process.cwd()", language="deno")
+ assert execution.results[0].text == "/home/user"
+
+ ctx = await async_sandbox.create_code_context("/home", language="deno")
+ execution = await async_sandbox.run_code("process.cwd()", context=ctx)
+ assert execution.results[0].text == "/home"
+
+
+async def test_typescript(async_sandbox: AsyncSandbox):
+ execution = await async_sandbox.run_code(
+ """
+function subtract(x: number, y: number): number {
+ return x - y;
+}
+
+subtract(1, 2);
+""",
+ language="deno",
+ )
+ assert execution.results[0].text == "-1"
+
+
+async def test_display(async_sandbox: AsyncSandbox):
+ code = """
+{
+ [Symbol.for("Jupyter.display")]() {
+ return {
+ // Plain text content
+ "text/plain": "Hello world!",
+
+ // HTML output
+ "text/html": "Hello world!
",
+ }
+ }
+}
+ """
+ execution = await async_sandbox.run_code(code, language="deno")
+ assert execution.results[0].text == "Hello world!"
+ assert execution.results[0].html == "Hello world!
"
diff --git a/python/tests/sync/test_default_kernels.py b/python/tests/sync/test_default_kernels.py
index cb21cdc..d0daf82 100644
--- a/python/tests/sync/test_default_kernels.py
+++ b/python/tests/sync/test_default_kernels.py
@@ -1,6 +1,20 @@
+import pytest
+
from e2b_code_interpreter.code_interpreter_sync import Sandbox
def test_js_kernel(sandbox: Sandbox):
execution = sandbox.run_code("console.log('Hello, World!')", language="js")
assert execution.logs.stdout == ["Hello, World!\n"]
+
+
+@pytest.mark.skip_debug()
+def test_r_kernel(sandbox: Sandbox):
+ execution = sandbox.run_code('print("Hello, World!")', language="r")
+ assert execution.logs.stdout == ['[1] "Hello, World!"\n']
+
+
+@pytest.mark.skip_debug()
+def test_java_kernel(sandbox: Sandbox):
+ execution = sandbox.run_code('System.out.println("Hello, World!")', language="java")
+ assert execution.logs.stdout[0] == "Hello, World!"
diff --git a/template/Dockerfile b/template/Dockerfile
index 5717108..5a2e1af 100644
--- a/template/Dockerfile
+++ b/template/Dockerfile
@@ -27,6 +27,12 @@ RUN npm install -g node-gyp
RUN npm install -g --unsafe-perm ijavascript
RUN ijsinstall --install=global
+# Deno Kernel
+COPY --from=denoland/deno:bin-2.0.4 /deno /usr/bin/deno
+RUN chmod +x /usr/bin/deno
+RUN deno jupyter --unstable --install
+COPY ./deno.json /root/.local/share/jupyter/kernels/deno/kernel.json
+
# Bash Kernel
RUN pip install bash_kernel
RUN python -m bash_kernel.install
diff --git a/template/deno.json b/template/deno.json
new file mode 100644
index 0000000..3c491e4
--- /dev/null
+++ b/template/deno.json
@@ -0,0 +1,14 @@
+{
+ "argv": [
+ "/usr/bin/deno",
+ "jupyter",
+ "--kernel",
+ "--conn",
+ "{connection_file}"
+ ],
+ "display_name": "Deno",
+ "env": {
+ "NO_COLOR": "1"
+ },
+ "language": "typescript"
+}
diff --git a/template/server/api/models/result.py b/template/server/api/models/result.py
index e63cdd0..90e24a9 100644
--- a/template/server/api/models/result.py
+++ b/template/server/api/models/result.py
@@ -45,7 +45,10 @@ def __init__(self, is_main_result: bool, data: [str, str]):
self.is_main_result = is_main_result
self.text = data.pop("text/plain", None)
- if self.text and self.text.startswith("'") and self.text.endswith("'"):
+ if self.text and (
+ (self.text.startswith("'") and self.text.endswith("'"))
+ or (self.text.startswith('"') and self.text.endswith('"'))
+ ):
self.text = self.text[1:-1]
self.html = data.pop("text/html", None)
diff --git a/template/server/contexts.py b/template/server/contexts.py
index 87b8cad..3af317d 100644
--- a/template/server/contexts.py
+++ b/template/server/contexts.py
@@ -36,9 +36,8 @@ async def create_context(client, websockets: dict, language: str, cwd: str) -> C
response = await client.post(f"{JUPYTER_BASE_URL}/api/sessions", json=data)
if not response.is_success:
- return PlainTextResponse(
+ raise Exception(
f"Failed to create context: {response.text}",
- status_code=500,
)
session_data = response.json()
@@ -53,7 +52,7 @@ async def create_context(client, websockets: dict, language: str, cwd: str) -> C
logger.info(f"Setting working directory to {cwd}")
try:
- await ws.change_current_directory(cwd)
+ await ws.change_current_directory(cwd, language)
except ExecutionError as e:
return PlainTextResponse(
"Failed to set working directory",
diff --git a/template/server/main.py b/template/server/main.py
index ba35abf..fa7760a 100644
--- a/template/server/main.py
+++ b/template/server/main.py
@@ -89,9 +89,13 @@ async def post_execute(request: ExecutionRequest):
context_id = default_websockets.get(language)
if not context_id:
- context = await create_context(
- client, websockets, language, "/home/user"
- )
+ try:
+ context = await create_context(
+ client, websockets, language, "/home/user"
+ )
+ except Exception as e:
+ return PlainTextResponse(str(e), status_code=500)
+
context_id = context.id
default_websockets[language] = context_id
@@ -110,7 +114,10 @@ async def post_execute(request: ExecutionRequest):
)
return StreamingListJsonResponse(
- ws.execute(request.code, env_vars=request.env_vars)
+ ws.execute(
+ request.code,
+ env_vars=request.env_vars,
+ )
)
@@ -121,7 +128,10 @@ async def post_contexts(request: CreateContext) -> Context:
language = normalize_language(request.language)
cwd = request.cwd or "/home/user"
- return await create_context(client, websockets, language, cwd)
+ try:
+ return await create_context(client, websockets, language, cwd)
+ except Exception as e:
+ return PlainTextResponse(str(e), status_code=500)
@app.get("/contexts")
diff --git a/template/server/messaging.py b/template/server/messaging.py
index 6d4635c..4f537aa 100644
--- a/template/server/messaging.py
+++ b/template/server/messaging.py
@@ -1,3 +1,4 @@
+import datetime
import json
import logging
import uuid
@@ -95,14 +96,21 @@ def _get_execute_request(
"session": self.session_id,
"msg_type": "execute_request",
"version": "5.3",
+ "date": datetime.datetime.now(datetime.timezone.utc).isoformat(),
},
"parent_header": {},
- "metadata": {},
+ "metadata": {
+ "trusted": True,
+ "deletedCells": [],
+ "recordTiming": False,
+ "cellId": str(uuid.uuid4()),
+ },
"content": {
"code": code,
"silent": background,
"store_history": True,
"user_expressions": {},
+ "stop_on_error": True,
"allow_stdin": False,
},
}
@@ -127,10 +135,29 @@ async def _wait_for_result(self, message_id: str):
yield output.model_dump(exclude_none=True)
- async def change_current_directory(self, path: Union[str, StrictStr]):
+ async def change_current_directory(
+ self, path: Union[str, StrictStr], language: str
+ ):
message_id = str(uuid.uuid4())
self._executions[message_id] = Execution(in_background=True)
- request = self._get_execute_request(message_id, f"%cd {path}", True)
+ if language == "python":
+ request = self._get_execute_request(message_id, f"%cd {path}", True)
+ elif language == "deno":
+ request = self._get_execute_request(
+ message_id, f"Deno.chdir('{path}')", True
+ )
+ elif language == "js":
+ request = self._get_execute_request(
+ message_id, f"process.chdir('{path}')", True
+ )
+ elif language == "r":
+ request = self._get_execute_request(message_id, f"setwd('{path}')", True)
+ elif language == "java":
+ request = self._get_execute_request(
+ message_id, f"System.setProperty('user.dir', '{path}')", True
+ )
+ else:
+ return
await self._ws.send(request)
@@ -165,11 +192,12 @@ async def execute(
indent = len(line) - len(line.lstrip())
break
- code = (
- indent * " "
- + f"os.environ.set_envs_for_execution({vars_to_set})\n"
- + code
- )
+ if self.language == "python":
+ code = (
+ indent * " "
+ + f"os.environ.set_envs_for_execution({vars_to_set})\n"
+ + code
+ )
logger.info(code)
request = self._get_execute_request(message_id, code, False)
@@ -192,7 +220,7 @@ async def _receive_message(self):
async for message in self._ws:
await self._process_message(json.loads(message))
except Exception as e:
- logger.error(f"WebSocket received error while receiving messages: {e}")
+ logger.error(f"WebSocket received error while receiving messages: {str(e)}")
async def _process_message(self, data: dict):
"""
@@ -308,11 +336,21 @@ async def _process_message(self, data: dict):
execution.errored = True
await queue.put(
Error(
- name=data["content"]["ename"],
- value=data["content"]["evalue"],
- traceback="".join(data["content"]["traceback"]),
+ name=data["content"].get("ename", ""),
+ value=data["content"].get("evalue", ""),
+ traceback="".join(data["content"].get("traceback", [])),
)
)
+ elif data["content"]["status"] == "abort":
+ logger.debug(f"Execution {parent_msg_ig} was aborted")
+ await queue.put(
+ Error(
+ name="ExecutionAborted",
+ value="Execution was aborted",
+ traceback="",
+ )
+ )
+ await queue.put(EndOfExecution())
elif data["content"]["status"] == "ok":
pass
diff --git a/template/test.Dockerfile b/template/test.Dockerfile
index 195f3ab..6dea866 100644
--- a/template/test.Dockerfile
+++ b/template/test.Dockerfile
@@ -23,6 +23,12 @@ RUN npm install -g node-gyp
RUN npm install -g --unsafe-perm ijavascript
RUN ijsinstall --install=global
+# Deno Kernel
+COPY --from=denoland/deno:bin-2.0.4 /deno /usr/bin/deno
+RUN chmod +x /usr/bin/deno
+RUN deno jupyter --unstable --install
+COPY ./template/deno.json /root/.local/share/jupyter/kernels/deno/kernel.json
+
# Create separate virtual environment for server
RUN python -m venv $SERVER_PATH/.venv