@ian-pascoe/pi-codemode 0.1.0
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/LICENSE +21 -0
- package/README.md +241 -0
- package/package.json +59 -0
- package/src/codemode-cell-transform.ts +612 -0
- package/src/codemode-deno-launch.ts +59 -0
- package/src/codemode-deno-process.ts +208 -0
- package/src/codemode-observer-ui.ts +517 -0
- package/src/codemode-presentation-output.ts +16 -0
- package/src/codemode-runtime.ts +24 -0
- package/src/codemode-session-coordinator.ts +1297 -0
- package/src/codemode-session-files.ts +80 -0
- package/src/codemode-tool-catalog.ts +350 -0
- package/src/codemode-tool-contract.ts +480 -0
- package/src/codemode-tool-exposure.ts +159 -0
- package/src/codemode-tool-rendering.ts +487 -0
- package/src/codemode-worker-protocol.ts +480 -0
- package/src/codemode-worker.ts +1092 -0
- package/src/index.ts +1 -0
- package/src/pi-agent-session-capture.ts +157 -0
- package/src/pi-codemode-extension.ts +469 -0
- package/src/pi-codemode-settings.ts +168 -0
- package/src/pi-tool-bridge.ts +744 -0
|
@@ -0,0 +1,612 @@
|
|
|
1
|
+
import ts from "typescript";
|
|
2
|
+
|
|
3
|
+
/** Successful pure transform output consumed only by the worker-owned Notebook runtime. */
|
|
4
|
+
export type TransformedCodeModeCell = {
|
|
5
|
+
/** TypeScript body with only generated declaration operations rewritten. */
|
|
6
|
+
readonly source: string;
|
|
7
|
+
/** Placeholder the worker replaces with an inaccessible per-Cell helper identifier. */
|
|
8
|
+
readonly internalIdentifierPlaceholder: string;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
/** Expected parse or unsupported-syntax failure returned at the Cell boundary. */
|
|
12
|
+
export type CodeModeCellTransformError = {
|
|
13
|
+
readonly code: "syntax" | "unsupported-syntax";
|
|
14
|
+
readonly message: string;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
/** Result of parsing and transforming one Cell without evaluating guest TypeScript. */
|
|
18
|
+
export type CodeModeCellTransformResult =
|
|
19
|
+
| { readonly ok: true; readonly cell: TransformedCodeModeCell }
|
|
20
|
+
| { readonly ok: false; readonly error: CodeModeCellTransformError };
|
|
21
|
+
|
|
22
|
+
type SourceEdit = {
|
|
23
|
+
readonly start: number;
|
|
24
|
+
readonly end: number;
|
|
25
|
+
readonly replacement: string;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
type CodeModeVariableKind = "const" | "let" | "var";
|
|
29
|
+
type UnsupportedModuleSyntax = "dynamic-import" | "import-meta" | "static-module";
|
|
30
|
+
type ParsedTypeScriptSourceFile = ts.SourceFile & {
|
|
31
|
+
readonly parseDiagnostics: readonly ts.DiagnosticWithLocation[];
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const INTERNAL_IDENTIFIER_PREFIX = "__piCodeModeCellInternal";
|
|
35
|
+
|
|
36
|
+
function chooseInternalIdentifier(script: string): string {
|
|
37
|
+
let suffix = 0;
|
|
38
|
+
let candidate = INTERNAL_IDENTIFIER_PREFIX;
|
|
39
|
+
while (script.includes(candidate)) {
|
|
40
|
+
suffix += 1;
|
|
41
|
+
candidate = `${INTERNAL_IDENTIFIER_PREFIX}${suffix}`;
|
|
42
|
+
}
|
|
43
|
+
return candidate;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function applySourceEdits(script: string, edits: readonly SourceEdit[]): string {
|
|
47
|
+
let transformed = script;
|
|
48
|
+
const descendingEdits = [...edits].sort(
|
|
49
|
+
(left, right) => right.start - left.start || right.end - left.end,
|
|
50
|
+
);
|
|
51
|
+
for (const edit of descendingEdits) {
|
|
52
|
+
transformed = `${transformed.slice(0, edit.start)}${edit.replacement}${transformed.slice(edit.end)}`;
|
|
53
|
+
}
|
|
54
|
+
return transformed;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function nodeSource(node: ts.Node, sourceFile: ts.SourceFile, script: string): string {
|
|
58
|
+
return script.slice(node.getStart(sourceFile), node.end);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function collectBindingNames(name: ts.BindingName, names: string[]): void {
|
|
62
|
+
if (ts.isIdentifier(name)) {
|
|
63
|
+
if (!names.includes(name.text)) names.push(name.text);
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
for (const element of name.elements) {
|
|
67
|
+
if (!ts.isOmittedExpression(element)) collectBindingNames(element.name, names);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function renderPropertyName(
|
|
72
|
+
name: ts.PropertyName,
|
|
73
|
+
sourceFile: ts.SourceFile,
|
|
74
|
+
script: string,
|
|
75
|
+
): string {
|
|
76
|
+
return ts.isComputedPropertyName(name)
|
|
77
|
+
? `[${nodeSource(name.expression, sourceFile, script)}]`
|
|
78
|
+
: nodeSource(name, sourceFile, script);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function renderBindingElementAssignmentTarget(
|
|
82
|
+
element: ts.BindingElement,
|
|
83
|
+
sourceFile: ts.SourceFile,
|
|
84
|
+
script: string,
|
|
85
|
+
internalIdentifier: string,
|
|
86
|
+
includePropertyName: boolean,
|
|
87
|
+
): string {
|
|
88
|
+
const target = renderBindingAssignmentTarget(
|
|
89
|
+
element.name,
|
|
90
|
+
sourceFile,
|
|
91
|
+
script,
|
|
92
|
+
internalIdentifier,
|
|
93
|
+
);
|
|
94
|
+
const initializedTarget =
|
|
95
|
+
element.initializer === undefined
|
|
96
|
+
? target
|
|
97
|
+
: `${target} = ${nodeSource(element.initializer, sourceFile, script)}`;
|
|
98
|
+
if (element.dotDotDotToken !== undefined) return `...${initializedTarget}`;
|
|
99
|
+
if (!includePropertyName) return initializedTarget;
|
|
100
|
+
const propertyName =
|
|
101
|
+
element.propertyName ?? (ts.isIdentifier(element.name) ? element.name : undefined);
|
|
102
|
+
return propertyName === undefined
|
|
103
|
+
? initializedTarget
|
|
104
|
+
: `${renderPropertyName(propertyName, sourceFile, script)}: ${initializedTarget}`;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function renderBindingAssignmentTarget(
|
|
108
|
+
name: ts.BindingName,
|
|
109
|
+
sourceFile: ts.SourceFile,
|
|
110
|
+
script: string,
|
|
111
|
+
internalIdentifier: string,
|
|
112
|
+
): string {
|
|
113
|
+
if (ts.isIdentifier(name)) {
|
|
114
|
+
return `${internalIdentifier}.init[${JSON.stringify(name.text)}]`;
|
|
115
|
+
}
|
|
116
|
+
if (ts.isArrayBindingPattern(name)) {
|
|
117
|
+
return `[${name.elements
|
|
118
|
+
.map((element) =>
|
|
119
|
+
ts.isOmittedExpression(element)
|
|
120
|
+
? ""
|
|
121
|
+
: renderBindingElementAssignmentTarget(
|
|
122
|
+
element,
|
|
123
|
+
sourceFile,
|
|
124
|
+
script,
|
|
125
|
+
internalIdentifier,
|
|
126
|
+
false,
|
|
127
|
+
),
|
|
128
|
+
)
|
|
129
|
+
.join(", ")}]`;
|
|
130
|
+
}
|
|
131
|
+
return `{ ${name.elements
|
|
132
|
+
.map((element) =>
|
|
133
|
+
renderBindingElementAssignmentTarget(element, sourceFile, script, internalIdentifier, true),
|
|
134
|
+
)
|
|
135
|
+
.join(", ")} }`;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function variableKind(declarationList: ts.VariableDeclarationList): CodeModeVariableKind {
|
|
139
|
+
if ((declarationList.flags & ts.NodeFlags.Const) !== 0) return "const";
|
|
140
|
+
if ((declarationList.flags & ts.NodeFlags.Let) !== 0) return "let";
|
|
141
|
+
return "var";
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function hasUsingDeclaration(declarationList: ts.VariableDeclarationList): boolean {
|
|
145
|
+
return (declarationList.flags & ts.NodeFlags.Using) !== 0;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function renderVariableDeclaration(
|
|
149
|
+
declaration: ts.VariableDeclaration,
|
|
150
|
+
declarationList: ts.VariableDeclarationList,
|
|
151
|
+
sourceFile: ts.SourceFile,
|
|
152
|
+
script: string,
|
|
153
|
+
internalIdentifier: string,
|
|
154
|
+
initializerOverride?: string,
|
|
155
|
+
): string {
|
|
156
|
+
const kind = variableKind(declarationList);
|
|
157
|
+
if (
|
|
158
|
+
kind === "var" &&
|
|
159
|
+
declaration.initializer === undefined &&
|
|
160
|
+
initializerOverride === undefined
|
|
161
|
+
) {
|
|
162
|
+
return "void 0";
|
|
163
|
+
}
|
|
164
|
+
const names: string[] = [];
|
|
165
|
+
collectBindingNames(declaration.name, names);
|
|
166
|
+
const entries = names.map((name) => `[${JSON.stringify(name)}, ${JSON.stringify(kind)}]`);
|
|
167
|
+
const rawInitializer =
|
|
168
|
+
initializerOverride ??
|
|
169
|
+
(declaration.initializer === undefined
|
|
170
|
+
? "undefined"
|
|
171
|
+
: nodeSource(declaration.initializer, sourceFile, script));
|
|
172
|
+
const initializer =
|
|
173
|
+
declaration.type === undefined
|
|
174
|
+
? rawInitializer
|
|
175
|
+
: `(${rawInitializer} as ${nodeSource(declaration.type, sourceFile, script)})`;
|
|
176
|
+
const target = renderBindingAssignmentTarget(
|
|
177
|
+
declaration.name,
|
|
178
|
+
sourceFile,
|
|
179
|
+
script,
|
|
180
|
+
internalIdentifier,
|
|
181
|
+
);
|
|
182
|
+
return `await ${internalIdentifier}.declare([${entries.join(", ")}], async () => { (${target} = ${initializer}); })`;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function renderVariableDeclarationList(
|
|
186
|
+
declarationList: ts.VariableDeclarationList,
|
|
187
|
+
sourceFile: ts.SourceFile,
|
|
188
|
+
script: string,
|
|
189
|
+
internalIdentifier: string,
|
|
190
|
+
): string[] {
|
|
191
|
+
return declarationList.declarations.map((declaration) =>
|
|
192
|
+
renderVariableDeclaration(declaration, declarationList, sourceFile, script, internalIdentifier),
|
|
193
|
+
);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function renderFunctionDeclaration(
|
|
197
|
+
declaration: ts.FunctionDeclaration,
|
|
198
|
+
sourceFile: ts.SourceFile,
|
|
199
|
+
script: string,
|
|
200
|
+
internalIdentifier: string,
|
|
201
|
+
): string | undefined {
|
|
202
|
+
if (declaration.name === undefined || declaration.body === undefined) return undefined;
|
|
203
|
+
const name = declaration.name.text;
|
|
204
|
+
const declarationStart = declaration.getStart(sourceFile);
|
|
205
|
+
const anonymousFunction = `${script.slice(declarationStart, declaration.name.getStart(sourceFile))}${script.slice(declaration.name.end, declaration.end)}`;
|
|
206
|
+
return `await ${internalIdentifier}.declare([[${JSON.stringify(name)}, "function"]], async () => { ${internalIdentifier}.init[${JSON.stringify(name)}] = ${anonymousFunction}; });`;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
function renderClassDeclaration(
|
|
210
|
+
declaration: ts.ClassDeclaration,
|
|
211
|
+
sourceFile: ts.SourceFile,
|
|
212
|
+
script: string,
|
|
213
|
+
internalIdentifier: string,
|
|
214
|
+
): string | undefined {
|
|
215
|
+
if (declaration.name === undefined) return undefined;
|
|
216
|
+
const name = declaration.name.text;
|
|
217
|
+
return `await ${internalIdentifier}.declare([[${JSON.stringify(name)}, "class"]], async () => { ${internalIdentifier}.init[${JSON.stringify(name)}] = ${nodeSource(declaration, sourceFile, script)}; });`;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function collectDeclarationListBindingNames(
|
|
221
|
+
declarationList: ts.VariableDeclarationList,
|
|
222
|
+
names: string[],
|
|
223
|
+
): void {
|
|
224
|
+
for (const declaration of declarationList.declarations) {
|
|
225
|
+
collectBindingNames(declaration.name, names);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
function addLoopBodyDeclaration(
|
|
230
|
+
body: ts.Statement,
|
|
231
|
+
declaration: string,
|
|
232
|
+
sourceFile: ts.SourceFile,
|
|
233
|
+
edits: SourceEdit[],
|
|
234
|
+
): void {
|
|
235
|
+
if (ts.isBlock(body)) {
|
|
236
|
+
edits.push({
|
|
237
|
+
start: body.getStart(sourceFile) + 1,
|
|
238
|
+
end: body.getStart(sourceFile) + 1,
|
|
239
|
+
replacement: `\n${declaration};\n`,
|
|
240
|
+
});
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
edits.push({
|
|
244
|
+
start: body.getStart(sourceFile),
|
|
245
|
+
end: body.getStart(sourceFile),
|
|
246
|
+
replacement: `{ ${declaration}; `,
|
|
247
|
+
});
|
|
248
|
+
edits.push({ start: body.end, end: body.end, replacement: " }" });
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
function transformForIterationDeclaration(
|
|
252
|
+
statement: ts.ForInStatement | ts.ForOfStatement,
|
|
253
|
+
declarationList: ts.VariableDeclarationList,
|
|
254
|
+
sourceFile: ts.SourceFile,
|
|
255
|
+
script: string,
|
|
256
|
+
internalIdentifier: string,
|
|
257
|
+
edits: SourceEdit[],
|
|
258
|
+
): void {
|
|
259
|
+
const declaration = declarationList.declarations[0];
|
|
260
|
+
if (declaration === undefined) {
|
|
261
|
+
throw new Error("Pi CodeMode: TypeScript parser returned an empty loop declaration");
|
|
262
|
+
}
|
|
263
|
+
const iterationIdentifier = `${internalIdentifier}Iteration`;
|
|
264
|
+
edits.push({
|
|
265
|
+
start: declarationList.getStart(sourceFile),
|
|
266
|
+
end: declarationList.end,
|
|
267
|
+
replacement: `const ${iterationIdentifier}`,
|
|
268
|
+
});
|
|
269
|
+
addLoopBodyDeclaration(
|
|
270
|
+
statement.statement,
|
|
271
|
+
renderVariableDeclaration(
|
|
272
|
+
declaration,
|
|
273
|
+
declarationList,
|
|
274
|
+
sourceFile,
|
|
275
|
+
script,
|
|
276
|
+
internalIdentifier,
|
|
277
|
+
iterationIdentifier,
|
|
278
|
+
),
|
|
279
|
+
sourceFile,
|
|
280
|
+
edits,
|
|
281
|
+
);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
function collectNestedProgramVarEdits(
|
|
285
|
+
statement: ts.Statement,
|
|
286
|
+
sourceFile: ts.SourceFile,
|
|
287
|
+
script: string,
|
|
288
|
+
internalIdentifier: string,
|
|
289
|
+
hoistedVarNames: string[],
|
|
290
|
+
edits: SourceEdit[],
|
|
291
|
+
): void {
|
|
292
|
+
const collectNested = (nestedStatement: ts.Statement): void =>
|
|
293
|
+
collectNestedProgramVarEdits(
|
|
294
|
+
nestedStatement,
|
|
295
|
+
sourceFile,
|
|
296
|
+
script,
|
|
297
|
+
internalIdentifier,
|
|
298
|
+
hoistedVarNames,
|
|
299
|
+
edits,
|
|
300
|
+
);
|
|
301
|
+
|
|
302
|
+
if (ts.isVariableStatement(statement)) {
|
|
303
|
+
const declarationList = statement.declarationList;
|
|
304
|
+
if (variableKind(declarationList) === "var") {
|
|
305
|
+
collectDeclarationListBindingNames(declarationList, hoistedVarNames);
|
|
306
|
+
const declarations = renderVariableDeclarationList(
|
|
307
|
+
declarationList,
|
|
308
|
+
sourceFile,
|
|
309
|
+
script,
|
|
310
|
+
internalIdentifier,
|
|
311
|
+
);
|
|
312
|
+
edits.push({
|
|
313
|
+
start: statement.getStart(sourceFile),
|
|
314
|
+
end: statement.end,
|
|
315
|
+
replacement: `{ ${declarations.join("; ")}; }`,
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
return;
|
|
319
|
+
}
|
|
320
|
+
if (ts.isBlock(statement)) {
|
|
321
|
+
for (const child of statement.statements) collectNested(child);
|
|
322
|
+
return;
|
|
323
|
+
}
|
|
324
|
+
if (ts.isIfStatement(statement)) {
|
|
325
|
+
collectNested(statement.thenStatement);
|
|
326
|
+
if (statement.elseStatement !== undefined) collectNested(statement.elseStatement);
|
|
327
|
+
return;
|
|
328
|
+
}
|
|
329
|
+
if (ts.isLabeledStatement(statement)) {
|
|
330
|
+
collectNested(statement.statement);
|
|
331
|
+
return;
|
|
332
|
+
}
|
|
333
|
+
if (ts.isSwitchStatement(statement)) {
|
|
334
|
+
for (const clause of statement.caseBlock.clauses) {
|
|
335
|
+
for (const child of clause.statements) collectNested(child);
|
|
336
|
+
}
|
|
337
|
+
return;
|
|
338
|
+
}
|
|
339
|
+
if (ts.isTryStatement(statement)) {
|
|
340
|
+
collectNested(statement.tryBlock);
|
|
341
|
+
if (statement.catchClause !== undefined) collectNested(statement.catchClause.block);
|
|
342
|
+
if (statement.finallyBlock !== undefined) collectNested(statement.finallyBlock);
|
|
343
|
+
return;
|
|
344
|
+
}
|
|
345
|
+
if (ts.isWhileStatement(statement) || ts.isDoStatement(statement)) {
|
|
346
|
+
collectNested(statement.statement);
|
|
347
|
+
return;
|
|
348
|
+
}
|
|
349
|
+
if (ts.isForStatement(statement)) {
|
|
350
|
+
const initializer = statement.initializer;
|
|
351
|
+
if (
|
|
352
|
+
initializer !== undefined &&
|
|
353
|
+
ts.isVariableDeclarationList(initializer) &&
|
|
354
|
+
variableKind(initializer) === "var"
|
|
355
|
+
) {
|
|
356
|
+
collectDeclarationListBindingNames(initializer, hoistedVarNames);
|
|
357
|
+
const declarations = renderVariableDeclarationList(
|
|
358
|
+
initializer,
|
|
359
|
+
sourceFile,
|
|
360
|
+
script,
|
|
361
|
+
internalIdentifier,
|
|
362
|
+
);
|
|
363
|
+
edits.push({
|
|
364
|
+
start: initializer.getStart(sourceFile),
|
|
365
|
+
end: initializer.end,
|
|
366
|
+
replacement: `(${declarations.join(", ")})`,
|
|
367
|
+
});
|
|
368
|
+
}
|
|
369
|
+
collectNested(statement.statement);
|
|
370
|
+
return;
|
|
371
|
+
}
|
|
372
|
+
if (ts.isForInStatement(statement) || ts.isForOfStatement(statement)) {
|
|
373
|
+
const initializer = statement.initializer;
|
|
374
|
+
if (ts.isVariableDeclarationList(initializer) && variableKind(initializer) === "var") {
|
|
375
|
+
collectDeclarationListBindingNames(initializer, hoistedVarNames);
|
|
376
|
+
transformForIterationDeclaration(
|
|
377
|
+
statement,
|
|
378
|
+
initializer,
|
|
379
|
+
sourceFile,
|
|
380
|
+
script,
|
|
381
|
+
internalIdentifier,
|
|
382
|
+
edits,
|
|
383
|
+
);
|
|
384
|
+
}
|
|
385
|
+
collectNested(statement.statement);
|
|
386
|
+
}
|
|
387
|
+
// Functions, classes, modules, and source `with` introduce boundaries whose `var`
|
|
388
|
+
// declarations are deliberately Cell-local rather than Program-scope Notebook Bindings.
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
function hasModifier(node: ts.Node, kind: ts.SyntaxKind): boolean {
|
|
392
|
+
return (
|
|
393
|
+
ts.canHaveModifiers(node) && ts.getModifiers(node)?.some((item) => item.kind === kind) === true
|
|
394
|
+
);
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
function findUnsupportedModuleSyntax(
|
|
398
|
+
sourceFile: ts.SourceFile,
|
|
399
|
+
): UnsupportedModuleSyntax | undefined {
|
|
400
|
+
let found: UnsupportedModuleSyntax | undefined;
|
|
401
|
+
const visit = (node: ts.Node): void => {
|
|
402
|
+
if (found !== undefined) return;
|
|
403
|
+
if (
|
|
404
|
+
ts.isImportDeclaration(node) ||
|
|
405
|
+
ts.isImportEqualsDeclaration(node) ||
|
|
406
|
+
ts.isExportDeclaration(node) ||
|
|
407
|
+
ts.isExportAssignment(node) ||
|
|
408
|
+
ts.isImportTypeNode(node) ||
|
|
409
|
+
hasModifier(node, ts.SyntaxKind.ExportKeyword) ||
|
|
410
|
+
hasModifier(node, ts.SyntaxKind.DefaultKeyword)
|
|
411
|
+
) {
|
|
412
|
+
found = "static-module";
|
|
413
|
+
return;
|
|
414
|
+
}
|
|
415
|
+
if (ts.isCallExpression(node) && node.expression.kind === ts.SyntaxKind.ImportKeyword) {
|
|
416
|
+
found = "dynamic-import";
|
|
417
|
+
return;
|
|
418
|
+
}
|
|
419
|
+
if (ts.isMetaProperty(node) && node.keywordToken === ts.SyntaxKind.ImportKeyword) {
|
|
420
|
+
found = "import-meta";
|
|
421
|
+
return;
|
|
422
|
+
}
|
|
423
|
+
ts.forEachChild(node, visit);
|
|
424
|
+
};
|
|
425
|
+
visit(sourceFile);
|
|
426
|
+
return found;
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
function syntaxErrorFromDiagnostic(
|
|
430
|
+
diagnostic: ts.DiagnosticWithLocation,
|
|
431
|
+
): CodeModeCellTransformError {
|
|
432
|
+
return {
|
|
433
|
+
code: "syntax",
|
|
434
|
+
message: ts.flattenDiagnosticMessageText(diagnostic.messageText, "\n"),
|
|
435
|
+
};
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
function parseCodeModeProgram(
|
|
439
|
+
script: string,
|
|
440
|
+
):
|
|
441
|
+
| { readonly ok: true; readonly sourceFile: ts.SourceFile }
|
|
442
|
+
| { readonly ok: false; readonly error: CodeModeCellTransformError } {
|
|
443
|
+
const sourceFile = ts.createSourceFile(
|
|
444
|
+
"codemode-cell.ts",
|
|
445
|
+
script,
|
|
446
|
+
ts.ScriptTarget.Latest,
|
|
447
|
+
true,
|
|
448
|
+
ts.ScriptKind.TS,
|
|
449
|
+
);
|
|
450
|
+
// SAFETY: TypeScript's createSourceFile result owns parseDiagnostics at runtime, but the public SourceFile contract omits this parser-owned field.
|
|
451
|
+
const parsedSourceFile = sourceFile as ParsedTypeScriptSourceFile;
|
|
452
|
+
const diagnostic = parsedSourceFile.parseDiagnostics[0];
|
|
453
|
+
if (diagnostic !== undefined) return { ok: false, error: syntaxErrorFromDiagnostic(diagnostic) };
|
|
454
|
+
|
|
455
|
+
const unsupported = findUnsupportedModuleSyntax(sourceFile);
|
|
456
|
+
if (unsupported === "static-module") {
|
|
457
|
+
return {
|
|
458
|
+
ok: false,
|
|
459
|
+
error: {
|
|
460
|
+
code: "unsupported-syntax",
|
|
461
|
+
message: "CodeMode Cell imports and exports are not supported",
|
|
462
|
+
},
|
|
463
|
+
};
|
|
464
|
+
}
|
|
465
|
+
if (unsupported === "dynamic-import") {
|
|
466
|
+
return {
|
|
467
|
+
ok: false,
|
|
468
|
+
error: {
|
|
469
|
+
code: "unsupported-syntax",
|
|
470
|
+
message: "CodeMode Cell dynamic import is not supported",
|
|
471
|
+
},
|
|
472
|
+
};
|
|
473
|
+
}
|
|
474
|
+
if (unsupported === "import-meta") {
|
|
475
|
+
return {
|
|
476
|
+
ok: false,
|
|
477
|
+
error: {
|
|
478
|
+
code: "unsupported-syntax",
|
|
479
|
+
message: "CodeMode Cell import.meta is not supported",
|
|
480
|
+
},
|
|
481
|
+
};
|
|
482
|
+
}
|
|
483
|
+
return { ok: true, sourceFile };
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
function isDirective(statement: ts.Statement): boolean {
|
|
487
|
+
return ts.isExpressionStatement(statement) && ts.isStringLiteral(statement.expression);
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
function directVariableStatementError(
|
|
491
|
+
statement: ts.VariableStatement,
|
|
492
|
+
): CodeModeCellTransformError | undefined {
|
|
493
|
+
if (hasUsingDeclaration(statement.declarationList)) {
|
|
494
|
+
return {
|
|
495
|
+
code: "unsupported-syntax",
|
|
496
|
+
message: "CodeMode Cell top-level using declarations are not supported",
|
|
497
|
+
};
|
|
498
|
+
}
|
|
499
|
+
const kind = variableKind(statement.declarationList);
|
|
500
|
+
if (
|
|
501
|
+
kind === "const" &&
|
|
502
|
+
statement.declarationList.declarations.some(
|
|
503
|
+
(declaration) => declaration.initializer === undefined,
|
|
504
|
+
)
|
|
505
|
+
) {
|
|
506
|
+
return { code: "syntax", message: "CodeMode Cell const declarations require an initializer" };
|
|
507
|
+
}
|
|
508
|
+
return undefined;
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
/** Parses TypeScript and rewrites one Cell into the explicit persistent Notebook Binding dialect. */
|
|
512
|
+
export function transformCodeModeCell(script: string): CodeModeCellTransformResult {
|
|
513
|
+
const parsed = parseCodeModeProgram(script);
|
|
514
|
+
if (!parsed.ok) return parsed;
|
|
515
|
+
const sourceFile = parsed.sourceFile;
|
|
516
|
+
const internalIdentifier = chooseInternalIdentifier(script);
|
|
517
|
+
const edits: SourceEdit[] = [];
|
|
518
|
+
const finalStatement = sourceFile.statements.at(-1);
|
|
519
|
+
if (finalStatement !== undefined && ts.isExpressionStatement(finalStatement)) {
|
|
520
|
+
edits.push({
|
|
521
|
+
start: finalStatement.getStart(sourceFile),
|
|
522
|
+
end: finalStatement.end,
|
|
523
|
+
replacement: `return (${nodeSource(finalStatement.expression, sourceFile, script)});`,
|
|
524
|
+
});
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
const hoistedVarNames: string[] = [];
|
|
528
|
+
const functionPrologue: string[] = [];
|
|
529
|
+
for (const statement of sourceFile.statements) {
|
|
530
|
+
if (ts.isVariableStatement(statement)) {
|
|
531
|
+
const error = directVariableStatementError(statement);
|
|
532
|
+
if (error !== undefined) return { ok: false, error };
|
|
533
|
+
const declarationList = statement.declarationList;
|
|
534
|
+
if (variableKind(declarationList) === "var") {
|
|
535
|
+
collectDeclarationListBindingNames(declarationList, hoistedVarNames);
|
|
536
|
+
}
|
|
537
|
+
edits.push({
|
|
538
|
+
start: statement.getStart(sourceFile),
|
|
539
|
+
end: statement.end,
|
|
540
|
+
replacement: `${renderVariableDeclarationList(
|
|
541
|
+
declarationList,
|
|
542
|
+
sourceFile,
|
|
543
|
+
script,
|
|
544
|
+
internalIdentifier,
|
|
545
|
+
).join("; ")};`,
|
|
546
|
+
});
|
|
547
|
+
continue;
|
|
548
|
+
}
|
|
549
|
+
if (ts.isFunctionDeclaration(statement)) {
|
|
550
|
+
const declaration = renderFunctionDeclaration(
|
|
551
|
+
statement,
|
|
552
|
+
sourceFile,
|
|
553
|
+
script,
|
|
554
|
+
internalIdentifier,
|
|
555
|
+
);
|
|
556
|
+
if (declaration !== undefined) {
|
|
557
|
+
functionPrologue.push(declaration);
|
|
558
|
+
edits.push({
|
|
559
|
+
start: statement.getStart(sourceFile),
|
|
560
|
+
end: statement.end,
|
|
561
|
+
replacement: "void 0;",
|
|
562
|
+
});
|
|
563
|
+
}
|
|
564
|
+
continue;
|
|
565
|
+
}
|
|
566
|
+
if (ts.isClassDeclaration(statement)) {
|
|
567
|
+
const declaration = renderClassDeclaration(statement, sourceFile, script, internalIdentifier);
|
|
568
|
+
if (declaration !== undefined) {
|
|
569
|
+
edits.push({
|
|
570
|
+
start: statement.getStart(sourceFile),
|
|
571
|
+
end: statement.end,
|
|
572
|
+
replacement: declaration,
|
|
573
|
+
});
|
|
574
|
+
}
|
|
575
|
+
continue;
|
|
576
|
+
}
|
|
577
|
+
collectNestedProgramVarEdits(
|
|
578
|
+
statement,
|
|
579
|
+
sourceFile,
|
|
580
|
+
script,
|
|
581
|
+
internalIdentifier,
|
|
582
|
+
hoistedVarNames,
|
|
583
|
+
edits,
|
|
584
|
+
);
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
const prologue: string[] = [];
|
|
588
|
+
if (hoistedVarNames.length > 0) {
|
|
589
|
+
prologue.push(`${internalIdentifier}.hoistVars(${JSON.stringify(hoistedVarNames)});`);
|
|
590
|
+
}
|
|
591
|
+
prologue.push(...functionPrologue);
|
|
592
|
+
if (prologue.length > 0) {
|
|
593
|
+
let insertionPosition = 0;
|
|
594
|
+
for (const statement of sourceFile.statements) {
|
|
595
|
+
if (!isDirective(statement)) break;
|
|
596
|
+
insertionPosition = statement.end;
|
|
597
|
+
}
|
|
598
|
+
edits.push({
|
|
599
|
+
start: insertionPosition,
|
|
600
|
+
end: insertionPosition,
|
|
601
|
+
replacement: `\n${prologue.join("\n")}\n`,
|
|
602
|
+
});
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
return {
|
|
606
|
+
ok: true,
|
|
607
|
+
cell: {
|
|
608
|
+
source: applySourceEdits(script, edits),
|
|
609
|
+
internalIdentifierPlaceholder: internalIdentifier,
|
|
610
|
+
},
|
|
611
|
+
};
|
|
612
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { createRequire } from "node:module";
|
|
2
|
+
import { dirname, resolve } from "node:path";
|
|
3
|
+
|
|
4
|
+
/** Fully resolved Deno command for one persistent CodeMode worker process. */
|
|
5
|
+
export type CodeModeDenoLaunch = {
|
|
6
|
+
readonly command: string;
|
|
7
|
+
readonly args: readonly string[];
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
const denoPlatformPackages = new Map([
|
|
11
|
+
["darwin-arm64", "@deno/darwin-arm64"],
|
|
12
|
+
["darwin-x64", "@deno/darwin-x64"],
|
|
13
|
+
["linux-arm64", "@deno/linux-arm64-glibc"],
|
|
14
|
+
["linux-x64", "@deno/linux-x64-glibc"],
|
|
15
|
+
["win32-arm64", "@deno/win32-arm64"],
|
|
16
|
+
["win32-x64", "@deno/win32-x64"],
|
|
17
|
+
]);
|
|
18
|
+
|
|
19
|
+
/** Resolves the pinned installed Deno binary and denied-permission worker command. */
|
|
20
|
+
export function resolveCodeModeDenoLaunch(
|
|
21
|
+
workerPath: string,
|
|
22
|
+
sessionId: string,
|
|
23
|
+
): CodeModeDenoLaunch {
|
|
24
|
+
const packageDirectory = dirname(dirname(workerPath));
|
|
25
|
+
const packageRequire = createRequire(resolve(packageDirectory, "package.json"));
|
|
26
|
+
const denoPackageJson = packageRequire.resolve("deno/package.json");
|
|
27
|
+
const denoRequire = createRequire(denoPackageJson);
|
|
28
|
+
const platformPackage = denoPlatformPackages.get(`${process.platform}-${process.arch}`);
|
|
29
|
+
if (platformPackage === undefined) {
|
|
30
|
+
throw new Error(
|
|
31
|
+
`Pi CodeMode: Deno process does not support ${process.platform}-${process.arch}`,
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
const denoPlatformDirectory = dirname(denoRequire.resolve(`${platformPackage}/package.json`));
|
|
35
|
+
return {
|
|
36
|
+
command: resolve(denoPlatformDirectory, process.platform === "win32" ? "deno.exe" : "deno"),
|
|
37
|
+
args: [
|
|
38
|
+
"run",
|
|
39
|
+
"--quiet",
|
|
40
|
+
"--no-prompt",
|
|
41
|
+
"--no-config",
|
|
42
|
+
"--no-lock",
|
|
43
|
+
"--cached-only",
|
|
44
|
+
"--no-npm",
|
|
45
|
+
"--node-modules-dir=none",
|
|
46
|
+
"--v8-flags=--max-old-space-size=128,--stack-size=1024",
|
|
47
|
+
"--deny-read",
|
|
48
|
+
"--deny-write",
|
|
49
|
+
"--deny-net",
|
|
50
|
+
"--deny-env",
|
|
51
|
+
"--deny-sys",
|
|
52
|
+
"--deny-run",
|
|
53
|
+
"--deny-ffi",
|
|
54
|
+
"--deny-import",
|
|
55
|
+
workerPath,
|
|
56
|
+
sessionId,
|
|
57
|
+
],
|
|
58
|
+
};
|
|
59
|
+
}
|