@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.
- package/CHANGELOG.md +27 -0
- package/dist/cli.js +5547 -2759
- package/dist/types/config/settings-schema.d.ts +44 -14
- package/dist/types/eval/__tests__/julia-prelude.test.d.ts +1 -0
- package/dist/types/eval/backend-helpers.d.ts +23 -0
- package/dist/types/eval/executor-base.d.ts +118 -0
- package/dist/types/eval/index.d.ts +2 -0
- package/dist/types/eval/jl/executor.d.ts +44 -0
- package/dist/types/eval/jl/index.d.ts +11 -0
- package/dist/types/eval/jl/kernel.d.ts +28 -0
- package/dist/types/eval/jl/prelude.d.ts +1 -0
- package/dist/types/eval/jl/runtime.d.ts +22 -0
- package/dist/types/eval/kernel-base.d.ts +105 -0
- package/dist/types/eval/py/kernel.d.ts +3 -61
- package/dist/types/eval/rb/executor.d.ts +77 -0
- package/dist/types/eval/rb/index.d.ts +11 -0
- package/dist/types/eval/rb/kernel.d.ts +31 -0
- package/dist/types/eval/rb/prelude.d.ts +1 -0
- package/dist/types/eval/rb/runtime.d.ts +23 -0
- package/dist/types/eval/runtime-env.d.ts +24 -0
- package/dist/types/eval/types.d.ts +3 -3
- package/dist/types/extensibility/extensions/runner.d.ts +2 -15
- package/dist/types/extensibility/hooks/loader.d.ts +1 -25
- package/dist/types/extensibility/session-handler-types.d.ts +23 -0
- package/dist/types/jsonrpc/message-framing.d.ts +35 -0
- package/dist/types/mnemopi/embed-client.d.ts +7 -24
- package/dist/types/modes/components/chat-transcript-builder.d.ts +1 -1
- package/dist/types/modes/components/custom-editor.d.ts +11 -0
- package/dist/types/modes/components/selector-helpers.d.ts +53 -0
- package/dist/types/modes/interactive-mode.d.ts +0 -2
- package/dist/types/modes/theme/theme.d.ts +8 -1
- package/dist/types/modes/types.d.ts +0 -2
- package/dist/types/modes/utils/copy-targets.d.ts +1 -1
- package/dist/types/modes/utils/interactive-context-helpers.d.ts +14 -0
- package/dist/types/modes/utils/transcript-render-helpers.d.ts +54 -0
- package/dist/types/sdk.d.ts +1 -1
- package/dist/types/session/agent-session.d.ts +2 -2
- package/dist/types/stt/asr-client.d.ts +3 -29
- package/dist/types/subprocess/worker-client.d.ts +149 -0
- package/dist/types/subprocess/worker-runtime.d.ts +107 -0
- package/dist/types/tiny/title-client.d.ts +14 -34
- package/dist/types/tools/eval-backends.d.ts +6 -3
- package/dist/types/tools/eval-render.d.ts +6 -5
- package/dist/types/tools/eval.d.ts +13 -15
- package/dist/types/tools/index.d.ts +3 -3
- package/dist/types/tts/tts-client.d.ts +3 -28
- package/dist/types/tui/code-cell.d.ts +7 -0
- package/dist/types/utils/file-display-mode.d.ts +1 -1
- package/dist/types/web/parallel.d.ts +6 -0
- package/package.json +12 -12
- package/src/config/settings-schema.ts +50 -18
- package/src/config/settings.ts +5 -0
- package/src/dap/client.ts +13 -107
- package/src/eval/__tests__/julia-prelude.test.ts +77 -0
- package/src/eval/backend-helpers.ts +48 -0
- package/src/eval/executor-base.ts +425 -0
- package/src/eval/index.ts +2 -0
- package/src/eval/jl/executor.ts +540 -0
- package/src/eval/jl/index.ts +54 -0
- package/src/eval/jl/kernel.ts +235 -0
- package/src/eval/jl/prelude.jl +930 -0
- package/src/eval/jl/prelude.ts +3 -0
- package/src/eval/jl/runner.jl +634 -0
- package/src/eval/jl/runtime.ts +118 -0
- package/src/eval/js/index.ts +3 -14
- package/src/eval/kernel-base.ts +569 -0
- package/src/eval/py/executor.ts +43 -252
- package/src/eval/py/index.ts +9 -20
- package/src/eval/py/kernel.ts +29 -544
- package/src/eval/rb/executor.ts +504 -0
- package/src/eval/rb/index.ts +54 -0
- package/src/eval/rb/kernel.ts +230 -0
- package/src/eval/rb/prelude.rb +721 -0
- package/src/eval/rb/prelude.ts +3 -0
- package/src/eval/rb/runner.rb +474 -0
- package/src/eval/rb/runtime.ts +132 -0
- package/src/eval/runtime-env.ts +104 -0
- package/src/eval/types.ts +3 -3
- package/src/extensibility/extensions/runner.ts +4 -11
- package/src/extensibility/hooks/loader.ts +3 -21
- package/src/extensibility/session-handler-types.ts +21 -0
- package/src/internal-urls/docs-index.generated.txt +1 -1
- package/src/jsonrpc/message-framing.ts +142 -0
- package/src/lsp/client.ts +13 -109
- package/src/mnemopi/embed-client.ts +43 -198
- package/src/modes/components/agent-dashboard.ts +17 -40
- package/src/modes/components/chat-transcript-builder.ts +18 -102
- package/src/modes/components/custom-editor.ts +19 -0
- package/src/modes/components/extensions/extension-dashboard.ts +4 -13
- package/src/modes/components/extensions/extension-list.ts +16 -44
- package/src/modes/components/history-search.ts +4 -16
- package/src/modes/components/selector-helpers.ts +129 -0
- package/src/modes/components/settings-selector.ts +7 -5
- package/src/modes/components/tree-selector.ts +13 -18
- package/src/modes/controllers/event-controller.ts +3 -9
- package/src/modes/controllers/input-controller.ts +32 -54
- package/src/modes/interactive-mode.ts +5 -7
- package/src/modes/theme/theme.ts +35 -0
- package/src/modes/types.ts +0 -2
- package/src/modes/utils/copy-targets.ts +3 -2
- package/src/modes/utils/interactive-context-helpers.ts +27 -0
- package/src/modes/utils/transcript-render-helpers.ts +157 -0
- package/src/modes/utils/ui-helpers.ts +21 -126
- package/src/prompts/system/system-prompt.md +10 -10
- package/src/prompts/tools/bash.md +0 -1
- package/src/prompts/tools/eval.md +6 -4
- package/src/prompts/tools/find.md +0 -4
- package/src/prompts/tools/read.md +1 -2
- package/src/prompts/tools/replace.md +1 -1
- package/src/prompts/tools/search.md +0 -1
- package/src/sdk.ts +13 -7
- package/src/session/agent-session.ts +9 -7
- package/src/stt/asr-client.ts +35 -215
- package/src/stt/asr-worker.ts +29 -181
- package/src/subprocess/worker-client.ts +297 -0
- package/src/subprocess/worker-runtime.ts +277 -0
- package/src/task/executor.ts +4 -4
- package/src/tiny/title-client.ts +53 -219
- package/src/tiny/worker.ts +29 -180
- package/src/tools/eval-backends.ts +10 -3
- package/src/tools/eval-render.ts +17 -8
- package/src/tools/eval.ts +187 -22
- package/src/tools/index.ts +51 -28
- package/src/tts/tts-client.ts +38 -206
- package/src/tts/tts-worker.ts +23 -97
- package/src/tui/code-cell.ts +12 -1
- package/src/utils/file-display-mode.ts +2 -3
- package/src/web/parallel.ts +43 -42
- package/src/web/search/providers/parallel.ts +10 -99
package/src/tools/eval-render.ts
CHANGED
|
@@ -2,11 +2,12 @@
|
|
|
2
2
|
* TUI rendering for the eval tool.
|
|
3
3
|
*
|
|
4
4
|
* Split out from `eval.ts` so the renderer can be imported by `renderers.ts`
|
|
5
|
-
* without dragging the eval *runtime* (JS/Python backends ->
|
|
6
|
-
* task executor -> sdk -> extension loader -> root barrel)
|
|
7
|
-
* module graph. That transitive chain re-enters
|
|
8
|
-
* is still initializing, which previously
|
|
9
|
-
* `Cannot access 'evalToolRenderer' before
|
|
5
|
+
* without dragging the eval *runtime* (JS/Python/Ruby/Julia backends ->
|
|
6
|
+
* agent bridge -> task executor -> sdk -> extension loader -> root barrel)
|
|
7
|
+
* into the renderer module graph. That transitive chain re-enters
|
|
8
|
+
* `renderers.ts` while `eval.ts` is still initializing, which previously
|
|
9
|
+
* crashed module load with a TDZ `Cannot access 'evalToolRenderer' before
|
|
10
|
+
* initialization`.
|
|
10
11
|
*/
|
|
11
12
|
import type { Component } from "@oh-my-pi/pi-tui";
|
|
12
13
|
import { Markdown, Text } from "@oh-my-pi/pi-tui";
|
|
@@ -41,8 +42,11 @@ import {
|
|
|
41
42
|
} from "./render-utils";
|
|
42
43
|
export const EVAL_DEFAULT_PREVIEW_LINES = 10;
|
|
43
44
|
|
|
44
|
-
function languageForHighlighter(language: EvalLanguage | undefined): "python" | "javascript" {
|
|
45
|
-
|
|
45
|
+
function languageForHighlighter(language: EvalLanguage | undefined): "python" | "javascript" | "ruby" | "julia" {
|
|
46
|
+
if (language === "js") return "javascript";
|
|
47
|
+
if (language === "ruby") return "ruby";
|
|
48
|
+
if (language === "julia") return "julia";
|
|
49
|
+
return "python";
|
|
46
50
|
}
|
|
47
51
|
|
|
48
52
|
interface EvalRenderCellArg {
|
|
@@ -70,7 +74,10 @@ interface EvalRenderCell {
|
|
|
70
74
|
}
|
|
71
75
|
|
|
72
76
|
function normalizeRenderLanguage(value: string | undefined): EvalLanguage {
|
|
73
|
-
|
|
77
|
+
if (value === "js") return "js";
|
|
78
|
+
if (value === "rb" || value === "ruby") return "ruby";
|
|
79
|
+
if (value === "jl" || value === "julia") return "julia";
|
|
80
|
+
return "python";
|
|
74
81
|
}
|
|
75
82
|
|
|
76
83
|
function getRenderCells(args: EvalRenderArgs | undefined): EvalRenderCell[] {
|
|
@@ -506,6 +513,7 @@ export const evalToolRenderer = {
|
|
|
506
513
|
{
|
|
507
514
|
code: cell.code,
|
|
508
515
|
language: languageForHighlighter(cell.language),
|
|
516
|
+
showLanguage: true,
|
|
509
517
|
index: i,
|
|
510
518
|
total: cells.length,
|
|
511
519
|
title: cell.title,
|
|
@@ -608,6 +616,7 @@ export const evalToolRenderer = {
|
|
|
608
616
|
{
|
|
609
617
|
code: cell.code,
|
|
610
618
|
language: languageForHighlighter(cell.language ?? details?.language),
|
|
619
|
+
showLanguage: true,
|
|
611
620
|
index: i,
|
|
612
621
|
total: cellResults.length,
|
|
613
622
|
title: cell.title,
|
package/src/tools/eval.ts
CHANGED
|
@@ -2,7 +2,7 @@ import type { AgentTool, AgentToolContext, AgentToolResult, AgentToolUpdateCallb
|
|
|
2
2
|
import type { ImageContent, ToolExample } from "@oh-my-pi/pi-ai";
|
|
3
3
|
import { prompt } from "@oh-my-pi/pi-utils";
|
|
4
4
|
import { type } from "arktype";
|
|
5
|
-
import { jsBackend, pythonBackend } from "../eval";
|
|
5
|
+
import { jsBackend, juliaBackend, pythonBackend, rubyBackend } from "../eval";
|
|
6
6
|
import type { ExecutorBackend, ExecutorBackendResult } from "../eval/backend";
|
|
7
7
|
import { EVAL_TIMEOUT_PAUSE_OP, EVAL_TIMEOUT_RESUME_OP } from "../eval/bridge-timeout";
|
|
8
8
|
import { IdleTimeout } from "../eval/idle-timeout";
|
|
@@ -14,7 +14,7 @@ import { webpExclusionForModel } from "../utils/image-loading";
|
|
|
14
14
|
import { formatDimensionNote, resizeImage } from "../utils/image-resize";
|
|
15
15
|
import type { ToolSession } from ".";
|
|
16
16
|
import { truncateForPrompt } from "./approval";
|
|
17
|
-
import { resolveEvalBackends } from "./eval-backends";
|
|
17
|
+
import { type EvalBackendsAllowance, resolveEvalBackends } from "./eval-backends";
|
|
18
18
|
import { upsertStatusEvent } from "./eval-render";
|
|
19
19
|
import { resolveOutputMaxColumns, resolveOutputSinkHeadBytes } from "./output-meta";
|
|
20
20
|
import { ToolAbortError, ToolError } from "./tool-errors";
|
|
@@ -23,29 +23,112 @@ import { clampTimeout } from "./tool-timeouts";
|
|
|
23
23
|
|
|
24
24
|
export { EVAL_DEFAULT_PREVIEW_LINES, evalToolRenderer } from "./eval-render";
|
|
25
25
|
|
|
26
|
-
/**
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
26
|
+
/** Language tokens the eval tool accepts, in stable display order. */
|
|
27
|
+
export type EvalLanguageToken = "py" | "js" | "rb" | "jl";
|
|
28
|
+
const EVAL_LANGUAGE_ORDER: readonly EvalLanguageToken[] = ["py", "js", "rb", "jl"];
|
|
29
|
+
const EVAL_LANGUAGE_RUNTIME: Record<EvalLanguageToken, string> = {
|
|
30
|
+
py: '"py" for the IPython kernel',
|
|
31
|
+
js: '"js" for the persistent JS VM',
|
|
32
|
+
rb: '"rb" for the persistent Ruby kernel',
|
|
33
|
+
jl: '"jl" for the persistent Julia kernel',
|
|
34
|
+
};
|
|
35
|
+
const EVAL_LANGUAGE_NAME: Record<EvalLanguageToken, string> = {
|
|
36
|
+
py: "Python",
|
|
37
|
+
js: "JavaScript",
|
|
38
|
+
rb: "Ruby",
|
|
39
|
+
jl: "Julia",
|
|
40
|
+
};
|
|
41
|
+
const EVAL_CELLS_DESCRIPTION =
|
|
42
|
+
"cells executed in order. State persists within each language across cells and tool calls.";
|
|
43
|
+
|
|
44
|
+
/** Join names as an English "or" list: ["A"]→"A", ["A","B"]→"A or B", 3+→"A, B, or C". */
|
|
45
|
+
function joinWithOr(items: readonly string[]): string {
|
|
46
|
+
if (items.length <= 1) return items[0] ?? "";
|
|
47
|
+
if (items.length === 2) return `${items[0]} or ${items[1]}`;
|
|
48
|
+
return `${items.slice(0, -1).join(", ")}, or ${items[items.length - 1]}`;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function describeLanguageField(langs: readonly EvalLanguageToken[]): string {
|
|
52
|
+
return `runtime: ${langs.map(lang => EVAL_LANGUAGE_RUNTIME[lang]).join(", ")}`;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function describeCodeField(langs: readonly EvalLanguageToken[]): string {
|
|
56
|
+
const replLangs = langs.filter(lang => lang === "rb" || lang === "jl");
|
|
57
|
+
// No persistent REPL backends → keep the original py/js phrasing verbatim so the
|
|
58
|
+
// default (rb/jl off) wire schema stays byte-identical to the pre-feature one.
|
|
59
|
+
if (replLangs.length === 0) return "cell body, verbatim. Use top-level await freely.";
|
|
60
|
+
const awaitLangs = langs.filter(lang => lang === "py" || lang === "js");
|
|
61
|
+
const clauses: string[] = [];
|
|
62
|
+
if (awaitLangs.length > 0) clauses.push(`Top-level \`await\` is available in ${awaitLangs.join("/")}`);
|
|
63
|
+
clauses.push(`${replLangs.join("/")} auto-display the last expression like a REPL`);
|
|
64
|
+
return `cell body, verbatim. ${clauses.join("; ")}.`;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** One-line discovery summary listing the runtimes available this session. */
|
|
68
|
+
function summarizeEvalLanguages(langs: readonly EvalLanguageToken[]): string {
|
|
69
|
+
const names = langs.map(lang => EVAL_LANGUAGE_NAME[lang]);
|
|
70
|
+
const list = names.length > 0 ? joinWithOr(names) : "Python or JavaScript";
|
|
71
|
+
// "in-process" matches the historical py/js summary; persistent kernels (rb/jl) switch wording.
|
|
72
|
+
const backend = langs.some(lang => lang === "rb" || lang === "jl") ? "a persistent" : "an in-process";
|
|
73
|
+
return `Execute ${list} code in ${backend} eval backend`;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** Resolved-allowance → enabled language tokens, preserving display order. */
|
|
77
|
+
function enabledEvalLanguages(backends: EvalBackendsAllowance): EvalLanguageToken[] {
|
|
78
|
+
const allowed: Record<EvalLanguageToken, boolean> = {
|
|
79
|
+
py: backends.python,
|
|
80
|
+
js: backends.js,
|
|
81
|
+
rb: backends.ruby,
|
|
82
|
+
jl: backends.julia,
|
|
83
|
+
};
|
|
84
|
+
return EVAL_LANGUAGE_ORDER.filter(lang => allowed[lang]);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const evalCellCommonFields = {
|
|
33
88
|
"title?": type("string").describe('short label shown in transcript (e.g. "imports", "load config")'),
|
|
34
89
|
"timeout?": type("number").describe("per-cell timeout in seconds"),
|
|
35
90
|
"reset?": type("boolean").describe(
|
|
36
91
|
"wipe this cell's language kernel before running. Other languages are untouched.",
|
|
37
92
|
),
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Per-cell input. Each cell runs in order; state persists within a language
|
|
97
|
+
* across cells and across tool calls. This static schema carries the full
|
|
98
|
+
* language union for typing; {@link buildEvalSchema} narrows the wire copy per
|
|
99
|
+
* session so disabled backends are never advertised to the model.
|
|
100
|
+
*/
|
|
101
|
+
const evalCellSchema = type({
|
|
102
|
+
language: type("'py' | 'js' | 'rb' | 'jl'").describe(describeLanguageField(EVAL_LANGUAGE_ORDER)),
|
|
103
|
+
code: type("string").describe(describeCodeField(EVAL_LANGUAGE_ORDER)),
|
|
104
|
+
...evalCellCommonFields,
|
|
38
105
|
});
|
|
39
106
|
export type EvalCellInput = typeof evalCellSchema.infer;
|
|
40
107
|
|
|
41
108
|
export const evalSchema = type({
|
|
42
|
-
cells: evalCellSchema
|
|
43
|
-
.array()
|
|
44
|
-
.atLeastLength(1)
|
|
45
|
-
.describe("cells executed in order. State persists within each language across cells and tool calls."),
|
|
109
|
+
cells: evalCellSchema.array().atLeastLength(1).describe(EVAL_CELLS_DESCRIPTION),
|
|
46
110
|
});
|
|
47
111
|
export type EvalToolParams = typeof evalSchema.infer;
|
|
48
112
|
|
|
113
|
+
/**
|
|
114
|
+
* Build a session-scoped copy of the eval schema whose `language` enum and field
|
|
115
|
+
* descriptions advertise only the runtimes enabled for this session. Disabled
|
|
116
|
+
* backends never reach the model: the wire schema, BM25 discovery corpus, and
|
|
117
|
+
* tool description stay in lockstep with {@link resolveEvalBackends}. The static
|
|
118
|
+
* {@link evalSchema} (full union) remains the type-level source of truth.
|
|
119
|
+
*/
|
|
120
|
+
function buildEvalSchema(langs: readonly EvalLanguageToken[]): typeof evalSchema {
|
|
121
|
+
const cellSchema = type({
|
|
122
|
+
language: type.enumerated(...langs).describe(describeLanguageField(langs)),
|
|
123
|
+
code: type("string").describe(describeCodeField(langs)),
|
|
124
|
+
...evalCellCommonFields,
|
|
125
|
+
});
|
|
126
|
+
const schema = type({
|
|
127
|
+
cells: cellSchema.array().atLeastLength(1).describe(EVAL_CELLS_DESCRIPTION),
|
|
128
|
+
});
|
|
129
|
+
return schema as unknown as typeof evalSchema;
|
|
130
|
+
}
|
|
131
|
+
|
|
49
132
|
export type EvalToolResult = {
|
|
50
133
|
content: Array<{ type: "text"; text: string }>;
|
|
51
134
|
details: EvalToolDetails | undefined;
|
|
@@ -88,6 +171,8 @@ function formatDisplayOutputsForText(outputs: EvalDisplayOutput[]): string {
|
|
|
88
171
|
export interface EvalToolDescriptionOptions {
|
|
89
172
|
py?: boolean;
|
|
90
173
|
js?: boolean;
|
|
174
|
+
rb?: boolean;
|
|
175
|
+
jl?: boolean;
|
|
91
176
|
/**
|
|
92
177
|
* Whether `agent()` is allowed in this session. Driven by the parent's
|
|
93
178
|
* spawn policy (`getSessionSpawns`). Defaults to `true` for backward
|
|
@@ -101,8 +186,10 @@ export interface EvalToolDescriptionOptions {
|
|
|
101
186
|
export function getEvalToolDescription(options: EvalToolDescriptionOptions = {}): string {
|
|
102
187
|
const py = options.py ?? true;
|
|
103
188
|
const js = options.js ?? true;
|
|
189
|
+
const rb = options.rb ?? false;
|
|
190
|
+
const jl = options.jl ?? false;
|
|
104
191
|
const spawns = options.spawns ?? true;
|
|
105
|
-
return prompt.render(evalDescription, { py, js, spawns });
|
|
192
|
+
return prompt.render(evalDescription, { py, js, rb, jl, spawns });
|
|
106
193
|
}
|
|
107
194
|
|
|
108
195
|
export interface EvalToolOptions {
|
|
@@ -142,19 +229,61 @@ async function resolveBackend(session: ToolSession, language: EvalLanguage): Pro
|
|
|
142
229
|
const backends = resolveEvalBackends(session);
|
|
143
230
|
const allowPy = backends.python;
|
|
144
231
|
const allowJs = backends.js;
|
|
232
|
+
const allowRb = backends.ruby;
|
|
233
|
+
const allowJl = backends.julia;
|
|
145
234
|
|
|
146
235
|
if (language === "python") {
|
|
147
236
|
if (!allowPy) throw new ToolError("Python backend is disabled (PI_PY=0 or eval.py = false).");
|
|
148
237
|
if (!(await pythonBackend.isAvailable(session))) {
|
|
238
|
+
const alternatives = [allowJs ? '"js"' : null, allowRb ? '"rb"' : null, allowJl ? '"jl"' : null].filter(
|
|
239
|
+
Boolean,
|
|
240
|
+
);
|
|
149
241
|
throw new ToolError(
|
|
150
|
-
|
|
242
|
+
alternatives.length > 0
|
|
243
|
+
? `Python backend is unavailable in this session. Pass language: ${alternatives.join(" or ")} or install the python kernel.`
|
|
244
|
+
: 'Python backend is unavailable in this session. Install the python kernel to use language: "py".',
|
|
151
245
|
);
|
|
152
246
|
}
|
|
153
247
|
return { backend: pythonBackend };
|
|
154
248
|
}
|
|
249
|
+
if (language === "ruby") {
|
|
250
|
+
if (!allowRb) throw new ToolError("Ruby backend is disabled (PI_RB=0 or eval.rb = false).");
|
|
251
|
+
if (!(await rubyBackend.isAvailable(session))) {
|
|
252
|
+
const alternatives = [allowJs ? '"js"' : null, allowPy ? '"py"' : null, allowJl ? '"jl"' : null].filter(
|
|
253
|
+
Boolean,
|
|
254
|
+
);
|
|
255
|
+
throw new ToolError(
|
|
256
|
+
alternatives.length > 0
|
|
257
|
+
? `Ruby backend is unavailable in this session. Pass language: ${alternatives.join(" or ")} or install Ruby.`
|
|
258
|
+
: 'Ruby backend is unavailable in this session. Install Ruby to use language: "rb".',
|
|
259
|
+
);
|
|
260
|
+
}
|
|
261
|
+
return { backend: rubyBackend };
|
|
262
|
+
}
|
|
263
|
+
if (language === "julia") {
|
|
264
|
+
if (!allowJl) throw new ToolError("Julia backend is disabled (PI_JL=0 or eval.jl = false).");
|
|
265
|
+
if (!(await juliaBackend.isAvailable(session))) {
|
|
266
|
+
const alternatives = [allowJs ? '"js"' : null, allowPy ? '"py"' : null, allowRb ? '"rb"' : null].filter(
|
|
267
|
+
Boolean,
|
|
268
|
+
);
|
|
269
|
+
throw new ToolError(
|
|
270
|
+
alternatives.length > 0
|
|
271
|
+
? `Julia backend is unavailable in this session. Pass language: ${alternatives.join(" or ")} or install Julia.`
|
|
272
|
+
: 'Julia backend is unavailable in this session. Install Julia to use language: "jl".',
|
|
273
|
+
);
|
|
274
|
+
}
|
|
275
|
+
return { backend: juliaBackend };
|
|
276
|
+
}
|
|
155
277
|
if (!allowJs) throw new ToolError("JavaScript backend is disabled (PI_JS=0 or eval.js = false).");
|
|
156
278
|
return { backend: jsBackend };
|
|
157
279
|
}
|
|
280
|
+
function formatEvalInputLanguage(value: string): string {
|
|
281
|
+
if (value === "py" || value === "python") return "python";
|
|
282
|
+
if (value === "js" || value === "javascript") return "javascript";
|
|
283
|
+
if (value === "rb" || value === "ruby") return "ruby";
|
|
284
|
+
if (value === "jl" || value === "julia") return "julia";
|
|
285
|
+
return value;
|
|
286
|
+
}
|
|
158
287
|
|
|
159
288
|
export class EvalTool implements AgentTool<typeof evalSchema> {
|
|
160
289
|
readonly name = "eval";
|
|
@@ -164,7 +293,8 @@ export class EvalTool implements AgentTool<typeof evalSchema> {
|
|
|
164
293
|
const cells = Array.isArray(params.cells) ? params.cells : [];
|
|
165
294
|
const firstCell = cells[0] as Partial<EvalCellInput> | undefined;
|
|
166
295
|
if (!firstCell) return [];
|
|
167
|
-
const language =
|
|
296
|
+
const language =
|
|
297
|
+
typeof firstCell.language === "string" ? formatEvalInputLanguage(firstCell.language) : "javascript (default)";
|
|
168
298
|
const code = typeof firstCell.code === "string" ? firstCell.code : "";
|
|
169
299
|
const lines = [`Language: ${language}`, `Code:\n${truncateForPrompt(code)}`];
|
|
170
300
|
if (cells.length > 1) {
|
|
@@ -172,7 +302,9 @@ export class EvalTool implements AgentTool<typeof evalSchema> {
|
|
|
172
302
|
}
|
|
173
303
|
return lines;
|
|
174
304
|
};
|
|
175
|
-
|
|
305
|
+
get summary(): string {
|
|
306
|
+
return summarizeEvalLanguages(this.#enabledLanguages());
|
|
307
|
+
}
|
|
176
308
|
readonly loadMode = "discoverable";
|
|
177
309
|
readonly label = "Eval";
|
|
178
310
|
get description(): string {
|
|
@@ -180,7 +312,13 @@ export class EvalTool implements AgentTool<typeof evalSchema> {
|
|
|
180
312
|
const backends = resolveEvalBackends(this.session);
|
|
181
313
|
const sessionSpawns = this.session.getSessionSpawns?.() ?? "*";
|
|
182
314
|
const spawnsAllowed = sessionSpawns !== "" && sessionSpawns !== null;
|
|
183
|
-
return getEvalToolDescription({
|
|
315
|
+
return getEvalToolDescription({
|
|
316
|
+
py: backends.python,
|
|
317
|
+
js: backends.js,
|
|
318
|
+
rb: backends.ruby,
|
|
319
|
+
jl: backends.julia,
|
|
320
|
+
spawns: spawnsAllowed,
|
|
321
|
+
});
|
|
184
322
|
}
|
|
185
323
|
readonly examples: readonly ToolExample<typeof evalSchema.infer>[] = [
|
|
186
324
|
{
|
|
@@ -201,7 +339,16 @@ export class EvalTool implements AgentTool<typeof evalSchema> {
|
|
|
201
339
|
},
|
|
202
340
|
},
|
|
203
341
|
];
|
|
204
|
-
|
|
342
|
+
get parameters(): typeof evalSchema {
|
|
343
|
+
const langs = this.#enabledLanguages();
|
|
344
|
+
if (langs.length === 0 || langs.length === EVAL_LANGUAGE_ORDER.length) return evalSchema;
|
|
345
|
+
const key = langs.join(",");
|
|
346
|
+
if (this.#paramsKey !== key) {
|
|
347
|
+
this.#cachedParams = buildEvalSchema(langs);
|
|
348
|
+
this.#paramsKey = key;
|
|
349
|
+
}
|
|
350
|
+
return this.#cachedParams ?? evalSchema;
|
|
351
|
+
}
|
|
205
352
|
readonly concurrency = "exclusive";
|
|
206
353
|
readonly strict = true;
|
|
207
354
|
readonly intent = (args: Partial<typeof evalSchema.infer>): string | undefined => {
|
|
@@ -209,13 +356,24 @@ export class EvalTool implements AgentTool<typeof evalSchema> {
|
|
|
209
356
|
const first = cells.find(c => c && typeof c === "object");
|
|
210
357
|
if (!first) return "evaluating";
|
|
211
358
|
const title = typeof first.title === "string" ? first.title : undefined;
|
|
212
|
-
const language = typeof first.language === "string" ? first.language : "
|
|
359
|
+
const language = typeof first.language === "string" ? formatEvalInputLanguage(first.language) : "javascript";
|
|
213
360
|
const label = title || `running ${language}`;
|
|
214
361
|
return cells.length > 1 ? `${label} (+${cells.length - 1})` : label;
|
|
215
362
|
};
|
|
216
363
|
|
|
217
364
|
readonly #proxyExecutor?: EvalProxyExecutor;
|
|
218
365
|
|
|
366
|
+
#paramsKey?: string;
|
|
367
|
+
#cachedParams?: typeof evalSchema;
|
|
368
|
+
|
|
369
|
+
/**
|
|
370
|
+
* Languages enabled for this session, in display order. Detached tools (no
|
|
371
|
+
* session) fall back to the shipped defaults (py/js; rb/jl are opt-in).
|
|
372
|
+
*/
|
|
373
|
+
#enabledLanguages(): EvalLanguageToken[] {
|
|
374
|
+
return this.session ? enabledEvalLanguages(resolveEvalBackends(this.session)) : ["py", "js"];
|
|
375
|
+
}
|
|
376
|
+
|
|
219
377
|
constructor(
|
|
220
378
|
private readonly session: ToolSession | null,
|
|
221
379
|
options?: EvalToolOptions,
|
|
@@ -243,7 +401,14 @@ export class EvalTool implements AgentTool<typeof evalSchema> {
|
|
|
243
401
|
const cells: ResolvedEvalCell[] = [];
|
|
244
402
|
for (let i = 0; i < params.cells.length; i++) {
|
|
245
403
|
const cell = params.cells[i];
|
|
246
|
-
const language: EvalLanguage =
|
|
404
|
+
const language: EvalLanguage =
|
|
405
|
+
cell.language === "py"
|
|
406
|
+
? "python"
|
|
407
|
+
: cell.language === "rb"
|
|
408
|
+
? "ruby"
|
|
409
|
+
: cell.language === "jl"
|
|
410
|
+
? "julia"
|
|
411
|
+
: "js";
|
|
247
412
|
const resolved = await resolveBackend(session, language);
|
|
248
413
|
cells.push({
|
|
249
414
|
index: i,
|
|
@@ -348,7 +513,7 @@ export class EvalTool implements AgentTool<typeof evalSchema> {
|
|
|
348
513
|
// The per-cell `timeout` is a budget on the cell runtime's *own*
|
|
349
514
|
// work. Host-side `agent()`/`parallel()`/`completion()` bridge calls suspend
|
|
350
515
|
// that budget entirely and restart a fresh timeout window when control
|
|
351
|
-
// returns to
|
|
516
|
+
// returns to the active backend runtime. Compute, stdout, `log()`/`phase()`, and
|
|
352
517
|
// ordinary tool calls all count against the budget. The watchdog drives
|
|
353
518
|
// `combinedSignal`; we pass no wall-clock deadline downstream so the
|
|
354
519
|
// backends never arm a competing fixed timer.
|
package/src/tools/index.ts
CHANGED
|
@@ -7,7 +7,9 @@ import type { Rule } from "../capability/rule";
|
|
|
7
7
|
import type { PromptTemplate } from "../config/prompt-templates";
|
|
8
8
|
import type { Settings } from "../config/settings";
|
|
9
9
|
import { EditTool } from "../edit";
|
|
10
|
+
import { checkJuliaKernelAvailability } from "../eval/jl/kernel";
|
|
10
11
|
import { checkPythonKernelAvailability } from "../eval/py/kernel";
|
|
12
|
+
import { checkRubyKernelAvailability } from "../eval/rb/kernel";
|
|
11
13
|
import type { ToolPathWithSource } from "../extensibility/custom-tools";
|
|
12
14
|
import type { Skill } from "../extensibility/skills";
|
|
13
15
|
import type { GoalModeState, GoalRuntime } from "../goals";
|
|
@@ -165,7 +167,7 @@ export interface ToolSession {
|
|
|
165
167
|
suppressSpawnAdvisory?: boolean;
|
|
166
168
|
/** Optional fetch implementation injected into the URL read pipeline (tests, proxies). Defaults to global fetch. */
|
|
167
169
|
fetch?: FetchImpl;
|
|
168
|
-
/** Skip
|
|
170
|
+
/** Skip subprocess-kernel availability checks and warmup */
|
|
169
171
|
skipPythonPreflight?: boolean;
|
|
170
172
|
/** Pre-loaded context files (AGENTS.md, etc) */
|
|
171
173
|
contextFiles?: ContextFileEntry[];
|
|
@@ -202,13 +204,13 @@ export interface ToolSession {
|
|
|
202
204
|
requireYieldTool?: boolean;
|
|
203
205
|
/** Task recursion depth (0 = top-level, 1 = first child, etc.) */
|
|
204
206
|
taskDepth?: number;
|
|
205
|
-
/** Get shared eval executor session ID. Subagents inherit this to share JS/Python state. */
|
|
207
|
+
/** Get shared eval executor session ID. Subagents inherit this to share JS/Python/Ruby/Julia state. */
|
|
206
208
|
getEvalSessionId?: () => string | null;
|
|
207
209
|
/** Get session file */
|
|
208
210
|
getSessionFile: () => string | null;
|
|
209
211
|
/** Get eval kernel owner ID for session-scoped retained-kernel cleanup. */
|
|
210
212
|
getEvalKernelOwnerId?: () => string | null;
|
|
211
|
-
/** Reject new eval
|
|
213
|
+
/** Reject new eval work once session disposal has started. */
|
|
212
214
|
assertEvalExecutionAllowed?: () => void;
|
|
213
215
|
/** Track tool-owned eval work so session disposal can await/abort it like direct session eval runs. */
|
|
214
216
|
trackEvalExecution?<T>(execution: Promise<T>, abortController: AbortController): Promise<T>;
|
|
@@ -486,36 +488,57 @@ export async function createTools(session: ToolSession, toolNames?: string[]): P
|
|
|
486
488
|
const backends = resolveEvalBackends(session);
|
|
487
489
|
const allowPython = backends.python;
|
|
488
490
|
const allowJs = backends.js;
|
|
489
|
-
const
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
//
|
|
493
|
-
//
|
|
491
|
+
const allowRuby = backends.ruby;
|
|
492
|
+
const allowJulia = backends.julia;
|
|
493
|
+
const skipEvalPreflight = session.skipPythonPreflight === true;
|
|
494
|
+
// Eval tool is enabled if ANY backend is reachable. JS needs no preflight, so
|
|
495
|
+
// we only probe Python/Ruby/Julia when JS is disabled — otherwise allowEval is
|
|
496
|
+
// already true and per-backend availability is checked at first invocation.
|
|
494
497
|
let pythonAvailable = true;
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
498
|
+
let rubyAvailable = true;
|
|
499
|
+
let juliaAvailable = true;
|
|
500
|
+
const evalRequested = requestedTools === undefined || requestedTools.includes("eval");
|
|
501
|
+
if (!skipEvalPreflight && !allowJs && evalRequested) {
|
|
502
|
+
if (allowPython) {
|
|
503
|
+
const availability = await logger.time(
|
|
504
|
+
"createTools:pythonCheck",
|
|
505
|
+
checkPythonKernelAvailability,
|
|
506
|
+
session.cwd,
|
|
507
|
+
session.settings.get("python.interpreter")?.trim() || undefined,
|
|
508
|
+
);
|
|
509
|
+
pythonAvailable = availability.ok;
|
|
510
|
+
if (!availability.ok) {
|
|
511
|
+
logger.warn("Python kernel unavailable and JS backend disabled", { reason: availability.reason });
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
if (allowRuby) {
|
|
515
|
+
const availability = await checkRubyKernelAvailability(
|
|
516
|
+
session.cwd,
|
|
517
|
+
session.settings.get("ruby.interpreter")?.trim() || undefined,
|
|
518
|
+
);
|
|
519
|
+
rubyAvailable = availability.ok;
|
|
520
|
+
if (!availability.ok) {
|
|
521
|
+
logger.warn("Ruby kernel unavailable and JS backend disabled", { reason: availability.reason });
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
if (allowJulia) {
|
|
525
|
+
const availability = await checkJuliaKernelAvailability(
|
|
526
|
+
session.cwd,
|
|
527
|
+
session.settings.get("julia.interpreter")?.trim() || undefined,
|
|
528
|
+
);
|
|
529
|
+
juliaAvailable = availability.ok;
|
|
530
|
+
if (!availability.ok) {
|
|
531
|
+
logger.warn("Julia kernel unavailable and JS backend disabled", { reason: availability.reason });
|
|
532
|
+
}
|
|
512
533
|
}
|
|
513
534
|
}
|
|
514
535
|
|
|
515
536
|
const effectivePythonAllowed = allowPython && pythonAvailable;
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
537
|
+
const effectiveRubyAllowed = allowRuby && rubyAvailable;
|
|
538
|
+
const effectiveJuliaAllowed = allowJulia && juliaAvailable;
|
|
539
|
+
// Eval is exposed whenever any backend is reachable. A backend may be
|
|
540
|
+
// unreachable, in which case eval dispatches exclusively to the others.
|
|
541
|
+
const allowEval = effectivePythonAllowed || allowJs || effectiveRubyAllowed || effectiveJuliaAllowed;
|
|
519
542
|
|
|
520
543
|
// Auto-include AST counterparts when their text-based sibling is present
|
|
521
544
|
if (requestedTools) {
|