Skip to content

Commit a151715

Browse files
authored
Merge pull request #17 from e2b-dev/move-start-cmd-to-envd
Move start cmd to envd
2 parents 2f1bb5e + f477965 commit a151715

File tree

14 files changed

+114
-62
lines changed

14 files changed

+114
-62
lines changed

js/src/code-interpreter.ts

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,11 @@
11
import { ProcessMessage, Sandbox, SandboxOpts } from 'e2b'
22
import { Result, JupyterKernelWebSocket, Execution } from './messaging'
3-
import { createDeferredPromise } from './utils'
3+
import { createDeferredPromise, id } from './utils'
44

55
interface Kernels {
66
[kernelID: string]: JupyterKernelWebSocket
77
}
88

9-
export interface CreateKernelProps {
10-
path: string
11-
kernelName?: string
12-
}
13-
149
/**
1510
* E2B code interpreter sandbox extension.
1611
*/
@@ -117,13 +112,16 @@ export class JupyterExtension {
117112
* the retrieval of the necessary WebSocket URL from the kernel's information.
118113
*
119114
* @param kernelID The unique identifier of the kernel to connect to.
115+
* @param sessionID The unique identifier of the session to connect to.
120116
* @throws {Error} Throws an error if the connection to the kernel's WebSocket cannot be established.
121117
*/
122-
private async connectToKernelWS(kernelID: string) {
118+
private async connectToKernelWS(kernelID: string, sessionID?: string) {
123119
const url = `${this.sandbox.getProtocol('ws')}://${this.sandbox.getHostname(
124120
8888
125121
)}/api/kernels/${kernelID}/channels`
126-
const ws = new JupyterKernelWebSocket(url)
122+
123+
sessionID = sessionID || id(16)
124+
const ws = new JupyterKernelWebSocket(url, sessionID)
127125
await ws.connect()
128126
this.connectedKernels[kernelID] = ws
129127

@@ -147,15 +145,15 @@ export class JupyterExtension {
147145
cwd: string = '/home/user',
148146
kernelName?: string
149147
): Promise<string> {
150-
const data: CreateKernelProps = { path: cwd }
148+
const data = { path: cwd, kernel: {name: "python3"}, type: "notebook", name: id(16) }
151149
if (kernelName) {
152-
data.kernelName = kernelName
150+
data.kernel.name = kernelName
153151
}
154152

155153
const response = await fetch(
156154
`${this.sandbox.getProtocol()}://${this.sandbox.getHostname(
157155
8888
158-
)}/api/kernels`,
156+
)}/api/sessions`,
159157
{
160158
method: 'POST',
161159
body: JSON.stringify(data)
@@ -166,8 +164,10 @@ export class JupyterExtension {
166164
throw new Error(`Failed to create kernel: ${response.statusText}`)
167165
}
168166

169-
const kernelID = (await response.json()).id
170-
await this.connectToKernelWS(kernelID)
167+
const sessionInfo = await response.json()
168+
const kernelID = sessionInfo.kernel.id
169+
await this.connectToKernelWS(kernelID, sessionInfo.id)
170+
171171
return kernelID
172172
}
173173

js/src/messaging.ts

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -226,7 +226,11 @@ export class Execution {
226226
/**
227227
* An Error object if an error occurred, null otherwise.
228228
*/
229-
public error?: ExecutionError
229+
public error?: ExecutionError,
230+
/**
231+
* Execution count of the cell.
232+
*/
233+
public executionCount?: number
230234
) { }
231235

232236
/**
@@ -305,7 +309,7 @@ export class JupyterKernelWebSocket {
305309
* Does not start WebSocket connection!
306310
* You need to call connect() method first.
307311
*/
308-
constructor(private readonly url: string) { }
312+
constructor(private readonly url: string, private readonly sessionID: string) { }
309313

310314
// public
311315
/**
@@ -401,6 +405,7 @@ export class JupyterKernelWebSocket {
401405
}
402406
} else if (message.msg_type == 'execute_input') {
403407
cell.inputAccepted = true
408+
cell.execution.executionCount = message.content.execution_count
404409
} else {
405410
console.warn('[UNHANDLED MESSAGE TYPE]:', message.msg_type)
406411
}
@@ -486,12 +491,11 @@ export class JupyterKernelWebSocket {
486491
* @param code Code to be executed.
487492
*/
488493
private sendExecuteRequest(msg_id: string, code: string) {
489-
const session = id(16)
490494
return {
491495
header: {
492496
msg_id: msg_id,
493497
username: 'e2b',
494-
session: session,
498+
session: this.sessionID,
495499
msg_type: 'execute_request',
496500
version: '5.3'
497501
},
@@ -500,7 +504,7 @@ export class JupyterKernelWebSocket {
500504
content: {
501505
code: code,
502506
silent: false,
503-
store_history: false,
507+
store_history: true,
504508
user_expressions: {},
505509
allow_stdin: false
506510
}

js/tests/executionCount.test.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { CodeInterpreter } from '../src'
2+
3+
import { expect, test } from 'vitest'
4+
5+
test('execution count', async () => {
6+
const sandbox = await CodeInterpreter.create()
7+
8+
await sandbox.notebook.execCell('!pwd')
9+
const result = await sandbox.notebook.execCell('!pwd')
10+
11+
12+
await sandbox.close()
13+
14+
expect(result.executionCount).toEqual(2)
15+
})

python/e2b_code_interpreter/main.py

Lines changed: 22 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
import logging
44
import threading
5+
import uuid
6+
57
import requests
68

79
from concurrent.futures import Future
@@ -91,16 +93,16 @@ def exec_cell(
9193
ws = ws_future.result(timeout=timeout)
9294
else:
9395
logger.debug(f"Creating new websocket connection to kernel {kernel_id}")
94-
ws = self._connect_to_kernel_ws(kernel_id, timeout=timeout)
96+
ws = self._connect_to_kernel_ws(kernel_id, None, timeout=timeout)
9597

96-
session_id = ws.send_execution_message(code, on_stdout, on_stderr, on_result)
98+
message_id = ws.send_execution_message(code, on_stdout, on_stderr, on_result)
9799
logger.debug(
98-
f"Sent execution message to kernel {kernel_id}, session_id: {session_id}"
100+
f"Sent execution message to kernel {kernel_id}, message_id: {message_id}"
99101
)
100102

101-
result = ws.get_result(session_id, timeout=timeout)
103+
result = ws.get_result(message_id, timeout=timeout)
102104
logger.debug(
103-
f"Received result from kernel {kernel_id}, session_id: {session_id}, result: {result}"
105+
f"Received result from kernel {kernel_id}, message_id: {message_id}, result: {result}"
104106
)
105107

106108
return result
@@ -139,24 +141,26 @@ def create_kernel(
139141
:param timeout: Timeout for the kernel creation request.
140142
:return: Kernel id of the created kernel
141143
"""
142-
data = {"path": cwd}
144+
data = {"path": cwd, "kernel": {"name": "python3"}, "type": "notebook", "name": str(uuid.uuid4())}
143145
if kernel_name:
144-
data["kernel_name"] = kernel_name
146+
data["kernel"]['name'] = kernel_name
145147
logger.debug(f"Creating kernel with data: {data}")
146148

147149
response = requests.post(
148-
f"{self._sandbox.get_protocol()}://{self._sandbox.get_hostname(8888)}/api/kernels",
150+
f"{self._sandbox.get_protocol()}://{self._sandbox.get_hostname(8888)}/api/sessions",
149151
json=data,
150152
timeout=timeout,
151153
)
152154
if not response.ok:
153155
raise KernelException(f"Failed to create kernel: {response.text}")
154156

155-
kernel_id = response.json()["id"]
157+
session_data = response.json()
158+
kernel_id = session_data["kernel"]["id"]
159+
session_id = session_data["id"]
156160
logger.debug(f"Created kernel {kernel_id}")
157161

158162
threading.Thread(
159-
target=self._connect_to_kernel_ws, args=(kernel_id, timeout)
163+
target=self._connect_to_kernel_ws, args=(kernel_id, session_id, timeout)
160164
).start()
161165
return kernel_id
162166

@@ -186,7 +190,7 @@ def restart_kernel(
186190
logger.debug(f"Restarted kernel {kernel_id}")
187191

188192
threading.Thread(
189-
target=self._connect_to_kernel_ws, args=(kernel_id, timeout)
193+
target=self._connect_to_kernel_ws, args=(kernel_id, None, timeout)
190194
).start()
191195

192196
def shutdown_kernel(
@@ -243,7 +247,7 @@ def close(self):
243247
ws.result().close()
244248

245249
def _connect_to_kernel_ws(
246-
self, kernel_id: str, timeout: Optional[float] = TIMEOUT
250+
self, kernel_id: str, session_id: Optional[str], timeout: Optional[float] = TIMEOUT
247251
) -> JupyterKernelWebSocket:
248252
"""
249253
Establishes a WebSocket connection to a specified Jupyter kernel.
@@ -257,9 +261,12 @@ def _connect_to_kernel_ws(
257261
future = Future()
258262
self._connected_kernels[kernel_id] = future
259263

264+
session_id = session_id or str(uuid.uuid4())
260265
ws = JupyterKernelWebSocket(
261266
url=f"{self._sandbox.get_protocol('ws')}://{self._sandbox.get_hostname(8888)}/api/kernels/{kernel_id}/channels",
267+
session_id=session_id
262268
)
269+
263270
ws.connect(timeout=timeout)
264271
logger.debug(f"Connected to kernel's ({kernel_id}) websocket.")
265272

@@ -279,12 +286,14 @@ def setup_default_kernel():
279286
kernel_id = self._sandbox.filesystem.read(
280287
"/root/.jupyter/kernel_id", timeout=timeout
281288
)
289+
282290
if kernel_id is None and not self._sandbox.is_open:
283291
return
284292

285293
kernel_id = kernel_id.strip()
294+
286295
logger.debug(f"Default kernel id: {kernel_id}")
287-
self._connect_to_kernel_ws(kernel_id, timeout=timeout)
296+
self._connect_to_kernel_ws(kernel_id, None, timeout=timeout)
288297
self._kernel_id_set.set_result(kernel_id)
289298

290299
threading.Thread(target=setup_default_kernel).start()

python/e2b_code_interpreter/messaging.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ class CellExecution:
2525
"""
2626

2727
input_accepted: bool = False
28+
2829
on_stdout: Optional[Callable[[ProcessMessage], Any]] = None
2930
on_stderr: Optional[Callable[[ProcessMessage], Any]] = None
3031
on_result: Optional[Callable[[Result], Any]] = None
@@ -44,8 +45,9 @@ def __init__(
4445

4546
class JupyterKernelWebSocket:
4647

47-
def __init__(self, url: str):
48+
def __init__(self, url: str, session_id: str):
4849
self.url = url
50+
self.session_id = session_id
4951
self._cells: Dict[str, CellExecution] = {}
5052
self._waiting_for_replies: Dict[str, DeferredFuture] = {}
5153
self._queue_in = Queue()
@@ -101,14 +103,13 @@ def connect(self, timeout: float = TIMEOUT):
101103

102104
logger.debug("WebSocket started")
103105

104-
@staticmethod
105-
def _get_execute_request(msg_id: str, code: str) -> str:
106+
def _get_execute_request(self, msg_id: str, code: str) -> str:
106107
return json.dumps(
107108
{
108109
"header": {
109110
"msg_id": msg_id,
110111
"username": "e2b",
111-
"session": str(uuid.uuid4()),
112+
"session": self.session_id,
112113
"msg_type": "execute_request",
113114
"version": "5.3",
114115
},
@@ -117,7 +118,7 @@ def _get_execute_request(msg_id: str, code: str) -> str:
117118
"content": {
118119
"code": code,
119120
"silent": False,
120-
"store_history": False,
121+
"store_history": True,
121122
"user_expressions": {},
122123
"allow_stdin": False,
123124
},
@@ -237,6 +238,7 @@ def _receive_message(self, data: dict):
237238

238239
elif data["msg_type"] == "execute_input":
239240
logger.debug(f"Input accepted for {parent_msg_ig}")
241+
cell.partial_result.execution_count = data["content"]["execution_count"]
240242
cell.input_accepted = True
241243
else:
242244
logger.warning(f"[UNHANDLED MESSAGE TYPE]: {data['msg_type']}")

python/e2b_code_interpreter/models.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,8 @@ class Config:
222222
"Logs printed to stdout and stderr during execution."
223223
error: Optional[Error] = None
224224
"Error object if an error occurred, None otherwise."
225+
execution_count: Optional[int] = None
226+
"Execution count of the cell."
225227

226228
@property
227229
def text(self) -> Optional[str]:
@@ -249,7 +251,7 @@ def serialize_results(results: List[Result]) -> List[Dict[str, str]]:
249251
serialized = []
250252
for result in results:
251253
serialized_dict = {key: result[key] for key in result.formats()}
252-
serialized_dict['text'] = result.text
254+
serialized_dict["text"] = result.text
253255
serialized.append(serialized_dict)
254256
return serialized
255257

python/example.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,8 @@
2424
"""
2525

2626
with CodeInterpreter() as sandbox:
27-
result = sandbox.notebook.exec_cell(code)
27+
print(sandbox.id)
28+
execution = sandbox.notebook.exec_cell(code)
2829

29-
print(result.results[0].formats())
30-
print(len(result.results))
30+
print(execution.results[0].formats())
31+
print(len(execution.results))

python/poetry.lock

Lines changed: 5 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

python/pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ python = "^3.8"
1414

1515
pydantic = ">1, <3"
1616
websocket-client = "^1.7.0"
17-
e2b = ">=0.17.0"
17+
e2b = ">=0.17.1"
1818

1919
[tool.poetry.group.dev.dependencies]
2020
black = "^24.3.0"

python/tests/test_execution_count.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
from e2b_code_interpreter.main import CodeInterpreter
2+
3+
4+
def test_execution_count():
5+
with CodeInterpreter() as sandbox:
6+
sandbox.notebook.exec_cell("echo 'E2B is awesome!'")
7+
result = sandbox.notebook.exec_cell("!pwd")
8+
assert result.execution_count == 2

0 commit comments

Comments
 (0)