jupyterlab-codex-sidebar 0.1.4 → 0.1.6

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.
Files changed (153) hide show
  1. package/.claude/settings.local.json +9 -0
  2. package/.github/workflows/unit-tests.yml +27 -0
  3. package/.jupyterlab-playwright.log +0 -0
  4. package/README.md +83 -9
  5. package/docs/images/codex-sidebar-screenshot.png +0 -0
  6. package/jupyterlab_codex/handlers.py +938 -297
  7. package/jupyterlab_codex/labextension/package.json +13 -3
  8. package/jupyterlab_codex/labextension/static/525.224526d045c727069de6.js +2 -0
  9. package/jupyterlab_codex/labextension/static/737.e7de3ad9dd6ded798340.js +1 -0
  10. package/jupyterlab_codex/labextension/static/remoteEntry.6ef5e7167763a316c000.js +1 -0
  11. package/jupyterlab_codex/protocol.py +297 -0
  12. package/jupyterlab_codex/runner.py +58 -15
  13. package/jupyterlab_codex/sessions.py +582 -97
  14. package/lib/codexChat.d.ts +13 -0
  15. package/lib/codexChat.js +2506 -0
  16. package/lib/codexChat.js.map +1 -0
  17. package/lib/codexChatAttachmentDedup.d.ts +10 -0
  18. package/lib/codexChatAttachmentDedup.js +35 -0
  19. package/lib/codexChatAttachmentDedup.js.map +1 -0
  20. package/lib/codexChatAttachmentLimit.d.ts +18 -0
  21. package/lib/codexChatAttachmentLimit.js +50 -0
  22. package/lib/codexChatAttachmentLimit.js.map +1 -0
  23. package/lib/codexChatAttachmentState.d.ts +15 -0
  24. package/lib/codexChatAttachmentState.js +16 -0
  25. package/lib/codexChatAttachmentState.js.map +1 -0
  26. package/lib/codexChatDocumentUtils.d.ts +70 -0
  27. package/lib/codexChatDocumentUtils.js +506 -0
  28. package/lib/codexChatDocumentUtils.js.map +1 -0
  29. package/lib/codexChatFormatting.d.ts +11 -0
  30. package/lib/codexChatFormatting.js +83 -0
  31. package/lib/codexChatFormatting.js.map +1 -0
  32. package/lib/codexChatNotice.d.ts +3 -0
  33. package/lib/codexChatNotice.js +74 -0
  34. package/lib/codexChatNotice.js.map +1 -0
  35. package/lib/codexChatPersistence.d.ts +35 -0
  36. package/lib/codexChatPersistence.js +158 -0
  37. package/lib/codexChatPersistence.js.map +1 -0
  38. package/lib/codexChatPrimitives.d.ts +44 -0
  39. package/lib/codexChatPrimitives.js +156 -0
  40. package/lib/codexChatPrimitives.js.map +1 -0
  41. package/lib/codexChatRender.d.ts +24 -0
  42. package/lib/codexChatRender.js +293 -0
  43. package/lib/codexChatRender.js.map +1 -0
  44. package/lib/codexChatSessionFactory.d.ts +15 -0
  45. package/lib/codexChatSessionFactory.js +45 -0
  46. package/lib/codexChatSessionFactory.js.map +1 -0
  47. package/lib/codexChatSessionKey.d.ts +3 -0
  48. package/lib/codexChatSessionKey.js +14 -0
  49. package/lib/codexChatSessionKey.js.map +1 -0
  50. package/lib/codexChatStorage.d.ts +4 -0
  51. package/lib/codexChatStorage.js +37 -0
  52. package/lib/codexChatStorage.js.map +1 -0
  53. package/lib/codexSessionResolver.d.ts +12 -0
  54. package/lib/codexSessionResolver.js +38 -0
  55. package/lib/codexSessionResolver.js.map +1 -0
  56. package/lib/handlers/activitySummarizer.d.ts +15 -0
  57. package/lib/handlers/activitySummarizer.js +327 -0
  58. package/lib/handlers/activitySummarizer.js.map +1 -0
  59. package/lib/handlers/codexMessageTypes.d.ts +30 -0
  60. package/lib/handlers/codexMessageTypes.js +2 -0
  61. package/lib/handlers/codexMessageTypes.js.map +1 -0
  62. package/lib/handlers/codexMessageUtils.d.ts +46 -0
  63. package/lib/handlers/codexMessageUtils.js +144 -0
  64. package/lib/handlers/codexMessageUtils.js.map +1 -0
  65. package/lib/handlers/handleCodexSocketMessage.d.ts +107 -0
  66. package/lib/handlers/handleCodexSocketMessage.js +78 -0
  67. package/lib/handlers/handleCodexSocketMessage.js.map +1 -0
  68. package/lib/handlers/sessionSyncHandler.d.ts +34 -0
  69. package/lib/handlers/sessionSyncHandler.js +181 -0
  70. package/lib/handlers/sessionSyncHandler.js.map +1 -0
  71. package/lib/hooks/useCodexSocket.d.ts +15 -0
  72. package/lib/hooks/useCodexSocket.js +84 -0
  73. package/lib/hooks/useCodexSocket.js.map +1 -0
  74. package/lib/index.js +1 -1
  75. package/lib/index.js.map +1 -1
  76. package/lib/panel.d.ts +1 -11
  77. package/lib/panel.js +1 -2815
  78. package/lib/panel.js.map +1 -1
  79. package/lib/protocol.d.ts +235 -0
  80. package/lib/protocol.js +278 -0
  81. package/lib/protocol.js.map +1 -0
  82. package/package.json +13 -3
  83. package/playwright.config.cjs +27 -0
  84. package/playwright.unit.config.cjs +19 -0
  85. package/pyproject.toml +1 -1
  86. package/release.sh +52 -14
  87. package/scripts/run_playwright_e2e.sh +96 -0
  88. package/scripts/run_playwright_freeze_repro.sh +58 -0
  89. package/scripts/run_playwright_queue_repro.sh +60 -0
  90. package/scripts/run_playwright_repro.sh +55 -0
  91. package/src/codexChat.tsx +3914 -0
  92. package/src/codexChatAttachmentDedup.ts +47 -0
  93. package/src/codexChatAttachmentLimit.ts +81 -0
  94. package/src/codexChatAttachmentState.ts +37 -0
  95. package/src/codexChatDocumentUtils.ts +644 -0
  96. package/src/codexChatFormatting.ts +94 -0
  97. package/src/codexChatNotice.ts +95 -0
  98. package/src/codexChatPersistence.ts +191 -0
  99. package/src/codexChatPrimitives.tsx +446 -0
  100. package/src/codexChatRender.tsx +376 -0
  101. package/src/codexChatSessionFactory.ts +79 -0
  102. package/src/codexChatSessionKey.ts +16 -0
  103. package/src/codexChatStorage.ts +36 -0
  104. package/src/codexSessionResolver.ts +56 -0
  105. package/src/handlers/activitySummarizer.ts +369 -0
  106. package/src/handlers/codexMessageTypes.ts +34 -0
  107. package/src/handlers/codexMessageUtils.ts +217 -0
  108. package/src/handlers/handleCodexSocketMessage.ts +204 -0
  109. package/src/handlers/sessionSyncHandler.ts +308 -0
  110. package/src/hooks/useCodexSocket.ts +109 -0
  111. package/src/index.ts +1 -1
  112. package/src/panel.tsx +1 -4184
  113. package/src/protocol.ts +582 -0
  114. package/style/index.css +480 -11
  115. package/test-results/.last-run.json +4 -0
  116. package/test.py +0 -0
  117. package/tests/e2e/cell-output-error-tail.spec.js +156 -0
  118. package/tests/e2e/codex-ui-test-helpers.js +138 -0
  119. package/tests/e2e/fixtures/notebooks/error-output-tail.ipynb +58 -0
  120. package/tests/e2e/fixtures/notebooks/error-output-tail.py +19 -0
  121. package/tests/e2e/fixtures/notebooks/tab1.ipynb +322 -0
  122. package/tests/e2e/fixtures/notebooks/tab1.py +272 -0
  123. package/tests/e2e/fixtures/notebooks/tab2.ipynb +252 -0
  124. package/tests/e2e/fixtures/notebooks/tab2.py +231 -0
  125. package/tests/e2e/fixtures/notebooks/tab3.ipynb +403 -0
  126. package/tests/e2e/fixtures/notebooks/tab3.py +331 -0
  127. package/tests/e2e/fixtures/notebooks/tab4.py +339 -0
  128. package/tests/e2e/freeze-notebook-tabs-repro.spec.js +295 -0
  129. package/tests/e2e/mock-codex-cli-flood.py +127 -0
  130. package/tests/e2e/mock-codex-cli-prompt-echo.py +88 -0
  131. package/tests/e2e/mock-codex-cli.py +95 -0
  132. package/tests/e2e/queue-multitab-repro.spec.js +189 -0
  133. package/tests/test_handlers.py +116 -0
  134. package/tests/test_protocol.py +169 -0
  135. package/tests/test_session_store_limits.py +50 -0
  136. package/tests/unit/codexChatAttachmentDedup.spec.ts +56 -0
  137. package/tests/unit/codexChatAttachmentLimit.spec.ts +57 -0
  138. package/tests/unit/codexChatAttachmentState.spec.ts +71 -0
  139. package/tests/unit/codexChatDocumentUtils.spec.ts +63 -0
  140. package/tests/unit/codexChatLimit.spec.ts +18 -0
  141. package/tests/unit/codexChatNotice.spec.ts +45 -0
  142. package/tests/unit/codexChatPersistence.spec.ts +199 -0
  143. package/tests/unit/codexChatSessionFactory.spec.ts +94 -0
  144. package/tests/unit/codexChatSessionKey.spec.ts +18 -0
  145. package/tests/unit/codexMessageUtils.spec.ts +89 -0
  146. package/tests/unit/codexSessionResolver.spec.ts +92 -0
  147. package/tests/unit/handleCodexSocketMessage.spec.ts +476 -0
  148. package/tsconfig.tsbuildinfo +1 -1
  149. package/webpack.config.js +6 -0
  150. package/jupyterlab_codex/labextension/static/504.335f3447c84ba3d74517.js +0 -2
  151. package/jupyterlab_codex/labextension/static/972.8e856719e40acc1ef4cb.js +0 -1
  152. package/jupyterlab_codex/labextension/static/remoteEntry.a2982f776a1f0f515640.js +0 -1
  153. /package/jupyterlab_codex/labextension/static/{504.335f3447c84ba3d74517.js.LICENSE.txt → 525.224526d045c727069de6.js.LICENSE.txt} +0 -0
@@ -0,0 +1,582 @@
1
+ export const PROTOCOL_VERSION = '1.0.0' as const;
2
+
3
+ export type ServerProtocolVersion = typeof PROTOCOL_VERSION;
4
+ export type ProtocolVersion = string;
5
+
6
+ export type RunMode = 'resume' | 'fallback';
7
+ export type StatusState = 'ready' | 'running';
8
+
9
+ export interface SelectionPreview {
10
+ locationLabel: string;
11
+ previewText: string;
12
+ }
13
+
14
+ export interface ModelCatalogEntry {
15
+ model: string;
16
+ displayName: string;
17
+ reasoningEfforts?: string[];
18
+ defaultReasoningEffort?: string;
19
+ }
20
+
21
+ export interface ServerMessageBase {
22
+ protocolVersion?: ProtocolVersion;
23
+ type: string;
24
+ }
25
+
26
+ export interface ServerCliDefaultsMessage extends ServerMessageBase {
27
+ type: 'cli_defaults';
28
+ model?: string | null;
29
+ reasoningEffort?: string | null;
30
+ availableModels?: ModelCatalogEntry[];
31
+ }
32
+
33
+ export interface ServerRateLimitsMessage extends ServerMessageBase {
34
+ type: 'rate_limits';
35
+ snapshot: unknown;
36
+ }
37
+
38
+ export type ServerSessionResolution =
39
+ | 'client'
40
+ | 'client-new'
41
+ | 'force-new'
42
+ | 'mapping'
43
+ | 'mapping-on-missing'
44
+ | 'mapping-on-mismatch'
45
+ | 'new'
46
+ | 'new-on-mismatch';
47
+
48
+ export interface ServerDeleteAllSessionsMessage extends ServerMessageBase {
49
+ type: 'delete_all_sessions';
50
+ ok: boolean;
51
+ deletedCount: number;
52
+ failedCount: number;
53
+ message: string;
54
+ }
55
+
56
+ export interface ServerStatusMessage extends ServerMessageBase {
57
+ type: 'status';
58
+ state: StatusState;
59
+ runId?: string;
60
+ sessionId?: string;
61
+ sessionContextKey?: string;
62
+ notebookPath?: string;
63
+ runMode?: RunMode;
64
+ pairedOk?: boolean;
65
+ pairedPath?: string;
66
+ pairedOsPath?: string;
67
+ pairedMessage?: string;
68
+ notebookMode?: string;
69
+ sessionResolution?: ServerSessionResolution | string;
70
+ sessionResolutionNotice?: string;
71
+ history?: Array<{
72
+ role: 'user' | 'assistant' | 'system';
73
+ content: string;
74
+ selectionPreview?: SelectionPreview;
75
+ cellOutputPreview?: SelectionPreview;
76
+ }>;
77
+ effectiveSandbox?: string;
78
+ }
79
+
80
+ export interface ServerOutputMessage extends ServerMessageBase {
81
+ type: 'output';
82
+ runId: string;
83
+ sessionId: string;
84
+ sessionContextKey?: string;
85
+ notebookPath: string;
86
+ role: 'user' | 'assistant' | 'system';
87
+ text: string;
88
+ }
89
+
90
+ export interface ServerEventMessage extends ServerMessageBase {
91
+ type: 'event';
92
+ runId: string;
93
+ sessionId: string;
94
+ sessionContextKey?: string;
95
+ notebookPath: string;
96
+ payload: unknown;
97
+ }
98
+
99
+ export interface ServerDoneMessage extends ServerMessageBase {
100
+ type: 'done';
101
+ runId: string;
102
+ sessionId: string;
103
+ sessionContextKey?: string;
104
+ notebookPath: string;
105
+ exitCode: number | null;
106
+ cancelled?: boolean;
107
+ fileChanged?: boolean;
108
+ runMode?: RunMode;
109
+ pairedOk?: boolean;
110
+ pairedPath?: string;
111
+ pairedOsPath?: string;
112
+ pairedMessage?: string;
113
+ notebookMode?: string;
114
+ }
115
+
116
+ export interface ServerErrorMessage extends ServerMessageBase {
117
+ type: 'error';
118
+ runId?: string;
119
+ sessionId?: string;
120
+ sessionContextKey?: string;
121
+ notebookPath?: string;
122
+ message: string;
123
+ runMode?: RunMode;
124
+ suggestedCommandPath?: string;
125
+ pairedOk?: boolean;
126
+ pairedPath?: string;
127
+ pairedOsPath?: string;
128
+ pairedMessage?: string;
129
+ notebookMode?: string;
130
+ }
131
+
132
+ export type ServerMessage =
133
+ | ServerCliDefaultsMessage
134
+ | ServerRateLimitsMessage
135
+ | ServerDeleteAllSessionsMessage
136
+ | ServerStatusMessage
137
+ | ServerOutputMessage
138
+ | ServerEventMessage
139
+ | ServerDoneMessage
140
+ | ServerErrorMessage;
141
+
142
+ export interface ParsedServerStartSessionMessage {
143
+ type: 'start_session';
144
+ sessionId: string;
145
+ sessionContextKey: string;
146
+ notebookPath: string;
147
+ forceNewThread: boolean;
148
+ commandPath: string;
149
+ }
150
+
151
+ export interface ParsedClientSendMessage {
152
+ type: 'send';
153
+ sessionId: string;
154
+ sessionContextKey: string;
155
+ content: string;
156
+ notebookPath: string;
157
+ commandPath: string;
158
+ model: string;
159
+ reasoningEffort: string;
160
+ sandbox: string;
161
+ selection: string;
162
+ cellOutput: string;
163
+ images: unknown[];
164
+ uiSelectionPreview?: unknown;
165
+ uiCellOutputPreview?: unknown;
166
+ selectionTruncated: boolean;
167
+ cellOutputTruncated: boolean;
168
+ }
169
+
170
+ export interface ParsedClientDeleteSessionMessage {
171
+ type: 'delete_session';
172
+ sessionId: string;
173
+ }
174
+
175
+ export interface ParsedClientCancelMessage {
176
+ type: 'cancel';
177
+ runId: string;
178
+ }
179
+
180
+ export interface ParsedClientEndSessionMessage {
181
+ type: 'end_session';
182
+ sessionId: string;
183
+ }
184
+
185
+ export interface ParsedClientRefreshRateLimitsMessage {
186
+ type: 'refresh_rate_limits';
187
+ }
188
+
189
+ export interface ParsedClientDeleteAllSessionsMessage {
190
+ type: 'delete_all_sessions';
191
+ }
192
+
193
+ export interface ParsedClientStartSessionMessage {
194
+ type: 'start_session';
195
+ sessionId: string;
196
+ notebookPath: string;
197
+ sessionContextKey: string;
198
+ forceNewThread: boolean;
199
+ commandPath: string;
200
+ }
201
+
202
+ export type ParsedClientMessage =
203
+ | ParsedClientSendMessage
204
+ | ParsedClientDeleteSessionMessage
205
+ | ParsedClientDeleteAllSessionsMessage
206
+ | ParsedClientCancelMessage
207
+ | ParsedClientEndSessionMessage
208
+ | ParsedClientRefreshRateLimitsMessage
209
+ | ParsedClientStartSessionMessage;
210
+
211
+ export interface ClientStartSessionMessage {
212
+ type: 'start_session';
213
+ sessionId: string;
214
+ notebookPath: string;
215
+ sessionContextKey: string;
216
+ forceNewThread: boolean;
217
+ commandPath?: string;
218
+ }
219
+
220
+ export interface ClientSendMessage {
221
+ type: 'send';
222
+ sessionId: string;
223
+ sessionContextKey: string;
224
+ content: string;
225
+ notebookPath: string;
226
+ commandPath?: string;
227
+ model: string;
228
+ reasoningEffort: string;
229
+ sandbox: string;
230
+ selection?: string;
231
+ cellOutput?: string;
232
+ images?: { name: string; dataUrl: string }[];
233
+ uiSelectionPreview?: unknown;
234
+ uiCellOutputPreview?: unknown;
235
+ selectionTruncated?: boolean;
236
+ cellOutputTruncated?: boolean;
237
+ }
238
+
239
+ export interface ClientCancelMessage {
240
+ type: 'cancel';
241
+ runId: string;
242
+ }
243
+
244
+ export interface ClientDeleteSessionMessage {
245
+ type: 'delete_session';
246
+ sessionId: string;
247
+ }
248
+
249
+ export interface ClientDeleteAllSessionsMessage {
250
+ type: 'delete_all_sessions';
251
+ }
252
+
253
+ export type ClientMessage =
254
+ | ClientStartSessionMessage
255
+ | ClientSendMessage
256
+ | ClientCancelMessage
257
+ | ClientDeleteSessionMessage
258
+ | ClientDeleteAllSessionsMessage;
259
+
260
+ export function parseServerMessage(raw: unknown): ServerMessage | null {
261
+ if (typeof raw !== 'string' && (raw === null || typeof raw !== 'object')) {
262
+ return null;
263
+ }
264
+ const parsed = typeof raw === 'string' ? parseJson(raw) : raw;
265
+ if (parsed === null || typeof parsed !== 'object') {
266
+ return null;
267
+ }
268
+ const message = parsed as Record<string, unknown>;
269
+ const type = typeof message.type === 'string' ? message.type : '';
270
+ if (!type) {
271
+ return null;
272
+ }
273
+
274
+ switch (type) {
275
+ case 'cli_defaults':
276
+ const cliDefaultsMessage: ServerCliDefaultsMessage = {
277
+ type,
278
+ protocolVersion: typeof message.protocolVersion === 'string' ? message.protocolVersion : undefined
279
+ };
280
+ if (Object.prototype.hasOwnProperty.call(message, 'model')) {
281
+ cliDefaultsMessage.model =
282
+ typeof message.model === 'string' || message.model === null ? (message.model as string | null) : null;
283
+ }
284
+ if (Object.prototype.hasOwnProperty.call(message, 'reasoningEffort')) {
285
+ cliDefaultsMessage.reasoningEffort =
286
+ typeof message.reasoningEffort === 'string' || message.reasoningEffort === null
287
+ ? (message.reasoningEffort as string | null)
288
+ : null;
289
+ }
290
+ if (Object.prototype.hasOwnProperty.call(message, 'availableModels')) {
291
+ cliDefaultsMessage.availableModels = parseModelCatalog(
292
+ Array.isArray(message.availableModels) ? message.availableModels : []
293
+ );
294
+ }
295
+ return cliDefaultsMessage;
296
+ case 'rate_limits':
297
+ return {
298
+ type,
299
+ protocolVersion: typeof message.protocolVersion === 'string' ? message.protocolVersion : undefined,
300
+ snapshot: message.snapshot ?? null
301
+ } as ServerRateLimitsMessage;
302
+ case 'delete_all_sessions':
303
+ if (
304
+ typeof message.ok !== 'boolean' ||
305
+ !Number.isFinite(Number(message.deletedCount)) ||
306
+ !Number.isFinite(Number(message.failedCount)) ||
307
+ typeof message.message !== 'string'
308
+ ) {
309
+ return null;
310
+ }
311
+ return {
312
+ type,
313
+ protocolVersion: typeof message.protocolVersion === 'string' ? message.protocolVersion : undefined,
314
+ ok: message.ok,
315
+ deletedCount: Number(message.deletedCount),
316
+ failedCount: Number(message.failedCount),
317
+ message: message.message
318
+ } as ServerDeleteAllSessionsMessage;
319
+ case 'status':
320
+ return {
321
+ type,
322
+ protocolVersion: typeof message.protocolVersion === 'string' ? message.protocolVersion : undefined,
323
+ state: message.state === 'running' ? 'running' : 'ready',
324
+ runId: typeof message.runId === 'string' ? message.runId : undefined,
325
+ sessionId: typeof message.sessionId === 'string' ? message.sessionId : undefined,
326
+ sessionContextKey: typeof message.sessionContextKey === 'string' ? message.sessionContextKey : undefined,
327
+ notebookPath: typeof message.notebookPath === 'string' ? message.notebookPath : undefined,
328
+ runMode: message.runMode === 'fallback' ? 'fallback' : message.runMode === 'resume' ? 'resume' : undefined,
329
+ pairedOk: typeof message.pairedOk === 'boolean' ? message.pairedOk : undefined,
330
+ pairedPath: typeof message.pairedPath === 'string' ? message.pairedPath : undefined,
331
+ pairedOsPath: typeof message.pairedOsPath === 'string' ? message.pairedOsPath : undefined,
332
+ pairedMessage: typeof message.pairedMessage === 'string' ? message.pairedMessage : undefined,
333
+ notebookMode: typeof message.notebookMode === 'string' ? message.notebookMode : undefined,
334
+ sessionResolution: typeof message.sessionResolution === 'string' ? message.sessionResolution : undefined,
335
+ sessionResolutionNotice:
336
+ typeof message.sessionResolutionNotice === 'string' ? message.sessionResolutionNotice : undefined,
337
+ history: Array.isArray(message.history) ? (message.history as ServerStatusMessage['history']) : undefined,
338
+ effectiveSandbox: typeof message.effectiveSandbox === 'string' ? message.effectiveSandbox : undefined
339
+ };
340
+ case 'output':
341
+ if (
342
+ typeof message.runId !== 'string' ||
343
+ typeof message.sessionId !== 'string' ||
344
+ typeof message.notebookPath !== 'string' ||
345
+ typeof message.text !== 'string'
346
+ ) {
347
+ return null;
348
+ }
349
+ return {
350
+ type,
351
+ protocolVersion: typeof message.protocolVersion === 'string' ? message.protocolVersion : undefined,
352
+ runId: message.runId,
353
+ sessionId: message.sessionId,
354
+ sessionContextKey: typeof message.sessionContextKey === 'string' ? message.sessionContextKey : undefined,
355
+ notebookPath: message.notebookPath,
356
+ role: typeof message.role === 'string' ? (message.role as 'user' | 'assistant' | 'system') : 'assistant',
357
+ text: message.text
358
+ };
359
+ case 'event':
360
+ if (
361
+ typeof message.runId !== 'string' ||
362
+ typeof message.sessionId !== 'string' ||
363
+ typeof message.notebookPath !== 'string'
364
+ ) {
365
+ return null;
366
+ }
367
+ return {
368
+ type,
369
+ protocolVersion: typeof message.protocolVersion === 'string' ? message.protocolVersion : undefined,
370
+ runId: message.runId,
371
+ sessionId: message.sessionId,
372
+ sessionContextKey: typeof message.sessionContextKey === 'string' ? message.sessionContextKey : undefined,
373
+ notebookPath: message.notebookPath,
374
+ payload: message.payload
375
+ } as ServerEventMessage;
376
+ case 'done':
377
+ if (
378
+ typeof message.runId !== 'string' ||
379
+ typeof message.sessionId !== 'string' ||
380
+ typeof message.notebookPath !== 'string'
381
+ ) {
382
+ return null;
383
+ }
384
+ let exitCode: number | null = null;
385
+ if (message.exitCode === null) {
386
+ exitCode = null;
387
+ } else if (typeof message.exitCode === 'number' && Number.isFinite(message.exitCode)) {
388
+ exitCode = message.exitCode;
389
+ } else if (typeof message.exitCode === 'string') {
390
+ const parsedExitCode = Number(message.exitCode.trim());
391
+ exitCode = Number.isFinite(parsedExitCode) ? parsedExitCode : null;
392
+ }
393
+ return {
394
+ type,
395
+ protocolVersion: typeof message.protocolVersion === 'string' ? message.protocolVersion : undefined,
396
+ runId: message.runId,
397
+ sessionId: message.sessionId,
398
+ sessionContextKey: typeof message.sessionContextKey === 'string' ? message.sessionContextKey : undefined,
399
+ notebookPath: message.notebookPath,
400
+ exitCode,
401
+ cancelled: typeof message.cancelled === 'boolean' ? message.cancelled : false,
402
+ fileChanged: typeof message.fileChanged === 'boolean' ? message.fileChanged : false,
403
+ runMode: message.runMode === 'fallback' ? 'fallback' : message.runMode === 'resume' ? 'resume' : undefined,
404
+ pairedOk: typeof message.pairedOk === 'boolean' ? message.pairedOk : undefined,
405
+ pairedPath: typeof message.pairedPath === 'string' ? message.pairedPath : undefined,
406
+ pairedOsPath: typeof message.pairedOsPath === 'string' ? message.pairedOsPath : undefined,
407
+ pairedMessage: typeof message.pairedMessage === 'string' ? message.pairedMessage : undefined,
408
+ notebookMode: typeof message.notebookMode === 'string' ? message.notebookMode : undefined
409
+ };
410
+ case 'error':
411
+ if (typeof message.message !== 'string') {
412
+ return null;
413
+ }
414
+ return {
415
+ type,
416
+ protocolVersion: typeof message.protocolVersion === 'string' ? message.protocolVersion : undefined,
417
+ runId: typeof message.runId === 'string' ? message.runId : undefined,
418
+ sessionId: typeof message.sessionId === 'string' ? message.sessionId : undefined,
419
+ sessionContextKey: typeof message.sessionContextKey === 'string' ? message.sessionContextKey : undefined,
420
+ notebookPath: typeof message.notebookPath === 'string' ? message.notebookPath : undefined,
421
+ message: message.message,
422
+ runMode: message.runMode === 'fallback' ? 'fallback' : message.runMode === 'resume' ? 'resume' : undefined,
423
+ suggestedCommandPath:
424
+ typeof message.suggestedCommandPath === 'string' ? message.suggestedCommandPath : undefined,
425
+ pairedOk: typeof message.pairedOk === 'boolean' ? message.pairedOk : undefined,
426
+ pairedPath: typeof message.pairedPath === 'string' ? message.pairedPath : undefined,
427
+ pairedOsPath: typeof message.pairedOsPath === 'string' ? message.pairedOsPath : undefined,
428
+ pairedMessage: typeof message.pairedMessage === 'string' ? message.pairedMessage : undefined,
429
+ notebookMode: typeof message.notebookMode === 'string' ? message.notebookMode : undefined
430
+ };
431
+ }
432
+ return null;
433
+ }
434
+
435
+ export function parseModelCatalog(rawModels: unknown): ModelCatalogEntry[] {
436
+ if (!Array.isArray(rawModels)) {
437
+ return [];
438
+ }
439
+ const seen = new Set<string>();
440
+ const catalog: ModelCatalogEntry[] = [];
441
+ for (const item of rawModels) {
442
+ if (!item || typeof item !== 'object') {
443
+ continue;
444
+ }
445
+ const rawModel = (item as Record<string, unknown>).model;
446
+ const rawDisplayName = (item as Record<string, unknown>).displayName;
447
+ const rawReasoningEfforts =
448
+ (item as Record<string, unknown>).reasoningEfforts ??
449
+ (item as Record<string, unknown>).supportedReasoningEfforts;
450
+ const rawDefaultReasoningEffort = (item as Record<string, unknown>).defaultReasoningEffort;
451
+ if (typeof rawModel !== 'string') {
452
+ continue;
453
+ }
454
+ const model = rawModel.trim();
455
+ if (!model || seen.has(model)) {
456
+ continue;
457
+ }
458
+ seen.add(model);
459
+ const displayName =
460
+ typeof rawDisplayName === 'string' && rawDisplayName.trim() ? rawDisplayName.trim() : model;
461
+ const defaultReasoningEffort =
462
+ typeof rawDefaultReasoningEffort === 'string' && rawDefaultReasoningEffort.trim()
463
+ ? rawDefaultReasoningEffort.trim()
464
+ : '';
465
+
466
+ const reasoningEfforts = Array.isArray(rawReasoningEfforts)
467
+ ? rawReasoningEfforts.reduce<string[]>((acc, effortCandidate: unknown) => {
468
+ const effort = coerceReasoningEffortEntry(effortCandidate);
469
+ if (!effort || acc.includes(effort)) {
470
+ return acc;
471
+ }
472
+ acc.push(effort);
473
+ return acc;
474
+ }, [])
475
+ : [];
476
+
477
+ catalog.push({
478
+ model,
479
+ displayName,
480
+ ...(reasoningEfforts.length > 0 ? { reasoningEfforts } : {}),
481
+ ...(defaultReasoningEffort ? { defaultReasoningEffort } : {})
482
+ });
483
+ }
484
+ return catalog;
485
+ }
486
+
487
+ export function buildStartSessionMessage(input: {
488
+ sessionId: string;
489
+ sessionContextKey: string;
490
+ notebookPath: string;
491
+ forceNewThread?: boolean;
492
+ commandPath?: string;
493
+ }): ClientStartSessionMessage {
494
+ return {
495
+ type: 'start_session',
496
+ sessionId: input.sessionId,
497
+ sessionContextKey: input.sessionContextKey,
498
+ notebookPath: input.notebookPath,
499
+ forceNewThread: input.forceNewThread === true,
500
+ commandPath: input.commandPath ? input.commandPath.trim() : undefined
501
+ };
502
+ }
503
+
504
+ export function buildSendMessage(input: {
505
+ sessionId: string;
506
+ sessionContextKey: string;
507
+ notebookPath: string;
508
+ commandPath: string;
509
+ content: string;
510
+ model: string;
511
+ reasoningEffort: string;
512
+ sandbox: string;
513
+ selection?: string;
514
+ cellOutput?: string;
515
+ images?: { name: string; dataUrl: string }[];
516
+ uiSelectionPreview?: unknown;
517
+ uiCellOutputPreview?: unknown;
518
+ selectionTruncated?: boolean;
519
+ cellOutputTruncated?: boolean;
520
+ }): ClientSendMessage {
521
+ return {
522
+ type: 'send',
523
+ sessionId: input.sessionId,
524
+ sessionContextKey: input.sessionContextKey,
525
+ notebookPath: input.notebookPath,
526
+ ...(input.commandPath ? { commandPath: input.commandPath } : {}),
527
+ content: input.content,
528
+ model: input.model,
529
+ reasoningEffort: input.reasoningEffort,
530
+ sandbox: input.sandbox,
531
+ ...(input.selection ? { selection: input.selection } : {}),
532
+ ...(input.cellOutput ? { cellOutput: input.cellOutput } : {}),
533
+ ...(input.images ? { images: input.images } : {}),
534
+ ...(input.uiSelectionPreview ? { uiSelectionPreview: input.uiSelectionPreview } : {}),
535
+ ...(input.uiCellOutputPreview ? { uiCellOutputPreview: input.uiCellOutputPreview } : {}),
536
+ ...(input.selectionTruncated ? { selectionTruncated: true } : {}),
537
+ ...(input.cellOutputTruncated ? { cellOutputTruncated: true } : {})
538
+ };
539
+ }
540
+
541
+ export function buildCancelMessage(runId: string): ClientCancelMessage {
542
+ return {
543
+ type: 'cancel',
544
+ runId
545
+ };
546
+ }
547
+
548
+ export function buildDeleteSessionMessage(sessionId: string): ClientDeleteSessionMessage {
549
+ return {
550
+ type: 'delete_session',
551
+ sessionId
552
+ };
553
+ }
554
+
555
+ export function buildDeleteAllSessionsMessage(): ClientDeleteAllSessionsMessage {
556
+ return {
557
+ type: 'delete_all_sessions'
558
+ };
559
+ }
560
+
561
+ function parseJson(raw: string): unknown | null {
562
+ try {
563
+ return JSON.parse(raw);
564
+ } catch {
565
+ return null;
566
+ }
567
+ }
568
+
569
+ function coerceReasoningEffortEntry(value: unknown): string {
570
+ if (typeof value === 'string') {
571
+ return value.trim();
572
+ }
573
+ if (
574
+ value &&
575
+ typeof value === 'object' &&
576
+ 'reasoningEffort' in (value as Record<string, unknown>)
577
+ ) {
578
+ const candidate = (value as { reasoningEffort?: unknown }).reasoningEffort;
579
+ return typeof candidate === 'string' ? candidate.trim() : '';
580
+ }
581
+ return '';
582
+ }