@code-yeongyu/senpi-codemode 2026.7.25-2
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/CHANGELOG.md +250 -0
- package/LICENSE +22 -0
- package/README.md +161 -0
- package/package.json +58 -0
- package/src/bridge/http-server.ts +236 -0
- package/src/bridge/protocol.ts +198 -0
- package/src/bridge/reserved.ts +9 -0
- package/src/bridges/agent-bridge.ts +197 -0
- package/src/bridges/output-bridge.ts +96 -0
- package/src/bridges/schema-injection.ts +3 -0
- package/src/codemode/runtime.ts +258 -0
- package/src/codemode/tools.ts +106 -0
- package/src/completion/handler.ts +192 -0
- package/src/completion/tool-bridge.ts +55 -0
- package/src/config/settings.ts +215 -0
- package/src/extension/runtime-factory.ts +114 -0
- package/src/extension/session-manager-proxy.ts +116 -0
- package/src/extension/session-manager.ts +215 -0
- package/src/host-sdk.ts +1 -0
- package/src/index.ts +181 -0
- package/src/interpreters/detect.ts +161 -0
- package/src/kernels/jl/kernel.ts +37 -0
- package/src/kernels/jl/prelude.jl +283 -0
- package/src/kernels/jl/runner.jl +327 -0
- package/src/kernels/js/context-manager.ts +296 -0
- package/src/kernels/js/inline-worker-entry.js +23 -0
- package/src/kernels/js/inline-worker.ts +15 -0
- package/src/kernels/js/kernel-contract.ts +38 -0
- package/src/kernels/js/local-module-loader.ts +108 -0
- package/src/kernels/js/prelude.ts +15 -0
- package/src/kernels/js/rewrite-imports.ts +164 -0
- package/src/kernels/js/run-queue.ts +82 -0
- package/src/kernels/js/worker-core.d.ts +18 -0
- package/src/kernels/js/worker-core.js +94 -0
- package/src/kernels/js/worker-entry.js +23 -0
- package/src/kernels/js/worker-host.ts +117 -0
- package/src/kernels/js/worker-indirect-eval.js +88 -0
- package/src/kernels/js/worker-runtime.js +401 -0
- package/src/kernels/py/kernel-contract.ts +32 -0
- package/src/kernels/py/kernel.ts +290 -0
- package/src/kernels/py/prelude.py +954 -0
- package/src/kernels/py/process.ts +119 -0
- package/src/kernels/py/transport.ts +237 -0
- package/src/kernels/rb/kernel.ts +26 -0
- package/src/kernels/rb/prelude.rb +270 -0
- package/src/kernels/rb/runner.rb +204 -0
- package/src/kernels/shared/subprocess-contract.ts +22 -0
- package/src/kernels/shared/subprocess-kernel.ts +266 -0
- package/src/kernels/shared/subprocess-process.ts +174 -0
- package/src/kernels/shared/subprocess-queue.ts +101 -0
- package/src/kernels/shared/subprocess-run.ts +98 -0
- package/src/output/output-meta.ts +89 -0
- package/src/output/streaming-output.ts +296 -0
- package/src/prompt/eval-prompt.ts +319 -0
- package/src/timeouts/bridge-timeout.ts +16 -0
- package/src/timeouts/idle-timeout.ts +84 -0
- package/src/tool/cell-handler.ts +279 -0
- package/src/tool/eval-tool.ts +285 -0
- package/src/tool/image.ts +274 -0
- package/src/tool/json-tree.ts +247 -0
- package/src/tool/render.ts +876 -0
- package/src/tool/status-events.ts +12 -0
- package/src/tool/types.ts +114 -0
|
@@ -0,0 +1,401 @@
|
|
|
1
|
+
// allow: SIZE_OK — private runtime state and installed globals must stay in one worker module.
|
|
2
|
+
import { mkdir, readFile, stat, writeFile } from "node:fs/promises";
|
|
3
|
+
import { dirname, isAbsolute, join, normalize, resolve, sep } from "node:path";
|
|
4
|
+
import { inspect } from "node:util";
|
|
5
|
+
import { awaitMaybePromise, indirectEval, wrapUserCode } from "./worker-indirect-eval.js";
|
|
6
|
+
|
|
7
|
+
const PREPARED_CELL_PREFIX = "/*senpi:prepared-cell*/";
|
|
8
|
+
const INTERNAL_URL = /^([a-z][a-z0-9+.-]*):\/\/(.*)$/iu;
|
|
9
|
+
const BASE64_STRICT_RE = /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/u;
|
|
10
|
+
const DECIMAL_CSV_RE = /^\d{1,3}(?:,\d{1,3})*$/u;
|
|
11
|
+
|
|
12
|
+
export class JsWorkerRuntime {
|
|
13
|
+
#cwd;
|
|
14
|
+
#parallelPoolWidth;
|
|
15
|
+
#localRoots;
|
|
16
|
+
#env = new Map();
|
|
17
|
+
#hooks = null;
|
|
18
|
+
|
|
19
|
+
constructor(options) {
|
|
20
|
+
this.#cwd = options.cwd;
|
|
21
|
+
this.#parallelPoolWidth = options.parallelPoolWidth;
|
|
22
|
+
this.#localRoots = { ...(options.localRoots ?? {}) };
|
|
23
|
+
if (options.artifactsDir && !this.#localRoots.local) this.#localRoots.local = join(options.artifactsDir, "local");
|
|
24
|
+
this.#installGlobals();
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
async run(code, cellId, hooks) {
|
|
28
|
+
this.#hooks = hooks;
|
|
29
|
+
try {
|
|
30
|
+
let prelude = "";
|
|
31
|
+
let cellCode = code;
|
|
32
|
+
if (code.startsWith(PREPARED_CELL_PREFIX)) {
|
|
33
|
+
const prepared = JSON.parse(code.slice(PREPARED_CELL_PREFIX.length));
|
|
34
|
+
if (!isPlainObject(prepared) || typeof prepared.prelude !== "string" || typeof prepared.code !== "string") throw new Error("Invalid prepared JavaScript cell payload");
|
|
35
|
+
({ prelude, code: cellCode } = prepared);
|
|
36
|
+
}
|
|
37
|
+
if (prelude) indirectEval(prelude, `${cellId}:prelude`);
|
|
38
|
+
return await awaitMaybePromise(indirectEval(wrapUserCode(cellCode), cellId));
|
|
39
|
+
} finally {
|
|
40
|
+
this.#hooks = null;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
#installGlobals() {
|
|
45
|
+
globalThis.print = (...values) => this.#emitText("stdout", `${values.map(formatValue).join(" ")}\n`);
|
|
46
|
+
globalThis.display = value => this.#display(value);
|
|
47
|
+
globalThis.log = message => this.#hooks?.emit({ type: "log", message: String(message) });
|
|
48
|
+
globalThis.phase = title => this.#hooks?.emit({ type: "phase", title: String(title) });
|
|
49
|
+
globalThis.env = (key, value) => this.#envHelper(key, value);
|
|
50
|
+
globalThis.read = async (path, options, ...rest) => await this.#read(path, helperOptions("read", options, rest));
|
|
51
|
+
globalThis.write = async (path, content) => await this.#write(path, content);
|
|
52
|
+
globalThis.output = async (...args) => await this.#output(args);
|
|
53
|
+
globalThis.agent = async (prompt, options, ...rest) => await this.#agent(prompt, options, rest);
|
|
54
|
+
globalThis.parallel = async thunks => await this.#parallel(thunks);
|
|
55
|
+
globalThis.pipeline = async (items, ...stages) => await this.#pipeline(items, stages);
|
|
56
|
+
globalThis.completion = async (prompt, opts) => await this.#callTool("completion", { prompt, opts });
|
|
57
|
+
globalThis.tool = new Proxy(
|
|
58
|
+
{},
|
|
59
|
+
{
|
|
60
|
+
get: (_target, prop) => {
|
|
61
|
+
if (typeof prop !== "string") return undefined;
|
|
62
|
+
return async args => await this.#callTool(prop, args ?? {});
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
);
|
|
66
|
+
globalThis.tools = globalThis.tool;
|
|
67
|
+
const originalLog = console.log.bind(console);
|
|
68
|
+
const originalError = console.error.bind(console);
|
|
69
|
+
const originalStdoutWrite = process.stdout.write;
|
|
70
|
+
const originalStderrWrite = process.stderr.write;
|
|
71
|
+
const routeWrite = (stream, originalWrite, streamName) => {
|
|
72
|
+
const write = originalWrite.bind(stream);
|
|
73
|
+
return (chunk, encoding, callback) => {
|
|
74
|
+
if (!this.#hooks) return write(chunk, encoding, callback);
|
|
75
|
+
const callbackValue = typeof encoding === "function" ? encoding : callback;
|
|
76
|
+
const encodingValue = typeof encoding === "string" ? encoding : undefined;
|
|
77
|
+
this.#emitText(streamName, chunkToString(chunk, encodingValue));
|
|
78
|
+
if (typeof callbackValue === "function") callbackValue();
|
|
79
|
+
return true;
|
|
80
|
+
};
|
|
81
|
+
};
|
|
82
|
+
process.stdout.write = routeWrite(process.stdout, originalStdoutWrite, "stdout");
|
|
83
|
+
process.stderr.write = routeWrite(process.stderr, originalStderrWrite, "stderr");
|
|
84
|
+
console.log = (...values) => this.#emitText("stdout", `${values.map(formatValue).join(" ")}\n`);
|
|
85
|
+
console.error = (...values) => this.#emitText("stderr", `${values.map(formatValue).join(" ")}\n`);
|
|
86
|
+
globalThis.__senpi_restore_console__ = () => {
|
|
87
|
+
console.log = originalLog;
|
|
88
|
+
console.error = originalError;
|
|
89
|
+
process.stdout.write = originalStdoutWrite;
|
|
90
|
+
process.stderr.write = originalStderrWrite;
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
#emitText(stream, data) {
|
|
95
|
+
this.#hooks?.emit({ type: "text", stream, data });
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
#emitStatus(event) {
|
|
99
|
+
this.#hooks?.emit({ type: "status", event });
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
#display(value) {
|
|
103
|
+
if (value && typeof value === "object") {
|
|
104
|
+
if (value.type === "markdown" && typeof value.text === "string") {
|
|
105
|
+
this.#hooks?.emit({ type: "display", mimeType: "text/markdown", dataBase64: encodeBase64(value.text) });
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
if (value.type === "image" && typeof value.mimeType === "string") {
|
|
109
|
+
const dataBase64 = imageBase64(value.data);
|
|
110
|
+
if (dataBase64 !== undefined) {
|
|
111
|
+
this.#hooks?.emit({ type: "display", mimeType: value.mimeType, dataBase64 });
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
this.#emitText(
|
|
115
|
+
"stdout",
|
|
116
|
+
`[display: image dropped — \`data\` must be a base64 string, Uint8Array/Buffer, or ArrayBuffer; got ${describeImageData(value.data)}]\n`,
|
|
117
|
+
);
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
if (typeof value.mimeType === "string" && typeof value.dataBase64 === "string") {
|
|
121
|
+
this.#hooks?.emit({ type: "display", mimeType: value.mimeType, dataBase64: value.dataBase64 });
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
try {
|
|
125
|
+
this.#hooks?.emit({ type: "display", mimeType: "application/json", dataBase64: encodeBase64(JSON.stringify(value)) });
|
|
126
|
+
} catch (error) {
|
|
127
|
+
if (!(error instanceof TypeError)) throw error;
|
|
128
|
+
this.#emitText("stdout", `${inspect(value, { colors: false, depth: 5 })}\n`);
|
|
129
|
+
}
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
this.#emitText("stdout", `${String(value)}\n`);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
#envHelper(key, value) {
|
|
136
|
+
if (key === undefined || key === null || key === "") {
|
|
137
|
+
const merged = Object.fromEntries(Object.entries({ ...process.env, ...Object.fromEntries(this.#env) }).sort());
|
|
138
|
+
this.#emitStatus({ op: "env", count: Object.keys(merged).length, keys: Object.keys(merged).slice(0, 20) });
|
|
139
|
+
return merged;
|
|
140
|
+
}
|
|
141
|
+
const name = String(key);
|
|
142
|
+
if (value !== undefined) {
|
|
143
|
+
const stringValue = String(value);
|
|
144
|
+
this.#env.set(name, stringValue);
|
|
145
|
+
this.#emitStatus({ op: "env", key: name, value: stringValue, action: "set" });
|
|
146
|
+
return stringValue;
|
|
147
|
+
}
|
|
148
|
+
const result = this.#env.get(name) ?? process.env[name];
|
|
149
|
+
this.#emitStatus({ op: "env", key: name, value: result, action: "get" });
|
|
150
|
+
return result;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
async #read(rawPath, options) {
|
|
154
|
+
const path = this.#resolvePath(String(rawPath), "read");
|
|
155
|
+
const info = await stat(path);
|
|
156
|
+
if (info.isDirectory()) throw new Error(`Directory paths are not supported by read(): ${path}`);
|
|
157
|
+
let text = await readFile(path, "utf8");
|
|
158
|
+
const offset = typeof options.offset === "number" ? options.offset : 1;
|
|
159
|
+
const limit = typeof options.limit === "number" ? options.limit : undefined;
|
|
160
|
+
if (offset > 1 || limit !== undefined) {
|
|
161
|
+
const lines = text.split(/\r?\n/u);
|
|
162
|
+
const start = Math.max(0, offset - 1);
|
|
163
|
+
text = lines.slice(start, limit === undefined ? undefined : start + limit).join("\n");
|
|
164
|
+
}
|
|
165
|
+
this.#emitStatus({ op: "read", path, bytes: info.size, chars: text.length });
|
|
166
|
+
return text;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
async #write(rawPath, content) {
|
|
170
|
+
const path = this.#resolvePath(String(rawPath), "write");
|
|
171
|
+
const data = await writeData(content);
|
|
172
|
+
await mkdir(dirname(path), { recursive: true });
|
|
173
|
+
await writeFile(path, data);
|
|
174
|
+
this.#emitStatus({ op: "write", path, bytes: typeof data === "string" ? Buffer.byteLength(data) : data.byteLength });
|
|
175
|
+
return path;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
#resolvePath(rawPath, operation) {
|
|
179
|
+
const match = INTERNAL_URL.exec(rawPath);
|
|
180
|
+
if (!match) return isAbsolute(rawPath) ? normalize(rawPath) : resolve(this.#cwd, rawPath);
|
|
181
|
+
const scheme = match[1].toLowerCase();
|
|
182
|
+
const root = this.#localRoots[scheme];
|
|
183
|
+
if (!root) throw new Error(`Protocol paths are not supported by ${operation}(): ${rawPath}`);
|
|
184
|
+
let relative;
|
|
185
|
+
try {
|
|
186
|
+
relative = decodeURIComponent(match[2].replaceAll("\\", "/"));
|
|
187
|
+
} catch (error) {
|
|
188
|
+
if (error instanceof URIError) throw new Error(`Invalid URL encoding in ${scheme}:// path: ${rawPath}`);
|
|
189
|
+
throw error;
|
|
190
|
+
}
|
|
191
|
+
if (isAbsolute(relative) || relative.split("/").includes("..")) {
|
|
192
|
+
throw new Error(`Path traversal is not allowed in ${scheme}:// URLs: ${rawPath}`);
|
|
193
|
+
}
|
|
194
|
+
const rootPath = resolve(root);
|
|
195
|
+
const path = resolve(rootPath, relative);
|
|
196
|
+
if (path !== rootPath && !path.startsWith(`${rootPath}${sep}`)) throw new Error(`${scheme}:// path escapes its root`);
|
|
197
|
+
return path;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
async #output(args) {
|
|
201
|
+
let ids = args;
|
|
202
|
+
let options = {};
|
|
203
|
+
const last = args.at(-1);
|
|
204
|
+
if (isPlainObject(last)) {
|
|
205
|
+
ids = args.slice(0, -1);
|
|
206
|
+
options = last;
|
|
207
|
+
}
|
|
208
|
+
return await this.#callTool(reservedTool("__senpi_reserved_output_tool__", "output"), {
|
|
209
|
+
ids: ids.map(String),
|
|
210
|
+
...options,
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
async #agent(prompt, options, rest) {
|
|
215
|
+
const parsed = optionsArg({
|
|
216
|
+
name: "agent",
|
|
217
|
+
value: options,
|
|
218
|
+
rest,
|
|
219
|
+
keys: ["agent", "model", "label", "schema", "isolated", "apply", "merge"],
|
|
220
|
+
example: "{ agent, model, label, schema, isolated, apply, merge, handle }",
|
|
221
|
+
});
|
|
222
|
+
const { handle, ...callArgs } = parsed;
|
|
223
|
+
const response = await this.#callTool(reservedTool("__senpi_reserved_agent_tool__", "agent"), {
|
|
224
|
+
prompt: String(prompt),
|
|
225
|
+
...callArgs,
|
|
226
|
+
handle: Boolean(handle),
|
|
227
|
+
});
|
|
228
|
+
const responseRecord = isPlainObject(response) ? response : {};
|
|
229
|
+
const text = Object.hasOwn(responseRecord, "text") ? responseRecord.text : response;
|
|
230
|
+
const output = Object.hasOwn(callArgs, "schema")
|
|
231
|
+
? Object.hasOwn(responseRecord, "data")
|
|
232
|
+
? responseRecord.data
|
|
233
|
+
: JSON.parse(String(text))
|
|
234
|
+
: text;
|
|
235
|
+
if (!handle) return output;
|
|
236
|
+
const details = isPlainObject(responseRecord.details) ? responseRecord.details : responseRecord;
|
|
237
|
+
const id = details.id;
|
|
238
|
+
if (id === undefined || id === null) return { text, output: text, handle: null, id: null, agent: null };
|
|
239
|
+
const node = {
|
|
240
|
+
text,
|
|
241
|
+
output: text,
|
|
242
|
+
handle: details.handle ?? `agent://${id}`,
|
|
243
|
+
id,
|
|
244
|
+
agent: details.agent ?? callArgs.agent ?? null,
|
|
245
|
+
};
|
|
246
|
+
if (Object.hasOwn(callArgs, "schema")) node.data = output;
|
|
247
|
+
for (const key of ["isolated", "patchPath", "branchName", "nestedPatches", "changesApplied", "isolationSummary"]) {
|
|
248
|
+
if (details[key] !== undefined) node[key] = details[key];
|
|
249
|
+
}
|
|
250
|
+
return node;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
async #callTool(toolName, args) {
|
|
254
|
+
const hooks = this.#hooks;
|
|
255
|
+
if (!hooks) throw new Error("tool call outside active JS cell");
|
|
256
|
+
if (
|
|
257
|
+
typeof globalThis.__senpi_timeout_pause_op__ !== "string" ||
|
|
258
|
+
typeof globalThis.__senpi_timeout_resume_op__ !== "string"
|
|
259
|
+
) {
|
|
260
|
+
throw new Error("timeout bridge is unavailable");
|
|
261
|
+
}
|
|
262
|
+
this.#emitStatus({ op: globalThis.__senpi_timeout_pause_op__ });
|
|
263
|
+
try {
|
|
264
|
+
return await hooks.callTool(toolName, args);
|
|
265
|
+
} finally {
|
|
266
|
+
this.#emitStatus({ op: globalThis.__senpi_timeout_resume_op__ });
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
async #parallel(thunks) {
|
|
271
|
+
const list = Array.from(thunks ?? []);
|
|
272
|
+
if (list.length === 0) return [];
|
|
273
|
+
const configuredWidth = Number.isFinite(this.#parallelPoolWidth) ? Math.trunc(this.#parallelPoolWidth) : 1;
|
|
274
|
+
const workerCount = Math.min(Math.max(1, configuredWidth), list.length);
|
|
275
|
+
const results = new Array(list.length);
|
|
276
|
+
let next = 0;
|
|
277
|
+
let firstError;
|
|
278
|
+
let firstErrorIndex = list.length;
|
|
279
|
+
let hasError = false;
|
|
280
|
+
const worker = async () => {
|
|
281
|
+
while (true) {
|
|
282
|
+
const index = next;
|
|
283
|
+
next += 1;
|
|
284
|
+
if (index >= list.length) return;
|
|
285
|
+
try {
|
|
286
|
+
const thunk = list[index];
|
|
287
|
+
if (typeof thunk !== "function") throw new TypeError("parallel() expects an iterable of functions");
|
|
288
|
+
results[index] = await thunk(index);
|
|
289
|
+
} catch (error) {
|
|
290
|
+
if (!hasError || index < firstErrorIndex) {
|
|
291
|
+
hasError = true;
|
|
292
|
+
firstErrorIndex = index;
|
|
293
|
+
firstError = error;
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
};
|
|
298
|
+
await Promise.all(Array.from({ length: workerCount }, worker));
|
|
299
|
+
if (hasError) throw firstError;
|
|
300
|
+
return results;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
async #pipeline(items, stages) {
|
|
304
|
+
let current = Array.from(items ?? []);
|
|
305
|
+
for (const stage of stages) {
|
|
306
|
+
if (typeof stage !== "function") throw new TypeError("pipeline() stages must be functions");
|
|
307
|
+
current = await this.#parallel(current.map(item => async () => await stage(item)));
|
|
308
|
+
}
|
|
309
|
+
return current;
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
function isPlainObject(value) {
|
|
314
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
function optionsArg(options) {
|
|
318
|
+
const { name, value, rest, keys, example } = options;
|
|
319
|
+
if (isPlainObject(value)) {
|
|
320
|
+
if (rest.some(item => item !== undefined && item !== null)) throw new TypeError(`${name}() options cannot mix object and positional forms`);
|
|
321
|
+
return value;
|
|
322
|
+
}
|
|
323
|
+
const values = [value, ...rest];
|
|
324
|
+
for (let index = keys.length; index < values.length; index += 1) {
|
|
325
|
+
if (values[index] !== undefined && values[index] !== null) throw new TypeError(`${name}() accepts ${example}`);
|
|
326
|
+
}
|
|
327
|
+
return Object.fromEntries(keys.flatMap((key, index) => values[index] === undefined || values[index] === null ? [] : [[key, values[index]]]));
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
function helperOptions(name, value, rest) {
|
|
331
|
+
return optionsArg({ name, value, rest, keys: ["offset", "limit"], example: "{ offset, limit }" });
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
function reservedTool(globalName, helperName) {
|
|
335
|
+
const value = globalThis[globalName];
|
|
336
|
+
if (typeof value !== "string") throw new Error(`${helperName}() bridge is unavailable`);
|
|
337
|
+
return value;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
async function writeData(value) {
|
|
341
|
+
if (typeof value === "string" || value instanceof Uint8Array) return value;
|
|
342
|
+
if (value instanceof Blob) return new Uint8Array(await value.arrayBuffer());
|
|
343
|
+
if (value instanceof ArrayBuffer) return new Uint8Array(value);
|
|
344
|
+
if (ArrayBuffer.isView(value)) return new Uint8Array(value.buffer, value.byteOffset, value.byteLength);
|
|
345
|
+
throw new TypeError("write() expects string, Blob, ArrayBuffer, or TypedArray data");
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
function imageBase64(data) {
|
|
349
|
+
if (typeof data === "string") {
|
|
350
|
+
if (isStrictBase64(data)) return data;
|
|
351
|
+
if (!DECIMAL_CSV_RE.test(data)) return undefined;
|
|
352
|
+
const parts = data.split(",");
|
|
353
|
+
const bytes = new Uint8Array(parts.length);
|
|
354
|
+
for (let index = 0; index < parts.length; index += 1) {
|
|
355
|
+
const byte = Number(parts[index]);
|
|
356
|
+
if (!Number.isInteger(byte) || byte < 0 || byte > 255) return undefined;
|
|
357
|
+
bytes[index] = byte;
|
|
358
|
+
}
|
|
359
|
+
return Buffer.from(bytes).toString("base64");
|
|
360
|
+
}
|
|
361
|
+
if (data instanceof Uint8Array) return Buffer.from(data).toString("base64");
|
|
362
|
+
if (data instanceof ArrayBuffer) return Buffer.from(data).toString("base64");
|
|
363
|
+
if (ArrayBuffer.isView(data)) return Buffer.from(data.buffer, data.byteOffset, data.byteLength).toString("base64");
|
|
364
|
+
if (isPlainObject(data) && data.type === "Buffer" && Array.isArray(data.data)) {
|
|
365
|
+
const bytes = new Uint8Array(data.data.length);
|
|
366
|
+
for (let index = 0; index < data.data.length; index += 1) {
|
|
367
|
+
const byte = data.data[index];
|
|
368
|
+
if (typeof byte !== "number" || !Number.isInteger(byte) || byte < 0 || byte > 255) return undefined;
|
|
369
|
+
bytes[index] = byte;
|
|
370
|
+
}
|
|
371
|
+
return Buffer.from(bytes).toString("base64");
|
|
372
|
+
}
|
|
373
|
+
return undefined;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
function isStrictBase64(value) {
|
|
377
|
+
return value.length > 0 && value.length % 4 === 0 && BASE64_STRICT_RE.test(value);
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
function describeImageData(data) {
|
|
381
|
+
if (data === null) return "null";
|
|
382
|
+
if (data instanceof Uint8Array) return "Uint8Array";
|
|
383
|
+
if (data instanceof ArrayBuffer) return "ArrayBuffer";
|
|
384
|
+
if (ArrayBuffer.isView(data)) return data.constructor.name;
|
|
385
|
+
if (typeof data === "string") return `string(${data.length})`;
|
|
386
|
+
return typeof data;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
function chunkToString(chunk, encoding) {
|
|
390
|
+
if (typeof chunk === "string") return chunk;
|
|
391
|
+
if (chunk instanceof Uint8Array) return Buffer.from(chunk).toString(encoding ?? "utf8");
|
|
392
|
+
return String(chunk);
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
function encodeBase64(value) {
|
|
396
|
+
return Buffer.from(value, "utf8").toString("base64");
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
function formatValue(value) {
|
|
400
|
+
return typeof value === "string" ? value : inspect(value, { colors: false, depth: 5 });
|
|
401
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { BridgeConnectionConfig, KernelToHostMessage } from "../../bridge/protocol.ts";
|
|
2
|
+
import type { KernelSpawnProcess } from "./process.ts";
|
|
3
|
+
import type { PythonTransportResult } from "./transport.ts";
|
|
4
|
+
|
|
5
|
+
export interface PythonKernelStartOptions {
|
|
6
|
+
readonly interpreterPath: string;
|
|
7
|
+
readonly sessionId: string;
|
|
8
|
+
readonly cwd: string;
|
|
9
|
+
readonly connection: BridgeConnectionConfig;
|
|
10
|
+
readonly env?: NodeJS.ProcessEnv;
|
|
11
|
+
readonly startupTimeoutMs?: number;
|
|
12
|
+
readonly onMessage?: (message: KernelToHostMessage) => void;
|
|
13
|
+
readonly spawnProcess?: KernelSpawnProcess;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface PythonKernelRunOptions {
|
|
17
|
+
readonly cellId: string;
|
|
18
|
+
readonly code: string;
|
|
19
|
+
readonly timeoutMs?: number;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export type ResultMessage = PythonTransportResult;
|
|
23
|
+
|
|
24
|
+
export interface PendingRun {
|
|
25
|
+
readonly input: PythonKernelRunOptions;
|
|
26
|
+
readonly resolve: (result: ResultMessage) => void;
|
|
27
|
+
readonly reject: (error: unknown) => void;
|
|
28
|
+
startedAt: number | null;
|
|
29
|
+
timeoutTimer: NodeJS.Timeout | null;
|
|
30
|
+
escalationTimer?: NodeJS.Timeout;
|
|
31
|
+
interruptReason?: string;
|
|
32
|
+
}
|