@oh-my-pi/pi-coding-agent 16.1.13 → 16.1.14

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 (129) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/dist/cli.js +5547 -2759
  3. package/dist/types/config/settings-schema.d.ts +44 -14
  4. package/dist/types/eval/__tests__/julia-prelude.test.d.ts +1 -0
  5. package/dist/types/eval/backend-helpers.d.ts +23 -0
  6. package/dist/types/eval/executor-base.d.ts +118 -0
  7. package/dist/types/eval/index.d.ts +2 -0
  8. package/dist/types/eval/jl/executor.d.ts +44 -0
  9. package/dist/types/eval/jl/index.d.ts +11 -0
  10. package/dist/types/eval/jl/kernel.d.ts +28 -0
  11. package/dist/types/eval/jl/prelude.d.ts +1 -0
  12. package/dist/types/eval/jl/runtime.d.ts +22 -0
  13. package/dist/types/eval/kernel-base.d.ts +105 -0
  14. package/dist/types/eval/py/kernel.d.ts +3 -61
  15. package/dist/types/eval/rb/executor.d.ts +77 -0
  16. package/dist/types/eval/rb/index.d.ts +11 -0
  17. package/dist/types/eval/rb/kernel.d.ts +31 -0
  18. package/dist/types/eval/rb/prelude.d.ts +1 -0
  19. package/dist/types/eval/rb/runtime.d.ts +23 -0
  20. package/dist/types/eval/runtime-env.d.ts +24 -0
  21. package/dist/types/eval/types.d.ts +3 -3
  22. package/dist/types/extensibility/extensions/runner.d.ts +2 -15
  23. package/dist/types/extensibility/hooks/loader.d.ts +1 -25
  24. package/dist/types/extensibility/session-handler-types.d.ts +23 -0
  25. package/dist/types/jsonrpc/message-framing.d.ts +35 -0
  26. package/dist/types/mnemopi/embed-client.d.ts +7 -24
  27. package/dist/types/modes/components/chat-transcript-builder.d.ts +1 -1
  28. package/dist/types/modes/components/custom-editor.d.ts +11 -0
  29. package/dist/types/modes/components/selector-helpers.d.ts +53 -0
  30. package/dist/types/modes/interactive-mode.d.ts +0 -2
  31. package/dist/types/modes/theme/theme.d.ts +8 -1
  32. package/dist/types/modes/types.d.ts +0 -2
  33. package/dist/types/modes/utils/copy-targets.d.ts +1 -1
  34. package/dist/types/modes/utils/interactive-context-helpers.d.ts +14 -0
  35. package/dist/types/modes/utils/transcript-render-helpers.d.ts +54 -0
  36. package/dist/types/sdk.d.ts +1 -1
  37. package/dist/types/session/agent-session.d.ts +2 -2
  38. package/dist/types/stt/asr-client.d.ts +3 -29
  39. package/dist/types/subprocess/worker-client.d.ts +149 -0
  40. package/dist/types/subprocess/worker-runtime.d.ts +107 -0
  41. package/dist/types/tiny/title-client.d.ts +14 -34
  42. package/dist/types/tools/eval-backends.d.ts +6 -3
  43. package/dist/types/tools/eval-render.d.ts +6 -5
  44. package/dist/types/tools/eval.d.ts +13 -15
  45. package/dist/types/tools/index.d.ts +3 -3
  46. package/dist/types/tts/tts-client.d.ts +3 -28
  47. package/dist/types/tui/code-cell.d.ts +7 -0
  48. package/dist/types/utils/file-display-mode.d.ts +1 -1
  49. package/dist/types/web/parallel.d.ts +6 -0
  50. package/package.json +12 -12
  51. package/src/config/settings-schema.ts +50 -18
  52. package/src/config/settings.ts +5 -0
  53. package/src/dap/client.ts +13 -107
  54. package/src/eval/__tests__/julia-prelude.test.ts +77 -0
  55. package/src/eval/backend-helpers.ts +48 -0
  56. package/src/eval/executor-base.ts +425 -0
  57. package/src/eval/index.ts +2 -0
  58. package/src/eval/jl/executor.ts +540 -0
  59. package/src/eval/jl/index.ts +54 -0
  60. package/src/eval/jl/kernel.ts +235 -0
  61. package/src/eval/jl/prelude.jl +930 -0
  62. package/src/eval/jl/prelude.ts +3 -0
  63. package/src/eval/jl/runner.jl +634 -0
  64. package/src/eval/jl/runtime.ts +118 -0
  65. package/src/eval/js/index.ts +3 -14
  66. package/src/eval/kernel-base.ts +569 -0
  67. package/src/eval/py/executor.ts +43 -252
  68. package/src/eval/py/index.ts +9 -20
  69. package/src/eval/py/kernel.ts +29 -544
  70. package/src/eval/rb/executor.ts +504 -0
  71. package/src/eval/rb/index.ts +54 -0
  72. package/src/eval/rb/kernel.ts +230 -0
  73. package/src/eval/rb/prelude.rb +721 -0
  74. package/src/eval/rb/prelude.ts +3 -0
  75. package/src/eval/rb/runner.rb +474 -0
  76. package/src/eval/rb/runtime.ts +132 -0
  77. package/src/eval/runtime-env.ts +104 -0
  78. package/src/eval/types.ts +3 -3
  79. package/src/extensibility/extensions/runner.ts +4 -11
  80. package/src/extensibility/hooks/loader.ts +3 -21
  81. package/src/extensibility/session-handler-types.ts +21 -0
  82. package/src/internal-urls/docs-index.generated.txt +1 -1
  83. package/src/jsonrpc/message-framing.ts +142 -0
  84. package/src/lsp/client.ts +13 -109
  85. package/src/mnemopi/embed-client.ts +43 -198
  86. package/src/modes/components/agent-dashboard.ts +17 -40
  87. package/src/modes/components/chat-transcript-builder.ts +18 -102
  88. package/src/modes/components/custom-editor.ts +19 -0
  89. package/src/modes/components/extensions/extension-dashboard.ts +4 -13
  90. package/src/modes/components/extensions/extension-list.ts +16 -44
  91. package/src/modes/components/history-search.ts +4 -16
  92. package/src/modes/components/selector-helpers.ts +129 -0
  93. package/src/modes/components/settings-selector.ts +7 -5
  94. package/src/modes/components/tree-selector.ts +13 -18
  95. package/src/modes/controllers/event-controller.ts +3 -9
  96. package/src/modes/controllers/input-controller.ts +32 -54
  97. package/src/modes/interactive-mode.ts +5 -7
  98. package/src/modes/theme/theme.ts +35 -0
  99. package/src/modes/types.ts +0 -2
  100. package/src/modes/utils/copy-targets.ts +3 -2
  101. package/src/modes/utils/interactive-context-helpers.ts +27 -0
  102. package/src/modes/utils/transcript-render-helpers.ts +157 -0
  103. package/src/modes/utils/ui-helpers.ts +21 -126
  104. package/src/prompts/system/system-prompt.md +10 -10
  105. package/src/prompts/tools/bash.md +0 -1
  106. package/src/prompts/tools/eval.md +6 -4
  107. package/src/prompts/tools/find.md +0 -4
  108. package/src/prompts/tools/read.md +1 -2
  109. package/src/prompts/tools/replace.md +1 -1
  110. package/src/prompts/tools/search.md +0 -1
  111. package/src/sdk.ts +13 -7
  112. package/src/session/agent-session.ts +9 -7
  113. package/src/stt/asr-client.ts +35 -215
  114. package/src/stt/asr-worker.ts +29 -181
  115. package/src/subprocess/worker-client.ts +297 -0
  116. package/src/subprocess/worker-runtime.ts +277 -0
  117. package/src/task/executor.ts +4 -4
  118. package/src/tiny/title-client.ts +53 -219
  119. package/src/tiny/worker.ts +29 -180
  120. package/src/tools/eval-backends.ts +10 -3
  121. package/src/tools/eval-render.ts +17 -8
  122. package/src/tools/eval.ts +187 -22
  123. package/src/tools/index.ts +51 -28
  124. package/src/tts/tts-client.ts +38 -206
  125. package/src/tts/tts-worker.ts +23 -97
  126. package/src/tui/code-cell.ts +12 -1
  127. package/src/utils/file-display-mode.ts +2 -3
  128. package/src/web/parallel.ts +43 -42
  129. package/src/web/search/providers/parallel.ts +10 -99
@@ -122,7 +122,7 @@ export const TAB_GROUPS: Record<SettingTab, readonly string[]> = {
122
122
  context: ["General", "Compaction", "Rules (TTSR)", "Experimental"],
123
123
  memory: ["General", "Auto-Learn", "Mnemopi", "Hindsight"],
124
124
  files: ["Editing", "Reading", "Read Summaries", "LSP"],
125
- shell: ["Bash", "Eval & Python"],
125
+ shell: ["Bash", "Eval & Runtimes"],
126
126
  tools: [
127
127
  "Available Tools",
128
128
  "Todos",
@@ -2703,18 +2703,6 @@ export const SETTINGS_SCHEMA = {
2703
2703
  },
2704
2704
  },
2705
2705
 
2706
- readHashLines: {
2707
- type: "boolean",
2708
- default: true,
2709
- ui: {
2710
- tab: "files",
2711
- group: "Reading",
2712
- label: "Hash Lines",
2713
- description:
2714
- "Include snapshot-tag headers and line numbers in read output for hashline edit mode ([PATH#TAG] plus LINE:content)",
2715
- },
2716
- },
2717
-
2718
2706
  "read.defaultLimit": {
2719
2707
  type: "number",
2720
2708
  default: 300,
@@ -2981,7 +2969,7 @@ export const SETTINGS_SCHEMA = {
2981
2969
  default: true,
2982
2970
  ui: {
2983
2971
  tab: "shell",
2984
- group: "Eval & Python",
2972
+ group: "Eval & Runtimes",
2985
2973
  label: "Python Eval Backend",
2986
2974
  description: "Allow the eval tool to dispatch Python cells to the IPython kernel",
2987
2975
  },
@@ -2992,20 +2980,42 @@ export const SETTINGS_SCHEMA = {
2992
2980
  default: true,
2993
2981
  ui: {
2994
2982
  tab: "shell",
2995
- group: "Eval & Python",
2983
+ group: "Eval & Runtimes",
2996
2984
  label: "JavaScript Eval Backend",
2997
2985
  description: "Allow the eval tool to dispatch JavaScript cells to the in-process runtime",
2998
2986
  },
2999
2987
  },
3000
2988
 
3001
- // Python kernel knobs (consumed by the eval py backend and the /python slash command)
2989
+ "eval.rb": {
2990
+ type: "boolean",
2991
+ default: false,
2992
+ ui: {
2993
+ tab: "shell",
2994
+ group: "Eval & Runtimes",
2995
+ label: "Ruby Eval Backend",
2996
+ description: "Allow the eval tool to dispatch Ruby cells to the persistent Ruby kernel",
2997
+ },
2998
+ },
2999
+
3000
+ "eval.jl": {
3001
+ type: "boolean",
3002
+ default: false,
3003
+ ui: {
3004
+ tab: "shell",
3005
+ group: "Eval & Runtimes",
3006
+ label: "Julia Eval Backend",
3007
+ description: "Allow the eval tool to dispatch Julia cells to the persistent Julia kernel",
3008
+ },
3009
+ },
3010
+
3011
+ // Runtime knobs (consumed by eval backends and the /python slash command)
3002
3012
  "python.kernelMode": {
3003
3013
  type: "enum",
3004
3014
  values: ["session", "per-call"] as const,
3005
3015
  default: "session",
3006
3016
  ui: {
3007
3017
  tab: "shell",
3008
- group: "Eval & Python",
3018
+ group: "Eval & Runtimes",
3009
3019
  label: "Python Kernel Mode",
3010
3020
  description: "Keep the IPython kernel alive across eval calls or start fresh each time",
3011
3021
  },
@@ -3015,12 +3025,34 @@ export const SETTINGS_SCHEMA = {
3015
3025
  default: "",
3016
3026
  ui: {
3017
3027
  tab: "shell",
3018
- group: "Eval & Python",
3028
+ group: "Eval & Runtimes",
3019
3029
  label: "Python Interpreter",
3020
3030
  description:
3021
3031
  "Optional path to an exact Python executable. When set, automatic Python runtime discovery is skipped.",
3022
3032
  },
3023
3033
  },
3034
+ "ruby.interpreter": {
3035
+ type: "string",
3036
+ default: "",
3037
+ ui: {
3038
+ tab: "shell",
3039
+ group: "Eval & Runtimes",
3040
+ label: "Ruby Interpreter",
3041
+ description:
3042
+ "Optional path to an exact Ruby executable. When set, automatic Ruby runtime discovery is skipped.",
3043
+ },
3044
+ },
3045
+ "julia.interpreter": {
3046
+ type: "string",
3047
+ default: "",
3048
+ ui: {
3049
+ tab: "shell",
3050
+ group: "Eval & Runtimes",
3051
+ label: "Julia Interpreter",
3052
+ description:
3053
+ "Optional path to an exact Julia executable. When set, automatic Julia runtime discovery is skipped.",
3054
+ },
3055
+ },
3024
3056
 
3025
3057
  // ────────────────────────────────────────────────────────────────────────
3026
3058
  // Tools
@@ -949,6 +949,11 @@ export class Settings {
949
949
  delete raw["power.preventDisplaySleep"];
950
950
  }
951
951
 
952
+ // readHashLines: removed. Hashline anchors are now driven solely by
953
+ // edit.mode === "hashline"; the separate read toggle only ever produced
954
+ // the incoherent "hashline edits without addressable anchors" state.
955
+ delete raw.readHashLines;
956
+
952
957
  return raw;
953
958
  }
954
959
 
package/src/dap/client.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import * as fs from "node:fs/promises";
2
2
  import { isEnoent, logger, ptree } from "@oh-my-pi/pi-utils";
3
3
  import { NON_INTERACTIVE_ENV } from "../exec/non-interactive-env";
4
+ import { MessageFramer } from "../jsonrpc/message-framing";
4
5
  import { ToolAbortError } from "../tools/tool-errors";
5
6
  import type {
6
7
  DapCapabilities,
@@ -29,69 +30,6 @@ type DapReverseRequestHandler = (args: unknown) => unknown | Promise<unknown>;
29
30
 
30
31
  const DEFAULT_REQUEST_TIMEOUT_MS = 30_000;
31
32
 
32
- // Reused for all full decodes; each decode() resets state, so a single
33
- // instance is safe and avoids per-message TextDecoder allocation.
34
- const MESSAGE_DECODER = new TextDecoder("utf-8");
35
-
36
- /**
37
- * Locate the `\r\n\r\n` header terminator across the pending chunk list.
38
- * Returns the absolute byte index of the first `\r`, or -1 when not present.
39
- * Equivalent to scanning the contiguous concatenation of the chunks.
40
- */
41
- function findHeaderEndInChunks(chunks: Buffer[]): number {
42
- let global = 0;
43
- let b0 = -1;
44
- let b1 = -1;
45
- let b2 = -1;
46
- for (const chunk of chunks) {
47
- for (let i = 0; i < chunk.length; i++) {
48
- const b3 = chunk[i];
49
- if (b0 === 13 && b1 === 10 && b2 === 13 && b3 === 10) {
50
- return global - 3;
51
- }
52
- b0 = b1;
53
- b1 = b2;
54
- b2 = b3;
55
- global++;
56
- }
57
- }
58
- return -1;
59
- }
60
-
61
- /** Copy the byte range [from, to) out of the pending chunk list into one Buffer. */
62
- function copyChunkRange(chunks: Buffer[], from: number, to: number): Buffer {
63
- const out = Buffer.allocUnsafe(to - from);
64
- let global = 0;
65
- let written = 0;
66
- for (const chunk of chunks) {
67
- const chunkEnd = global + chunk.length;
68
- if (chunkEnd > from && global < to) {
69
- const start = Math.max(from, global) - global;
70
- const end = Math.min(to, chunkEnd) - global;
71
- chunk.copy(out, written, start, end);
72
- written += end - start;
73
- }
74
- global = chunkEnd;
75
- if (global >= to) break;
76
- }
77
- return out;
78
- }
79
-
80
- /** Drop the first `count` bytes from the pending chunk list in place. */
81
- function dropChunkFront(chunks: Buffer[], count: number): void {
82
- let removed = 0;
83
- while (chunks.length > 0) {
84
- const head = chunks[0];
85
- if (removed + head.length <= count) {
86
- removed += head.length;
87
- chunks.shift();
88
- } else {
89
- chunks[0] = head.subarray(count - removed);
90
- break;
91
- }
92
- }
93
- }
94
-
95
33
  async function writeMessage(sink: DapWriteSink, message: DapRequestMessage | DapResponseMessage): Promise<void> {
96
34
  const content = JSON.stringify(message);
97
35
  sink.write(`Content-Length: ${Buffer.byteLength(content, "utf-8")}\r\n\r\n`);
@@ -452,52 +390,25 @@ export class DapClient {
452
390
  this.#isReading = true;
453
391
  const reader = this.#readable.getReader();
454
392
 
455
- // Incoming bytes are buffered as a list of chunks and only joined when a
456
- // full message is framed (mirrors the LSP reader) — concatenating the
457
- // accumulator on every read is O(n^2) for messages spanning many reads.
458
- const pendingChunks: Buffer[] = [];
459
- let pendingLen = 0;
460
- if (this.#messageBuffer.length > 0) {
461
- pendingChunks.push(this.#messageBuffer);
462
- pendingLen = this.#messageBuffer.length;
463
- }
393
+ const framer = new MessageFramer(this.#messageBuffer);
464
394
 
465
395
  try {
466
396
  while (true) {
467
397
  const { done, value } = await reader.read();
468
398
  if (done) break;
469
399
 
470
- pendingChunks.push(Buffer.from(value));
471
- pendingLen += value.length;
400
+ framer.push(Buffer.from(value));
472
401
 
473
402
  // Drain every complete message currently buffered.
474
- while (true) {
475
- const headerEnd = findHeaderEndInChunks(pendingChunks);
476
- if (headerEnd === -1) break;
477
-
478
- const headerText = MESSAGE_DECODER.decode(copyChunkRange(pendingChunks, 0, headerEnd));
479
- const contentLengthMatch = headerText.match(/Content-Length: (\d+)/i);
480
- if (!contentLengthMatch) {
481
- // Non-protocol bytes (e.g. an adapter printing to stdout).
482
- // Drop past the bogus terminator and resync instead of
483
- // stalling on the same junk header forever.
484
- logger.warn("DAP framing resync: header block without Content-Length", {
485
- adapter: this.adapter.name,
486
- header: headerText.slice(0, 200),
487
- });
488
- dropChunkFront(pendingChunks, headerEnd + 4);
489
- pendingLen -= headerEnd + 4;
490
- continue;
491
- }
492
-
493
- const contentLength = Number.parseInt(contentLengthMatch[1], 10);
494
- const messageStart = headerEnd + 4; // Skip \r\n\r\n
495
- const messageEnd = messageStart + contentLength;
496
- if (pendingLen < messageEnd) break;
497
-
498
- const messageText = MESSAGE_DECODER.decode(copyChunkRange(pendingChunks, messageStart, messageEnd));
499
- dropChunkFront(pendingChunks, messageEnd);
500
- pendingLen -= messageEnd;
403
+ for (const messageText of framer.drain(headerText => {
404
+ // Non-protocol bytes (e.g. an adapter printing to stdout).
405
+ // Drop past the bogus terminator and resync instead of
406
+ // stalling on the same junk header forever.
407
+ logger.warn("DAP framing resync: header block without Content-Length", {
408
+ adapter: this.adapter.name,
409
+ header: headerText.slice(0, 200),
410
+ });
411
+ })) {
501
412
  this.#lastActivity = Date.now();
502
413
 
503
414
  // A malformed message must not kill the reader — later
@@ -523,12 +434,7 @@ export class DapClient {
523
434
  this.#rejectPendingRequests(new Error(`DAP connection closed: ${toErrorMessage(error)}`));
524
435
  } finally {
525
436
  // Persist any unparsed remainder so a restarted reader resumes mid-message.
526
- this.#messageBuffer =
527
- pendingChunks.length === 0
528
- ? Buffer.alloc(0)
529
- : pendingChunks.length === 1
530
- ? pendingChunks[0]
531
- : Buffer.concat(pendingChunks, pendingLen);
437
+ this.#messageBuffer = framer.remainder();
532
438
  reader.releaseLock();
533
439
  this.#isReading = false;
534
440
  }
@@ -0,0 +1,77 @@
1
+ import { afterEach, describe, expect, it } from "bun:test";
2
+ import * as path from "node:path";
3
+ import { $which, TempDir } from "@oh-my-pi/pi-utils";
4
+ import { disposeJuliaKernelSessionsByOwner, executeJulia } from "../jl/executor";
5
+
6
+ const HAS_JULIA = Boolean($which("julia"));
7
+ const OWNER_ID = "julia-prelude-tests";
8
+
9
+ describe.skipIf(!HAS_JULIA)("eval Julia prelude helpers", () => {
10
+ afterEach(async () => {
11
+ await disposeJuliaKernelSessionsByOwner(OWNER_ID);
12
+ });
13
+
14
+ it("supports tree keyword options and unified diff", async () => {
15
+ using tempDir = TempDir.createSync("@omp-eval-julia-helpers-");
16
+ await Bun.write(path.join(tempDir.path(), "a.txt"), "same\nold\n");
17
+ await Bun.write(path.join(tempDir.path(), "b.txt"), "same\nnew\n");
18
+ await Bun.write(path.join(tempDir.path(), "dir", "child.txt"), "child");
19
+
20
+ const result = await executeJulia(
21
+ `
22
+ d = diff("a.txt", "b.txt")
23
+ println("DIFF_DELETE=", occursin("-old", d))
24
+ println("DIFF_ADD=", occursin("+new", d))
25
+ t = tree(".", max_depth=2)
26
+ println("TREE_CHILD=", occursin("child.txt", t))
27
+ nothing
28
+ `,
29
+ {
30
+ cwd: tempDir.path(),
31
+ sessionId: `julia-prelude-diff:${crypto.randomUUID()}`,
32
+ kernelOwnerId: OWNER_ID,
33
+ reset: true,
34
+ },
35
+ );
36
+
37
+ expect(result.exitCode).toBe(0);
38
+ expect(result.output).toContain("DIFF_DELETE=true");
39
+ expect(result.output).toContain("DIFF_ADD=true");
40
+ expect(result.output).toContain("TREE_CHILD=true");
41
+ });
42
+
43
+ it("supports output ranges, JSON queries, metadata, and ANSI stripping", async () => {
44
+ using tempDir = TempDir.createSync("@omp-eval-julia-output-");
45
+ const artifactsDir = path.join(tempDir.path(), "session-artifacts");
46
+ await Bun.write(path.join(artifactsDir, "alpha.md"), "one\ntwo\nthree\nfour");
47
+ await Bun.write(path.join(artifactsDir, "json.md"), JSON.stringify({ items: [{ name: "a" }, { name: "b" }] }));
48
+ await Bun.write(path.join(artifactsDir, "ansi.md"), "\u001b[31mred\u001b[0m");
49
+
50
+ const result = await executeJulia(
51
+ `
52
+ println("RANGE=", replace(output("alpha", offset=2, limit=2), "\\n" => "|"))
53
+ println("QUERY=", output("json", query=".items[1].name"))
54
+ println("STRIPPED=", output("ansi", format="stripped"))
55
+ meta = output("alpha", format="json")
56
+ println("META=", meta["id"], ":", meta["char_count"] > 0)
57
+ multi = output("alpha", "json")
58
+ println("MULTI=", length(multi), ":", multi[1]["id"], ":", multi[2]["id"])
59
+ nothing
60
+ `,
61
+ {
62
+ cwd: tempDir.path(),
63
+ artifactsDir,
64
+ sessionId: `julia-prelude-output:${crypto.randomUUID()}`,
65
+ kernelOwnerId: OWNER_ID,
66
+ reset: true,
67
+ },
68
+ );
69
+
70
+ expect(result.exitCode).toBe(0);
71
+ expect(result.output).toContain("RANGE=two|three");
72
+ expect(result.output).toContain('QUERY="b"');
73
+ expect(result.output).toContain("STRIPPED=red");
74
+ expect(result.output).toContain("META=alpha:true");
75
+ expect(result.output).toContain("MULTI=2:alpha:json");
76
+ });
77
+ });
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Helpers shared by the per-language eval backend definitions (jl/js/py/rb
3
+ * index modules): session-id namespacing, settings access, and projection of
4
+ * executor results into the ExecutorBackend result shape.
5
+ */
6
+ import type { ToolSession } from "../tools";
7
+ import type { ExecutorBackendResult } from "./backend";
8
+ import type { EvalDisplayOutput } from "./types";
9
+
10
+ export function namespaceSessionId(sessionId: string, prefix: string): string {
11
+ return sessionId.startsWith(prefix) ? sessionId : `${prefix}${sessionId}`;
12
+ }
13
+
14
+ export function readSetting<T>(session: ToolSession, key: string): T | undefined {
15
+ const settings = session.settings as { get?: (key: string) => T | undefined } | undefined;
16
+ return settings?.get?.(key);
17
+ }
18
+
19
+ export function readInterpreterSetting(session: ToolSession, key: string): string | undefined {
20
+ const value = readSetting<unknown>(session, key);
21
+ return typeof value === "string" ? value.trim() || undefined : undefined;
22
+ }
23
+
24
+ export function toExecutorBackendResult(result: {
25
+ output: string;
26
+ exitCode: number | undefined;
27
+ cancelled: boolean;
28
+ truncated: boolean;
29
+ artifactId?: string | undefined;
30
+ totalLines: number;
31
+ totalBytes: number;
32
+ outputLines: number;
33
+ outputBytes: number;
34
+ displayOutputs: EvalDisplayOutput[];
35
+ }): ExecutorBackendResult {
36
+ return {
37
+ output: result.output,
38
+ exitCode: result.exitCode,
39
+ cancelled: result.cancelled,
40
+ truncated: result.truncated,
41
+ artifactId: result.artifactId,
42
+ totalLines: result.totalLines,
43
+ totalBytes: result.totalBytes,
44
+ outputLines: result.outputLines,
45
+ outputBytes: result.outputBytes,
46
+ displayOutputs: result.displayOutputs,
47
+ };
48
+ }