@oh-my-pi/pi-coding-agent 5.7.67 → 5.7.69
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 +9 -0
- package/README.md +6 -6
- package/package.json +8 -7
- package/src/migrations.ts +1 -34
- package/src/vendor/photon/index.js +4 -2
- package/src/vendor/photon/photon_rs_bg.wasm.b64.js +1 -0
- package/src/core/python-executor-display.test.ts +0 -42
- package/src/core/python-executor-lifecycle.test.ts +0 -99
- package/src/core/python-executor-mapping.test.ts +0 -41
- package/src/core/python-executor-per-call.test.ts +0 -49
- package/src/core/python-executor-session.test.ts +0 -103
- package/src/core/python-executor-streaming.test.ts +0 -77
- package/src/core/python-executor-timeout.test.ts +0 -35
- package/src/core/python-executor.lifecycle.test.ts +0 -139
- package/src/core/python-executor.result.test.ts +0 -49
- package/src/core/python-executor.test.ts +0 -180
- package/src/core/python-kernel-display.test.ts +0 -54
- package/src/core/python-kernel-env.test.ts +0 -138
- package/src/core/python-kernel-session.test.ts +0 -87
- package/src/core/python-kernel-ws.test.ts +0 -104
- package/src/core/python-kernel.lifecycle.test.ts +0 -249
- package/src/core/python-kernel.test.ts +0 -461
- package/src/core/python-modules.test.ts +0 -102
- package/src/core/python-prelude.test.ts +0 -140
- package/src/core/settings-manager-python.test.ts +0 -23
- package/src/core/streaming-output.test.ts +0 -26
- package/src/core/system-prompt.python.test.ts +0 -17
- package/src/core/tools/index.test.ts +0 -212
- package/src/core/tools/python-execution.test.ts +0 -68
- package/src/core/tools/python-fallback.test.ts +0 -72
- package/src/core/tools/python-renderer.test.ts +0 -36
- package/src/core/tools/python-tool-mode.test.ts +0 -43
- package/src/core/tools/python.test.ts +0 -121
- package/src/core/tools/schema-validation.test.ts +0 -530
- package/src/core/tools/web-scrapers/academic.test.ts +0 -239
- package/src/core/tools/web-scrapers/business.test.ts +0 -82
- package/src/core/tools/web-scrapers/dev-platforms.test.ts +0 -254
- package/src/core/tools/web-scrapers/documentation.test.ts +0 -85
- package/src/core/tools/web-scrapers/finance-media.test.ts +0 -144
- package/src/core/tools/web-scrapers/git-hosting.test.ts +0 -272
- package/src/core/tools/web-scrapers/media.test.ts +0 -138
- package/src/core/tools/web-scrapers/package-managers-2.test.ts +0 -199
- package/src/core/tools/web-scrapers/package-managers.test.ts +0 -171
- package/src/core/tools/web-scrapers/package-registries.test.ts +0 -259
- package/src/core/tools/web-scrapers/research.test.ts +0 -107
- package/src/core/tools/web-scrapers/security.test.ts +0 -103
- package/src/core/tools/web-scrapers/social-extended.test.ts +0 -192
- package/src/core/tools/web-scrapers/social.test.ts +0 -259
- package/src/core/tools/web-scrapers/stackexchange.test.ts +0 -120
- package/src/core/tools/web-scrapers/standards.test.ts +0 -122
- package/src/core/tools/web-scrapers/wikipedia.test.ts +0 -73
- package/src/core/tools/web-scrapers/youtube.test.ts +0 -198
- package/src/discovery/helpers.test.ts +0 -131
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from "bun:test";
|
|
2
|
-
import { executePythonWithKernel, type PythonKernelExecutor } from "./python-executor";
|
|
3
|
-
import type { KernelDisplayOutput, KernelExecuteOptions, KernelExecuteResult } from "./python-kernel";
|
|
4
|
-
|
|
5
|
-
class FakeKernel implements PythonKernelExecutor {
|
|
6
|
-
private result: KernelExecuteResult;
|
|
7
|
-
private onExecute: (options?: KernelExecuteOptions) => Promise<void> | void;
|
|
8
|
-
|
|
9
|
-
constructor(result: KernelExecuteResult, onExecute: (options?: KernelExecuteOptions) => Promise<void> | void) {
|
|
10
|
-
this.result = result;
|
|
11
|
-
this.onExecute = onExecute;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
async execute(_code: string, options?: KernelExecuteOptions): Promise<KernelExecuteResult> {
|
|
15
|
-
await this.onExecute(options);
|
|
16
|
-
return this.result;
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
describe("executePythonWithKernel display outputs", () => {
|
|
21
|
-
it("aggregates display outputs in order", async () => {
|
|
22
|
-
const outputs: KernelDisplayOutput[] = [
|
|
23
|
-
{ type: "json", data: { foo: "bar" } },
|
|
24
|
-
{ type: "image", data: "abc", mimeType: "image/png" },
|
|
25
|
-
];
|
|
26
|
-
|
|
27
|
-
const kernel = new FakeKernel(
|
|
28
|
-
{ status: "ok", cancelled: false, timedOut: false, stdinRequested: false },
|
|
29
|
-
async (options) => {
|
|
30
|
-
if (!options?.onDisplay) return;
|
|
31
|
-
for (const output of outputs) {
|
|
32
|
-
await options.onDisplay(output);
|
|
33
|
-
}
|
|
34
|
-
},
|
|
35
|
-
);
|
|
36
|
-
|
|
37
|
-
const result = await executePythonWithKernel(kernel, "print('hi')");
|
|
38
|
-
|
|
39
|
-
expect(result.exitCode).toBe(0);
|
|
40
|
-
expect(result.displayOutputs).toEqual(outputs);
|
|
41
|
-
});
|
|
42
|
-
});
|
|
@@ -1,99 +0,0 @@
|
|
|
1
|
-
import { afterEach, describe, expect, it, vi } from "bun:test";
|
|
2
|
-
import { disposeAllKernelSessions, executePython } from "./python-executor";
|
|
3
|
-
import type { KernelExecuteResult } from "./python-kernel";
|
|
4
|
-
import * as pythonKernel from "./python-kernel";
|
|
5
|
-
|
|
6
|
-
class FakeKernel {
|
|
7
|
-
execute = vi.fn(async () => this.result);
|
|
8
|
-
shutdown = vi.fn(async () => {});
|
|
9
|
-
ping = vi.fn(async () => true);
|
|
10
|
-
alive = true;
|
|
11
|
-
|
|
12
|
-
constructor(private readonly result: KernelExecuteResult) {}
|
|
13
|
-
|
|
14
|
-
isAlive(): boolean {
|
|
15
|
-
return this.alive;
|
|
16
|
-
}
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
const OK_RESULT: KernelExecuteResult = {
|
|
20
|
-
status: "ok",
|
|
21
|
-
cancelled: false,
|
|
22
|
-
timedOut: false,
|
|
23
|
-
stdinRequested: false,
|
|
24
|
-
};
|
|
25
|
-
|
|
26
|
-
afterEach(async () => {
|
|
27
|
-
vi.restoreAllMocks();
|
|
28
|
-
await disposeAllKernelSessions();
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
describe("executePython lifecycle", () => {
|
|
32
|
-
it("starts and shuts down per-call kernels", async () => {
|
|
33
|
-
const kernel = new FakeKernel(OK_RESULT);
|
|
34
|
-
vi.spyOn(pythonKernel, "checkPythonKernelAvailability").mockResolvedValue({ ok: true });
|
|
35
|
-
const startSpy = vi
|
|
36
|
-
.spyOn(pythonKernel.PythonKernel, "start")
|
|
37
|
-
.mockResolvedValue(kernel as unknown as pythonKernel.PythonKernel);
|
|
38
|
-
|
|
39
|
-
await executePython("print('hi')", { kernelMode: "per-call", cwd: process.cwd() });
|
|
40
|
-
|
|
41
|
-
expect(startSpy).toHaveBeenCalledTimes(1);
|
|
42
|
-
expect(kernel.execute).toHaveBeenCalledTimes(1);
|
|
43
|
-
expect(kernel.shutdown).toHaveBeenCalledTimes(1);
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
it("reuses session kernels until reset", async () => {
|
|
47
|
-
const kernel = new FakeKernel(OK_RESULT);
|
|
48
|
-
vi.spyOn(pythonKernel, "checkPythonKernelAvailability").mockResolvedValue({ ok: true });
|
|
49
|
-
const startSpy = vi
|
|
50
|
-
.spyOn(pythonKernel.PythonKernel, "start")
|
|
51
|
-
.mockResolvedValue(kernel as unknown as pythonKernel.PythonKernel);
|
|
52
|
-
|
|
53
|
-
await executePython("1 + 1", { kernelMode: "session", sessionId: "test-session", cwd: process.cwd() });
|
|
54
|
-
await executePython("2 + 2", { kernelMode: "session", sessionId: "test-session", cwd: process.cwd() });
|
|
55
|
-
|
|
56
|
-
expect(startSpy).toHaveBeenCalledTimes(1);
|
|
57
|
-
expect(kernel.execute).toHaveBeenCalledTimes(2);
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
it("resets session kernels when requested", async () => {
|
|
61
|
-
const kernel = new FakeKernel(OK_RESULT);
|
|
62
|
-
const kernelNext = new FakeKernel(OK_RESULT);
|
|
63
|
-
vi.spyOn(pythonKernel, "checkPythonKernelAvailability").mockResolvedValue({ ok: true });
|
|
64
|
-
const startSpy = vi
|
|
65
|
-
.spyOn(pythonKernel.PythonKernel, "start")
|
|
66
|
-
.mockResolvedValueOnce(kernel as unknown as pythonKernel.PythonKernel)
|
|
67
|
-
.mockResolvedValueOnce(kernelNext as unknown as pythonKernel.PythonKernel);
|
|
68
|
-
|
|
69
|
-
await executePython("1 + 1", { kernelMode: "session", sessionId: "reset-session", cwd: process.cwd() });
|
|
70
|
-
await executePython("2 + 2", {
|
|
71
|
-
kernelMode: "session",
|
|
72
|
-
sessionId: "reset-session",
|
|
73
|
-
reset: true,
|
|
74
|
-
cwd: process.cwd(),
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
expect(startSpy).toHaveBeenCalledTimes(2);
|
|
78
|
-
expect(kernel.shutdown).toHaveBeenCalledTimes(1);
|
|
79
|
-
expect(kernelNext.execute).toHaveBeenCalledTimes(1);
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
it("restarts session kernels when they are dead", async () => {
|
|
83
|
-
const kernel = new FakeKernel(OK_RESULT);
|
|
84
|
-
const kernelNext = new FakeKernel(OK_RESULT);
|
|
85
|
-
kernel.alive = false;
|
|
86
|
-
vi.spyOn(pythonKernel, "checkPythonKernelAvailability").mockResolvedValue({ ok: true });
|
|
87
|
-
const startSpy = vi
|
|
88
|
-
.spyOn(pythonKernel.PythonKernel, "start")
|
|
89
|
-
.mockResolvedValueOnce(kernel as unknown as pythonKernel.PythonKernel)
|
|
90
|
-
.mockResolvedValueOnce(kernelNext as unknown as pythonKernel.PythonKernel);
|
|
91
|
-
|
|
92
|
-
await executePython("1 + 1", { kernelMode: "session", sessionId: "dead-session", cwd: process.cwd() });
|
|
93
|
-
|
|
94
|
-
expect(startSpy).toHaveBeenCalledTimes(2);
|
|
95
|
-
expect(kernel.shutdown).toHaveBeenCalledTimes(1);
|
|
96
|
-
expect(kernel.execute).toHaveBeenCalledTimes(0);
|
|
97
|
-
expect(kernelNext.execute).toHaveBeenCalledTimes(1);
|
|
98
|
-
});
|
|
99
|
-
});
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from "bun:test";
|
|
2
|
-
import { executePythonWithKernel, type PythonKernelExecutor } from "./python-executor";
|
|
3
|
-
import type { KernelExecuteOptions, KernelExecuteResult } from "./python-kernel";
|
|
4
|
-
|
|
5
|
-
class FakeKernel implements PythonKernelExecutor {
|
|
6
|
-
constructor(
|
|
7
|
-
private result: KernelExecuteResult,
|
|
8
|
-
private onExecute: (options?: KernelExecuteOptions) => void = () => {},
|
|
9
|
-
) {}
|
|
10
|
-
|
|
11
|
-
async execute(_code: string, options?: KernelExecuteOptions): Promise<KernelExecuteResult> {
|
|
12
|
-
this.onExecute(options);
|
|
13
|
-
return this.result;
|
|
14
|
-
}
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
describe("executePythonWithKernel mapping", () => {
|
|
18
|
-
it("annotates timeout cancellations", async () => {
|
|
19
|
-
const kernel = new FakeKernel({ status: "ok", cancelled: true, timedOut: true, stdinRequested: false });
|
|
20
|
-
const result = await executePythonWithKernel(kernel, "sleep(10)", { timeout: 5000 });
|
|
21
|
-
|
|
22
|
-
expect(result.cancelled).toBe(true);
|
|
23
|
-
expect(result.exitCode).toBeUndefined();
|
|
24
|
-
expect(result.output).toContain("Command timed out after 5 seconds");
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
it("maps error status to non-zero exit code", async () => {
|
|
28
|
-
const kernel = new FakeKernel(
|
|
29
|
-
{ status: "error", cancelled: false, timedOut: false, stdinRequested: false },
|
|
30
|
-
(options) => {
|
|
31
|
-
options?.onChunk?.("traceback\n");
|
|
32
|
-
},
|
|
33
|
-
);
|
|
34
|
-
|
|
35
|
-
const result = await executePythonWithKernel(kernel, "1 / 0");
|
|
36
|
-
|
|
37
|
-
expect(result.exitCode).toBe(1);
|
|
38
|
-
expect(result.output).toContain("traceback");
|
|
39
|
-
expect(result.stdinRequested).toBe(false);
|
|
40
|
-
});
|
|
41
|
-
});
|
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from "bun:test";
|
|
2
|
-
import { executePython } from "./python-executor";
|
|
3
|
-
import type { KernelExecuteOptions, KernelExecuteResult } from "./python-kernel";
|
|
4
|
-
import { PythonKernel } from "./python-kernel";
|
|
5
|
-
|
|
6
|
-
interface KernelStub {
|
|
7
|
-
execute: (code: string, options?: KernelExecuteOptions) => Promise<KernelExecuteResult>;
|
|
8
|
-
shutdown: () => Promise<void>;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
describe("executePython (per-call)", () => {
|
|
12
|
-
it("shuts down kernel on timed-out cancellation", async () => {
|
|
13
|
-
process.env.OMP_PYTHON_SKIP_CHECK = "1";
|
|
14
|
-
|
|
15
|
-
let shutdownCalls = 0;
|
|
16
|
-
const kernel: KernelStub = {
|
|
17
|
-
execute: async () => ({
|
|
18
|
-
status: "ok",
|
|
19
|
-
cancelled: true,
|
|
20
|
-
timedOut: true,
|
|
21
|
-
stdinRequested: false,
|
|
22
|
-
}),
|
|
23
|
-
shutdown: async () => {
|
|
24
|
-
shutdownCalls += 1;
|
|
25
|
-
},
|
|
26
|
-
};
|
|
27
|
-
|
|
28
|
-
const kernelClass = PythonKernel as unknown as {
|
|
29
|
-
start: (options: { cwd: string }) => Promise<KernelStub>;
|
|
30
|
-
};
|
|
31
|
-
const originalStart = kernelClass.start;
|
|
32
|
-
kernelClass.start = async () => kernel;
|
|
33
|
-
|
|
34
|
-
try {
|
|
35
|
-
const result = await executePython("sleep(10)", {
|
|
36
|
-
kernelMode: "per-call",
|
|
37
|
-
timeout: 2000,
|
|
38
|
-
cwd: "/tmp",
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
expect(result.cancelled).toBe(true);
|
|
42
|
-
expect(result.exitCode).toBeUndefined();
|
|
43
|
-
expect(result.output).toContain("Command timed out after 2 seconds");
|
|
44
|
-
expect(shutdownCalls).toBe(1);
|
|
45
|
-
} finally {
|
|
46
|
-
kernelClass.start = originalStart;
|
|
47
|
-
}
|
|
48
|
-
});
|
|
49
|
-
});
|
|
@@ -1,103 +0,0 @@
|
|
|
1
|
-
import { afterEach, describe, expect, it, vi } from "bun:test";
|
|
2
|
-
import { disposeAllKernelSessions, executePython } from "./python-executor";
|
|
3
|
-
import * as pythonKernel from "./python-kernel";
|
|
4
|
-
|
|
5
|
-
class FakeKernel {
|
|
6
|
-
executeCalls = 0;
|
|
7
|
-
shutdownCalls = 0;
|
|
8
|
-
alive = true;
|
|
9
|
-
constructor(private readonly shouldThrow: boolean = false) {}
|
|
10
|
-
|
|
11
|
-
isAlive(): boolean {
|
|
12
|
-
return this.alive;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
async execute(): Promise<{ status: "ok"; cancelled: false; timedOut: false; stdinRequested: false }> {
|
|
16
|
-
this.executeCalls += 1;
|
|
17
|
-
if (this.shouldThrow) {
|
|
18
|
-
this.alive = false;
|
|
19
|
-
throw new Error("kernel crashed");
|
|
20
|
-
}
|
|
21
|
-
return { status: "ok", cancelled: false, timedOut: false, stdinRequested: false };
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
async ping(): Promise<boolean> {
|
|
25
|
-
return this.alive;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
async shutdown(): Promise<void> {
|
|
29
|
-
this.shutdownCalls += 1;
|
|
30
|
-
this.alive = false;
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
describe("executePython session lifecycle", () => {
|
|
35
|
-
afterEach(async () => {
|
|
36
|
-
vi.restoreAllMocks();
|
|
37
|
-
await disposeAllKernelSessions();
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
it("restarts session when kernel is not alive", async () => {
|
|
41
|
-
const kernel1 = new FakeKernel();
|
|
42
|
-
kernel1.alive = false;
|
|
43
|
-
const kernel2 = new FakeKernel();
|
|
44
|
-
vi.spyOn(pythonKernel, "checkPythonKernelAvailability").mockResolvedValue({ ok: true });
|
|
45
|
-
const startSpy = vi
|
|
46
|
-
.spyOn(pythonKernel.PythonKernel, "start")
|
|
47
|
-
.mockResolvedValueOnce(kernel1 as unknown as pythonKernel.PythonKernel)
|
|
48
|
-
.mockResolvedValueOnce(kernel2 as unknown as pythonKernel.PythonKernel);
|
|
49
|
-
|
|
50
|
-
await executePython("print('hi')", { cwd: "/tmp", sessionId: "session-1", kernelMode: "session" });
|
|
51
|
-
|
|
52
|
-
expect(startSpy).toHaveBeenCalledTimes(2);
|
|
53
|
-
expect(kernel1.executeCalls).toBe(0);
|
|
54
|
-
expect(kernel1.shutdownCalls).toBe(1);
|
|
55
|
-
expect(kernel2.executeCalls).toBe(1);
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
it("restarts after an execution failure when kernel is dead", async () => {
|
|
59
|
-
const kernel1 = new FakeKernel(true);
|
|
60
|
-
const kernel2 = new FakeKernel();
|
|
61
|
-
const starts = [kernel1, kernel2];
|
|
62
|
-
vi.spyOn(pythonKernel, "checkPythonKernelAvailability").mockResolvedValue({ ok: true });
|
|
63
|
-
const startSpy = vi.spyOn(pythonKernel.PythonKernel, "start").mockImplementation(async () => {
|
|
64
|
-
const next = starts.shift();
|
|
65
|
-
if (!next) {
|
|
66
|
-
throw new Error("No kernel available");
|
|
67
|
-
}
|
|
68
|
-
return next as unknown as pythonKernel.PythonKernel;
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
await executePython("raise", { cwd: "/tmp", sessionId: "session-2", kernelMode: "session" });
|
|
72
|
-
|
|
73
|
-
expect(startSpy).toHaveBeenCalledTimes(2);
|
|
74
|
-
expect(kernel1.executeCalls).toBe(1);
|
|
75
|
-
expect(kernel2.executeCalls).toBe(1);
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
it("resets existing session when requested", async () => {
|
|
79
|
-
const kernel1 = new FakeKernel();
|
|
80
|
-
const kernel2 = new FakeKernel();
|
|
81
|
-
const starts = [kernel1, kernel2];
|
|
82
|
-
vi.spyOn(pythonKernel, "checkPythonKernelAvailability").mockResolvedValue({ ok: true });
|
|
83
|
-
const startSpy = vi.spyOn(pythonKernel.PythonKernel, "start").mockImplementation(async () => {
|
|
84
|
-
const next = starts.shift();
|
|
85
|
-
if (!next) {
|
|
86
|
-
throw new Error("No kernel available");
|
|
87
|
-
}
|
|
88
|
-
return next as unknown as pythonKernel.PythonKernel;
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
await executePython("print('one')", { cwd: "/tmp", sessionId: "session-3", kernelMode: "session" });
|
|
92
|
-
await executePython("print('two')", {
|
|
93
|
-
cwd: "/tmp",
|
|
94
|
-
sessionId: "session-3",
|
|
95
|
-
kernelMode: "session",
|
|
96
|
-
reset: true,
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
expect(startSpy).toHaveBeenCalledTimes(2);
|
|
100
|
-
expect(kernel1.shutdownCalls).toBe(1);
|
|
101
|
-
expect(kernel2.executeCalls).toBe(1);
|
|
102
|
-
});
|
|
103
|
-
});
|
|
@@ -1,77 +0,0 @@
|
|
|
1
|
-
import { afterEach, describe, expect, it } from "bun:test";
|
|
2
|
-
import { rmSync } from "node:fs";
|
|
3
|
-
import { executePythonWithKernel, type PythonKernelExecutor } from "./python-executor";
|
|
4
|
-
import type { KernelExecuteOptions, KernelExecuteResult } from "./python-kernel";
|
|
5
|
-
import { DEFAULT_MAX_BYTES } from "./tools/truncate";
|
|
6
|
-
|
|
7
|
-
class FakeKernel implements PythonKernelExecutor {
|
|
8
|
-
private result: KernelExecuteResult;
|
|
9
|
-
private onExecute: (options?: KernelExecuteOptions) => void;
|
|
10
|
-
|
|
11
|
-
constructor(result: KernelExecuteResult, onExecute: (options?: KernelExecuteOptions) => void) {
|
|
12
|
-
this.result = result;
|
|
13
|
-
this.onExecute = onExecute;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
async execute(_code: string, options?: KernelExecuteOptions): Promise<KernelExecuteResult> {
|
|
17
|
-
this.onExecute(options);
|
|
18
|
-
return this.result;
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
const cleanupPaths: string[] = [];
|
|
23
|
-
|
|
24
|
-
afterEach(() => {
|
|
25
|
-
while (cleanupPaths.length > 0) {
|
|
26
|
-
const path = cleanupPaths.pop();
|
|
27
|
-
if (path) {
|
|
28
|
-
try {
|
|
29
|
-
rmSync(path, { force: true });
|
|
30
|
-
} catch {}
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
describe("executePythonWithKernel streaming", () => {
|
|
36
|
-
it("truncates large output and writes full output file", async () => {
|
|
37
|
-
const largeOutput = "a".repeat(DEFAULT_MAX_BYTES + 128);
|
|
38
|
-
const kernel = new FakeKernel(
|
|
39
|
-
{ status: "ok", cancelled: false, timedOut: false, stdinRequested: false },
|
|
40
|
-
(options) => {
|
|
41
|
-
options?.onChunk?.(largeOutput);
|
|
42
|
-
},
|
|
43
|
-
);
|
|
44
|
-
|
|
45
|
-
const result = await executePythonWithKernel(kernel, "print('hi')");
|
|
46
|
-
|
|
47
|
-
expect(result.truncated).toBe(true);
|
|
48
|
-
expect(result.fullOutputPath).toBeDefined();
|
|
49
|
-
expect(result.output.length).toBeLessThan(largeOutput.length);
|
|
50
|
-
if (result.fullOutputPath) {
|
|
51
|
-
cleanupPaths.push(result.fullOutputPath);
|
|
52
|
-
}
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
it("annotates timed out runs", async () => {
|
|
56
|
-
const kernel = new FakeKernel({ status: "ok", cancelled: true, timedOut: true, stdinRequested: false }, () => {});
|
|
57
|
-
|
|
58
|
-
const result = await executePythonWithKernel(kernel, "sleep", { timeout: 2000 });
|
|
59
|
-
|
|
60
|
-
expect(result.cancelled).toBe(true);
|
|
61
|
-
expect(result.exitCode).toBeUndefined();
|
|
62
|
-
expect(result.output).toContain("Command timed out after 2 seconds");
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
it("sanitizes ANSI and carriage returns", async () => {
|
|
66
|
-
const kernel = new FakeKernel(
|
|
67
|
-
{ status: "ok", cancelled: false, timedOut: false, stdinRequested: false },
|
|
68
|
-
(options) => {
|
|
69
|
-
options?.onChunk?.("\u001b[31mhello\r\n");
|
|
70
|
-
},
|
|
71
|
-
);
|
|
72
|
-
|
|
73
|
-
const result = await executePythonWithKernel(kernel, "print('hello')");
|
|
74
|
-
|
|
75
|
-
expect(result.output).toBe("hello\n");
|
|
76
|
-
});
|
|
77
|
-
});
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from "bun:test";
|
|
2
|
-
import { executePythonWithKernel, type PythonKernelExecutor } from "./python-executor";
|
|
3
|
-
import type { KernelExecuteOptions, KernelExecuteResult } from "./python-kernel";
|
|
4
|
-
|
|
5
|
-
class FakeKernel implements PythonKernelExecutor {
|
|
6
|
-
private result: KernelExecuteResult;
|
|
7
|
-
private onExecute: (options?: KernelExecuteOptions) => void;
|
|
8
|
-
|
|
9
|
-
constructor(result: KernelExecuteResult, onExecute: (options?: KernelExecuteOptions) => void) {
|
|
10
|
-
this.result = result;
|
|
11
|
-
this.onExecute = onExecute;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
async execute(_code: string, options?: KernelExecuteOptions): Promise<KernelExecuteResult> {
|
|
15
|
-
this.onExecute(options);
|
|
16
|
-
return this.result;
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
describe("executePythonWithKernel cancellation", () => {
|
|
21
|
-
it("annotates timeouts when cancelled", async () => {
|
|
22
|
-
const kernel = new FakeKernel(
|
|
23
|
-
{ status: "ok", cancelled: true, timedOut: true, stdinRequested: false },
|
|
24
|
-
(options) => {
|
|
25
|
-
options?.onChunk?.("tick\n");
|
|
26
|
-
},
|
|
27
|
-
);
|
|
28
|
-
|
|
29
|
-
const result = await executePythonWithKernel(kernel, "sleep(10)", { timeout: 5000 });
|
|
30
|
-
|
|
31
|
-
expect(result.cancelled).toBe(true);
|
|
32
|
-
expect(result.exitCode).toBeUndefined();
|
|
33
|
-
expect(result.output).toContain("Command timed out after 5 seconds");
|
|
34
|
-
});
|
|
35
|
-
});
|
|
@@ -1,139 +0,0 @@
|
|
|
1
|
-
import { afterEach, describe, expect, it } from "bun:test";
|
|
2
|
-
import { disposeAllKernelSessions, executePython } from "./python-executor";
|
|
3
|
-
import { type KernelExecuteOptions, type KernelExecuteResult, PythonKernel } from "./python-kernel";
|
|
4
|
-
|
|
5
|
-
process.env.OMP_PYTHON_SKIP_CHECK = "1";
|
|
6
|
-
|
|
7
|
-
class FakeKernel {
|
|
8
|
-
private result: KernelExecuteResult;
|
|
9
|
-
private onExecute?: (options?: KernelExecuteOptions) => void;
|
|
10
|
-
private alive: boolean;
|
|
11
|
-
readonly executeCalls: string[] = [];
|
|
12
|
-
shutdownCalls = 0;
|
|
13
|
-
|
|
14
|
-
constructor(
|
|
15
|
-
result: KernelExecuteResult,
|
|
16
|
-
options: { alive?: boolean; onExecute?: (options?: KernelExecuteOptions) => void } = {},
|
|
17
|
-
) {
|
|
18
|
-
this.result = result;
|
|
19
|
-
this.onExecute = options.onExecute;
|
|
20
|
-
this.alive = options.alive ?? true;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
isAlive(): boolean {
|
|
24
|
-
return this.alive;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
async execute(code: string, options?: KernelExecuteOptions): Promise<KernelExecuteResult> {
|
|
28
|
-
this.executeCalls.push(code);
|
|
29
|
-
this.onExecute?.(options);
|
|
30
|
-
return this.result;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
async shutdown(): Promise<void> {
|
|
34
|
-
this.shutdownCalls += 1;
|
|
35
|
-
this.alive = false;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
async ping(): Promise<boolean> {
|
|
39
|
-
return this.alive;
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
const okResult: KernelExecuteResult = {
|
|
44
|
-
status: "ok",
|
|
45
|
-
cancelled: false,
|
|
46
|
-
timedOut: false,
|
|
47
|
-
stdinRequested: false,
|
|
48
|
-
};
|
|
49
|
-
|
|
50
|
-
describe("executePython session lifecycle", () => {
|
|
51
|
-
const originalStart = PythonKernel.start;
|
|
52
|
-
|
|
53
|
-
afterEach(async () => {
|
|
54
|
-
PythonKernel.start = originalStart;
|
|
55
|
-
await disposeAllKernelSessions();
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
it("reuses a session kernel across calls", async () => {
|
|
59
|
-
let startCount = 0;
|
|
60
|
-
const kernel = new FakeKernel(okResult, { onExecute: (options) => options?.onChunk?.("ok\n") });
|
|
61
|
-
PythonKernel.start = async () => {
|
|
62
|
-
startCount += 1;
|
|
63
|
-
return kernel as unknown as PythonKernel;
|
|
64
|
-
};
|
|
65
|
-
|
|
66
|
-
const first = await executePython("print('one')", { sessionId: "session-1" });
|
|
67
|
-
const second = await executePython("print('two')", { sessionId: "session-1" });
|
|
68
|
-
|
|
69
|
-
expect(startCount).toBe(1);
|
|
70
|
-
expect(kernel.executeCalls).toEqual(["print('one')", "print('two')"]);
|
|
71
|
-
expect(first.output).toContain("ok");
|
|
72
|
-
expect(second.output).toContain("ok");
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
it("restarts the session kernel when not alive", async () => {
|
|
76
|
-
const deadKernel = new FakeKernel(okResult, { alive: false });
|
|
77
|
-
const liveKernel = new FakeKernel(okResult, { onExecute: (options) => options?.onChunk?.("live\n") });
|
|
78
|
-
const kernels = [deadKernel, liveKernel];
|
|
79
|
-
let startCount = 0;
|
|
80
|
-
|
|
81
|
-
PythonKernel.start = async () => {
|
|
82
|
-
startCount += 1;
|
|
83
|
-
return kernels.shift() as unknown as PythonKernel;
|
|
84
|
-
};
|
|
85
|
-
|
|
86
|
-
const result = await executePython("print('restart')", { sessionId: "session-restart" });
|
|
87
|
-
|
|
88
|
-
expect(startCount).toBe(2);
|
|
89
|
-
expect(deadKernel.shutdownCalls).toBe(1);
|
|
90
|
-
expect(deadKernel.executeCalls).toEqual([]);
|
|
91
|
-
expect(liveKernel.executeCalls).toEqual(["print('restart')"]);
|
|
92
|
-
expect(result.output).toContain("live");
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
it("resets the session kernel when requested", async () => {
|
|
96
|
-
const firstKernel = new FakeKernel(okResult);
|
|
97
|
-
const secondKernel = new FakeKernel(okResult);
|
|
98
|
-
const kernels = [firstKernel, secondKernel];
|
|
99
|
-
let startCount = 0;
|
|
100
|
-
|
|
101
|
-
PythonKernel.start = async () => {
|
|
102
|
-
startCount += 1;
|
|
103
|
-
return kernels.shift() as unknown as PythonKernel;
|
|
104
|
-
};
|
|
105
|
-
|
|
106
|
-
await executePython("print('one')", { sessionId: "session-reset" });
|
|
107
|
-
await executePython("print('two')", { sessionId: "session-reset", reset: true });
|
|
108
|
-
|
|
109
|
-
expect(startCount).toBe(2);
|
|
110
|
-
expect(firstKernel.shutdownCalls).toBe(1);
|
|
111
|
-
expect(secondKernel.executeCalls).toEqual(["print('two')"]);
|
|
112
|
-
});
|
|
113
|
-
|
|
114
|
-
it("uses per-call kernels when configured", async () => {
|
|
115
|
-
const kernelA = new FakeKernel(okResult);
|
|
116
|
-
const kernelB = new FakeKernel(okResult);
|
|
117
|
-
const kernels = [kernelA, kernelB];
|
|
118
|
-
let startCount = 0;
|
|
119
|
-
let shutdownCount = 0;
|
|
120
|
-
|
|
121
|
-
PythonKernel.start = async () => {
|
|
122
|
-
startCount += 1;
|
|
123
|
-
return kernels.shift() as unknown as PythonKernel;
|
|
124
|
-
};
|
|
125
|
-
|
|
126
|
-
kernelA.shutdown = async () => {
|
|
127
|
-
shutdownCount += 1;
|
|
128
|
-
};
|
|
129
|
-
kernelB.shutdown = async () => {
|
|
130
|
-
shutdownCount += 1;
|
|
131
|
-
};
|
|
132
|
-
|
|
133
|
-
await executePython("print('one')", { kernelMode: "per-call" });
|
|
134
|
-
await executePython("print('two')", { kernelMode: "per-call" });
|
|
135
|
-
|
|
136
|
-
expect(startCount).toBe(2);
|
|
137
|
-
expect(shutdownCount).toBe(2);
|
|
138
|
-
});
|
|
139
|
-
});
|
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from "bun:test";
|
|
2
|
-
import { executePythonWithKernel, type PythonKernelExecutor } from "./python-executor";
|
|
3
|
-
import type { KernelExecuteOptions, KernelExecuteResult } from "./python-kernel";
|
|
4
|
-
|
|
5
|
-
class FakeKernel implements PythonKernelExecutor {
|
|
6
|
-
private result: KernelExecuteResult;
|
|
7
|
-
private onExecute?: (options?: KernelExecuteOptions) => void;
|
|
8
|
-
|
|
9
|
-
constructor(result: KernelExecuteResult, onExecute?: (options?: KernelExecuteOptions) => void) {
|
|
10
|
-
this.result = result;
|
|
11
|
-
this.onExecute = onExecute;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
async execute(_code: string, options?: KernelExecuteOptions): Promise<KernelExecuteResult> {
|
|
15
|
-
this.onExecute?.(options);
|
|
16
|
-
return this.result;
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
describe("executePythonWithKernel result mapping", () => {
|
|
21
|
-
it("adds timeout annotation when cancelled", async () => {
|
|
22
|
-
const kernel = new FakeKernel({
|
|
23
|
-
status: "ok",
|
|
24
|
-
cancelled: true,
|
|
25
|
-
timedOut: true,
|
|
26
|
-
stdinRequested: false,
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
const result = await executePythonWithKernel(kernel, "sleep()", { timeout: 5000 });
|
|
30
|
-
|
|
31
|
-
expect(result.exitCode).toBeUndefined();
|
|
32
|
-
expect(result.cancelled).toBe(true);
|
|
33
|
-
expect(result.output).toContain("Command timed out after 5 seconds");
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
it("maps kernel error status to exit code 1", async () => {
|
|
37
|
-
const kernel = new FakeKernel(
|
|
38
|
-
{ status: "error", cancelled: false, timedOut: false, stdinRequested: false },
|
|
39
|
-
(options) => {
|
|
40
|
-
options?.onChunk?.("Traceback...\n");
|
|
41
|
-
},
|
|
42
|
-
);
|
|
43
|
-
|
|
44
|
-
const result = await executePythonWithKernel(kernel, "raise ValueError('boom')");
|
|
45
|
-
|
|
46
|
-
expect(result.exitCode).toBe(1);
|
|
47
|
-
expect(result.output).toContain("Traceback");
|
|
48
|
-
});
|
|
49
|
-
});
|