@oh-my-pi/pi-coding-agent 12.17.0 → 12.18.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/CHANGELOG.md +42 -0
- package/package.json +414 -92
- package/src/capability/index.ts +5 -8
- package/src/cli/stats-cli.ts +3 -3
- package/src/exec/bash-executor.ts +92 -58
- package/src/ipy/executor.ts +42 -32
- package/src/ipy/gateway-coordinator.ts +5 -8
- package/src/ipy/kernel.ts +28 -47
- package/src/main.ts +56 -61
- package/src/mcp/config.ts +102 -5
- package/src/mcp/index.ts +3 -1
- package/src/mcp/loader.ts +3 -0
- package/src/mcp/manager.ts +3 -0
- package/src/modes/controllers/extension-ui-controller.ts +28 -6
- package/src/modes/interactive-mode.ts +15 -18
- package/src/modes/types.ts +1 -0
- package/src/modes/utils/ui-helpers.ts +7 -0
- package/src/sdk.ts +97 -115
- package/src/system-prompt.ts +15 -36
- package/src/tools/bash-interactive.ts +46 -42
- package/src/tools/bash.ts +6 -7
- package/src/tools/gemini-image.ts +2 -11
- package/src/tools/index.ts +18 -27
- package/src/utils/shell-snapshot.ts +18 -3
- package/src/web/search/providers/gemini.ts +2 -24
- package/src/utils/timings.ts +0 -26
|
@@ -34,6 +34,8 @@ export interface BashResult {
|
|
|
34
34
|
artifactId?: string;
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
+
const HARD_TIMEOUT_GRACE_MS = 5_000;
|
|
38
|
+
|
|
37
39
|
const shellSessions = new Map<string, Shell>();
|
|
38
40
|
|
|
39
41
|
export async function executeBash(command: string, options?: BashExecutorOptions): Promise<BashResult> {
|
|
@@ -65,74 +67,106 @@ export async function executeBash(command: string, options?: BashExecutorOptions
|
|
|
65
67
|
};
|
|
66
68
|
}
|
|
67
69
|
|
|
70
|
+
const sessionKey = buildSessionKey(shell, prefix, snapshotPath, shellEnv, options?.sessionKey);
|
|
71
|
+
let shellSession = shellSessions.get(sessionKey);
|
|
72
|
+
if (!shellSession) {
|
|
73
|
+
shellSession = new Shell({ sessionEnv: shellEnv, snapshotPath: snapshotPath ?? undefined });
|
|
74
|
+
shellSessions.set(sessionKey, shellSession);
|
|
75
|
+
}
|
|
76
|
+
const signal = options?.signal;
|
|
77
|
+
const abortHandler = () => {
|
|
78
|
+
shellSession.abort(signal?.reason instanceof Error ? signal.reason.message : undefined);
|
|
79
|
+
};
|
|
80
|
+
if (signal) {
|
|
81
|
+
signal.addEventListener("abort", abortHandler, { once: true });
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
let hardTimeoutTimer: NodeJS.Timeout | undefined;
|
|
85
|
+
const hardTimeoutDeferred = Promise.withResolvers<"hard-timeout">();
|
|
86
|
+
const baseTimeoutMs = Math.max(1_000, options?.timeout ?? 300_000);
|
|
87
|
+
const hardTimeoutMs = baseTimeoutMs + HARD_TIMEOUT_GRACE_MS;
|
|
88
|
+
hardTimeoutTimer = setTimeout(() => {
|
|
89
|
+
shellSession.abort(`Hard timeout after ${Math.round(hardTimeoutMs / 1000)}s`);
|
|
90
|
+
hardTimeoutDeferred.resolve("hard-timeout");
|
|
91
|
+
}, hardTimeoutMs);
|
|
92
|
+
|
|
93
|
+
let resetSession = false;
|
|
94
|
+
|
|
68
95
|
try {
|
|
69
|
-
const
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
96
|
+
const runPromise = shellSession.run(
|
|
97
|
+
{
|
|
98
|
+
command: finalCommand,
|
|
99
|
+
cwd: options?.cwd,
|
|
100
|
+
env: options?.env,
|
|
101
|
+
timeoutMs: options?.timeout,
|
|
102
|
+
signal,
|
|
103
|
+
},
|
|
104
|
+
(err, chunk) => {
|
|
105
|
+
if (!err) {
|
|
106
|
+
enqueueChunk(chunk);
|
|
107
|
+
}
|
|
108
|
+
},
|
|
109
|
+
);
|
|
110
|
+
|
|
111
|
+
const winner = await Promise.race([
|
|
112
|
+
runPromise.then(result => ({ kind: "result" as const, result })),
|
|
113
|
+
hardTimeoutDeferred.promise.then(() => ({ kind: "hard-timeout" as const })),
|
|
114
|
+
]);
|
|
115
|
+
|
|
116
|
+
await pendingChunks;
|
|
117
|
+
|
|
118
|
+
if (winner.kind === "hard-timeout") {
|
|
119
|
+
resetSession = true;
|
|
120
|
+
return {
|
|
121
|
+
exitCode: undefined,
|
|
122
|
+
cancelled: true,
|
|
123
|
+
...(await sink.dump(`Command exceeded hard timeout after ${Math.round(hardTimeoutMs / 1000)} seconds`)),
|
|
124
|
+
};
|
|
74
125
|
}
|
|
75
126
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
127
|
+
// Handle timeout
|
|
128
|
+
if (winner.result.timedOut) {
|
|
129
|
+
const annotation = options?.timeout
|
|
130
|
+
? `Command timed out after ${Math.round(options.timeout / 1000)} seconds`
|
|
131
|
+
: "Command timed out";
|
|
132
|
+
resetSession = true;
|
|
133
|
+
return {
|
|
134
|
+
exitCode: undefined,
|
|
135
|
+
cancelled: true,
|
|
136
|
+
...(await sink.dump(annotation)),
|
|
137
|
+
};
|
|
82
138
|
}
|
|
83
139
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
command: finalCommand,
|
|
88
|
-
cwd: options?.cwd,
|
|
89
|
-
env: options?.env,
|
|
90
|
-
timeoutMs: options?.timeout,
|
|
91
|
-
signal,
|
|
92
|
-
},
|
|
93
|
-
(err, chunk) => {
|
|
94
|
-
if (!err) {
|
|
95
|
-
enqueueChunk(chunk);
|
|
96
|
-
}
|
|
97
|
-
},
|
|
98
|
-
);
|
|
99
|
-
|
|
100
|
-
await pendingChunks;
|
|
101
|
-
|
|
102
|
-
// Handle timeout
|
|
103
|
-
if (result.timedOut) {
|
|
104
|
-
const annotation = options?.timeout
|
|
105
|
-
? `Command timed out after ${Math.round(options.timeout / 1000)} seconds`
|
|
106
|
-
: "Command timed out";
|
|
107
|
-
return {
|
|
108
|
-
exitCode: undefined,
|
|
109
|
-
cancelled: true,
|
|
110
|
-
...(await sink.dump(annotation)),
|
|
111
|
-
};
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
// Handle cancellation
|
|
115
|
-
if (result.cancelled) {
|
|
116
|
-
return {
|
|
117
|
-
exitCode: undefined,
|
|
118
|
-
cancelled: true,
|
|
119
|
-
...(await sink.dump("Command cancelled")),
|
|
120
|
-
};
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
// Normal completion
|
|
140
|
+
// Handle cancellation
|
|
141
|
+
if (winner.result.cancelled) {
|
|
142
|
+
resetSession = true;
|
|
124
143
|
return {
|
|
125
|
-
exitCode:
|
|
126
|
-
cancelled:
|
|
127
|
-
...(await sink.dump()),
|
|
144
|
+
exitCode: undefined,
|
|
145
|
+
cancelled: true,
|
|
146
|
+
...(await sink.dump("Command cancelled")),
|
|
128
147
|
};
|
|
129
|
-
} finally {
|
|
130
|
-
if (signal) {
|
|
131
|
-
signal.removeEventListener("abort", abortHandler);
|
|
132
|
-
}
|
|
133
148
|
}
|
|
149
|
+
|
|
150
|
+
// Normal completion
|
|
151
|
+
return {
|
|
152
|
+
exitCode: winner.result.exitCode,
|
|
153
|
+
cancelled: false,
|
|
154
|
+
...(await sink.dump()),
|
|
155
|
+
};
|
|
156
|
+
} catch (err) {
|
|
157
|
+
resetSession = true;
|
|
158
|
+
throw err;
|
|
134
159
|
} finally {
|
|
160
|
+
if (hardTimeoutTimer) {
|
|
161
|
+
clearTimeout(hardTimeoutTimer);
|
|
162
|
+
}
|
|
163
|
+
if (signal) {
|
|
164
|
+
signal.removeEventListener("abort", abortHandler);
|
|
165
|
+
}
|
|
135
166
|
await pendingChunks;
|
|
167
|
+
if (resetSession) {
|
|
168
|
+
shellSessions.delete(sessionKey);
|
|
169
|
+
}
|
|
136
170
|
}
|
|
137
171
|
}
|
|
138
172
|
|
package/src/ipy/executor.ts
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import * as path from "node:path";
|
|
2
|
-
import {
|
|
2
|
+
import { isEnoent, logger } from "@oh-my-pi/pi-utils";
|
|
3
3
|
import { getAgentDir, getProjectDir } from "@oh-my-pi/pi-utils/dirs";
|
|
4
4
|
import { OutputSink } from "../session/streaming-output";
|
|
5
|
-
import { time } from "../utils/timings";
|
|
6
5
|
import { shutdownSharedGateway } from "./gateway-coordinator";
|
|
7
6
|
import {
|
|
8
7
|
checkPythonKernelAvailability,
|
|
@@ -15,8 +14,6 @@ import {
|
|
|
15
14
|
import { discoverPythonModules } from "./modules";
|
|
16
15
|
import { PYTHON_PRELUDE } from "./prelude";
|
|
17
16
|
|
|
18
|
-
const debugStartup = $env.PI_DEBUG_STARTUP ? (stage: string) => process.stderr.write(`[startup] ${stage}\n`) : () => {};
|
|
19
|
-
|
|
20
17
|
const IDLE_TIMEOUT_MS = 5 * 60 * 1000; // 5 minutes
|
|
21
18
|
const MAX_KERNEL_SESSIONS = 4;
|
|
22
19
|
const CLEANUP_INTERVAL_MS = 30 * 1000; // 30 seconds
|
|
@@ -228,10 +225,7 @@ export async function warmPythonEnvironment(
|
|
|
228
225
|
const isTestEnv = Bun.env.BUN_ENV === "test" || Bun.env.NODE_ENV === "test";
|
|
229
226
|
let cacheState: PreludeCacheState | null = null;
|
|
230
227
|
try {
|
|
231
|
-
|
|
232
|
-
await ensureKernelAvailable(cwd);
|
|
233
|
-
debugStartup("warmPython:ensureKernel:done");
|
|
234
|
-
time("warmPython:ensureKernelAvailable");
|
|
228
|
+
await logger.timeAsync("warmPython:ensureKernelAvailable", () => ensureKernelAvailable(cwd));
|
|
235
229
|
} catch (err: unknown) {
|
|
236
230
|
const reason = err instanceof Error ? err.message : String(err);
|
|
237
231
|
cachedPreludeDocs = [];
|
|
@@ -255,16 +249,15 @@ export async function warmPythonEnvironment(
|
|
|
255
249
|
}
|
|
256
250
|
const resolvedSessionId = sessionId ?? `session:${cwd}`;
|
|
257
251
|
try {
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
252
|
+
const docs = await logger.timeAsync("warmPython:withKernelSession", () =>
|
|
253
|
+
withKernelSession(
|
|
254
|
+
resolvedSessionId,
|
|
255
|
+
cwd,
|
|
256
|
+
async kernel => kernel.introspectPrelude(),
|
|
257
|
+
useSharedGateway,
|
|
258
|
+
sessionFile,
|
|
259
|
+
),
|
|
265
260
|
);
|
|
266
|
-
debugStartup("warmPython:withKernelSession:done");
|
|
267
|
-
time("warmPython:withKernelSession");
|
|
268
261
|
cachedPreludeDocs = docs;
|
|
269
262
|
if (!isTestEnv && docs.length > 0) {
|
|
270
263
|
const state = cacheState ?? (await buildPreludeCacheState(cwd));
|
|
@@ -321,7 +314,6 @@ async function createKernelSession(
|
|
|
321
314
|
artifactsDir?: string,
|
|
322
315
|
isRetry?: boolean,
|
|
323
316
|
): Promise<KernelSession> {
|
|
324
|
-
debugStartup("kernel:createSession:entry");
|
|
325
317
|
const env: Record<string, string> | undefined =
|
|
326
318
|
sessionFile || artifactsDir
|
|
327
319
|
? {
|
|
@@ -332,10 +324,9 @@ async function createKernelSession(
|
|
|
332
324
|
|
|
333
325
|
let kernel: PythonKernel;
|
|
334
326
|
try {
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
time("createKernelSession:PythonKernel.start");
|
|
327
|
+
kernel = await logger.timeAsync("createKernelSession:PythonKernel.start", () =>
|
|
328
|
+
PythonKernel.start({ cwd, useSharedGateway, env }),
|
|
329
|
+
);
|
|
339
330
|
} catch (err) {
|
|
340
331
|
if (!isRetry && isResourceExhaustionError(err)) {
|
|
341
332
|
await recoverFromResourceExhaustion();
|
|
@@ -412,37 +403,56 @@ async function withKernelSession<T>(
|
|
|
412
403
|
sessionFile?: string,
|
|
413
404
|
artifactsDir?: string,
|
|
414
405
|
): Promise<T> {
|
|
415
|
-
debugStartup("kernel:withSession:entry");
|
|
416
406
|
let session = kernelSessions.get(sessionId);
|
|
417
407
|
if (!session) {
|
|
418
408
|
// Evict oldest session if at capacity
|
|
419
409
|
if (kernelSessions.size >= MAX_KERNEL_SESSIONS) {
|
|
420
410
|
await evictOldestSession();
|
|
421
411
|
}
|
|
422
|
-
session = await
|
|
423
|
-
|
|
412
|
+
session = await logger.timeAsync(
|
|
413
|
+
"kernel:createKernelSession",
|
|
414
|
+
createKernelSession,
|
|
415
|
+
sessionId,
|
|
416
|
+
cwd,
|
|
417
|
+
useSharedGateway,
|
|
418
|
+
sessionFile,
|
|
419
|
+
artifactsDir,
|
|
420
|
+
);
|
|
424
421
|
kernelSessions.set(sessionId, session);
|
|
425
422
|
startCleanupTimer();
|
|
426
423
|
}
|
|
427
424
|
|
|
428
425
|
const run = async (): Promise<T> => {
|
|
429
|
-
debugStartup("kernel:withSession:run");
|
|
430
426
|
session!.lastUsedAt = Date.now();
|
|
431
427
|
if (session!.dead || !session!.kernel.isAlive()) {
|
|
432
|
-
await
|
|
428
|
+
await logger.timeAsync(
|
|
429
|
+
"kernel:restartKernelSession",
|
|
430
|
+
restartKernelSession,
|
|
431
|
+
session!,
|
|
432
|
+
cwd,
|
|
433
|
+
useSharedGateway,
|
|
434
|
+
sessionFile,
|
|
435
|
+
artifactsDir,
|
|
436
|
+
);
|
|
433
437
|
}
|
|
434
438
|
try {
|
|
435
|
-
|
|
436
|
-
const result = await handler(session!.kernel);
|
|
437
|
-
debugStartup("kernel:withSession:handler:done");
|
|
439
|
+
const result = await logger.timeAsync("kernel:withSession:handler", handler, session!.kernel);
|
|
438
440
|
session!.restartCount = 0;
|
|
439
441
|
return result;
|
|
440
442
|
} catch (err) {
|
|
441
443
|
if (!session!.dead && session!.kernel.isAlive()) {
|
|
442
444
|
throw err;
|
|
443
445
|
}
|
|
444
|
-
await
|
|
445
|
-
|
|
446
|
+
await logger.timeAsync(
|
|
447
|
+
"kernel:restartKernelSession",
|
|
448
|
+
restartKernelSession,
|
|
449
|
+
session!,
|
|
450
|
+
cwd,
|
|
451
|
+
useSharedGateway,
|
|
452
|
+
sessionFile,
|
|
453
|
+
artifactsDir,
|
|
454
|
+
);
|
|
455
|
+
const result = await logger.timeAsync("kernel:postRestart:handler", handler, session!.kernel);
|
|
446
456
|
session!.restartCount = 0;
|
|
447
457
|
return result;
|
|
448
458
|
}
|
|
@@ -6,7 +6,6 @@ import { getAgentDir } from "@oh-my-pi/pi-utils/dirs";
|
|
|
6
6
|
import type { Subprocess } from "bun";
|
|
7
7
|
import { Settings } from "../config/settings";
|
|
8
8
|
import { getOrCreateSnapshot } from "../utils/shell-snapshot";
|
|
9
|
-
import { time } from "../utils/timings";
|
|
10
9
|
import { filterEnv, resolvePythonRuntime } from "./runtime";
|
|
11
10
|
|
|
12
11
|
const GATEWAY_DIR_NAME = "python-gateway";
|
|
@@ -315,12 +314,9 @@ async function killGateway(pid: number, context: string): Promise<void> {
|
|
|
315
314
|
export async function acquireSharedGateway(cwd: string): Promise<AcquireResult | null> {
|
|
316
315
|
try {
|
|
317
316
|
return await withGatewayLock(async () => {
|
|
318
|
-
|
|
319
|
-
const existingInfo = await readGatewayInfo();
|
|
320
|
-
time("acquireSharedGateway:readInfo");
|
|
317
|
+
const existingInfo = await logger.timeAsync("acquireSharedGateway:readInfo", () => readGatewayInfo());
|
|
321
318
|
if (existingInfo) {
|
|
322
|
-
if (await isGatewayAlive(existingInfo)) {
|
|
323
|
-
time("acquireSharedGateway:isAlive");
|
|
319
|
+
if (await logger.timeAsync("acquireSharedGateway:isAlive", () => isGatewayAlive(existingInfo))) {
|
|
324
320
|
localGatewayUrl = existingInfo.url;
|
|
325
321
|
isCoordinatorInitialized = true;
|
|
326
322
|
logger.debug("Reusing global Python gateway", { url: existingInfo.url });
|
|
@@ -334,8 +330,9 @@ export async function acquireSharedGateway(cwd: string): Promise<AcquireResult |
|
|
|
334
330
|
await clearGatewayInfo();
|
|
335
331
|
}
|
|
336
332
|
|
|
337
|
-
const { url, pid, pythonPath, venvPath } = await
|
|
338
|
-
|
|
333
|
+
const { url, pid, pythonPath, venvPath } = await logger.timeAsync("acquireSharedGateway:startGateway", () =>
|
|
334
|
+
startGatewayProcess(cwd),
|
|
335
|
+
);
|
|
339
336
|
const info: GatewayInfo = {
|
|
340
337
|
url,
|
|
341
338
|
pid,
|
package/src/ipy/kernel.ts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { $env, logger, Snowflake } from "@oh-my-pi/pi-utils";
|
|
2
2
|
import { $ } from "bun";
|
|
3
3
|
import { Settings } from "../config/settings";
|
|
4
|
-
import { time } from "../utils/timings";
|
|
5
4
|
import { htmlToBasicMarkdown } from "../web/scrapers/types";
|
|
6
5
|
import { acquireSharedGateway, releaseSharedGateway, shutdownSharedGateway } from "./gateway-coordinator";
|
|
7
6
|
import { loadPythonModules } from "./modules";
|
|
@@ -13,8 +12,6 @@ const TEXT_DECODER = new TextDecoder();
|
|
|
13
12
|
const TRACE_IPC = $env.PI_PYTHON_IPC_TRACE === "1";
|
|
14
13
|
const PRELUDE_INTROSPECTION_SNIPPET = "import json\nprint(json.dumps(__omp_prelude_docs__()))";
|
|
15
14
|
|
|
16
|
-
const debugStartup = $env.PI_DEBUG_STARTUP ? (stage: string) => process.stderr.write(`[startup] ${stage}\n`) : () => {};
|
|
17
|
-
|
|
18
15
|
class SharedGatewayCreateError extends Error {
|
|
19
16
|
constructor(
|
|
20
17
|
readonly status: number,
|
|
@@ -334,10 +331,9 @@ export class PythonKernel {
|
|
|
334
331
|
}
|
|
335
332
|
|
|
336
333
|
static async start(options: KernelStartOptions): Promise<PythonKernel> {
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
time("PythonKernel.start:availabilityCheck");
|
|
334
|
+
const availability = await logger.timeAsync("PythonKernel.start:availabilityCheck", () =>
|
|
335
|
+
checkPythonKernelAvailability(options.cwd),
|
|
336
|
+
);
|
|
341
337
|
if (!availability.ok) {
|
|
342
338
|
throw new Error(availability.reason ?? "Python kernel unavailable");
|
|
343
339
|
}
|
|
@@ -353,20 +349,18 @@ export class PythonKernel {
|
|
|
353
349
|
|
|
354
350
|
for (let attempt = 0; attempt < 2; attempt += 1) {
|
|
355
351
|
try {
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
time("PythonKernel.start:acquireSharedGateway");
|
|
352
|
+
const sharedResult = await logger.timeAsync("PythonKernel.start:acquireSharedGateway", () =>
|
|
353
|
+
acquireSharedGateway(options.cwd),
|
|
354
|
+
);
|
|
360
355
|
if (!sharedResult) {
|
|
361
356
|
throw new Error("Shared Python gateway unavailable");
|
|
362
357
|
}
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
time("PythonKernel.start:startWithSharedGateway");
|
|
358
|
+
const kernel = await logger.timeAsync("PythonKernel.start:startWithSharedGateway", () =>
|
|
359
|
+
PythonKernel.#startWithSharedGateway(sharedResult.url, options.cwd, options.env),
|
|
360
|
+
);
|
|
367
361
|
return kernel;
|
|
368
362
|
} catch (err) {
|
|
369
|
-
|
|
363
|
+
logger.debug("PythonKernel.start:sharedFailed");
|
|
370
364
|
if (attempt === 0 && err instanceof SharedGatewayCreateError && err.status >= 500) {
|
|
371
365
|
logger.warn("Shared gateway kernel creation failed, retrying", {
|
|
372
366
|
status: err.status,
|
|
@@ -436,55 +430,42 @@ export class PythonKernel {
|
|
|
436
430
|
cwd: string,
|
|
437
431
|
env?: Record<string, string | undefined>,
|
|
438
432
|
): Promise<PythonKernel> {
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
time("startWithSharedGateway:createKernel");
|
|
433
|
+
const createResponse = await logger.timeAsync("startWithSharedGateway:createKernel", () =>
|
|
434
|
+
fetch(`${gatewayUrl}/api/kernels`, {
|
|
435
|
+
method: "POST",
|
|
436
|
+
headers: { "Content-Type": "application/json" },
|
|
437
|
+
body: JSON.stringify({ name: "python3" }),
|
|
438
|
+
}),
|
|
439
|
+
);
|
|
447
440
|
|
|
448
441
|
if (!createResponse.ok) {
|
|
449
|
-
|
|
442
|
+
logger.debug(`sharedGateway:fetch:notOk:${createResponse.status}`);
|
|
450
443
|
await shutdownSharedGateway();
|
|
451
|
-
debugStartup("sharedGateway:fetch:shutdown");
|
|
452
444
|
const text = await createResponse.text();
|
|
453
|
-
debugStartup("sharedGateway:fetch:textRead");
|
|
454
445
|
throw new SharedGatewayCreateError(
|
|
455
446
|
createResponse.status,
|
|
456
447
|
`Failed to create kernel on shared gateway: ${text}`,
|
|
457
448
|
);
|
|
458
449
|
}
|
|
459
450
|
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
451
|
+
const kernelInfo = await logger.timeAsync(
|
|
452
|
+
"startWithSharedGateway:parseJson",
|
|
453
|
+
() => createResponse.json() as Promise<{ id: string }>,
|
|
454
|
+
);
|
|
463
455
|
const kernelId = kernelInfo.id;
|
|
464
456
|
|
|
465
457
|
const kernel = new PythonKernel(Snowflake.next(), kernelId, gatewayUrl, Snowflake.next(), "omp", true);
|
|
466
|
-
debugStartup("sharedGateway:kernelCreated");
|
|
467
458
|
|
|
468
459
|
try {
|
|
469
|
-
|
|
470
|
-
await kernel.#
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
await kernel.#initializeKernelEnvironment(cwd, env);
|
|
475
|
-
debugStartup("sharedGateway:initEnv:done");
|
|
476
|
-
time("startWithSharedGateway:initEnv");
|
|
477
|
-
debugStartup("sharedGateway:prelude:start");
|
|
478
|
-
const preludeResult = await kernel.execute(PYTHON_PRELUDE, { silent: true, storeHistory: false });
|
|
479
|
-
debugStartup("sharedGateway:prelude:done");
|
|
480
|
-
time("startWithSharedGateway:prelude");
|
|
460
|
+
await logger.timeAsync("startWithSharedGateway:connectWS", () => kernel.#connectWebSocket());
|
|
461
|
+
await logger.timeAsync("startWithSharedGateway:initEnv", () => kernel.#initializeKernelEnvironment(cwd, env));
|
|
462
|
+
const preludeResult = await logger.timeAsync("startWithSharedGateway:prelude", () =>
|
|
463
|
+
kernel.execute(PYTHON_PRELUDE, { silent: true, storeHistory: false }),
|
|
464
|
+
);
|
|
481
465
|
if (preludeResult.cancelled || preludeResult.status === "error") {
|
|
482
466
|
throw new Error("Failed to initialize Python kernel prelude");
|
|
483
467
|
}
|
|
484
|
-
|
|
485
|
-
await loadPythonModules(kernel, { cwd });
|
|
486
|
-
debugStartup("sharedGateway:loadModules:done");
|
|
487
|
-
time("startWithSharedGateway:loadModules");
|
|
468
|
+
await logger.timeAsync("startWithSharedGateway:loadModules", () => loadPythonModules(kernel, { cwd }));
|
|
488
469
|
return kernel;
|
|
489
470
|
} catch (err: unknown) {
|
|
490
471
|
await kernel.shutdown();
|