kedi-notebook 0.1.0__tar.gz
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.
- kedi_notebook-0.1.0/.gitignore +7 -0
- kedi_notebook-0.1.0/LICENSE +21 -0
- kedi_notebook-0.1.0/PKG-INFO +169 -0
- kedi_notebook-0.1.0/README.md +153 -0
- kedi_notebook-0.1.0/pyproject.toml +63 -0
- kedi_notebook-0.1.0/src/kedi_notebook/__init__.py +15 -0
- kedi_notebook-0.1.0/src/kedi_notebook/__main__.py +4 -0
- kedi_notebook-0.1.0/src/kedi_notebook/bridge.py +171 -0
- kedi_notebook-0.1.0/src/kedi_notebook/execution.py +91 -0
- kedi_notebook-0.1.0/src/kedi_notebook/host_environment.py +233 -0
- kedi_notebook-0.1.0/src/kedi_notebook/model_dependencies.py +95 -0
- kedi_notebook-0.1.0/src/kedi_notebook/pyright.py +605 -0
- kedi_notebook-0.1.0/src/kedi_notebook/runtime.py +928 -0
- kedi_notebook-0.1.0/src/kedi_notebook/sandbox_worker.py +339 -0
- kedi_notebook-0.1.0/src/kedi_notebook/secrets.py +158 -0
- kedi_notebook-0.1.0/src/kedi_notebook/server.py +865 -0
- kedi_notebook-0.1.0/src/kedi_notebook/static/grammars/kedi/highlights.scm +275 -0
- kedi_notebook-0.1.0/src/kedi_notebook/static/grammars/kedi/injections.scm +23 -0
- kedi_notebook-0.1.0/src/kedi_notebook/static/grammars/kedi/tree-sitter-kedi.wasm +0 -0
- kedi_notebook-0.1.0/src/kedi_notebook/static/grammars/python/highlights.scm +137 -0
- kedi_notebook-0.1.0/src/kedi_notebook/static/grammars/python/tree-sitter-python.wasm +0 -0
- kedi_notebook-0.1.0/src/kedi_notebook/static/kedi-editor.js +695 -0
- kedi_notebook-0.1.0/src/kedi_notebook/static/notebook/assets/LICENSE +93 -0
- kedi_notebook-0.1.0/src/kedi_notebook/static/notebook/assets/geist-latin-wght-normal.woff2 +0 -0
- kedi_notebook-0.1.0/src/kedi_notebook/static/notebook/assets/geist-mono-latin-wght-normal.woff2 +0 -0
- kedi_notebook-0.1.0/src/kedi_notebook/static/notebook/assets/kedi-logo-white.svg +9 -0
- kedi_notebook-0.1.0/src/kedi_notebook/static/notebook/index.html +196 -0
- kedi_notebook-0.1.0/src/kedi_notebook/static/notebook/notebook.css +1126 -0
- kedi_notebook-0.1.0/src/kedi_notebook/static/notebook/notebook.js +2007 -0
- kedi_notebook-0.1.0/src/kedi_notebook/static/pyodide-runtime.js +257 -0
- kedi_notebook-0.1.0/src/kedi_notebook/static/pyodide-worker.js +580 -0
- kedi_notebook-0.1.0/src/kedi_notebook/static/tree-sitter-highlighter.js +461 -0
- kedi_notebook-0.1.0/src/kedi_notebook/static/vendor/lucide.min.js +12 -0
- kedi_notebook-0.1.0/src/kedi_notebook/static/vendor/pyodide/LICENSE +373 -0
- kedi_notebook-0.1.0/src/kedi_notebook/static/vendor/pyodide/annotated_types-0.7.0-py3-none-any.whl +0 -0
- kedi_notebook-0.1.0/src/kedi_notebook/static/vendor/pyodide/micropip-0.11.1-py3-none-any.whl +0 -0
- kedi_notebook-0.1.0/src/kedi_notebook/static/vendor/pyodide/pydantic-2.12.5-py3-none-any.whl +0 -0
- kedi_notebook-0.1.0/src/kedi_notebook/static/vendor/pyodide/pydantic_core-2.41.5-cp314-cp314-pyemscripten_2026_0_wasm32.whl +0 -0
- kedi_notebook-0.1.0/src/kedi_notebook/static/vendor/pyodide/pyodide-lock.json +1 -0
- kedi_notebook-0.1.0/src/kedi_notebook/static/vendor/pyodide/pyodide.asm.mjs +2 -0
- kedi_notebook-0.1.0/src/kedi_notebook/static/vendor/pyodide/pyodide.asm.wasm +0 -0
- kedi_notebook-0.1.0/src/kedi_notebook/static/vendor/pyodide/pyodide.mjs +4 -0
- kedi_notebook-0.1.0/src/kedi_notebook/static/vendor/pyodide/python_stdlib.zip +0 -0
- kedi_notebook-0.1.0/src/kedi_notebook/static/vendor/pyodide/typing_extensions-4.15.0-py3-none-any.whl +0 -0
- kedi_notebook-0.1.0/src/kedi_notebook/static/vendor/pyodide/typing_inspection-0.4.2-py3-none-any.whl +0 -0
- kedi_notebook-0.1.0/src/kedi_notebook/static/vendor/web-tree-sitter.js +4007 -0
- kedi_notebook-0.1.0/src/kedi_notebook/static/vendor/web-tree-sitter.wasm +0 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Kedi Programming Language
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: kedi-notebook
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Local interactive notebook for the Kedi Programming Language
|
|
5
|
+
Project-URL: Source, https://github.com/kedi-lang/notebook
|
|
6
|
+
Project-URL: Tracker, https://github.com/kedi-lang/notebook/issues
|
|
7
|
+
License-Expression: MIT
|
|
8
|
+
License-File: LICENSE
|
|
9
|
+
Requires-Python: >=3.11
|
|
10
|
+
Requires-Dist: basedpyright<1.41,>=1.40.2
|
|
11
|
+
Requires-Dist: fastapi<0.163,>=0.162.0
|
|
12
|
+
Requires-Dist: kedi<0.2,>=0.1.2
|
|
13
|
+
Requires-Dist: python-dotenv<2,>=1.2.4
|
|
14
|
+
Requires-Dist: uvicorn<0.55,>=0.54
|
|
15
|
+
Description-Content-Type: text/markdown
|
|
16
|
+
|
|
17
|
+
# Kedi Notebook
|
|
18
|
+
|
|
19
|
+
Kedi Notebook is the local, incremental notebook interface for the Kedi
|
|
20
|
+
Programming Language. It provides editable Kedi, terminal, and Markdown cells,
|
|
21
|
+
with either browser-owned Pyodide or a selected host Python interpreter.
|
|
22
|
+
|
|
23
|
+

|
|
24
|
+
|
|
25
|
+
## Install and Run
|
|
26
|
+
|
|
27
|
+
The notebook is currently distributed from a Kedi source checkout. Install its
|
|
28
|
+
local dependencies and start it in one command from the Kedi repository root:
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
uv run --extra notebook kedi notebook
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
This uses the checked-out `notebook` submodule and Kedi source rather than a
|
|
35
|
+
published package. For notebook development, run from the Kedi repository root
|
|
36
|
+
with the notebook extra and development dependencies installed:
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
.venv/bin/python -m pytest notebook/tests
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
After syncing the extra, both entry points are equivalent:
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
kedi notebook
|
|
46
|
+
kedi-notebook
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
The default address is <http://127.0.0.1:8788/notebook/>. Add one or more host
|
|
50
|
+
Python installations explicitly when needed:
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
kedi-notebook --python /opt/homebrew/bin/python3.11
|
|
54
|
+
kedi notebook --python ~/.pyenv/versions/3.12.4/bin/python --port 8899
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Use `--host`, `--port`, `--cwd`, and `--no-open` to control local serving.
|
|
58
|
+
Loopback serving requires no credentials. A non-loopback `--host` is rejected
|
|
59
|
+
unless `--token` or `KEDI_NOTEBOOK_TOKEN` is set; the browser opened by the CLI
|
|
60
|
+
receives that token without leaving it in the visible URL.
|
|
61
|
+
|
|
62
|
+
At startup, the notebook server loads `.env` from `--cwd` without overriding
|
|
63
|
+
variables already present in its process. Set `KEDI_ADAPTER_MODEL` there or use
|
|
64
|
+
`> model:` in a Kedi cell before invoking a template. Calling `load_dotenv()`
|
|
65
|
+
inside a host Python cell affects only that cell worker, not the server process
|
|
66
|
+
that owns adapters; browser Python cannot read the host project's `.env`.
|
|
67
|
+
|
|
68
|
+
When a native Pydantic AI model requires a missing provider SDK, the notebook
|
|
69
|
+
installs only the provider group named by Pydantic AI, immediately before model
|
|
70
|
+
construction. OpenRouter, for example, requests the `openai` group. Unused
|
|
71
|
+
provider SDKs are not installed. Installation targets the notebook server's
|
|
72
|
+
Python environment, keeps its existing package versions, and requires package
|
|
73
|
+
index access on first use. It does not replay earlier statements in the cell.
|
|
74
|
+
Provider credentials are still required. A dependency conflict or failed
|
|
75
|
+
installation is shown as a cell error; specialized adapters retain their own
|
|
76
|
+
installation requirements.
|
|
77
|
+
|
|
78
|
+
The **Secret Manager** action in the top bar stores environment values outside
|
|
79
|
+
the notebook at `~/.kedi/notebook/secrets.json`. Stored values take precedence
|
|
80
|
+
over the server process and project `.env`, and the browser can list only their
|
|
81
|
+
names, never their values. Add a single value or import an arbitrary `.env`
|
|
82
|
+
path; relative paths resolve from `--cwd`. The store is atomically written with
|
|
83
|
+
user-only file permissions and is never included in browser storage. Saving,
|
|
84
|
+
importing, or deleting a value closes active sessions so the next cell starts a
|
|
85
|
+
runtime with the new environment.
|
|
86
|
+
|
|
87
|
+
## Runtime Model
|
|
88
|
+
|
|
89
|
+
Browser mode starts one persistent Pyodide worker as the page loads. Host mode
|
|
90
|
+
creates and reuses a project-specific virtual environment from the selected
|
|
91
|
+
Python executable, installs Kedi into it, and starts one persistent worker from
|
|
92
|
+
that environment. Kedi cells
|
|
93
|
+
execute incrementally against that session; completed cells remain editable
|
|
94
|
+
and rerunnable, and output is displayed under its source.
|
|
95
|
+
The bundled `filesystem` module works in both modes: browser mode accesses the
|
|
96
|
+
worker's temporary virtual filesystem, while host mode accesses the notebook
|
|
97
|
+
working directory on disk.
|
|
98
|
+
|
|
99
|
+
Every visible cell keeps its editor when focus moves elsewhere; inactive Kedi
|
|
100
|
+
cells retain syntax highlighting and can be edited or run directly. The cell
|
|
101
|
+
header can convert an individual cell between Kedi, Markdown, and Terminal.
|
|
102
|
+
Cells never collapse implicitly. Use the eye control to hide a cell explicitly;
|
|
103
|
+
the compact row remains available to show it again, and hidden state is saved
|
|
104
|
+
in `.kedinb` documents and local drafts.
|
|
105
|
+
|
|
106
|
+
Managed environments live under `~/.kedi/notebook/venvs` by default and have a
|
|
107
|
+
stable `kedi-notebook-py...` name derived from the base interpreter and working
|
|
108
|
+
directory. Set `KEDI_NOTEBOOK_ENV_HOME` to choose another root. The package
|
|
109
|
+
action beside a host runtime lists installed packages and installs newline-
|
|
110
|
+
separated requirement strings into the managed environment with streamed pip
|
|
111
|
+
output. The environment and installed packages persist across notebook server
|
|
112
|
+
runs while the directory remains available.
|
|
113
|
+
|
|
114
|
+
Cells beginning with `!` are terminal cells. Host commands run in the notebook
|
|
115
|
+
working directory, with `!python` and `!pip` bound to the managed environment.
|
|
116
|
+
Browser mode supports `!pip install`, `!uv add`, `!pip list`, `!echo`, and
|
|
117
|
+
`!pwd`. Terminal output streams into the active cell while it runs.
|
|
118
|
+
|
|
119
|
+
Use the square interrupt action while a cell is running. Interrupting replaces
|
|
120
|
+
the active Python worker, so the source remains rerunnable but live runtime
|
|
121
|
+
state is deliberately discarded. It also cancels that session's in-flight
|
|
122
|
+
Pydantic AI or LangChain model and async tool work, without cancelling another
|
|
123
|
+
session. Cancellation does not roll back completed effects or forcibly stop
|
|
124
|
+
arbitrary synchronous tool code running in a server thread.
|
|
125
|
+
|
|
126
|
+
Embedded-Python bridge operations and terminal execution have 120-second
|
|
127
|
+
timeouts; this is not a universal deadline for a complete model-backed cell.
|
|
128
|
+
Package installation has a 10-minute limit. Idle sessions expire after 30 minutes.
|
|
129
|
+
|
|
130
|
+
Notebook execution is non-transactional. Rerunning a cell is a new execution
|
|
131
|
+
attempt against current state; it does not roll back previous side effects.
|
|
132
|
+
Displayed cell numbers follow notebook order and remain unchanged across
|
|
133
|
+
reruns. Adding, moving, or deleting cells recomputes affected positions.
|
|
134
|
+
|
|
135
|
+
Notebook files are validated before they replace the current document. The UI
|
|
136
|
+
warns before discarding unsaved changes and limits files to 5 MB, notebooks to
|
|
137
|
+
1,000 cells, individual cell source to 1 MB, and retained inline output to
|
|
138
|
+
200,000 characters. Output beyond that boundary is marked as truncated.
|
|
139
|
+
|
|
140
|
+
Downloading asks which form to save. **Just notebook** writes cell sources,
|
|
141
|
+
types, visibility, and layout without outputs or environment values. **Save
|
|
142
|
+
progress** also writes retained cell outputs/results and the strict,
|
|
143
|
+
pickle-free `InteractiveSession` snapshot containing the current KediEnv. It
|
|
144
|
+
never includes Secret Manager or process environment values. Opening a progress
|
|
145
|
+
file restores the Kedi session on the next execution. If any live value cannot
|
|
146
|
+
be represented without changing its semantics, progress download is rejected
|
|
147
|
+
instead of producing a partial snapshot.
|
|
148
|
+
|
|
149
|
+
## Editing
|
|
150
|
+
|
|
151
|
+
Kedi cells provide Tree-sitter highlighting, Kedi and embedded-Python
|
|
152
|
+
completion, hover, references, rename, live diagnostics, signature help,
|
|
153
|
+
definition navigation, and runtime error markers. Rename is limited to the
|
|
154
|
+
active cell when its LSP context includes earlier executed cells, avoiding a
|
|
155
|
+
silent partial edit. Monaco falls back to a plain multiline editor if its external assets
|
|
156
|
+
cannot be loaded. Markdown cells safely render headings, paragraphs, lists,
|
|
157
|
+
fenced code, blockquotes, emphasis, links, and separators; raw HTML is not
|
|
158
|
+
executed.
|
|
159
|
+
|
|
160
|
+
Use `Shift+Enter` to run the active cell, `Ctrl/Cmd+S` to download the notebook,
|
|
161
|
+
`Ctrl/Cmd+Enter` to insert after the active cell, `Ctrl/Cmd+Shift+Enter` to
|
|
162
|
+
append, `Ctrl/Cmd+Backspace` to delete, and `Ctrl/Cmd+ArrowUp/ArrowDown` to
|
|
163
|
+
move a cell. Toolbar actions expose the same operations.
|
|
164
|
+
|
|
165
|
+
Monaco is currently pinned on jsDelivr. Pyodide, its standard library, Micropip,
|
|
166
|
+
and Pydantic are vendored in the notebook wheel, so browser Python can start
|
|
167
|
+
without downloading its core runtime from a CDN. Packages installed later with
|
|
168
|
+
Micropip may still need network access. Host execution remains usable through
|
|
169
|
+
the fallback editor if Monaco is unavailable.
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
# Kedi Notebook
|
|
2
|
+
|
|
3
|
+
Kedi Notebook is the local, incremental notebook interface for the Kedi
|
|
4
|
+
Programming Language. It provides editable Kedi, terminal, and Markdown cells,
|
|
5
|
+
with either browser-owned Pyodide or a selected host Python interpreter.
|
|
6
|
+
|
|
7
|
+

|
|
8
|
+
|
|
9
|
+
## Install and Run
|
|
10
|
+
|
|
11
|
+
The notebook is currently distributed from a Kedi source checkout. Install its
|
|
12
|
+
local dependencies and start it in one command from the Kedi repository root:
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
uv run --extra notebook kedi notebook
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
This uses the checked-out `notebook` submodule and Kedi source rather than a
|
|
19
|
+
published package. For notebook development, run from the Kedi repository root
|
|
20
|
+
with the notebook extra and development dependencies installed:
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
.venv/bin/python -m pytest notebook/tests
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
After syncing the extra, both entry points are equivalent:
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
kedi notebook
|
|
30
|
+
kedi-notebook
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
The default address is <http://127.0.0.1:8788/notebook/>. Add one or more host
|
|
34
|
+
Python installations explicitly when needed:
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
kedi-notebook --python /opt/homebrew/bin/python3.11
|
|
38
|
+
kedi notebook --python ~/.pyenv/versions/3.12.4/bin/python --port 8899
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Use `--host`, `--port`, `--cwd`, and `--no-open` to control local serving.
|
|
42
|
+
Loopback serving requires no credentials. A non-loopback `--host` is rejected
|
|
43
|
+
unless `--token` or `KEDI_NOTEBOOK_TOKEN` is set; the browser opened by the CLI
|
|
44
|
+
receives that token without leaving it in the visible URL.
|
|
45
|
+
|
|
46
|
+
At startup, the notebook server loads `.env` from `--cwd` without overriding
|
|
47
|
+
variables already present in its process. Set `KEDI_ADAPTER_MODEL` there or use
|
|
48
|
+
`> model:` in a Kedi cell before invoking a template. Calling `load_dotenv()`
|
|
49
|
+
inside a host Python cell affects only that cell worker, not the server process
|
|
50
|
+
that owns adapters; browser Python cannot read the host project's `.env`.
|
|
51
|
+
|
|
52
|
+
When a native Pydantic AI model requires a missing provider SDK, the notebook
|
|
53
|
+
installs only the provider group named by Pydantic AI, immediately before model
|
|
54
|
+
construction. OpenRouter, for example, requests the `openai` group. Unused
|
|
55
|
+
provider SDKs are not installed. Installation targets the notebook server's
|
|
56
|
+
Python environment, keeps its existing package versions, and requires package
|
|
57
|
+
index access on first use. It does not replay earlier statements in the cell.
|
|
58
|
+
Provider credentials are still required. A dependency conflict or failed
|
|
59
|
+
installation is shown as a cell error; specialized adapters retain their own
|
|
60
|
+
installation requirements.
|
|
61
|
+
|
|
62
|
+
The **Secret Manager** action in the top bar stores environment values outside
|
|
63
|
+
the notebook at `~/.kedi/notebook/secrets.json`. Stored values take precedence
|
|
64
|
+
over the server process and project `.env`, and the browser can list only their
|
|
65
|
+
names, never their values. Add a single value or import an arbitrary `.env`
|
|
66
|
+
path; relative paths resolve from `--cwd`. The store is atomically written with
|
|
67
|
+
user-only file permissions and is never included in browser storage. Saving,
|
|
68
|
+
importing, or deleting a value closes active sessions so the next cell starts a
|
|
69
|
+
runtime with the new environment.
|
|
70
|
+
|
|
71
|
+
## Runtime Model
|
|
72
|
+
|
|
73
|
+
Browser mode starts one persistent Pyodide worker as the page loads. Host mode
|
|
74
|
+
creates and reuses a project-specific virtual environment from the selected
|
|
75
|
+
Python executable, installs Kedi into it, and starts one persistent worker from
|
|
76
|
+
that environment. Kedi cells
|
|
77
|
+
execute incrementally against that session; completed cells remain editable
|
|
78
|
+
and rerunnable, and output is displayed under its source.
|
|
79
|
+
The bundled `filesystem` module works in both modes: browser mode accesses the
|
|
80
|
+
worker's temporary virtual filesystem, while host mode accesses the notebook
|
|
81
|
+
working directory on disk.
|
|
82
|
+
|
|
83
|
+
Every visible cell keeps its editor when focus moves elsewhere; inactive Kedi
|
|
84
|
+
cells retain syntax highlighting and can be edited or run directly. The cell
|
|
85
|
+
header can convert an individual cell between Kedi, Markdown, and Terminal.
|
|
86
|
+
Cells never collapse implicitly. Use the eye control to hide a cell explicitly;
|
|
87
|
+
the compact row remains available to show it again, and hidden state is saved
|
|
88
|
+
in `.kedinb` documents and local drafts.
|
|
89
|
+
|
|
90
|
+
Managed environments live under `~/.kedi/notebook/venvs` by default and have a
|
|
91
|
+
stable `kedi-notebook-py...` name derived from the base interpreter and working
|
|
92
|
+
directory. Set `KEDI_NOTEBOOK_ENV_HOME` to choose another root. The package
|
|
93
|
+
action beside a host runtime lists installed packages and installs newline-
|
|
94
|
+
separated requirement strings into the managed environment with streamed pip
|
|
95
|
+
output. The environment and installed packages persist across notebook server
|
|
96
|
+
runs while the directory remains available.
|
|
97
|
+
|
|
98
|
+
Cells beginning with `!` are terminal cells. Host commands run in the notebook
|
|
99
|
+
working directory, with `!python` and `!pip` bound to the managed environment.
|
|
100
|
+
Browser mode supports `!pip install`, `!uv add`, `!pip list`, `!echo`, and
|
|
101
|
+
`!pwd`. Terminal output streams into the active cell while it runs.
|
|
102
|
+
|
|
103
|
+
Use the square interrupt action while a cell is running. Interrupting replaces
|
|
104
|
+
the active Python worker, so the source remains rerunnable but live runtime
|
|
105
|
+
state is deliberately discarded. It also cancels that session's in-flight
|
|
106
|
+
Pydantic AI or LangChain model and async tool work, without cancelling another
|
|
107
|
+
session. Cancellation does not roll back completed effects or forcibly stop
|
|
108
|
+
arbitrary synchronous tool code running in a server thread.
|
|
109
|
+
|
|
110
|
+
Embedded-Python bridge operations and terminal execution have 120-second
|
|
111
|
+
timeouts; this is not a universal deadline for a complete model-backed cell.
|
|
112
|
+
Package installation has a 10-minute limit. Idle sessions expire after 30 minutes.
|
|
113
|
+
|
|
114
|
+
Notebook execution is non-transactional. Rerunning a cell is a new execution
|
|
115
|
+
attempt against current state; it does not roll back previous side effects.
|
|
116
|
+
Displayed cell numbers follow notebook order and remain unchanged across
|
|
117
|
+
reruns. Adding, moving, or deleting cells recomputes affected positions.
|
|
118
|
+
|
|
119
|
+
Notebook files are validated before they replace the current document. The UI
|
|
120
|
+
warns before discarding unsaved changes and limits files to 5 MB, notebooks to
|
|
121
|
+
1,000 cells, individual cell source to 1 MB, and retained inline output to
|
|
122
|
+
200,000 characters. Output beyond that boundary is marked as truncated.
|
|
123
|
+
|
|
124
|
+
Downloading asks which form to save. **Just notebook** writes cell sources,
|
|
125
|
+
types, visibility, and layout without outputs or environment values. **Save
|
|
126
|
+
progress** also writes retained cell outputs/results and the strict,
|
|
127
|
+
pickle-free `InteractiveSession` snapshot containing the current KediEnv. It
|
|
128
|
+
never includes Secret Manager or process environment values. Opening a progress
|
|
129
|
+
file restores the Kedi session on the next execution. If any live value cannot
|
|
130
|
+
be represented without changing its semantics, progress download is rejected
|
|
131
|
+
instead of producing a partial snapshot.
|
|
132
|
+
|
|
133
|
+
## Editing
|
|
134
|
+
|
|
135
|
+
Kedi cells provide Tree-sitter highlighting, Kedi and embedded-Python
|
|
136
|
+
completion, hover, references, rename, live diagnostics, signature help,
|
|
137
|
+
definition navigation, and runtime error markers. Rename is limited to the
|
|
138
|
+
active cell when its LSP context includes earlier executed cells, avoiding a
|
|
139
|
+
silent partial edit. Monaco falls back to a plain multiline editor if its external assets
|
|
140
|
+
cannot be loaded. Markdown cells safely render headings, paragraphs, lists,
|
|
141
|
+
fenced code, blockquotes, emphasis, links, and separators; raw HTML is not
|
|
142
|
+
executed.
|
|
143
|
+
|
|
144
|
+
Use `Shift+Enter` to run the active cell, `Ctrl/Cmd+S` to download the notebook,
|
|
145
|
+
`Ctrl/Cmd+Enter` to insert after the active cell, `Ctrl/Cmd+Shift+Enter` to
|
|
146
|
+
append, `Ctrl/Cmd+Backspace` to delete, and `Ctrl/Cmd+ArrowUp/ArrowDown` to
|
|
147
|
+
move a cell. Toolbar actions expose the same operations.
|
|
148
|
+
|
|
149
|
+
Monaco is currently pinned on jsDelivr. Pyodide, its standard library, Micropip,
|
|
150
|
+
and Pydantic are vendored in the notebook wheel, so browser Python can start
|
|
151
|
+
without downloading its core runtime from a CDN. Packages installed later with
|
|
152
|
+
Micropip may still need network access. Host execution remains usable through
|
|
153
|
+
the fallback editor if Monaco is unavailable.
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling==1.33.0"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "kedi-notebook"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Local interactive notebook for the Kedi Programming Language"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.11"
|
|
11
|
+
license = "MIT"
|
|
12
|
+
dependencies = [
|
|
13
|
+
"basedpyright>=1.40.2,<1.41",
|
|
14
|
+
"fastapi>=0.162.0,<0.163",
|
|
15
|
+
"kedi>=0.1.2,<0.2",
|
|
16
|
+
"python-dotenv>=1.2.4,<2",
|
|
17
|
+
"uvicorn>=0.54,<0.55",
|
|
18
|
+
]
|
|
19
|
+
|
|
20
|
+
[project.urls]
|
|
21
|
+
Source = "https://github.com/kedi-lang/notebook"
|
|
22
|
+
Tracker = "https://github.com/kedi-lang/notebook/issues"
|
|
23
|
+
|
|
24
|
+
[project.scripts]
|
|
25
|
+
kedi-notebook = "kedi_notebook.server:main"
|
|
26
|
+
|
|
27
|
+
[dependency-groups]
|
|
28
|
+
dev = [
|
|
29
|
+
"httpx==0.28.1",
|
|
30
|
+
"playwright==1.64.1",
|
|
31
|
+
"pytest==9.2.0",
|
|
32
|
+
"pytest-cov==7.1.0",
|
|
33
|
+
"ruff==0.17.4",
|
|
34
|
+
]
|
|
35
|
+
|
|
36
|
+
[tool.hatch.build]
|
|
37
|
+
include = [
|
|
38
|
+
"src/kedi_notebook/**/*.py",
|
|
39
|
+
"src/kedi_notebook/static/**",
|
|
40
|
+
]
|
|
41
|
+
|
|
42
|
+
[tool.hatch.build.targets.wheel]
|
|
43
|
+
packages = ["src/kedi_notebook"]
|
|
44
|
+
|
|
45
|
+
[tool.uv.sources]
|
|
46
|
+
kedi = { path = "..", editable = true }
|
|
47
|
+
|
|
48
|
+
[tool.ruff]
|
|
49
|
+
line-length = 100
|
|
50
|
+
target-version = "py311"
|
|
51
|
+
|
|
52
|
+
[tool.ruff.lint]
|
|
53
|
+
select = ["E", "F", "I", "UP"]
|
|
54
|
+
|
|
55
|
+
[tool.pytest.ini_options]
|
|
56
|
+
testpaths = ["tests"]
|
|
57
|
+
|
|
58
|
+
[tool.coverage.run]
|
|
59
|
+
branch = true
|
|
60
|
+
source = ["kedi_notebook"]
|
|
61
|
+
|
|
62
|
+
[tool.coverage.report]
|
|
63
|
+
show_missing = true
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"""Local interactive notebook support for Kedi."""
|
|
2
|
+
|
|
3
|
+
from .runtime import (
|
|
4
|
+
HostPython,
|
|
5
|
+
NotebookSessionManager,
|
|
6
|
+
discover_host_pythons,
|
|
7
|
+
)
|
|
8
|
+
from .secrets import NotebookSecretStore
|
|
9
|
+
|
|
10
|
+
__all__ = [
|
|
11
|
+
"HostPython",
|
|
12
|
+
"NotebookSecretStore",
|
|
13
|
+
"NotebookSessionManager",
|
|
14
|
+
"discover_host_pythons",
|
|
15
|
+
]
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import time
|
|
4
|
+
from collections import deque
|
|
5
|
+
from collections.abc import Iterator, Mapping
|
|
6
|
+
from threading import Condition, Lock
|
|
7
|
+
from typing import Any
|
|
8
|
+
from uuid import uuid4
|
|
9
|
+
|
|
10
|
+
_MAX_OUTPUT_CHARS = 200_000
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class BridgeCancelled(RuntimeError):
|
|
14
|
+
pass
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class BridgeRun:
|
|
18
|
+
def __init__(self, run_id: str) -> None:
|
|
19
|
+
self.run_id = run_id
|
|
20
|
+
self.condition = Condition()
|
|
21
|
+
self.pending: deque[dict[str, Any]] = deque()
|
|
22
|
+
self.waiting: set[str] = set()
|
|
23
|
+
self.responses: dict[str, dict[str, Any]] = {}
|
|
24
|
+
self.output_events: dict[str, deque[dict[str, str]]] = {}
|
|
25
|
+
self.output_sizes: dict[str, int] = {}
|
|
26
|
+
self.done = False
|
|
27
|
+
self.cancelled = False
|
|
28
|
+
self.updated_at = time.monotonic()
|
|
29
|
+
|
|
30
|
+
def request(self, payload: Mapping[str, Any], *, timeout: float) -> Mapping[str, Any]:
|
|
31
|
+
response: Mapping[str, Any] | None = None
|
|
32
|
+
for event in self.request_events(payload, timeout=timeout):
|
|
33
|
+
if event["type"] == "response":
|
|
34
|
+
response = event["response"]
|
|
35
|
+
if response is None:
|
|
36
|
+
raise RuntimeError("Playground bridge request returned no response")
|
|
37
|
+
return response
|
|
38
|
+
|
|
39
|
+
def request_events(
|
|
40
|
+
self,
|
|
41
|
+
payload: Mapping[str, Any],
|
|
42
|
+
*,
|
|
43
|
+
timeout: float,
|
|
44
|
+
) -> Iterator[dict[str, Any]]:
|
|
45
|
+
request_id = uuid4().hex
|
|
46
|
+
request = {"id": request_id, **dict(payload)}
|
|
47
|
+
with self.condition:
|
|
48
|
+
self._assert_open()
|
|
49
|
+
self.pending.append(request)
|
|
50
|
+
self.waiting.add(request_id)
|
|
51
|
+
self.output_events[request_id] = deque()
|
|
52
|
+
self.output_sizes[request_id] = 0
|
|
53
|
+
self.updated_at = time.monotonic()
|
|
54
|
+
self.condition.notify_all()
|
|
55
|
+
deadline = time.monotonic() + timeout
|
|
56
|
+
completed = False
|
|
57
|
+
try:
|
|
58
|
+
while True:
|
|
59
|
+
with self.condition:
|
|
60
|
+
while not self.output_events[request_id] and request_id not in self.responses:
|
|
61
|
+
self._assert_open()
|
|
62
|
+
remaining = deadline - time.monotonic()
|
|
63
|
+
if remaining <= 0:
|
|
64
|
+
raise TimeoutError("Timed out waiting for the browser model")
|
|
65
|
+
self.condition.wait(timeout=min(remaining, 1))
|
|
66
|
+
if self.output_events[request_id]:
|
|
67
|
+
event: dict[str, Any] = self.output_events[request_id].popleft()
|
|
68
|
+
else:
|
|
69
|
+
event = {
|
|
70
|
+
"type": "response",
|
|
71
|
+
"response": self.responses.pop(request_id),
|
|
72
|
+
}
|
|
73
|
+
completed = True
|
|
74
|
+
yield event
|
|
75
|
+
if completed:
|
|
76
|
+
return
|
|
77
|
+
finally:
|
|
78
|
+
with self.condition:
|
|
79
|
+
self.waiting.discard(request_id)
|
|
80
|
+
self.responses.pop(request_id, None)
|
|
81
|
+
self.output_events.pop(request_id, None)
|
|
82
|
+
self.output_sizes.pop(request_id, None)
|
|
83
|
+
|
|
84
|
+
def next_request(self, *, timeout: float) -> dict[str, Any] | None:
|
|
85
|
+
with self.condition:
|
|
86
|
+
deadline = time.monotonic() + timeout
|
|
87
|
+
while not self.pending and not self.done and not self.cancelled:
|
|
88
|
+
remaining = deadline - time.monotonic()
|
|
89
|
+
if remaining <= 0:
|
|
90
|
+
break
|
|
91
|
+
self.condition.wait(timeout=min(remaining, 1))
|
|
92
|
+
self.updated_at = time.monotonic()
|
|
93
|
+
return self.pending.popleft() if self.pending else None
|
|
94
|
+
|
|
95
|
+
def submit_response(self, request_id: str, response: Mapping[str, Any]) -> None:
|
|
96
|
+
with self.condition:
|
|
97
|
+
if request_id not in self.waiting:
|
|
98
|
+
raise ValueError("Response does not belong to an active bridge request")
|
|
99
|
+
if request_id in self.responses:
|
|
100
|
+
raise ValueError("Duplicate bridge response")
|
|
101
|
+
self.responses[request_id] = dict(response)
|
|
102
|
+
self.updated_at = time.monotonic()
|
|
103
|
+
self.condition.notify_all()
|
|
104
|
+
|
|
105
|
+
def submit_output(self, request_id: str, *, stream: str, text: str) -> None:
|
|
106
|
+
with self.condition:
|
|
107
|
+
if request_id not in self.waiting:
|
|
108
|
+
raise ValueError("Output does not belong to an active bridge request")
|
|
109
|
+
if stream not in {"stdout", "stderr"}:
|
|
110
|
+
raise ValueError("Bridge output stream must be stdout or stderr")
|
|
111
|
+
if not text:
|
|
112
|
+
return
|
|
113
|
+
remaining = _MAX_OUTPUT_CHARS - self.output_sizes[request_id]
|
|
114
|
+
if remaining <= 0:
|
|
115
|
+
return
|
|
116
|
+
text = text[:remaining]
|
|
117
|
+
self.output_sizes[request_id] += len(text)
|
|
118
|
+
self.output_events[request_id].append(
|
|
119
|
+
{"type": "output", "stream": stream, "text": text}
|
|
120
|
+
)
|
|
121
|
+
self.updated_at = time.monotonic()
|
|
122
|
+
self.condition.notify_all()
|
|
123
|
+
|
|
124
|
+
def finish(self) -> None:
|
|
125
|
+
with self.condition:
|
|
126
|
+
self.done = True
|
|
127
|
+
self.updated_at = time.monotonic()
|
|
128
|
+
self.condition.notify_all()
|
|
129
|
+
|
|
130
|
+
def cancel(self) -> None:
|
|
131
|
+
with self.condition:
|
|
132
|
+
self.cancelled = True
|
|
133
|
+
self.updated_at = time.monotonic()
|
|
134
|
+
self.condition.notify_all()
|
|
135
|
+
|
|
136
|
+
def _assert_open(self) -> None:
|
|
137
|
+
if self.cancelled:
|
|
138
|
+
raise BridgeCancelled("Playground run was cancelled")
|
|
139
|
+
if self.done:
|
|
140
|
+
raise RuntimeError("Playground bridge run is already complete")
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
class BridgeManager:
|
|
144
|
+
def __init__(self) -> None:
|
|
145
|
+
self._runs: dict[str, BridgeRun] = {}
|
|
146
|
+
self._lock = Lock()
|
|
147
|
+
|
|
148
|
+
def get_or_create(self, run_id: str) -> BridgeRun:
|
|
149
|
+
with self._lock:
|
|
150
|
+
run = self._runs.get(run_id)
|
|
151
|
+
if run is None:
|
|
152
|
+
run = BridgeRun(run_id)
|
|
153
|
+
self._runs[run_id] = run
|
|
154
|
+
return run
|
|
155
|
+
|
|
156
|
+
def cancel(self, run_id: str) -> None:
|
|
157
|
+
self.get_or_create(run_id).cancel()
|
|
158
|
+
|
|
159
|
+
def cleanup(self, *, max_age: float = 600) -> None:
|
|
160
|
+
cutoff = time.monotonic() - max_age
|
|
161
|
+
with self._lock:
|
|
162
|
+
stale = [
|
|
163
|
+
run_id
|
|
164
|
+
for run_id, run in self._runs.items()
|
|
165
|
+
if run.updated_at < cutoff and (run.done or run.cancelled)
|
|
166
|
+
]
|
|
167
|
+
for run_id in stale:
|
|
168
|
+
del self._runs[run_id]
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
__all__ = ["BridgeCancelled", "BridgeManager", "BridgeRun"]
|