cowork-dash 0.1.2__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.
- cowork_dash/__init__.py +35 -0
- cowork_dash/__main__.py +9 -0
- cowork_dash/agent.py +117 -0
- cowork_dash/app.py +1776 -0
- cowork_dash/assets/app.js +237 -0
- cowork_dash/assets/favicon.svg +1 -0
- cowork_dash/assets/styles.css +915 -0
- cowork_dash/canvas.py +318 -0
- cowork_dash/cli.py +273 -0
- cowork_dash/components.py +568 -0
- cowork_dash/config.py +91 -0
- cowork_dash/file_utils.py +226 -0
- cowork_dash/layout.py +250 -0
- cowork_dash/tools.py +699 -0
- cowork_dash-0.1.2.dist-info/METADATA +238 -0
- cowork_dash-0.1.2.dist-info/RECORD +19 -0
- cowork_dash-0.1.2.dist-info/WHEEL +4 -0
- cowork_dash-0.1.2.dist-info/entry_points.txt +2 -0
- cowork_dash-0.1.2.dist-info/licenses/LICENSE +21 -0
cowork_dash/tools.py
ADDED
|
@@ -0,0 +1,699 @@
|
|
|
1
|
+
from typing import Any, Dict, List, Optional
|
|
2
|
+
import sys
|
|
3
|
+
import io
|
|
4
|
+
import traceback
|
|
5
|
+
import subprocess
|
|
6
|
+
from contextlib import redirect_stdout, redirect_stderr
|
|
7
|
+
|
|
8
|
+
from .config import WORKSPACE_ROOT
|
|
9
|
+
from .canvas import parse_canvas_object
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
# =============================================================================
|
|
13
|
+
# JUPYTER-LIKE CODE EXECUTION TOOLS
|
|
14
|
+
# =============================================================================
|
|
15
|
+
|
|
16
|
+
class NotebookState:
|
|
17
|
+
"""
|
|
18
|
+
Maintains persistent state for Jupyter-like code execution.
|
|
19
|
+
|
|
20
|
+
This class manages:
|
|
21
|
+
- An ordered list of code cells (the "script")
|
|
22
|
+
- A persistent namespace for variable state across cells
|
|
23
|
+
- Execution history and outputs
|
|
24
|
+
- Canvas items generated during cell execution
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
def __init__(self):
|
|
28
|
+
self._cells: List[Dict[str, Any]] = []
|
|
29
|
+
self._namespace: Dict[str, Any] = {}
|
|
30
|
+
self._execution_count: int = 0
|
|
31
|
+
self._ipython_shell = None
|
|
32
|
+
self._canvas_items: List[Dict[str, Any]] = [] # Collected canvas items
|
|
33
|
+
self._initialize_namespace()
|
|
34
|
+
|
|
35
|
+
def _initialize_namespace(self):
|
|
36
|
+
"""Initialize the namespace with common imports and utilities."""
|
|
37
|
+
# Pre-populate with commonly used modules
|
|
38
|
+
init_code = """
|
|
39
|
+
import sys
|
|
40
|
+
import os
|
|
41
|
+
import json
|
|
42
|
+
from pathlib import Path
|
|
43
|
+
|
|
44
|
+
# Data science essentials (imported if available)
|
|
45
|
+
try:
|
|
46
|
+
import pandas as pd
|
|
47
|
+
except ImportError:
|
|
48
|
+
pass
|
|
49
|
+
|
|
50
|
+
try:
|
|
51
|
+
import numpy as np
|
|
52
|
+
except ImportError:
|
|
53
|
+
pass
|
|
54
|
+
|
|
55
|
+
try:
|
|
56
|
+
import matplotlib
|
|
57
|
+
matplotlib.use('Agg') # Non-interactive backend
|
|
58
|
+
import matplotlib.pyplot as plt
|
|
59
|
+
except ImportError:
|
|
60
|
+
pass
|
|
61
|
+
|
|
62
|
+
try:
|
|
63
|
+
import plotly.express as px
|
|
64
|
+
import plotly.graph_objects as go
|
|
65
|
+
except ImportError:
|
|
66
|
+
pass
|
|
67
|
+
"""
|
|
68
|
+
# Execute initialization silently
|
|
69
|
+
try:
|
|
70
|
+
exec(init_code, self._namespace)
|
|
71
|
+
except Exception:
|
|
72
|
+
pass # Ignore import errors for optional packages
|
|
73
|
+
|
|
74
|
+
# Inject add_to_canvas function that captures items
|
|
75
|
+
def _add_to_canvas_wrapper(content: Any) -> Dict[str, Any]:
|
|
76
|
+
"""Add content to the canvas for visualization.
|
|
77
|
+
|
|
78
|
+
Supports: DataFrames, matplotlib figures, plotly figures,
|
|
79
|
+
PIL images, and markdown strings.
|
|
80
|
+
"""
|
|
81
|
+
try:
|
|
82
|
+
parsed = parse_canvas_object(content, workspace_root=WORKSPACE_ROOT)
|
|
83
|
+
self._canvas_items.append(parsed)
|
|
84
|
+
return parsed
|
|
85
|
+
except Exception as e:
|
|
86
|
+
error_result = {
|
|
87
|
+
"type": "error",
|
|
88
|
+
"data": f"Failed to add to canvas: {str(e)}",
|
|
89
|
+
"error": str(e)
|
|
90
|
+
}
|
|
91
|
+
self._canvas_items.append(error_result)
|
|
92
|
+
return error_result
|
|
93
|
+
|
|
94
|
+
self._namespace["add_to_canvas"] = _add_to_canvas_wrapper
|
|
95
|
+
|
|
96
|
+
def _get_ipython(self):
|
|
97
|
+
"""Get or create an IPython InteractiveShell for enhanced execution."""
|
|
98
|
+
if self._ipython_shell is None:
|
|
99
|
+
try:
|
|
100
|
+
from IPython.core.interactiveshell import InteractiveShell
|
|
101
|
+
self._ipython_shell = InteractiveShell.instance()
|
|
102
|
+
# Share the namespace
|
|
103
|
+
self._ipython_shell.user_ns = self._namespace
|
|
104
|
+
except ImportError:
|
|
105
|
+
# IPython not available, will use exec() fallback
|
|
106
|
+
pass
|
|
107
|
+
return self._ipython_shell
|
|
108
|
+
|
|
109
|
+
@property
|
|
110
|
+
def cells(self) -> List[Dict[str, Any]]:
|
|
111
|
+
"""Return a copy of all cells."""
|
|
112
|
+
return [cell.copy() for cell in self._cells]
|
|
113
|
+
|
|
114
|
+
@property
|
|
115
|
+
def namespace(self) -> Dict[str, Any]:
|
|
116
|
+
"""Return the current namespace (variable state)."""
|
|
117
|
+
return self._namespace
|
|
118
|
+
|
|
119
|
+
def get_cell(self, cell_index: int) -> Optional[Dict[str, Any]]:
|
|
120
|
+
"""Get a cell by index."""
|
|
121
|
+
if 0 <= cell_index < len(self._cells):
|
|
122
|
+
return self._cells[cell_index].copy()
|
|
123
|
+
return None
|
|
124
|
+
|
|
125
|
+
def add_cell(self, code: str, cell_type: str = "code") -> Dict[str, Any]:
|
|
126
|
+
"""Add a new cell to the end of the script."""
|
|
127
|
+
cell = {
|
|
128
|
+
"index": len(self._cells),
|
|
129
|
+
"type": cell_type,
|
|
130
|
+
"source": code,
|
|
131
|
+
"execution_count": None,
|
|
132
|
+
"outputs": [],
|
|
133
|
+
"status": "pending"
|
|
134
|
+
}
|
|
135
|
+
self._cells.append(cell)
|
|
136
|
+
return cell.copy()
|
|
137
|
+
|
|
138
|
+
def insert_cell(self, index: int, code: str, cell_type: str = "code") -> Dict[str, Any]:
|
|
139
|
+
"""Insert a cell at a specific index."""
|
|
140
|
+
if index < 0:
|
|
141
|
+
index = 0
|
|
142
|
+
if index > len(self._cells):
|
|
143
|
+
index = len(self._cells)
|
|
144
|
+
|
|
145
|
+
cell = {
|
|
146
|
+
"index": index,
|
|
147
|
+
"type": cell_type,
|
|
148
|
+
"source": code,
|
|
149
|
+
"execution_count": None,
|
|
150
|
+
"outputs": [],
|
|
151
|
+
"status": "pending"
|
|
152
|
+
}
|
|
153
|
+
self._cells.insert(index, cell)
|
|
154
|
+
|
|
155
|
+
# Update indices for subsequent cells
|
|
156
|
+
for i in range(index + 1, len(self._cells)):
|
|
157
|
+
self._cells[i]["index"] = i
|
|
158
|
+
|
|
159
|
+
return cell.copy()
|
|
160
|
+
|
|
161
|
+
def modify_cell(self, cell_index: int, new_code: str) -> Dict[str, Any]:
|
|
162
|
+
"""Modify the code in an existing cell."""
|
|
163
|
+
if not (0 <= cell_index < len(self._cells)):
|
|
164
|
+
return {
|
|
165
|
+
"error": f"Cell index {cell_index} out of range. Valid range: 0-{len(self._cells) - 1}"
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
self._cells[cell_index]["source"] = new_code
|
|
169
|
+
self._cells[cell_index]["status"] = "modified"
|
|
170
|
+
self._cells[cell_index]["outputs"] = [] # Clear previous outputs
|
|
171
|
+
|
|
172
|
+
return self._cells[cell_index].copy()
|
|
173
|
+
|
|
174
|
+
def delete_cell(self, cell_index: int) -> Dict[str, Any]:
|
|
175
|
+
"""Delete a cell by index."""
|
|
176
|
+
if not (0 <= cell_index < len(self._cells)):
|
|
177
|
+
return {
|
|
178
|
+
"error": f"Cell index {cell_index} out of range. Valid range: 0-{len(self._cells) - 1}"
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
deleted_cell = self._cells.pop(cell_index)
|
|
182
|
+
|
|
183
|
+
# Update indices for subsequent cells
|
|
184
|
+
for i in range(cell_index, len(self._cells)):
|
|
185
|
+
self._cells[i]["index"] = i
|
|
186
|
+
|
|
187
|
+
return {"deleted": deleted_cell, "remaining_cells": len(self._cells)}
|
|
188
|
+
|
|
189
|
+
def execute_cell(self, cell_index: int) -> Dict[str, Any]:
|
|
190
|
+
"""Execute a single cell and capture its output."""
|
|
191
|
+
if not (0 <= cell_index < len(self._cells)):
|
|
192
|
+
return {
|
|
193
|
+
"error": f"Cell index {cell_index} out of range. Valid range: 0-{len(self._cells) - 1}"
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
cell = self._cells[cell_index]
|
|
197
|
+
|
|
198
|
+
if cell["type"] != "code":
|
|
199
|
+
return {
|
|
200
|
+
"index": cell_index,
|
|
201
|
+
"type": cell["type"],
|
|
202
|
+
"source": cell["source"],
|
|
203
|
+
"output": "(markdown cell - not executed)",
|
|
204
|
+
"status": "skipped"
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
self._execution_count += 1
|
|
208
|
+
cell["execution_count"] = self._execution_count
|
|
209
|
+
|
|
210
|
+
# Track canvas items added during this cell's execution
|
|
211
|
+
canvas_count_before = len(self._canvas_items)
|
|
212
|
+
|
|
213
|
+
# Capture stdout and stderr
|
|
214
|
+
stdout_capture = io.StringIO()
|
|
215
|
+
stderr_capture = io.StringIO()
|
|
216
|
+
|
|
217
|
+
result = {
|
|
218
|
+
"index": cell_index,
|
|
219
|
+
"execution_count": self._execution_count,
|
|
220
|
+
"source": cell["source"],
|
|
221
|
+
"stdout": "",
|
|
222
|
+
"stderr": "",
|
|
223
|
+
"result": None,
|
|
224
|
+
"error": None,
|
|
225
|
+
"status": "success",
|
|
226
|
+
"canvas_items": [] # Canvas items added during execution
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
try:
|
|
230
|
+
# Try IPython first for better execution handling
|
|
231
|
+
ipython = self._get_ipython()
|
|
232
|
+
|
|
233
|
+
if ipython is not None:
|
|
234
|
+
# Use IPython's run_cell for magic commands support
|
|
235
|
+
with redirect_stdout(stdout_capture), redirect_stderr(stderr_capture):
|
|
236
|
+
exec_result = ipython.run_cell(cell["source"], store_history=True)
|
|
237
|
+
|
|
238
|
+
result["stdout"] = stdout_capture.getvalue()
|
|
239
|
+
result["stderr"] = stderr_capture.getvalue()
|
|
240
|
+
|
|
241
|
+
if exec_result.success:
|
|
242
|
+
if exec_result.result is not None:
|
|
243
|
+
result["result"] = repr(exec_result.result)
|
|
244
|
+
else:
|
|
245
|
+
if exec_result.error_in_exec:
|
|
246
|
+
result["error"] = str(exec_result.error_in_exec)
|
|
247
|
+
result["status"] = "error"
|
|
248
|
+
elif exec_result.error_before_exec:
|
|
249
|
+
result["error"] = str(exec_result.error_before_exec)
|
|
250
|
+
result["status"] = "error"
|
|
251
|
+
else:
|
|
252
|
+
# Fallback to exec() if IPython is not available
|
|
253
|
+
with redirect_stdout(stdout_capture), redirect_stderr(stderr_capture):
|
|
254
|
+
# Compile to check for expression vs statement
|
|
255
|
+
code = cell["source"].strip()
|
|
256
|
+
|
|
257
|
+
# Try to evaluate as expression first (to get return value)
|
|
258
|
+
try:
|
|
259
|
+
# Check if it's a simple expression
|
|
260
|
+
compiled = compile(code, "<cell>", "eval")
|
|
261
|
+
exec_result = eval(compiled, self._namespace)
|
|
262
|
+
if exec_result is not None:
|
|
263
|
+
result["result"] = repr(exec_result)
|
|
264
|
+
except SyntaxError:
|
|
265
|
+
# It's a statement, execute it
|
|
266
|
+
exec(code, self._namespace)
|
|
267
|
+
|
|
268
|
+
result["stdout"] = stdout_capture.getvalue()
|
|
269
|
+
result["stderr"] = stderr_capture.getvalue()
|
|
270
|
+
|
|
271
|
+
except Exception:
|
|
272
|
+
result["error"] = traceback.format_exc()
|
|
273
|
+
result["status"] = "error"
|
|
274
|
+
result["stdout"] = stdout_capture.getvalue()
|
|
275
|
+
result["stderr"] = stderr_capture.getvalue()
|
|
276
|
+
|
|
277
|
+
# Capture any canvas items added during this cell's execution
|
|
278
|
+
canvas_items_added = self._canvas_items[canvas_count_before:]
|
|
279
|
+
result["canvas_items"] = canvas_items_added
|
|
280
|
+
|
|
281
|
+
# Store outputs in cell
|
|
282
|
+
cell["outputs"] = [result]
|
|
283
|
+
cell["status"] = result["status"]
|
|
284
|
+
|
|
285
|
+
return result
|
|
286
|
+
|
|
287
|
+
def execute_all(self) -> List[Dict[str, Any]]:
|
|
288
|
+
"""Execute all cells in order."""
|
|
289
|
+
results = []
|
|
290
|
+
for i in range(len(self._cells)):
|
|
291
|
+
results.append(self.execute_cell(i))
|
|
292
|
+
return results
|
|
293
|
+
|
|
294
|
+
def get_script(self) -> str:
|
|
295
|
+
"""Get all code cells concatenated as a single script."""
|
|
296
|
+
code_cells = [cell["source"] for cell in self._cells if cell["type"] == "code"]
|
|
297
|
+
return "\n\n".join(code_cells)
|
|
298
|
+
|
|
299
|
+
def get_variables(self) -> Dict[str, str]:
|
|
300
|
+
"""Get a summary of user-defined variables in the namespace."""
|
|
301
|
+
# Filter out modules, builtins, and private variables
|
|
302
|
+
user_vars = {}
|
|
303
|
+
for name, value in self._namespace.items():
|
|
304
|
+
if name.startswith("_"):
|
|
305
|
+
continue
|
|
306
|
+
if isinstance(value, type(sys)): # Skip modules
|
|
307
|
+
continue
|
|
308
|
+
if callable(value) and hasattr(value, "__module__"):
|
|
309
|
+
# Skip imported functions
|
|
310
|
+
if value.__module__ != "__main__" and value.__module__ not in [None, "builtins"]:
|
|
311
|
+
continue
|
|
312
|
+
try:
|
|
313
|
+
# Get a short repr
|
|
314
|
+
value_repr = repr(value)
|
|
315
|
+
if len(value_repr) > 100:
|
|
316
|
+
value_repr = value_repr[:97] + "..."
|
|
317
|
+
user_vars[name] = f"{type(value).__name__}: {value_repr}"
|
|
318
|
+
except Exception:
|
|
319
|
+
user_vars[name] = f"{type(value).__name__}: <unable to repr>"
|
|
320
|
+
return user_vars
|
|
321
|
+
|
|
322
|
+
def get_canvas_items(self) -> List[Dict[str, Any]]:
|
|
323
|
+
"""Get all canvas items collected during execution."""
|
|
324
|
+
return self._canvas_items.copy()
|
|
325
|
+
|
|
326
|
+
def clear_canvas_items(self) -> Dict[str, Any]:
|
|
327
|
+
"""Clear collected canvas items."""
|
|
328
|
+
count = len(self._canvas_items)
|
|
329
|
+
self._canvas_items = []
|
|
330
|
+
return {"cleared": count}
|
|
331
|
+
|
|
332
|
+
def reset(self):
|
|
333
|
+
"""Reset the notebook state (clear all cells and namespace)."""
|
|
334
|
+
self._cells = []
|
|
335
|
+
self._namespace = {}
|
|
336
|
+
self._execution_count = 0
|
|
337
|
+
self._canvas_items = []
|
|
338
|
+
self._initialize_namespace()
|
|
339
|
+
return {"status": "reset", "message": "Notebook state cleared"}
|
|
340
|
+
|
|
341
|
+
|
|
342
|
+
# Global notebook state instance
|
|
343
|
+
_notebook_state = NotebookState()
|
|
344
|
+
|
|
345
|
+
|
|
346
|
+
def create_cell(code: str, cell_type: str = "code") -> Dict[str, Any]:
|
|
347
|
+
"""
|
|
348
|
+
Create a new code or markdown cell and add it to the end of the script.
|
|
349
|
+
|
|
350
|
+
This simulates creating a new cell in a Jupyter notebook. The cell is added
|
|
351
|
+
but not executed - use execute_cell() to run it.
|
|
352
|
+
|
|
353
|
+
Args:
|
|
354
|
+
code: The Python code or markdown content for the cell
|
|
355
|
+
cell_type: Either "code" or "markdown" (default: "code")
|
|
356
|
+
|
|
357
|
+
Returns:
|
|
358
|
+
Dictionary with cell information including:
|
|
359
|
+
- index: The cell's position in the notebook
|
|
360
|
+
- type: The cell type
|
|
361
|
+
- source: The cell's code/content
|
|
362
|
+
- status: "pending" (not yet executed)
|
|
363
|
+
|
|
364
|
+
Examples:
|
|
365
|
+
# Create a code cell
|
|
366
|
+
create_cell("x = 42\\nprint(f'x = {x}')")
|
|
367
|
+
|
|
368
|
+
# Create a markdown cell
|
|
369
|
+
create_cell("## Analysis Results", cell_type="markdown")
|
|
370
|
+
"""
|
|
371
|
+
return _notebook_state.add_cell(code, cell_type)
|
|
372
|
+
|
|
373
|
+
|
|
374
|
+
def insert_cell(index: int, code: str, cell_type: str = "code") -> Dict[str, Any]:
|
|
375
|
+
"""
|
|
376
|
+
Insert a new cell at a specific position in the script.
|
|
377
|
+
|
|
378
|
+
This is useful when you need to add code between existing cells,
|
|
379
|
+
such as adding a missing import or intermediate calculation.
|
|
380
|
+
|
|
381
|
+
Args:
|
|
382
|
+
index: Position to insert the cell (0-based). Cells after this
|
|
383
|
+
position will be shifted down.
|
|
384
|
+
code: The Python code or markdown content
|
|
385
|
+
cell_type: Either "code" or "markdown" (default: "code")
|
|
386
|
+
|
|
387
|
+
Returns:
|
|
388
|
+
Dictionary with cell information including index and status
|
|
389
|
+
|
|
390
|
+
Examples:
|
|
391
|
+
# Insert an import at the beginning
|
|
392
|
+
insert_cell(0, "import pandas as pd")
|
|
393
|
+
|
|
394
|
+
# Insert a cell between cells 2 and 3
|
|
395
|
+
insert_cell(3, "intermediate_result = process(data)")
|
|
396
|
+
"""
|
|
397
|
+
return _notebook_state.insert_cell(index, code, cell_type)
|
|
398
|
+
|
|
399
|
+
|
|
400
|
+
def modify_cell(cell_index: int, new_code: str) -> Dict[str, Any]:
|
|
401
|
+
"""
|
|
402
|
+
Modify the code in an existing cell.
|
|
403
|
+
|
|
404
|
+
Use this to fix errors, update logic, or refine code in a cell.
|
|
405
|
+
The cell's outputs are cleared and status set to "modified".
|
|
406
|
+
You'll need to re-execute the cell to see the new results.
|
|
407
|
+
|
|
408
|
+
Args:
|
|
409
|
+
cell_index: The index of the cell to modify (0-based)
|
|
410
|
+
new_code: The new code to replace the existing code
|
|
411
|
+
|
|
412
|
+
Returns:
|
|
413
|
+
Dictionary with updated cell information, or error if index invalid
|
|
414
|
+
|
|
415
|
+
Examples:
|
|
416
|
+
# Fix a typo in cell 2
|
|
417
|
+
modify_cell(2, "result = data.groupby('category').mean()")
|
|
418
|
+
|
|
419
|
+
# Update a calculation
|
|
420
|
+
modify_cell(0, "threshold = 0.95 # Updated from 0.9")
|
|
421
|
+
"""
|
|
422
|
+
return _notebook_state.modify_cell(cell_index, new_code)
|
|
423
|
+
|
|
424
|
+
|
|
425
|
+
def delete_cell(cell_index: int) -> Dict[str, Any]:
|
|
426
|
+
"""
|
|
427
|
+
Delete a cell from the script.
|
|
428
|
+
|
|
429
|
+
Removes the cell at the specified index. Subsequent cells will have
|
|
430
|
+
their indices updated. Note: This does NOT undo any side effects
|
|
431
|
+
from executing the deleted cell (variables remain in namespace).
|
|
432
|
+
|
|
433
|
+
Args:
|
|
434
|
+
cell_index: The index of the cell to delete (0-based)
|
|
435
|
+
|
|
436
|
+
Returns:
|
|
437
|
+
Dictionary with deleted cell info and remaining cell count
|
|
438
|
+
|
|
439
|
+
Examples:
|
|
440
|
+
# Remove cell 3
|
|
441
|
+
delete_cell(3)
|
|
442
|
+
"""
|
|
443
|
+
return _notebook_state.delete_cell(cell_index)
|
|
444
|
+
|
|
445
|
+
|
|
446
|
+
def execute_cell(cell_index: int) -> Dict[str, Any]:
|
|
447
|
+
"""
|
|
448
|
+
Execute a single cell and return its output.
|
|
449
|
+
|
|
450
|
+
Runs the code in the specified cell within the persistent namespace.
|
|
451
|
+
Variables created or modified will be available to subsequent cells.
|
|
452
|
+
Captures stdout, stderr, and the cell's return value (if any).
|
|
453
|
+
|
|
454
|
+
Args:
|
|
455
|
+
cell_index: The index of the cell to execute (0-based)
|
|
456
|
+
|
|
457
|
+
Returns:
|
|
458
|
+
Dictionary containing:
|
|
459
|
+
- index: Cell index
|
|
460
|
+
- execution_count: Global execution counter
|
|
461
|
+
- source: The executed code
|
|
462
|
+
- stdout: Captured print() output
|
|
463
|
+
- stderr: Captured error output
|
|
464
|
+
- result: Return value of the last expression (if any)
|
|
465
|
+
- error: Error traceback (if execution failed)
|
|
466
|
+
- status: "success" or "error"
|
|
467
|
+
|
|
468
|
+
Examples:
|
|
469
|
+
# Execute the first cell
|
|
470
|
+
execute_cell(0)
|
|
471
|
+
|
|
472
|
+
# Execute and check for errors
|
|
473
|
+
result = execute_cell(2)
|
|
474
|
+
if result["status"] == "error":
|
|
475
|
+
print(result["error"])
|
|
476
|
+
"""
|
|
477
|
+
return _notebook_state.execute_cell(cell_index)
|
|
478
|
+
|
|
479
|
+
|
|
480
|
+
def execute_all_cells() -> List[Dict[str, Any]]:
|
|
481
|
+
"""
|
|
482
|
+
Execute all cells in the script in order.
|
|
483
|
+
|
|
484
|
+
Runs each cell sequentially from the beginning. Useful for
|
|
485
|
+
re-running the entire notebook after modifications.
|
|
486
|
+
|
|
487
|
+
Returns:
|
|
488
|
+
List of execution results for each cell
|
|
489
|
+
|
|
490
|
+
Examples:
|
|
491
|
+
# Run entire notebook
|
|
492
|
+
results = execute_all_cells()
|
|
493
|
+
errors = [r for r in results if r.get("status") == "error"]
|
|
494
|
+
"""
|
|
495
|
+
return _notebook_state.execute_all()
|
|
496
|
+
|
|
497
|
+
|
|
498
|
+
def get_script() -> Dict[str, Any]:
|
|
499
|
+
"""
|
|
500
|
+
Get the complete script and current state.
|
|
501
|
+
|
|
502
|
+
Returns all cells, the concatenated code, and current variable state.
|
|
503
|
+
Useful for reviewing the notebook or exporting the code.
|
|
504
|
+
|
|
505
|
+
Returns:
|
|
506
|
+
Dictionary containing:
|
|
507
|
+
- cells: List of all cells with their content and outputs
|
|
508
|
+
- script: All code cells concatenated as a single script
|
|
509
|
+
- variables: Summary of user-defined variables
|
|
510
|
+
- cell_count: Total number of cells
|
|
511
|
+
|
|
512
|
+
Examples:
|
|
513
|
+
# Review current state
|
|
514
|
+
state = get_script()
|
|
515
|
+
print(f"Notebook has {state['cell_count']} cells")
|
|
516
|
+
print(state['script'])
|
|
517
|
+
"""
|
|
518
|
+
return {
|
|
519
|
+
"cells": _notebook_state.cells,
|
|
520
|
+
"script": _notebook_state.get_script(),
|
|
521
|
+
"variables": _notebook_state.get_variables(),
|
|
522
|
+
"cell_count": len(_notebook_state.cells)
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
|
|
526
|
+
def get_variables() -> Dict[str, str]:
|
|
527
|
+
"""
|
|
528
|
+
Get a summary of all user-defined variables in the namespace.
|
|
529
|
+
|
|
530
|
+
Returns variable names with their types and values (truncated if long).
|
|
531
|
+
Useful for understanding what data is available for use in new cells.
|
|
532
|
+
|
|
533
|
+
Returns:
|
|
534
|
+
Dictionary mapping variable names to "type: value" strings
|
|
535
|
+
|
|
536
|
+
Examples:
|
|
537
|
+
# Check available variables
|
|
538
|
+
vars = get_variables()
|
|
539
|
+
for name, info in vars.items():
|
|
540
|
+
print(f"{name}: {info}")
|
|
541
|
+
"""
|
|
542
|
+
return _notebook_state.get_variables()
|
|
543
|
+
|
|
544
|
+
|
|
545
|
+
def reset_notebook() -> Dict[str, Any]:
|
|
546
|
+
"""
|
|
547
|
+
Reset the notebook state completely.
|
|
548
|
+
|
|
549
|
+
Clears all cells and resets the namespace to its initial state.
|
|
550
|
+
Use with caution - this cannot be undone.
|
|
551
|
+
|
|
552
|
+
Returns:
|
|
553
|
+
Dictionary confirming the reset
|
|
554
|
+
|
|
555
|
+
Examples:
|
|
556
|
+
# Start fresh
|
|
557
|
+
reset_notebook()
|
|
558
|
+
"""
|
|
559
|
+
return _notebook_state.reset()
|
|
560
|
+
|
|
561
|
+
|
|
562
|
+
def get_notebook_canvas_items() -> List[Dict[str, Any]]:
|
|
563
|
+
"""
|
|
564
|
+
Get all canvas items generated during notebook cell execution.
|
|
565
|
+
|
|
566
|
+
When code in cells calls add_to_canvas(), the items are collected here.
|
|
567
|
+
Use this to retrieve visualizations generated by executed code.
|
|
568
|
+
|
|
569
|
+
Returns:
|
|
570
|
+
List of canvas item dictionaries with type and data
|
|
571
|
+
|
|
572
|
+
Examples:
|
|
573
|
+
# After executing cells that created charts
|
|
574
|
+
items = get_notebook_canvas_items()
|
|
575
|
+
for item in items:
|
|
576
|
+
print(f"Type: {item['type']}")
|
|
577
|
+
"""
|
|
578
|
+
return _notebook_state.get_canvas_items()
|
|
579
|
+
|
|
580
|
+
|
|
581
|
+
def clear_notebook_canvas_items() -> Dict[str, Any]:
|
|
582
|
+
"""
|
|
583
|
+
Clear all canvas items collected from notebook execution.
|
|
584
|
+
|
|
585
|
+
Returns:
|
|
586
|
+
Dictionary with count of cleared items
|
|
587
|
+
"""
|
|
588
|
+
return _notebook_state.clear_canvas_items()
|
|
589
|
+
|
|
590
|
+
|
|
591
|
+
# =============================================================================
|
|
592
|
+
# CANVAS TOOLS
|
|
593
|
+
# =============================================================================
|
|
594
|
+
|
|
595
|
+
def add_to_canvas(content: Any) -> Dict[str, Any]:
|
|
596
|
+
"""Add an item to the canvas for visualization. Canvas is like a note-taking tool where
|
|
597
|
+
you can store charts, dataframes, images, and markdown text for the user to see.
|
|
598
|
+
|
|
599
|
+
Args:
|
|
600
|
+
content: Can be a pandas DataFrame, matplotlib Figure, plotly Figure,
|
|
601
|
+
PIL Image, dictionary (for Plotly JSON), or string (for Markdown)
|
|
602
|
+
workspace_root: Path to the workspace root directory
|
|
603
|
+
|
|
604
|
+
Returns:
|
|
605
|
+
Dictionary with the parsed canvas object
|
|
606
|
+
|
|
607
|
+
Examples:
|
|
608
|
+
# Add a DataFrame
|
|
609
|
+
import pandas as pd
|
|
610
|
+
df = pd.DataFrame({"A": [1, 2, 3], "B": [4, 5, 6]})
|
|
611
|
+
add_to_canvas(df)
|
|
612
|
+
|
|
613
|
+
# Add a Matplotlib chart
|
|
614
|
+
import matplotlib.pyplot as plt
|
|
615
|
+
fig, ax = plt.subplots()
|
|
616
|
+
ax.plot([1, 2, 3], [1, 4, 9])
|
|
617
|
+
add_to_canvas(fig)
|
|
618
|
+
|
|
619
|
+
# Add Markdown text
|
|
620
|
+
add_to_canvas("## Key Findings\\n- Point 1\\n- Point 2")
|
|
621
|
+
"""
|
|
622
|
+
try:
|
|
623
|
+
# Parse the content into canvas format
|
|
624
|
+
parsed = parse_canvas_object(content, workspace_root=WORKSPACE_ROOT)
|
|
625
|
+
# Return the parsed object (deepagents will handle the JSON serialization)
|
|
626
|
+
return parsed
|
|
627
|
+
except Exception as e:
|
|
628
|
+
return {
|
|
629
|
+
"type": "error",
|
|
630
|
+
"data": f"Failed to add to canvas: {str(e)}",
|
|
631
|
+
"error": str(e)
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
|
|
635
|
+
# =============================================================================
|
|
636
|
+
# BASH TOOL
|
|
637
|
+
# =============================================================================
|
|
638
|
+
|
|
639
|
+
def bash(command: str, timeout: int = 60) -> Dict[str, Any]:
|
|
640
|
+
"""Execute a bash command and return the output.
|
|
641
|
+
|
|
642
|
+
Runs the command in the workspace directory. Use this for file operations,
|
|
643
|
+
git commands, installing packages, or any shell operations.
|
|
644
|
+
|
|
645
|
+
Args:
|
|
646
|
+
command: The bash command to execute
|
|
647
|
+
timeout: Maximum time in seconds to wait for the command (default: 60)
|
|
648
|
+
|
|
649
|
+
Returns:
|
|
650
|
+
Dictionary containing:
|
|
651
|
+
- stdout: Standard output from the command
|
|
652
|
+
- stderr: Standard error output
|
|
653
|
+
- return_code: Exit code (0 typically means success)
|
|
654
|
+
- status: "success" or "error"
|
|
655
|
+
|
|
656
|
+
Examples:
|
|
657
|
+
# List files
|
|
658
|
+
bash("ls -la")
|
|
659
|
+
|
|
660
|
+
# Check git status
|
|
661
|
+
bash("git status")
|
|
662
|
+
|
|
663
|
+
# Install a package
|
|
664
|
+
bash("pip install pandas")
|
|
665
|
+
|
|
666
|
+
# Run a script
|
|
667
|
+
bash("python script.py")
|
|
668
|
+
"""
|
|
669
|
+
try:
|
|
670
|
+
result = subprocess.run(
|
|
671
|
+
command,
|
|
672
|
+
shell=True,
|
|
673
|
+
cwd=str(WORKSPACE_ROOT),
|
|
674
|
+
capture_output=True,
|
|
675
|
+
text=True,
|
|
676
|
+
timeout=timeout
|
|
677
|
+
)
|
|
678
|
+
|
|
679
|
+
return {
|
|
680
|
+
"stdout": result.stdout,
|
|
681
|
+
"stderr": result.stderr,
|
|
682
|
+
"return_code": result.returncode,
|
|
683
|
+
"status": "success" if result.returncode == 0 else "error"
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
except subprocess.TimeoutExpired:
|
|
687
|
+
return {
|
|
688
|
+
"stdout": "",
|
|
689
|
+
"stderr": f"Command timed out after {timeout} seconds",
|
|
690
|
+
"return_code": -1,
|
|
691
|
+
"status": "error"
|
|
692
|
+
}
|
|
693
|
+
except Exception as e:
|
|
694
|
+
return {
|
|
695
|
+
"stdout": "",
|
|
696
|
+
"stderr": str(e),
|
|
697
|
+
"return_code": -1,
|
|
698
|
+
"status": "error"
|
|
699
|
+
}
|