jupyterlab-codex-sidebar 0.1.2 → 0.1.3
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.
- package/AGENTS.md +5 -0
- package/README.md +27 -15
- package/{run_jupyterlab_codex.sh → install_dev.sh} +25 -19
- package/jupyterlab_codex/cli_defaults.py +1 -4
- package/jupyterlab_codex/handlers.py +133 -8
- package/jupyterlab_codex/labextension/package.json +4 -2
- package/jupyterlab_codex/labextension/static/00b26ac825e209505639.woff2 +0 -0
- package/jupyterlab_codex/labextension/static/036d4e95149b69ff9bcc.woff2 +0 -0
- package/jupyterlab_codex/labextension/static/07d8e303ce4fc12b4bb5.ttf +0 -0
- package/jupyterlab_codex/labextension/static/08ce98e51b04d58945a3.ttf +0 -0
- package/jupyterlab_codex/labextension/static/0cdd387c9590a1a9f979.woff2 +0 -0
- package/jupyterlab_codex/labextension/static/0d85ae7cc30f23790a7f.ttf +0 -0
- package/jupyterlab_codex/labextension/static/0f60d1b897938ec918c8.woff2 +0 -0
- package/jupyterlab_codex/labextension/static/11e4dc8a6471ff6d6ee5.woff +0 -0
- package/jupyterlab_codex/labextension/static/124.ac0734653995d3fe00f2.js +1 -0
- package/jupyterlab_codex/labextension/static/138ac28d1663b3037e9c.ttf +0 -0
- package/jupyterlab_codex/labextension/static/1ae6bd7475590e97e7f1.woff +0 -0
- package/jupyterlab_codex/labextension/static/1c67f068fea8bb09bf09.ttf +0 -0
- package/jupyterlab_codex/labextension/static/1e6f9579e90e2cac37f8.ttf +0 -0
- package/jupyterlab_codex/labextension/static/1ece03f79f95277d57dc.ttf +0 -0
- package/jupyterlab_codex/labextension/static/2014c523c3210bcc1666.woff +0 -0
- package/jupyterlab_codex/labextension/static/30da91e84c893f875e25.woff +0 -0
- package/jupyterlab_codex/labextension/static/3398dd02302557a793f2.woff +0 -0
- package/jupyterlab_codex/labextension/static/3931dd81faed86ba021b.ttf +0 -0
- package/jupyterlab_codex/labextension/static/489.ef138a7e916c5b465543.js +1 -0
- package/jupyterlab_codex/labextension/static/500e04d54f0d51666332.ttf +0 -0
- package/jupyterlab_codex/labextension/static/504.335f3447c84ba3d74517.js +2 -0
- package/jupyterlab_codex/labextension/static/51814d270d06ff0255db.woff2 +0 -0
- package/jupyterlab_codex/labextension/static/5d53e70ad607c2352162.woff2 +0 -0
- package/jupyterlab_codex/labextension/static/5e28753be717dac97f55.woff +0 -0
- package/jupyterlab_codex/labextension/static/68534840bcfdd2bffb6f.ttf +0 -0
- package/jupyterlab_codex/labextension/static/68e8c73ef42afd3ccec5.woff2 +0 -0
- package/jupyterlab_codex/labextension/static/6ab6b62e9b62dae2c00d.woff +0 -0
- package/jupyterlab_codex/labextension/static/6b47c40166b6dbe21a5d.woff2 +0 -0
- package/jupyterlab_codex/labextension/static/70ee1f64a20f2048c219.ttf +0 -0
- package/jupyterlab_codex/labextension/static/71d517d67827787cfabd.woff2 +0 -0
- package/jupyterlab_codex/labextension/static/73d591271b1604960cb1.woff2 +0 -0
- package/jupyterlab_codex/labextension/static/74444efd593c005e3f45.woff2 +0 -0
- package/jupyterlab_codex/labextension/static/7af58c5ec8f132a2ddde.woff2 +0 -0
- package/jupyterlab_codex/labextension/static/850c0af5c2238497feba.woff +0 -0
- package/jupyterlab_codex/labextension/static/8a8d244581371912b8f3.woff +0 -0
- package/jupyterlab_codex/labextension/static/9163df9c7122432e6495.ttf +0 -0
- package/jupyterlab_codex/labextension/static/91ee67500cc0129aa0ac.woff +0 -0
- package/jupyterlab_codex/labextension/static/95b6d2f1a50173bfedb8.ttf +0 -0
- package/jupyterlab_codex/labextension/static/972.d43137b7438a053eeb72.js +1 -0
- package/jupyterlab_codex/labextension/static/97479ca6cce906abc961.woff2 +0 -0
- package/jupyterlab_codex/labextension/static/99cd42a3c072d918f2f4.woff2 +0 -0
- package/jupyterlab_codex/labextension/static/99f9c6750b489c9462bf.woff +0 -0
- package/jupyterlab_codex/labextension/static/9be7ceb88004ab8ad124.woff +0 -0
- package/jupyterlab_codex/labextension/static/a4af7d414440a1c17908.woff2 +0 -0
- package/jupyterlab_codex/labextension/static/a6b2099fb555c60e3a0d.ttf +0 -0
- package/jupyterlab_codex/labextension/static/a6f7ec0d846ac7ad975a.woff +0 -0
- package/jupyterlab_codex/labextension/static/c2342cd8b869e01752a9.woff2 +0 -0
- package/jupyterlab_codex/labextension/static/c6368d87e8a1a3a5d337.woff +0 -0
- package/jupyterlab_codex/labextension/static/c647367d1dd4e1624687.ttf +0 -0
- package/jupyterlab_codex/labextension/static/c76c5d696297d51b9cb1.woff +0 -0
- package/jupyterlab_codex/labextension/static/c943cc986384f59e86be.woff +0 -0
- package/jupyterlab_codex/labextension/static/d0332f52868370fd83ae.ttf +0 -0
- package/jupyterlab_codex/labextension/static/d04c54219f9eaec6d4d4.woff2 +0 -0
- package/jupyterlab_codex/labextension/static/d96cdf2b3bdd4d64a8fd.woff +0 -0
- package/jupyterlab_codex/labextension/static/dc47344dbb6cb5b655c8.woff2 +0 -0
- package/jupyterlab_codex/labextension/static/de7701e42cf1f4cf0b76.woff2 +0 -0
- package/jupyterlab_codex/labextension/static/e14fed02b1aba7ce9f5a.woff +0 -0
- package/jupyterlab_codex/labextension/static/e99ae51144bf1232efcc.woff2 +0 -0
- package/jupyterlab_codex/labextension/static/ece03cfd83e22c212cde.woff +0 -0
- package/jupyterlab_codex/labextension/static/ed0b74372feefcbb9c06.ttf +0 -0
- package/jupyterlab_codex/labextension/static/f01f3e87d9c6a61c0c08.ttf +0 -0
- package/jupyterlab_codex/labextension/static/f1d6ef86f3b11a528bd5.woff +0 -0
- package/jupyterlab_codex/labextension/static/f36ea897e19f4a2e571d.ttf +0 -0
- package/jupyterlab_codex/labextension/static/f9377ab0271cda59af24.ttf +0 -0
- package/jupyterlab_codex/labextension/static/remoteEntry.b2fdc03a1c4582e79156.js +1 -0
- package/jupyterlab_codex/labextension/static/third-party-licenses.json +12 -0
- package/jupyterlab_codex/runner.py +169 -0
- package/jupyterlab_codex/sessions.py +74 -2
- package/lib/panel.d.ts +4 -0
- package/lib/panel.js +999 -231
- package/lib/panel.js.map +1 -1
- package/package.json +3 -1
- package/pyproject.toml +1 -1
- package/run_dev.sh +16 -0
- package/src/panel.tsx +1358 -336
- package/style/index.css +249 -29
- package/tsconfig.tsbuildinfo +1 -1
- package/jupyterlab_codex/labextension/static/375.d3c009ef437170530252.js +0 -2
- package/jupyterlab_codex/labextension/static/972.e892445dcfe89d335cf4.js +0 -1
- package/jupyterlab_codex/labextension/static/remoteEntry.00c43c61b0b2e7773f34.js +0 -1
- /package/jupyterlab_codex/labextension/static/{375.d3c009ef437170530252.js.LICENSE.txt → 504.335f3447c84ba3d74517.js.LICENSE.txt} +0 -0
package/AGENTS.md
CHANGED
|
@@ -41,3 +41,8 @@
|
|
|
41
41
|
- 사용자 프롬프트/출력 내용을 원문 저장하거나 네트워크로 전송하는 로직 변경
|
|
42
42
|
- localStorage/IndexedDB/세션 파일에 사용자 컨텍스트를 저장하는 로직 추가
|
|
43
43
|
- 외부 CLI/도구 실행 경로를 하드코딩하거나, 권한 범위를 확장하는 동작 변경
|
|
44
|
+
|
|
45
|
+
## UI Language Policy
|
|
46
|
+
|
|
47
|
+
- User-facing UI labels, buttons, notices, dialogs, and system messages must always be displayed in English.
|
|
48
|
+
- Do not introduce Korean (or other non-English) UI strings in code changes.
|
package/README.md
CHANGED
|
@@ -64,26 +64,25 @@ The backend runs `codex` as a local subprocess per request, streams JSONL events
|
|
|
64
64
|
## Install / Run
|
|
65
65
|
|
|
66
66
|
### Quick start (recommended for local development)
|
|
67
|
-
|
|
67
|
+
There are two development workflows:
|
|
68
|
+
|
|
69
|
+
- `install_dev.sh` : install/link only (does not start JupyterLab)
|
|
70
|
+
- `run_dev.sh` : install first, then start JupyterLab
|
|
71
|
+
|
|
72
|
+
Install only:
|
|
68
73
|
|
|
69
74
|
```bash
|
|
70
|
-
bash
|
|
75
|
+
bash install_dev.sh
|
|
71
76
|
```
|
|
72
77
|
|
|
73
|
-
|
|
74
|
-
1. install JS dependencies (`jlpm install`)
|
|
75
|
-
2. build frontend (`jlpm build`)
|
|
76
|
-
3. install Python package editable (`python -m pip install -e .`)
|
|
77
|
-
4. install server config snippet and enable extension
|
|
78
|
-
5. link labextension in `share/jupyter/labextensions/`
|
|
79
|
-
6. launch `jupyter lab`
|
|
80
|
-
|
|
81
|
-
You can pass JupyterLab options directly:
|
|
78
|
+
Install + run:
|
|
82
79
|
|
|
83
80
|
```bash
|
|
84
|
-
bash
|
|
81
|
+
bash run_dev.sh --ServerApp.port=8888
|
|
85
82
|
```
|
|
86
83
|
|
|
84
|
+
`run_dev.sh` internally runs `install_dev.sh` first.
|
|
85
|
+
|
|
87
86
|
### Manual local install
|
|
88
87
|
1. Build frontend
|
|
89
88
|
|
|
@@ -252,12 +251,25 @@ JupyterLab 4 우측 사이드바에서 Codex CLI(`codex exec --json`)를 채팅
|
|
|
252
251
|
|
|
253
252
|
## 설치/실행
|
|
254
253
|
### 빠른 실행(권장)
|
|
255
|
-
|
|
254
|
+
개발용 스크립트가 분리되어 있습니다.
|
|
255
|
+
|
|
256
|
+
- `install_dev.sh` : 설치/링크만 수행 (`jupyter lab` 실행 없음)
|
|
257
|
+
- `run_dev.sh` : 설치 후 JupyterLab 실행
|
|
258
|
+
|
|
259
|
+
설치만:
|
|
256
260
|
|
|
257
261
|
```bash
|
|
258
|
-
bash
|
|
262
|
+
bash install_dev.sh
|
|
259
263
|
```
|
|
260
264
|
|
|
265
|
+
설치 + 실행:
|
|
266
|
+
|
|
267
|
+
```bash
|
|
268
|
+
bash run_dev.sh --ServerApp.port=8888
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
`run_dev.sh`는 내부적으로 `install_dev.sh`를 먼저 실행한 뒤 JupyterLab을 시작합니다.
|
|
272
|
+
|
|
261
273
|
스크립트가 하는 일(요약):
|
|
262
274
|
- JS 의존성 설치(`jlpm install`) 및 빌드(`jlpm build`)
|
|
263
275
|
- 파이썬 패키지 editable 설치(`python -m pip install -e .`)
|
|
@@ -268,7 +280,7 @@ bash run_jupyterlab_codex.sh
|
|
|
268
280
|
추가로 JupyterLab 옵션을 넘기고 싶다면, 스크립트 뒤에 그대로 붙이면 됩니다.
|
|
269
281
|
|
|
270
282
|
```bash
|
|
271
|
-
bash
|
|
283
|
+
bash run_dev.sh --ServerApp.port=8888
|
|
272
284
|
```
|
|
273
285
|
|
|
274
286
|
### 수동 설치(개발/로컬)
|
|
@@ -9,10 +9,22 @@ set -euo pipefail
|
|
|
9
9
|
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
10
10
|
cd "$ROOT_DIR"
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
PYTHON_BIN="${PYTHON_BIN:-}"
|
|
13
|
+
if [ -z "$PYTHON_BIN" ]; then
|
|
14
|
+
if command -v python >/dev/null 2>&1; then
|
|
15
|
+
PYTHON_BIN="python"
|
|
16
|
+
elif command -v python3 >/dev/null 2>&1; then
|
|
17
|
+
PYTHON_BIN="python3"
|
|
18
|
+
else
|
|
19
|
+
echo "ERROR: python not found on PATH (also tried python3)."
|
|
20
|
+
exit 1
|
|
21
|
+
fi
|
|
22
|
+
fi
|
|
23
|
+
|
|
24
|
+
echo "[1/5] Checking Python/Jupyter prerequisites"
|
|
13
25
|
|
|
14
|
-
if ! command -v
|
|
15
|
-
echo "ERROR:
|
|
26
|
+
if ! command -v "$PYTHON_BIN" >/dev/null 2>&1; then
|
|
27
|
+
echo "ERROR: $PYTHON_BIN not found on PATH"
|
|
16
28
|
exit 1
|
|
17
29
|
fi
|
|
18
30
|
|
|
@@ -21,9 +33,9 @@ if ! command -v node >/dev/null 2>&1; then
|
|
|
21
33
|
exit 1
|
|
22
34
|
fi
|
|
23
35
|
|
|
24
|
-
if !
|
|
36
|
+
if ! "$PYTHON_BIN" -c 'import jupyterlab' >/dev/null 2>&1; then
|
|
25
37
|
echo "JupyterLab not found in this Python environment. Installing (jupyterlab>=4,<5; jupyter_server>=2,<3)..."
|
|
26
|
-
|
|
38
|
+
"$PYTHON_BIN" -m pip install -q "jupyterlab>=4,<5" "jupyter_server>=2,<3"
|
|
27
39
|
fi
|
|
28
40
|
|
|
29
41
|
JLPM="jlpm"
|
|
@@ -33,21 +45,22 @@ fi
|
|
|
33
45
|
|
|
34
46
|
if ! command -v jupyter >/dev/null 2>&1; then
|
|
35
47
|
echo "ERROR: jupyter command not found (expected after installing jupyterlab)."
|
|
36
|
-
echo "Try:
|
|
48
|
+
echo "Try: $PYTHON_BIN -m pip install \"jupyterlab>=4,<5\""
|
|
37
49
|
exit 1
|
|
38
50
|
fi
|
|
39
51
|
|
|
40
|
-
echo "[
|
|
52
|
+
echo "[2/5] Installing JS dependencies"
|
|
41
53
|
$JLPM install
|
|
42
54
|
|
|
43
|
-
echo "[
|
|
55
|
+
echo "[3/5] Building JupyterLab extension"
|
|
44
56
|
$JLPM run build
|
|
45
57
|
|
|
46
|
-
echo "[
|
|
47
|
-
|
|
58
|
+
echo "[4/5] Installing Python package (editable)"
|
|
59
|
+
"$PYTHON_BIN" -m pip install -e "$ROOT_DIR" --no-deps
|
|
60
|
+
|
|
61
|
+
echo "[5/5] Enabling server extension and linking labextension"
|
|
48
62
|
|
|
49
|
-
|
|
50
|
-
PREFIX="${CONDA_PREFIX:-$(python -c 'import sys; print(sys.prefix)')}"
|
|
63
|
+
PREFIX="${CONDA_PREFIX:-$("$PYTHON_BIN" -c 'import sys; print(sys.prefix)')}"
|
|
51
64
|
|
|
52
65
|
JUPYTER_CFG_DIR="$PREFIX/etc/jupyter/jupyter_server_config.d"
|
|
53
66
|
mkdir -p "$JUPYTER_CFG_DIR"
|
|
@@ -58,12 +71,5 @@ jupyter server extension list | sed -n '1,120p' || true
|
|
|
58
71
|
|
|
59
72
|
LABEXT_DIR="$PREFIX/share/jupyter/labextensions"
|
|
60
73
|
mkdir -p "$LABEXT_DIR"
|
|
61
|
-
|
|
62
|
-
echo "[5/6] Linking labextension into $LABEXT_DIR"
|
|
63
74
|
ln -sfn "$ROOT_DIR/jupyterlab_codex/labextension" "$LABEXT_DIR/jupyterlab-codex-sidebar"
|
|
64
|
-
|
|
65
|
-
echo "[6/6] Current labextension status"
|
|
66
75
|
jupyter labextension list
|
|
67
|
-
|
|
68
|
-
echo "Starting JupyterLab..."
|
|
69
|
-
exec jupyter lab --no-browser --ServerApp.open_browser=False "$@"
|
|
@@ -28,9 +28,7 @@ def load_cli_defaults_for_ui() -> Dict[str, Any]:
|
|
|
28
28
|
effective_model = env_model or (config_model.strip() if config_model else None)
|
|
29
29
|
effective_reasoning = (config_reasoning or "").strip().lower() or None
|
|
30
30
|
|
|
31
|
-
|
|
32
|
-
if effective_reasoning not in {"low", "medium", "high", "xhigh"}:
|
|
33
|
-
effective_reasoning = None
|
|
31
|
+
effective_reasoning = effective_reasoning or None
|
|
34
32
|
|
|
35
33
|
return {
|
|
36
34
|
"model": effective_model,
|
|
@@ -107,4 +105,3 @@ def _parse_toml_scalar(value: str) -> Any:
|
|
|
107
105
|
# Keep this simple; config values we care about are typically plain strings.
|
|
108
106
|
return value[1:-1]
|
|
109
107
|
return value
|
|
110
|
-
|
|
@@ -54,6 +54,20 @@ def _coerce_command_path(value: Any) -> str:
|
|
|
54
54
|
return value.strip()
|
|
55
55
|
|
|
56
56
|
|
|
57
|
+
def _coerce_session_context_key(value: Any) -> str:
|
|
58
|
+
if not isinstance(value, str):
|
|
59
|
+
return ""
|
|
60
|
+
return value.strip()
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def _coerce_bool_flag(value: Any) -> bool:
|
|
64
|
+
if isinstance(value, bool):
|
|
65
|
+
return value
|
|
66
|
+
if isinstance(value, str):
|
|
67
|
+
return value.strip().lower() in {"1", "true", "y", "yes", "on"}
|
|
68
|
+
return False
|
|
69
|
+
|
|
70
|
+
|
|
57
71
|
def _build_command_not_found_hint(requested_path: str) -> dict[str, str]:
|
|
58
72
|
requested_label = requested_path or "codex"
|
|
59
73
|
detected = shutil.which("codex")
|
|
@@ -85,6 +99,7 @@ class CodexWSHandler(WebSocketHandler):
|
|
|
85
99
|
def open(self):
|
|
86
100
|
self.write_message(json.dumps({"type": "status", "state": "ready"}))
|
|
87
101
|
self._send_cli_defaults()
|
|
102
|
+
self._send_model_catalog()
|
|
88
103
|
self._send_rate_limits_snapshot()
|
|
89
104
|
|
|
90
105
|
async def on_message(self, message: str):
|
|
@@ -104,6 +119,14 @@ class CodexWSHandler(WebSocketHandler):
|
|
|
104
119
|
await self._handle_send(payload)
|
|
105
120
|
return
|
|
106
121
|
|
|
122
|
+
if msg_type == "delete_session":
|
|
123
|
+
self._handle_delete_session(payload)
|
|
124
|
+
return
|
|
125
|
+
|
|
126
|
+
if msg_type == "delete_all_sessions":
|
|
127
|
+
self._handle_delete_all_sessions(payload)
|
|
128
|
+
return
|
|
129
|
+
|
|
107
130
|
if msg_type == "cancel":
|
|
108
131
|
await self._handle_cancel(payload)
|
|
109
132
|
return
|
|
@@ -129,14 +152,60 @@ class CodexWSHandler(WebSocketHandler):
|
|
|
129
152
|
except Exception:
|
|
130
153
|
return
|
|
131
154
|
|
|
155
|
+
def _send_model_catalog(self) -> None:
|
|
156
|
+
async def _send() -> None:
|
|
157
|
+
models = await self._runner.list_available_models()
|
|
158
|
+
if not models:
|
|
159
|
+
return
|
|
160
|
+
try:
|
|
161
|
+
self.write_message(json.dumps({"type": "cli_defaults", "availableModels": models}))
|
|
162
|
+
except Exception:
|
|
163
|
+
return
|
|
164
|
+
|
|
165
|
+
try:
|
|
166
|
+
asyncio.create_task(_send())
|
|
167
|
+
except Exception:
|
|
168
|
+
return
|
|
169
|
+
|
|
132
170
|
async def _handle_start_session(self, payload: Dict[str, Any]):
|
|
133
|
-
|
|
171
|
+
requested_session_id = payload.get("sessionId") or ""
|
|
172
|
+
if not isinstance(requested_session_id, str):
|
|
173
|
+
requested_session_id = str(requested_session_id)
|
|
174
|
+
requested_session_id = requested_session_id.strip()
|
|
175
|
+
force_new_thread = _coerce_bool_flag(payload.get("forceNewThread"))
|
|
134
176
|
notebook_path = payload.get("notebookPath", "")
|
|
177
|
+
if not isinstance(notebook_path, str):
|
|
178
|
+
notebook_path = str(notebook_path)
|
|
179
|
+
notebook_path = notebook_path.strip()
|
|
180
|
+
session_context_key = _coerce_session_context_key(payload.get("sessionContextKey"))
|
|
135
181
|
notebook_os_path = self._resolve_notebook_os_path(notebook_path)
|
|
136
182
|
|
|
137
|
-
|
|
183
|
+
resolved_session_id = requested_session_id
|
|
184
|
+
if force_new_thread:
|
|
185
|
+
previous_session_id = self._store.resolve_session_for_notebook(notebook_path, notebook_os_path)
|
|
186
|
+
if previous_session_id and previous_session_id != resolved_session_id:
|
|
187
|
+
self._store.delete_session(previous_session_id)
|
|
188
|
+
if not resolved_session_id:
|
|
189
|
+
resolved_session_id = str(uuid.uuid4())
|
|
190
|
+
else:
|
|
191
|
+
resolved_session_id = self._store.resolve_session_for_notebook(notebook_path, notebook_os_path) or resolved_session_id
|
|
192
|
+
if not resolved_session_id:
|
|
193
|
+
resolved_session_id = str(uuid.uuid4())
|
|
194
|
+
|
|
195
|
+
self._store.ensure_session(resolved_session_id, notebook_path, notebook_os_path)
|
|
138
196
|
if notebook_path:
|
|
139
|
-
self._store.update_notebook_path(
|
|
197
|
+
self._store.update_notebook_path(resolved_session_id, notebook_path, notebook_os_path)
|
|
198
|
+
|
|
199
|
+
raw_history = self._store.load_messages(resolved_session_id)
|
|
200
|
+
history = []
|
|
201
|
+
for item in raw_history:
|
|
202
|
+
role = item.get("role")
|
|
203
|
+
content = item.get("content")
|
|
204
|
+
if role not in {"user", "assistant", "system"}:
|
|
205
|
+
continue
|
|
206
|
+
if not isinstance(content, str):
|
|
207
|
+
continue
|
|
208
|
+
history.append({"role": role, "content": content})
|
|
140
209
|
|
|
141
210
|
paired_ok, paired_path, paired_os_path, paired_message = _compute_pairing_status(
|
|
142
211
|
notebook_path, notebook_os_path
|
|
@@ -146,8 +215,10 @@ class CodexWSHandler(WebSocketHandler):
|
|
|
146
215
|
{
|
|
147
216
|
"type": "status",
|
|
148
217
|
"state": "ready",
|
|
149
|
-
"sessionId":
|
|
218
|
+
"sessionId": resolved_session_id,
|
|
150
219
|
"notebookPath": notebook_path,
|
|
220
|
+
"sessionContextKey": session_context_key,
|
|
221
|
+
"history": history,
|
|
151
222
|
"pairedOk": paired_ok,
|
|
152
223
|
"pairedPath": paired_path,
|
|
153
224
|
"pairedOsPath": paired_os_path,
|
|
@@ -157,8 +228,12 @@ class CodexWSHandler(WebSocketHandler):
|
|
|
157
228
|
)
|
|
158
229
|
|
|
159
230
|
async def _handle_send(self, payload: Dict[str, Any]):
|
|
160
|
-
session_id = payload.get("sessionId") or
|
|
231
|
+
session_id = payload.get("sessionId") or ""
|
|
232
|
+
if not isinstance(session_id, str):
|
|
233
|
+
session_id = str(session_id)
|
|
234
|
+
session_id = session_id.strip() or str(uuid.uuid4())
|
|
161
235
|
content = payload.get("content", "")
|
|
236
|
+
session_context_key = _coerce_session_context_key(payload.get("sessionContextKey"))
|
|
162
237
|
selection = payload.get("selection", "")
|
|
163
238
|
cell_output = payload.get("cellOutput", "")
|
|
164
239
|
images_payload = payload.get("images")
|
|
@@ -217,9 +292,9 @@ class CodexWSHandler(WebSocketHandler):
|
|
|
217
292
|
"type": "error",
|
|
218
293
|
"runId": run_id,
|
|
219
294
|
"sessionId": session_id,
|
|
295
|
+
"sessionContextKey": session_context_key,
|
|
220
296
|
"notebookPath": notebook_path,
|
|
221
|
-
"message": paired_message
|
|
222
|
-
or "Jupytext paired file is required for this extension.",
|
|
297
|
+
"message": paired_message or "Jupytext paired file is required for this extension.",
|
|
223
298
|
"pairedOk": paired_ok,
|
|
224
299
|
"pairedPath": paired_path,
|
|
225
300
|
"pairedOsPath": paired_os_path,
|
|
@@ -234,6 +309,7 @@ class CodexWSHandler(WebSocketHandler):
|
|
|
234
309
|
"state": "ready",
|
|
235
310
|
"runId": run_id,
|
|
236
311
|
"sessionId": session_id,
|
|
312
|
+
"sessionContextKey": session_context_key,
|
|
237
313
|
"notebookPath": notebook_path,
|
|
238
314
|
"pairedOk": paired_ok,
|
|
239
315
|
"pairedPath": paired_path,
|
|
@@ -267,6 +343,7 @@ class CodexWSHandler(WebSocketHandler):
|
|
|
267
343
|
"state": "running",
|
|
268
344
|
"runId": run_id,
|
|
269
345
|
"sessionId": session_id,
|
|
346
|
+
"sessionContextKey": session_context_key,
|
|
270
347
|
"notebookPath": notebook_path,
|
|
271
348
|
"pairedOk": paired_ok,
|
|
272
349
|
"pairedPath": paired_path,
|
|
@@ -290,6 +367,7 @@ class CodexWSHandler(WebSocketHandler):
|
|
|
290
367
|
"type": "output",
|
|
291
368
|
"runId": run_id,
|
|
292
369
|
"sessionId": session_id,
|
|
370
|
+
"sessionContextKey": session_context_key,
|
|
293
371
|
"notebookPath": notebook_path,
|
|
294
372
|
"text": text,
|
|
295
373
|
}
|
|
@@ -306,6 +384,7 @@ class CodexWSHandler(WebSocketHandler):
|
|
|
306
384
|
"type": "event",
|
|
307
385
|
"runId": run_id,
|
|
308
386
|
"sessionId": session_id,
|
|
387
|
+
"sessionContextKey": session_context_key,
|
|
309
388
|
"notebookPath": notebook_path,
|
|
310
389
|
"payload": event,
|
|
311
390
|
}
|
|
@@ -348,6 +427,7 @@ class CodexWSHandler(WebSocketHandler):
|
|
|
348
427
|
"type": "done",
|
|
349
428
|
"runId": run_id,
|
|
350
429
|
"sessionId": session_id,
|
|
430
|
+
"sessionContextKey": session_context_key,
|
|
351
431
|
"notebookPath": notebook_path,
|
|
352
432
|
"exitCode": exit_code,
|
|
353
433
|
"fileChanged": file_changed,
|
|
@@ -365,6 +445,7 @@ class CodexWSHandler(WebSocketHandler):
|
|
|
365
445
|
"state": "ready",
|
|
366
446
|
"runId": run_id,
|
|
367
447
|
"sessionId": session_id,
|
|
448
|
+
"sessionContextKey": session_context_key,
|
|
368
449
|
"notebookPath": notebook_path,
|
|
369
450
|
"pairedOk": paired_ok,
|
|
370
451
|
"pairedPath": paired_path,
|
|
@@ -381,6 +462,7 @@ class CodexWSHandler(WebSocketHandler):
|
|
|
381
462
|
"type": "done",
|
|
382
463
|
"runId": run_id,
|
|
383
464
|
"sessionId": session_id,
|
|
465
|
+
"sessionContextKey": session_context_key,
|
|
384
466
|
"notebookPath": notebook_path,
|
|
385
467
|
"exitCode": None,
|
|
386
468
|
"cancelled": True,
|
|
@@ -399,6 +481,7 @@ class CodexWSHandler(WebSocketHandler):
|
|
|
399
481
|
"state": "ready",
|
|
400
482
|
"runId": run_id,
|
|
401
483
|
"sessionId": session_id,
|
|
484
|
+
"sessionContextKey": session_context_key,
|
|
402
485
|
"notebookPath": notebook_path,
|
|
403
486
|
"pairedOk": paired_ok,
|
|
404
487
|
"pairedPath": paired_path,
|
|
@@ -414,6 +497,7 @@ class CodexWSHandler(WebSocketHandler):
|
|
|
414
497
|
"type": "error",
|
|
415
498
|
"runId": run_id,
|
|
416
499
|
"sessionId": session_id,
|
|
500
|
+
"sessionContextKey": session_context_key,
|
|
417
501
|
"notebookPath": notebook_path,
|
|
418
502
|
"message": hint["message"],
|
|
419
503
|
"pairedOk": paired_ok,
|
|
@@ -431,6 +515,7 @@ class CodexWSHandler(WebSocketHandler):
|
|
|
431
515
|
"state": "ready",
|
|
432
516
|
"runId": run_id,
|
|
433
517
|
"sessionId": session_id,
|
|
518
|
+
"sessionContextKey": session_context_key,
|
|
434
519
|
"notebookPath": notebook_path,
|
|
435
520
|
"pairedOk": paired_ok,
|
|
436
521
|
"pairedPath": paired_path,
|
|
@@ -446,6 +531,7 @@ class CodexWSHandler(WebSocketHandler):
|
|
|
446
531
|
"type": "error",
|
|
447
532
|
"runId": run_id,
|
|
448
533
|
"sessionId": session_id,
|
|
534
|
+
"sessionContextKey": session_context_key,
|
|
449
535
|
"notebookPath": notebook_path,
|
|
450
536
|
"message": str(exc),
|
|
451
537
|
}
|
|
@@ -458,6 +544,7 @@ class CodexWSHandler(WebSocketHandler):
|
|
|
458
544
|
"state": "ready",
|
|
459
545
|
"runId": run_id,
|
|
460
546
|
"sessionId": session_id,
|
|
547
|
+
"sessionContextKey": session_context_key,
|
|
461
548
|
"notebookPath": notebook_path,
|
|
462
549
|
"pairedOk": paired_ok,
|
|
463
550
|
"pairedPath": paired_path,
|
|
@@ -479,8 +566,44 @@ class CodexWSHandler(WebSocketHandler):
|
|
|
479
566
|
"task": task,
|
|
480
567
|
"sessionId": session_id,
|
|
481
568
|
"notebookPath": notebook_path,
|
|
569
|
+
"sessionContextKey": session_context_key,
|
|
482
570
|
}
|
|
483
571
|
|
|
572
|
+
def _handle_delete_session(self, payload: Dict[str, Any]) -> None:
|
|
573
|
+
session_id = payload.get("sessionId")
|
|
574
|
+
if not isinstance(session_id, str):
|
|
575
|
+
session_id = str(session_id) if session_id else ""
|
|
576
|
+
session_id = session_id.strip()
|
|
577
|
+
if session_id:
|
|
578
|
+
self._store.delete_session(session_id)
|
|
579
|
+
|
|
580
|
+
def _handle_delete_all_sessions(self, payload: Dict[str, Any]) -> None:
|
|
581
|
+
del payload
|
|
582
|
+
response: Dict[str, Any] = {
|
|
583
|
+
"type": "delete_all_sessions",
|
|
584
|
+
"ok": False,
|
|
585
|
+
"deletedCount": 0,
|
|
586
|
+
"failedCount": 0,
|
|
587
|
+
"message": "Unknown error"
|
|
588
|
+
}
|
|
589
|
+
try:
|
|
590
|
+
deleted_count, failed_count = self._store.delete_all_sessions()
|
|
591
|
+
response["deletedCount"] = deleted_count
|
|
592
|
+
response["failedCount"] = failed_count
|
|
593
|
+
response["ok"] = failed_count == 0
|
|
594
|
+
response["message"] = (
|
|
595
|
+
f"Deleted {deleted_count} conversations" if deleted_count else "No conversations found to delete"
|
|
596
|
+
)
|
|
597
|
+
if failed_count:
|
|
598
|
+
response["message"] = f"Deleted {deleted_count} conversations, failed to delete {failed_count}"
|
|
599
|
+
except Exception as exc: # pragma: no cover - defensive path
|
|
600
|
+
response["message"] = str(exc)
|
|
601
|
+
|
|
602
|
+
try:
|
|
603
|
+
self.write_message(json.dumps(response))
|
|
604
|
+
except Exception:
|
|
605
|
+
return
|
|
606
|
+
|
|
484
607
|
def _send_rate_limits_snapshot(self, force: bool = False) -> None:
|
|
485
608
|
try:
|
|
486
609
|
snapshot = load_latest_rate_limits(force=force)
|
|
@@ -503,6 +626,7 @@ class CodexWSHandler(WebSocketHandler):
|
|
|
503
626
|
|
|
504
627
|
task = run_context["task"]
|
|
505
628
|
task.cancel()
|
|
629
|
+
session_context_key = _coerce_session_context_key(run_context.get("sessionContextKey"))
|
|
506
630
|
self.write_message(
|
|
507
631
|
json.dumps(
|
|
508
632
|
{
|
|
@@ -510,6 +634,7 @@ class CodexWSHandler(WebSocketHandler):
|
|
|
510
634
|
"state": "ready",
|
|
511
635
|
"runId": run_id,
|
|
512
636
|
"sessionId": run_context["sessionId"],
|
|
637
|
+
"sessionContextKey": session_context_key,
|
|
513
638
|
"notebookPath": run_context["notebookPath"],
|
|
514
639
|
}
|
|
515
640
|
)
|
|
@@ -820,7 +945,7 @@ def _sanitize_reasoning_effort(value: Any) -> str | None:
|
|
|
820
945
|
effort = value.strip().lower()
|
|
821
946
|
if not effort:
|
|
822
947
|
return None
|
|
823
|
-
if
|
|
948
|
+
if not re.fullmatch(r"[a-z][a-z0-9._-]*", effort):
|
|
824
949
|
return None
|
|
825
950
|
|
|
826
951
|
return effort
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "jupyterlab-codex-sidebar",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"author": "ILHO AHN",
|
|
5
5
|
"description": "Codex sidebar for JupyterLab",
|
|
6
6
|
"license": "BSD-3-Clause",
|
|
@@ -37,7 +37,9 @@
|
|
|
37
37
|
"@lumino/widgets": "^2.0.0",
|
|
38
38
|
"dompurify": "^3.3.1",
|
|
39
39
|
"highlight.js": "^11.11.1",
|
|
40
|
+
"katex": "^0.16.28",
|
|
40
41
|
"marked": "^16.4.2",
|
|
42
|
+
"marked-katex-extension": "^5.1.7",
|
|
41
43
|
"react": "^18.2.0",
|
|
42
44
|
"react-dom": "^18.2.0"
|
|
43
45
|
},
|
|
@@ -52,7 +54,7 @@
|
|
|
52
54
|
"extension": true,
|
|
53
55
|
"outputDir": "jupyterlab_codex/labextension",
|
|
54
56
|
"_build": {
|
|
55
|
-
"load": "static/remoteEntry.
|
|
57
|
+
"load": "static/remoteEntry.b2fdc03a1c4582e79156.js",
|
|
56
58
|
"extension": "./extension"
|
|
57
59
|
}
|
|
58
60
|
}
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";(self.webpackChunkjupyterlab_codex_sidebar=self.webpackChunkjupyterlab_codex_sidebar||[]).push([[124],{8124(e,n,t){t.r(n),t.d(n,{default:()=>s});var r=t(4502);const a=/^(\${1,2})(?!\$)((?:\\.|[^\\\n])*?(?:\\.|[^\\\n\$]))\1(?=[\s?!\.,:?!。,:]|$)/,i=/^(\${1,2})(?!\$)((?:\\.|[^\\\n])*?(?:\\.|[^\\\n\$]))\1/,l=/^(\${1,2})\n((?:\\[^]|[^\\])+?)\n\1(?:\n|$)/;function s(e={}){return{extensions:[d(e,o(e,!1)),(n=o(e,!0),{name:"blockKatex",level:"block",tokenizer(e,n){const t=e.match(l);if(t)return{type:"blockKatex",raw:t[0],text:t[2].trim(),displayMode:2===t[1].length}},renderer:n})]};var n}function o(e,n){return t=>r.default.renderToString(t.text,{...e,displayMode:t.displayMode})+(n?"\n":"")}function d(e,n){const t=e&&e.nonStandard,r=t?i:a;return{name:"inlineKatex",level:"inline",start(e){let n,a=e;for(;a;){if(n=a.indexOf("$"),-1===n)return;if((t?n>-1:0===n||" "===a.charAt(n-1))&&a.substring(n).match(r))return n;a=a.substring(n+1).replace(/^\$+/,"")}},tokenizer(e,n){const t=e.match(r);if(t)return{type:"inlineKatex",raw:t[0],text:t[2].trim(),displayMode:2===t[1].length}},renderer:n}}}}]);
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|