@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.
Files changed (53) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/README.md +6 -6
  3. package/package.json +8 -7
  4. package/src/migrations.ts +1 -34
  5. package/src/vendor/photon/index.js +4 -2
  6. package/src/vendor/photon/photon_rs_bg.wasm.b64.js +1 -0
  7. package/src/core/python-executor-display.test.ts +0 -42
  8. package/src/core/python-executor-lifecycle.test.ts +0 -99
  9. package/src/core/python-executor-mapping.test.ts +0 -41
  10. package/src/core/python-executor-per-call.test.ts +0 -49
  11. package/src/core/python-executor-session.test.ts +0 -103
  12. package/src/core/python-executor-streaming.test.ts +0 -77
  13. package/src/core/python-executor-timeout.test.ts +0 -35
  14. package/src/core/python-executor.lifecycle.test.ts +0 -139
  15. package/src/core/python-executor.result.test.ts +0 -49
  16. package/src/core/python-executor.test.ts +0 -180
  17. package/src/core/python-kernel-display.test.ts +0 -54
  18. package/src/core/python-kernel-env.test.ts +0 -138
  19. package/src/core/python-kernel-session.test.ts +0 -87
  20. package/src/core/python-kernel-ws.test.ts +0 -104
  21. package/src/core/python-kernel.lifecycle.test.ts +0 -249
  22. package/src/core/python-kernel.test.ts +0 -461
  23. package/src/core/python-modules.test.ts +0 -102
  24. package/src/core/python-prelude.test.ts +0 -140
  25. package/src/core/settings-manager-python.test.ts +0 -23
  26. package/src/core/streaming-output.test.ts +0 -26
  27. package/src/core/system-prompt.python.test.ts +0 -17
  28. package/src/core/tools/index.test.ts +0 -212
  29. package/src/core/tools/python-execution.test.ts +0 -68
  30. package/src/core/tools/python-fallback.test.ts +0 -72
  31. package/src/core/tools/python-renderer.test.ts +0 -36
  32. package/src/core/tools/python-tool-mode.test.ts +0 -43
  33. package/src/core/tools/python.test.ts +0 -121
  34. package/src/core/tools/schema-validation.test.ts +0 -530
  35. package/src/core/tools/web-scrapers/academic.test.ts +0 -239
  36. package/src/core/tools/web-scrapers/business.test.ts +0 -82
  37. package/src/core/tools/web-scrapers/dev-platforms.test.ts +0 -254
  38. package/src/core/tools/web-scrapers/documentation.test.ts +0 -85
  39. package/src/core/tools/web-scrapers/finance-media.test.ts +0 -144
  40. package/src/core/tools/web-scrapers/git-hosting.test.ts +0 -272
  41. package/src/core/tools/web-scrapers/media.test.ts +0 -138
  42. package/src/core/tools/web-scrapers/package-managers-2.test.ts +0 -199
  43. package/src/core/tools/web-scrapers/package-managers.test.ts +0 -171
  44. package/src/core/tools/web-scrapers/package-registries.test.ts +0 -259
  45. package/src/core/tools/web-scrapers/research.test.ts +0 -107
  46. package/src/core/tools/web-scrapers/security.test.ts +0 -103
  47. package/src/core/tools/web-scrapers/social-extended.test.ts +0 -192
  48. package/src/core/tools/web-scrapers/social.test.ts +0 -259
  49. package/src/core/tools/web-scrapers/stackexchange.test.ts +0 -120
  50. package/src/core/tools/web-scrapers/standards.test.ts +0 -122
  51. package/src/core/tools/web-scrapers/wikipedia.test.ts +0 -73
  52. package/src/core/tools/web-scrapers/youtube.test.ts +0 -198
  53. 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
- });