@code-yeongyu/senpi-codemode 2026.7.25-2

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 (63) hide show
  1. package/CHANGELOG.md +250 -0
  2. package/LICENSE +22 -0
  3. package/README.md +161 -0
  4. package/package.json +58 -0
  5. package/src/bridge/http-server.ts +236 -0
  6. package/src/bridge/protocol.ts +198 -0
  7. package/src/bridge/reserved.ts +9 -0
  8. package/src/bridges/agent-bridge.ts +197 -0
  9. package/src/bridges/output-bridge.ts +96 -0
  10. package/src/bridges/schema-injection.ts +3 -0
  11. package/src/codemode/runtime.ts +258 -0
  12. package/src/codemode/tools.ts +106 -0
  13. package/src/completion/handler.ts +192 -0
  14. package/src/completion/tool-bridge.ts +55 -0
  15. package/src/config/settings.ts +215 -0
  16. package/src/extension/runtime-factory.ts +114 -0
  17. package/src/extension/session-manager-proxy.ts +116 -0
  18. package/src/extension/session-manager.ts +215 -0
  19. package/src/host-sdk.ts +1 -0
  20. package/src/index.ts +181 -0
  21. package/src/interpreters/detect.ts +161 -0
  22. package/src/kernels/jl/kernel.ts +37 -0
  23. package/src/kernels/jl/prelude.jl +283 -0
  24. package/src/kernels/jl/runner.jl +327 -0
  25. package/src/kernels/js/context-manager.ts +296 -0
  26. package/src/kernels/js/inline-worker-entry.js +23 -0
  27. package/src/kernels/js/inline-worker.ts +15 -0
  28. package/src/kernels/js/kernel-contract.ts +38 -0
  29. package/src/kernels/js/local-module-loader.ts +108 -0
  30. package/src/kernels/js/prelude.ts +15 -0
  31. package/src/kernels/js/rewrite-imports.ts +164 -0
  32. package/src/kernels/js/run-queue.ts +82 -0
  33. package/src/kernels/js/worker-core.d.ts +18 -0
  34. package/src/kernels/js/worker-core.js +94 -0
  35. package/src/kernels/js/worker-entry.js +23 -0
  36. package/src/kernels/js/worker-host.ts +117 -0
  37. package/src/kernels/js/worker-indirect-eval.js +88 -0
  38. package/src/kernels/js/worker-runtime.js +401 -0
  39. package/src/kernels/py/kernel-contract.ts +32 -0
  40. package/src/kernels/py/kernel.ts +290 -0
  41. package/src/kernels/py/prelude.py +954 -0
  42. package/src/kernels/py/process.ts +119 -0
  43. package/src/kernels/py/transport.ts +237 -0
  44. package/src/kernels/rb/kernel.ts +26 -0
  45. package/src/kernels/rb/prelude.rb +270 -0
  46. package/src/kernels/rb/runner.rb +204 -0
  47. package/src/kernels/shared/subprocess-contract.ts +22 -0
  48. package/src/kernels/shared/subprocess-kernel.ts +266 -0
  49. package/src/kernels/shared/subprocess-process.ts +174 -0
  50. package/src/kernels/shared/subprocess-queue.ts +101 -0
  51. package/src/kernels/shared/subprocess-run.ts +98 -0
  52. package/src/output/output-meta.ts +89 -0
  53. package/src/output/streaming-output.ts +296 -0
  54. package/src/prompt/eval-prompt.ts +319 -0
  55. package/src/timeouts/bridge-timeout.ts +16 -0
  56. package/src/timeouts/idle-timeout.ts +84 -0
  57. package/src/tool/cell-handler.ts +279 -0
  58. package/src/tool/eval-tool.ts +285 -0
  59. package/src/tool/image.ts +274 -0
  60. package/src/tool/json-tree.ts +247 -0
  61. package/src/tool/render.ts +876 -0
  62. package/src/tool/status-events.ts +12 -0
  63. package/src/tool/types.ts +114 -0
@@ -0,0 +1,279 @@
1
+ import type { AgentToolResult, AgentToolUpdateCallback, ExtensionContext } from "@code-yeongyu/senpi";
2
+ import type { KernelToHostMessage } from "../bridge/protocol.ts";
3
+ import { RESERVED_AGENT_TOOL, RESERVED_OUTPUT_TOOL } from "../bridge/reserved.ts";
4
+ import { type AgentExecuteTool, runEvalAgent } from "../bridges/agent-bridge.ts";
5
+ import { runEvalOutput } from "../bridges/output-bridge.ts";
6
+ import type { CompletionRequest, CompletionResult } from "../completion/handler.ts";
7
+ import { handleCompletionToolCall } from "../completion/tool-bridge.ts";
8
+ import type { ResolvedCodemodeSettings } from "../config/settings.ts";
9
+ import {
10
+ type EvalImageResizer,
11
+ EvalOutputCollector,
12
+ type EvalOutputResult,
13
+ marshalToolResult,
14
+ toolResultIsError,
15
+ } from "./image.ts";
16
+ import { upsertStatusEvent } from "./status-events.ts";
17
+ import type { EvalKernel, EvalStatusEvent, EvalToolDetails, EvalToolInput } from "./types.ts";
18
+
19
+ type ResolvedToolReply = { readonly value: unknown; readonly toolCallOk: boolean };
20
+
21
+ const LIVE_OUTPUT_PREVIEW_LINES = 8;
22
+
23
+ export interface CellState {
24
+ readonly input: EvalToolInput;
25
+ readonly signal: AbortSignal;
26
+ readonly onUpdate: AgentToolUpdateCallback<EvalToolDetails> | undefined;
27
+ readonly toolCalls: EvalToolDetails["toolCalls"] extends readonly (infer Item)[] ? Item[] : never;
28
+ readonly pendingBridgeCalls: Promise<void>[];
29
+ readonly statusEvents: EvalStatusEvent[];
30
+ active: boolean;
31
+ output: string;
32
+ phase: string | undefined;
33
+ durationMs: number;
34
+ status: "pending" | "running" | "complete" | "error";
35
+ }
36
+
37
+ export interface CellBridgeRuntime {
38
+ readonly executeTool: AgentExecuteTool;
39
+ readonly settings: ResolvedCodemodeSettings;
40
+ readonly complete?: (request: CompletionRequest, ctx: ExtensionContext) => Promise<CompletionResult>;
41
+ readonly ctx: ExtensionContext;
42
+ readonly artifactPath?: string;
43
+ readonly imageResizer?: EvalImageResizer;
44
+ }
45
+
46
+ export class CellHandler {
47
+ readonly #kernel: EvalKernel;
48
+ readonly #state: CellState;
49
+ readonly #runtime: CellBridgeRuntime;
50
+ readonly #output: EvalOutputCollector;
51
+
52
+ constructor(kernel: EvalKernel, state: CellState, runtime: CellBridgeRuntime) {
53
+ this.#kernel = kernel;
54
+ this.#state = state;
55
+ this.#runtime = runtime;
56
+ const settings = runtime.settings.outputSink;
57
+ this.#output = new EvalOutputCollector({
58
+ headBytes: settings.headBytes,
59
+ maxColumns: settings.maxColumns,
60
+ model: runtime.ctx.model,
61
+ ...(runtime.artifactPath === undefined ? {} : { artifactPath: runtime.artifactPath }),
62
+ ...(runtime.imageResizer === undefined ? {} : { imageResizer: runtime.imageResizer }),
63
+ onChunk: (_aggregate, cell) => {
64
+ state.output = cell;
65
+ this.#emitUpdate(false);
66
+ },
67
+ });
68
+ state.status = "running";
69
+ this.#emitUpdate(false);
70
+ }
71
+
72
+ async handle(message: KernelToHostMessage): Promise<void> {
73
+ if (!this.#state.active) return;
74
+ switch (message.type) {
75
+ case "text":
76
+ this.#output.push(message.data);
77
+ return;
78
+ case "phase":
79
+ this.#state.phase = message.title;
80
+ this.#emitUpdate(false);
81
+ return;
82
+ case "status":
83
+ this.#recordStatus(message.event);
84
+ return;
85
+ case "log":
86
+ this.#output.push(`${message.message}\n`);
87
+ return;
88
+ case "display":
89
+ this.#output.display(message);
90
+ return;
91
+ case "tool-call": {
92
+ const pending = this.#handleToolCall(message);
93
+ this.#state.pendingBridgeCalls.push(pending);
94
+ await pending;
95
+ return;
96
+ }
97
+ case "ready":
98
+ case "init-failed":
99
+ case "result":
100
+ case "closed":
101
+ return;
102
+ default:
103
+ throw new TypeError(`Unhandled kernel message: ${String(message)}`);
104
+ }
105
+ }
106
+
107
+ async finalize(result: Extract<KernelToHostMessage, { type: "result" }>): Promise<AgentToolResult<EvalToolDetails>> {
108
+ this.#state.durationMs = result.durationMs;
109
+ if (result.ok) {
110
+ if (result.valueRepr) this.#output.push(`${result.valueRepr}\n`);
111
+ this.#state.status = "complete";
112
+ } else {
113
+ this.#output.push(`${result.error.message}\n`);
114
+ this.#state.status = "error";
115
+ }
116
+ return await this.#finish(!result.ok);
117
+ }
118
+
119
+ async finalizeCancellation(error: Error): Promise<AgentToolResult<EvalToolDetails>> {
120
+ this.#output.push(`${error.message}\n`);
121
+ this.#state.status = "error";
122
+ return await this.#finish(true);
123
+ }
124
+
125
+ async flushOutput(): Promise<void> {
126
+ await this.#output.flush();
127
+ }
128
+
129
+ async #finish(isError: boolean): Promise<AgentToolResult<EvalToolDetails>> {
130
+ const output = await this.#output.finish();
131
+ this.#state.output = output.output;
132
+ const details = this.#details(output, isError);
133
+ this.#emitUpdate(isError);
134
+ const text =
135
+ output.output ||
136
+ (output.images.length > 0
137
+ ? `(displayed ${output.images.length} image${output.images.length === 1 ? "" : "s"}; no text output)`
138
+ : "(no output)");
139
+ return { content: [{ type: "text", text }, ...output.images], details };
140
+ }
141
+
142
+ async #handleToolCall(message: Extract<KernelToHostMessage, { type: "tool-call" }>): Promise<void> {
143
+ if (message.toolName === "eval") {
144
+ const error = "recursive eval is not allowed";
145
+ this.#state.toolCalls.push({ name: message.toolName, ok: false, error });
146
+ this.#kernel.deliverToolReply({
147
+ type: "tool-reply",
148
+ callId: message.callId,
149
+ ok: false,
150
+ error: { message: error },
151
+ });
152
+ return;
153
+ }
154
+ if (message.toolName === RESERVED_AGENT_TOOL) {
155
+ await this.#deliverToolReply(message, async () => ({
156
+ value: await runEvalAgent(message.args, {
157
+ callId: message.callId,
158
+ taskToolName: this.#runtime.settings.taskTools.task,
159
+ executeTool: this.#runtime.executeTool,
160
+ signal: this.#state.signal,
161
+ emitStatus: (event) => this.#recordStatus(event),
162
+ }),
163
+ toolCallOk: true,
164
+ }));
165
+ return;
166
+ }
167
+ if (message.toolName === RESERVED_OUTPUT_TOOL) {
168
+ await this.#deliverToolReply(message, async () => ({
169
+ value: await runEvalOutput(message.args, {
170
+ taskOutputToolName: this.#runtime.settings.taskTools.output,
171
+ executeTool: this.#runtime.executeTool,
172
+ signal: this.#state.signal,
173
+ marshalToolResult,
174
+ }),
175
+ toolCallOk: true,
176
+ }));
177
+ return;
178
+ }
179
+ if (message.toolName === "completion" && this.#runtime.complete) {
180
+ const result = await handleCompletionToolCall({
181
+ message,
182
+ kernel: this.#kernel,
183
+ complete: this.#runtime.complete,
184
+ ctx: this.#runtime.ctx,
185
+ isActive: () => this.#state.active,
186
+ });
187
+ if (!this.#state.active) return;
188
+ this.#state.toolCalls.push(
189
+ result.ok
190
+ ? { name: message.toolName, ok: true }
191
+ : { name: message.toolName, ok: false, error: result.error },
192
+ );
193
+ this.#emitUpdate(false);
194
+ return;
195
+ }
196
+ await this.#deliverToolReply(message, async () => {
197
+ const result = await this.#runtime.executeTool(message.toolName, message.args, { signal: this.#state.signal });
198
+ return { value: marshalToolResult(result), toolCallOk: !toolResultIsError(result) };
199
+ });
200
+ }
201
+
202
+ async #deliverToolReply(
203
+ message: Extract<KernelToHostMessage, { type: "tool-call" }>,
204
+ resolve: () => Promise<ResolvedToolReply>,
205
+ ): Promise<void> {
206
+ try {
207
+ const reply = await resolve();
208
+ if (!this.#state.active) return;
209
+ this.#state.toolCalls.push({ name: message.toolName, ok: reply.toolCallOk });
210
+ this.#kernel.deliverToolReply({ type: "tool-reply", callId: message.callId, ok: true, value: reply.value });
211
+ } catch (error) {
212
+ if (!this.#state.active) return;
213
+ const text = error instanceof Error ? error.message : String(error);
214
+ this.#state.toolCalls.push({ name: message.toolName, ok: false, error: text });
215
+ this.#kernel.deliverToolReply({
216
+ type: "tool-reply",
217
+ callId: message.callId,
218
+ ok: false,
219
+ error: { message: text },
220
+ });
221
+ }
222
+ this.#emitUpdate(false);
223
+ }
224
+
225
+ #recordStatus(event: EvalStatusEvent): void {
226
+ if (!this.#runtime.settings.statusEvents) return;
227
+ upsertStatusEvent(this.#state.statusEvents, event);
228
+ this.#emitUpdate(false);
229
+ }
230
+
231
+ #details(output: EvalOutputResult | undefined, isError: boolean): EvalToolDetails {
232
+ const statusEvents = this.#state.statusEvents.length > 0 ? [...this.#state.statusEvents] : undefined;
233
+ return {
234
+ language: this.#state.input.language,
235
+ languages: [this.#state.input.language],
236
+ ...(this.#state.input.title === undefined ? {} : { title: this.#state.input.title }),
237
+ durationMs: this.#state.durationMs,
238
+ toolCalls: [...this.#state.toolCalls],
239
+ truncated: output?.truncated ?? false,
240
+ ...(isError ? { isError: true } : {}),
241
+ ...(this.#state.phase === undefined ? {} : { phase: this.#state.phase }),
242
+ cells: [
243
+ {
244
+ index: 0,
245
+ ...(this.#state.input.title === undefined ? {} : { title: this.#state.input.title }),
246
+ code: this.#state.input.code,
247
+ language: this.#state.input.language,
248
+ output: this.#state.output,
249
+ status: this.#state.status,
250
+ durationMs: this.#state.durationMs,
251
+ ...(statusEvents === undefined ? {} : { statusEvents }),
252
+ ...(output?.hasMarkdown ? { hasMarkdown: true } : {}),
253
+ },
254
+ ],
255
+ ...(statusEvents === undefined ? {} : { statusEvents }),
256
+ ...(output === undefined || output.jsonOutputs.length === 0 ? {} : { jsonOutputs: output.jsonOutputs }),
257
+ ...(output?.notice === undefined ? {} : { notice: output.notice }),
258
+ ...(output?.meta === undefined ? {} : { meta: output.meta }),
259
+ };
260
+ }
261
+
262
+ #liveUpdateText(): string {
263
+ const title = this.#state.input.title === undefined ? "" : ` ${this.#state.input.title}`;
264
+ const aggregateOutput = this.#output.aggregateText();
265
+ const outputLines = aggregateOutput.split("\n");
266
+ const hasTrailingNewline = aggregateOutput.endsWith("\n");
267
+ if (hasTrailingNewline) outputLines.pop();
268
+ const output = `${outputLines.slice(-LIVE_OUTPUT_PREVIEW_LINES).join("\n")}${hasTrailingNewline ? "\n" : ""}`;
269
+ return `1/1 cells ${this.#state.status}\n[1] ${this.#state.input.language}${title} ${this.#state.status}${output.length === 0 ? "" : `\n${output}`}`;
270
+ }
271
+
272
+ #emitUpdate(isError: boolean): void {
273
+ if (!this.#state.active) return;
274
+ this.#state.onUpdate?.({
275
+ content: [{ type: "text", text: this.#liveUpdateText() }],
276
+ details: this.#details(undefined, isError),
277
+ });
278
+ }
279
+ }
@@ -0,0 +1,285 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { join } from "node:path";
3
+ import type { AgentToolResult, AgentToolUpdateCallback, ExtensionContext, ToolDefinition } from "@code-yeongyu/senpi";
4
+ import type { CompletionRequest, CompletionResult } from "../completion/handler.ts";
5
+ import { defaultCodemodeSettings, type ResolvedCodemodeSettings } from "../config/settings.ts";
6
+ import type { EvalExecutionTracker } from "../extension/session-manager.ts";
7
+ import { buildEvalPrompt } from "../prompt/eval-prompt.ts";
8
+ import { TIMEOUT_PAUSE_OP, TIMEOUT_RESUME_OP } from "../timeouts/bridge-timeout.ts";
9
+ import { IdleTimeout } from "../timeouts/idle-timeout.ts";
10
+ import { CellHandler, type CellState } from "./cell-handler.ts";
11
+ import type { EvalImageResizer } from "./image.ts";
12
+ import {
13
+ createEvalInputSchema,
14
+ type EnabledEvalLanguages,
15
+ type EvalInputSchema,
16
+ type EvalKernel,
17
+ type EvalKernelManager,
18
+ type EvalToolDetails,
19
+ type EvalToolInput,
20
+ type ExecuteTool,
21
+ enabledLanguageList,
22
+ } from "./types.ts";
23
+
24
+ export type { EnabledEvalLanguages, EvalKernel, EvalKernelManager } from "./types.ts";
25
+
26
+ export interface CreateEvalToolOptions {
27
+ readonly enabledLanguages: EnabledEvalLanguages;
28
+ readonly kernelManager: EvalKernelManager;
29
+ readonly cellTimeoutSeconds: number;
30
+ readonly executeTool: ExecuteTool;
31
+ readonly complete?: (request: CompletionRequest, ctx: ExtensionContext) => Promise<CompletionResult>;
32
+ readonly settings?: ResolvedCodemodeSettings;
33
+ readonly artifactsDir?: string;
34
+ readonly imageResizer?: EvalImageResizer;
35
+ readonly executionTracker?: EvalExecutionTracker;
36
+ readonly proxyExecutor?: (params: EvalToolInput, signal?: AbortSignal) => Promise<AgentToolResult<EvalToolDetails>>;
37
+ readonly renderers?: Pick<ToolDefinition<EvalInputSchema, EvalToolDetails>, "renderCall" | "renderResult">;
38
+ /** Whether the task-tool spawn helpers (agent()/output()/<dag>) are advertised in the prompt. */
39
+ readonly spawns?: boolean;
40
+ /** Default agent name surfaced in the agent() helper docs when spawns are enabled. */
41
+ readonly spawnDefaultAgent?: string;
42
+ /** Active model id; selects the emphasis dialect of the eval prompt. */
43
+ readonly modelId?: string;
44
+ /** Preformatted host line rendered into the prompt's host-sizing note. */
45
+ readonly hostLine?: string;
46
+ }
47
+
48
+ interface EvalCellInvocation {
49
+ readonly cellId: string;
50
+ readonly input: EvalToolInput;
51
+ readonly signal: AbortSignal;
52
+ readonly onUpdate: AgentToolUpdateCallback<EvalToolDetails> | undefined;
53
+ readonly ctx: ExtensionContext;
54
+ }
55
+
56
+ interface CellExecutionOptions {
57
+ readonly callerSignal: AbortSignal;
58
+ readonly cellId: string;
59
+ readonly onAbort: (error: Error) => void;
60
+ readonly timeoutMs: number;
61
+ }
62
+
63
+ const INTERRUPT_DELIVERY_GRACE_MS = 100;
64
+
65
+ class CellExecution {
66
+ readonly #callerSignal: AbortSignal;
67
+ readonly #onAbort: (error: Error) => void;
68
+ readonly #abortPromise: Promise<never>;
69
+ readonly #watchdog: IdleTimeout;
70
+ #rejectAbort: ((reason?: unknown) => void) | undefined;
71
+ #kernel: EvalKernel | undefined;
72
+ #interruptDeadline: ReturnType<typeof setTimeout> | undefined;
73
+ #active = true;
74
+
75
+ constructor(options: CellExecutionOptions) {
76
+ this.#callerSignal = options.callerSignal;
77
+ this.#onAbort = options.onAbort;
78
+ this.#abortPromise = new Promise<never>((_resolve, reject) => {
79
+ this.#rejectAbort = reject;
80
+ });
81
+ this.#watchdog = new IdleTimeout({
82
+ cellId: options.cellId,
83
+ timeoutMs: options.timeoutMs,
84
+ onTimeout: ({ error }) => this.#abort(error),
85
+ });
86
+ this.#callerSignal.addEventListener("abort", this.#handleCallerAbort, { once: true });
87
+ }
88
+
89
+ pause(): void {
90
+ this.#watchdog.pause();
91
+ }
92
+ resume(): void {
93
+ this.#watchdog.resume();
94
+ }
95
+ setKernel(kernel: EvalKernel): void {
96
+ this.#kernel = kernel;
97
+ }
98
+ cancel(reason: unknown): void {
99
+ this.#abort(reason);
100
+ }
101
+ finish(): void {
102
+ this.#active = false;
103
+ this.#cleanup();
104
+ }
105
+
106
+ async wait<Result>(operation: Promise<Result>): Promise<Result> {
107
+ const guarded = operation.then(
108
+ (value): Result | Promise<never> => (this.#active ? value : this.#abortPromise),
109
+ (reason: unknown): Promise<never> => (this.#active ? Promise.reject(reason) : this.#abortPromise),
110
+ );
111
+ return await Promise.race([guarded, this.#abortPromise]);
112
+ }
113
+
114
+ readonly #handleCallerAbort = (): void => {
115
+ this.#abort(this.#callerSignal.reason);
116
+ };
117
+
118
+ #abort(reason: unknown): void {
119
+ if (!this.#active) return;
120
+ this.#active = false;
121
+ this.#cleanup();
122
+ const error = abortError(reason);
123
+ this.#onAbort(error);
124
+ const kernel = this.#kernel;
125
+ if (kernel === undefined) {
126
+ this.#settleAbort(error);
127
+ return;
128
+ }
129
+ this.#interruptDeadline = setTimeout(() => this.#settleAbort(error), INTERRUPT_DELIVERY_GRACE_MS);
130
+ void Promise.resolve()
131
+ .then(() => kernel.interrupt(error.message))
132
+ .then(
133
+ () => this.#settleAbort(error),
134
+ (interruptError: unknown) => this.#settleAbort(interruptError),
135
+ );
136
+ }
137
+
138
+ #settleAbort(reason: unknown): void {
139
+ const reject = this.#rejectAbort;
140
+ if (reject === undefined) return;
141
+ this.#rejectAbort = undefined;
142
+ if (this.#interruptDeadline !== undefined) clearTimeout(this.#interruptDeadline);
143
+ reject(reason);
144
+ }
145
+
146
+ #cleanup(): void {
147
+ this.#callerSignal.removeEventListener("abort", this.#handleCallerAbort);
148
+ this.#watchdog.dispose();
149
+ if (this.#interruptDeadline !== undefined) clearTimeout(this.#interruptDeadline);
150
+ this.#interruptDeadline = undefined;
151
+ }
152
+ }
153
+
154
+ export function createEvalTool(options: CreateEvalToolOptions): ToolDefinition<EvalInputSchema, EvalToolDetails> {
155
+ const parameters = createEvalInputSchema(options.enabledLanguages);
156
+ const prompt = buildEvalPrompt(options.enabledLanguages, {
157
+ spawns: options.spawns ?? false,
158
+ ...(options.spawnDefaultAgent === undefined ? {} : { spawnDefaultAgent: options.spawnDefaultAgent }),
159
+ ...(options.modelId === undefined ? {} : { modelId: options.modelId }),
160
+ ...(options.hostLine === undefined ? {} : { hostLine: options.hostLine }),
161
+ });
162
+ const languages = enabledLanguageList(options.enabledLanguages);
163
+ return {
164
+ name: "eval",
165
+ label: "Eval",
166
+ description: prompt.description,
167
+ promptSnippet: prompt.promptSnippet,
168
+ promptGuidelines: [...prompt.promptGuidelines],
169
+ parameters,
170
+ executionMode: "sequential",
171
+ ...(options.renderers?.renderCall === undefined ? {} : { renderCall: options.renderers.renderCall }),
172
+ ...(options.renderers?.renderResult === undefined ? {} : { renderResult: options.renderers.renderResult }),
173
+ async execute(toolCallId, params, signal, onUpdate, ctx) {
174
+ if (options.proxyExecutor) return await options.proxyExecutor(params, signal);
175
+ if (!languages.includes(params.language))
176
+ throw new RangeError(
177
+ `Unsupported eval language "${params.language}". Enabled languages: ${languages.join(", ")}`,
178
+ );
179
+ options.executionTracker?.assertEvalExecutionAllowed();
180
+ const lifecycleController = new AbortController();
181
+ const combinedSignal = signal
182
+ ? AbortSignal.any([signal, lifecycleController.signal])
183
+ : lifecycleController.signal;
184
+ const execution = runEvalCell(options, {
185
+ cellId: toolCallId,
186
+ input: params,
187
+ signal: combinedSignal,
188
+ onUpdate,
189
+ ctx,
190
+ });
191
+ return options.executionTracker
192
+ ? await options.executionTracker.trackEvalExecution(execution, lifecycleController)
193
+ : await execution;
194
+ },
195
+ };
196
+ }
197
+
198
+ async function runEvalCell(
199
+ options: CreateEvalToolOptions,
200
+ invocation: EvalCellInvocation,
201
+ ): Promise<AgentToolResult<EvalToolDetails>> {
202
+ if (invocation.signal.aborted) throw abortError(invocation.signal.reason);
203
+ const timeoutMs = Math.floor((invocation.input.timeout ?? options.cellTimeoutSeconds) * 1_000);
204
+ const bridgeAbortController = new AbortController();
205
+ const cellSignal = AbortSignal.any([invocation.signal, bridgeAbortController.signal]);
206
+ const bridgeContext: ExtensionContext = { ...invocation.ctx, signal: cellSignal };
207
+ const state: CellState = {
208
+ input: invocation.input,
209
+ signal: cellSignal,
210
+ onUpdate: invocation.onUpdate,
211
+ toolCalls: [],
212
+ pendingBridgeCalls: [],
213
+ statusEvents: [],
214
+ active: true,
215
+ output: "",
216
+ phase: undefined,
217
+ durationMs: 0,
218
+ status: "pending",
219
+ };
220
+ const execution = new CellExecution({
221
+ callerSignal: invocation.signal,
222
+ cellId: invocation.cellId,
223
+ onAbort: (error) => {
224
+ state.active = false;
225
+ bridgeAbortController.abort(error);
226
+ },
227
+ timeoutMs,
228
+ });
229
+ let handler: CellHandler | undefined;
230
+ try {
231
+ const acquired = await execution.wait(
232
+ options.kernelManager.getKernel(invocation.input.language, (message) => {
233
+ if (!state.active || !handler) return;
234
+ if (message.type === "status") {
235
+ if (message.event.op === TIMEOUT_PAUSE_OP) {
236
+ execution.pause();
237
+ return;
238
+ }
239
+ if (message.event.op === TIMEOUT_RESUME_OP) {
240
+ execution.resume();
241
+ return;
242
+ }
243
+ }
244
+ const pending = handler.handle(message);
245
+ void pending.catch((error: unknown) => execution.cancel(error));
246
+ }),
247
+ );
248
+ const kernel = acquired;
249
+ execution.setKernel(kernel);
250
+ handler = new CellHandler(kernel, state, {
251
+ executeTool: options.executeTool,
252
+ settings: options.settings ?? defaultCodemodeSettings,
253
+ ...(options.complete === undefined ? {} : { complete: options.complete }),
254
+ ctx: bridgeContext,
255
+ ...(options.artifactsDir === undefined
256
+ ? {}
257
+ : { artifactPath: join(options.artifactsDir, `eval-${randomUUID()}.log`) }),
258
+ ...(options.imageResizer === undefined ? {} : { imageResizer: options.imageResizer }),
259
+ });
260
+ if ("setContext" in options.kernelManager && typeof options.kernelManager.setContext === "function") {
261
+ options.kernelManager.setContext(bridgeContext);
262
+ }
263
+ if (invocation.input.reset) await execution.wait(kernel.reset());
264
+ const result = await execution.wait(kernel.run({ cellId: invocation.cellId, code: invocation.input.code }));
265
+ if (result.ok && state.pendingBridgeCalls.length > 0) await execution.wait(Promise.all(state.pendingBridgeCalls));
266
+ return await handler.finalize(result);
267
+ } catch (error) {
268
+ if (handler && error instanceof Error && error.name === "CodemodeSessionDisposedError") {
269
+ return await handler.finalizeCancellation(error);
270
+ }
271
+ throw error;
272
+ } finally {
273
+ state.active = false;
274
+ bridgeAbortController.abort();
275
+ execution.finish();
276
+ if (handler) await handler.flushOutput();
277
+ }
278
+ }
279
+
280
+ function abortError(reason: unknown): Error {
281
+ if (reason instanceof Error && reason.name !== "AbortError") return reason;
282
+ const error = new Error(typeof reason === "string" ? reason : "Eval interrupted", { cause: reason });
283
+ error.name = "AbortError";
284
+ return error;
285
+ }