@code-yeongyu/senpi-codemode 2026.7.31 → 2026.8.1
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 +33 -0
- package/README.md +6 -3
- package/package.json +3 -3
- package/src/extension/eval-status-ticker.ts +79 -0
- package/src/extension/eval-status.ts +36 -3
- package/src/index.ts +26 -12
- package/src/tool/cell-execution.ts +146 -0
- package/src/tool/cell-handler.ts +21 -119
- package/src/tool/cell-runtime.ts +157 -0
- package/src/tool/detached-cell-manager.ts +62 -208
- package/src/tool/detached-cell-notification.ts +98 -0
- package/src/tool/detached-cell-snapshot.ts +81 -0
- package/src/tool/detached-cell-state.ts +24 -0
- package/src/tool/detached-eval-result.ts +140 -0
- package/src/tool/detached-notification-queue.ts +53 -0
- package/src/tool/eval-request.ts +45 -0
- package/src/tool/eval-tool-options.ts +45 -0
- package/src/tool/eval-tool.ts +20 -342
package/src/tool/eval-tool.ts
CHANGED
|
@@ -1,206 +1,22 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
2
|
import { join } from "node:path";
|
|
3
|
-
import type { AgentToolResult,
|
|
4
|
-
import
|
|
5
|
-
import type { CompletionRequest, CompletionResult } from "../completion/handler.ts";
|
|
6
|
-
import { defaultCodemodeSettings, type ResolvedCodemodeSettings } from "../config/settings.ts";
|
|
7
|
-
import type { EvalExecutionTracker } from "../extension/session-manager.ts";
|
|
3
|
+
import type { AgentToolResult, ExtensionContext, ToolDefinition } from "@code-yeongyu/senpi";
|
|
4
|
+
import { defaultCodemodeSettings } from "../config/settings.ts";
|
|
8
5
|
import { buildEvalPrompt } from "../prompt/eval-prompt.ts";
|
|
9
6
|
import { TIMEOUT_PAUSE_OP, TIMEOUT_RESUME_OP } from "../timeouts/bridge-timeout.ts";
|
|
10
|
-
import {
|
|
7
|
+
import { abortError, CellExecution, defaultTimeoutFactory } from "./cell-execution.ts";
|
|
11
8
|
import { CellHandler, type CellState } from "./cell-handler.ts";
|
|
12
|
-
import { EvalDetachedCellManager
|
|
13
|
-
import
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
type EvalKernel,
|
|
22
|
-
type EvalKernelManager,
|
|
23
|
-
type EvalToolDetails,
|
|
24
|
-
type EvalToolInput,
|
|
25
|
-
type EvalToolRequest,
|
|
26
|
-
type ExecuteTool,
|
|
27
|
-
enabledLanguageList,
|
|
28
|
-
} from "./types.ts";
|
|
29
|
-
|
|
9
|
+
import { EvalDetachedCellManager } from "./detached-cell-manager.ts";
|
|
10
|
+
import { detachedKernelBusyError, executeEvalControl, resultAfterDetach } from "./detached-eval-result.ts";
|
|
11
|
+
import { evalTimeoutBehavior, isEvalControlRequest, parseEvalRequest } from "./eval-request.ts";
|
|
12
|
+
import type { CreateEvalToolOptions, EvalCellInvocation } from "./eval-tool-options.ts";
|
|
13
|
+
import { describeTimeoutState } from "./interrupt-note.ts";
|
|
14
|
+
import { createEvalInputSchema, type EvalInputSchema, type EvalToolDetails, enabledLanguageList } from "./types.ts";
|
|
15
|
+
|
|
16
|
+
export type { EvalTimeoutFactory } from "./cell-execution.ts";
|
|
17
|
+
export type { CreateEvalToolOptions } from "./eval-tool-options.ts";
|
|
30
18
|
export type { EnabledEvalLanguages, EvalKernel, EvalKernelManager } from "./types.ts";
|
|
31
19
|
|
|
32
|
-
export interface EvalTimeoutFactory {
|
|
33
|
-
create(options: IdleTimeoutOptions): TimeoutPauseHandle & { dispose(): void };
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
export interface CreateEvalToolOptions {
|
|
37
|
-
readonly enabledLanguages: EnabledEvalLanguages;
|
|
38
|
-
readonly kernelManager: EvalKernelManager;
|
|
39
|
-
readonly cellTimeoutSeconds: number;
|
|
40
|
-
readonly executeTool: ExecuteTool;
|
|
41
|
-
readonly listTools?: () => readonly EvalSchemaToolInfo[];
|
|
42
|
-
readonly complete?: (request: CompletionRequest, ctx: ExtensionContext) => Promise<CompletionResult>;
|
|
43
|
-
readonly settings?: ResolvedCodemodeSettings;
|
|
44
|
-
readonly artifactsDir?: string;
|
|
45
|
-
readonly imageResizer?: EvalImageResizer;
|
|
46
|
-
readonly executionTracker?: EvalExecutionTracker;
|
|
47
|
-
readonly cellManager?: EvalDetachedCellManager;
|
|
48
|
-
readonly timeoutFactory?: EvalTimeoutFactory;
|
|
49
|
-
readonly proxyExecutor?: (params: EvalToolInput, signal?: AbortSignal) => Promise<AgentToolResult<EvalToolDetails>>;
|
|
50
|
-
readonly renderers?: Pick<ToolDefinition<EvalInputSchema, EvalToolDetails>, "renderCall" | "renderResult">;
|
|
51
|
-
/** Whether the task-tool spawn helpers (agent()/output()/<dag>) are advertised in the prompt. */
|
|
52
|
-
readonly spawns?: boolean;
|
|
53
|
-
/** Default agent name surfaced in the agent() helper docs when spawns are enabled. */
|
|
54
|
-
readonly spawnDefaultAgent?: string;
|
|
55
|
-
/** Active model id; selects the emphasis dialect of the eval prompt. */
|
|
56
|
-
readonly modelId?: string;
|
|
57
|
-
/** Preformatted host line rendered into the prompt's host-sizing note. */
|
|
58
|
-
readonly hostLine?: string;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
interface EvalCellInvocation {
|
|
62
|
-
readonly cellId: string;
|
|
63
|
-
readonly input: EvalToolInput;
|
|
64
|
-
readonly signal: AbortSignal;
|
|
65
|
-
readonly onUpdate: AgentToolUpdateCallback<EvalToolDetails> | undefined;
|
|
66
|
-
readonly ctx: ExtensionContext;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
interface CellExecutionOptions {
|
|
70
|
-
readonly callerSignal: AbortSignal;
|
|
71
|
-
readonly cellId: string;
|
|
72
|
-
readonly timeoutMs: number;
|
|
73
|
-
readonly timeoutFactory: EvalTimeoutFactory;
|
|
74
|
-
readonly onTimeout: (error: Error) => void;
|
|
75
|
-
readonly onAbort: (error: Error) => void;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
const INTERRUPT_DELIVERY_GRACE_MS = 100;
|
|
79
|
-
const NON_INTERACTIVE_MODES = new Set(["print", "json"]);
|
|
80
|
-
|
|
81
|
-
const defaultTimeoutFactory: EvalTimeoutFactory = {
|
|
82
|
-
create(options): IdleTimeout {
|
|
83
|
-
return new IdleTimeout(options);
|
|
84
|
-
},
|
|
85
|
-
};
|
|
86
|
-
|
|
87
|
-
class CellExecution {
|
|
88
|
-
readonly #callerSignal: AbortSignal;
|
|
89
|
-
readonly #onAbort: (error: Error) => void;
|
|
90
|
-
readonly #abortPromise: Promise<never>;
|
|
91
|
-
readonly #detachedPromise: Promise<void>;
|
|
92
|
-
readonly #watchdog: TimeoutPauseHandle & { dispose(): void };
|
|
93
|
-
#rejectAbort: ((reason?: unknown) => void) | undefined;
|
|
94
|
-
#resolveDetached: (() => void) | undefined;
|
|
95
|
-
#kernel: EvalKernel | undefined;
|
|
96
|
-
#interruptDeadline: ReturnType<typeof setTimeout> | undefined;
|
|
97
|
-
#active = true;
|
|
98
|
-
|
|
99
|
-
constructor(options: CellExecutionOptions) {
|
|
100
|
-
this.#callerSignal = options.callerSignal;
|
|
101
|
-
this.#onAbort = options.onAbort;
|
|
102
|
-
this.#abortPromise = new Promise<never>((_resolve, reject) => {
|
|
103
|
-
this.#rejectAbort = reject;
|
|
104
|
-
});
|
|
105
|
-
this.#detachedPromise = new Promise<void>((resolve) => {
|
|
106
|
-
this.#resolveDetached = resolve;
|
|
107
|
-
});
|
|
108
|
-
this.#watchdog = options.timeoutFactory.create({
|
|
109
|
-
cellId: options.cellId,
|
|
110
|
-
timeoutMs: options.timeoutMs,
|
|
111
|
-
onTimeout: ({ error }) => options.onTimeout(error),
|
|
112
|
-
});
|
|
113
|
-
this.#callerSignal.addEventListener("abort", this.#handleCallerAbort, { once: true });
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
get detached(): Promise<void> {
|
|
117
|
-
return this.#detachedPromise;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
pause(): void {
|
|
121
|
-
this.#watchdog.pause();
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
resume(): void {
|
|
125
|
-
this.#watchdog.resume();
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
setKernel(kernel: EvalKernel): void {
|
|
129
|
-
this.#kernel = kernel;
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
detach(): void {
|
|
133
|
-
if (!this.#active) return;
|
|
134
|
-
this.#watchdog.dispose();
|
|
135
|
-
this.#resolveDetached?.();
|
|
136
|
-
this.#resolveDetached = undefined;
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
cancel(reason: unknown): void {
|
|
140
|
-
this.#abort(reason);
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
finish(): void {
|
|
144
|
-
this.#active = false;
|
|
145
|
-
this.#cleanup();
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
async wait<Result>(operation: Promise<Result>): Promise<Result> {
|
|
149
|
-
const guarded = operation.then(
|
|
150
|
-
(value): Result | Promise<never> => (this.#active ? value : this.#abortPromise),
|
|
151
|
-
(reason: unknown): Promise<never> => (this.#active ? Promise.reject(reason) : this.#abortPromise),
|
|
152
|
-
);
|
|
153
|
-
return await Promise.race([guarded, this.#abortPromise]);
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
readonly #handleCallerAbort = (): void => {
|
|
157
|
-
this.#abort(this.#callerSignal.reason);
|
|
158
|
-
};
|
|
159
|
-
|
|
160
|
-
/** Outcome of the most recent interrupt, when a kernel was interrupted. */
|
|
161
|
-
interruptStateRetained: Promise<boolean> | undefined;
|
|
162
|
-
|
|
163
|
-
#abort(reason: unknown): void {
|
|
164
|
-
if (!this.#active) return;
|
|
165
|
-
this.#active = false;
|
|
166
|
-
this.#cleanup();
|
|
167
|
-
const error = abortError(reason);
|
|
168
|
-
this.#onAbort(error);
|
|
169
|
-
const kernel = this.#kernel;
|
|
170
|
-
if (kernel === undefined) {
|
|
171
|
-
this.#settleAbort(error);
|
|
172
|
-
return;
|
|
173
|
-
}
|
|
174
|
-
this.#interruptDeadline = setTimeout(() => this.#settleAbort(error), INTERRUPT_DELIVERY_GRACE_MS);
|
|
175
|
-
void Promise.resolve()
|
|
176
|
-
.then(async () => {
|
|
177
|
-
const handle = await kernel.interrupt(error.message);
|
|
178
|
-
// Kernels predating the interrupt-outcome contract resolve void; leave
|
|
179
|
-
// the outcome undefined so callers report an honest unknown state.
|
|
180
|
-
this.interruptStateRetained = handle?.stateRetained;
|
|
181
|
-
})
|
|
182
|
-
.then(
|
|
183
|
-
() => this.#settleAbort(error),
|
|
184
|
-
(interruptError: unknown) => this.#settleAbort(interruptError),
|
|
185
|
-
);
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
#settleAbort(reason: unknown): void {
|
|
189
|
-
const reject = this.#rejectAbort;
|
|
190
|
-
if (reject === undefined) return;
|
|
191
|
-
this.#rejectAbort = undefined;
|
|
192
|
-
if (this.#interruptDeadline !== undefined) clearTimeout(this.#interruptDeadline);
|
|
193
|
-
reject(reason);
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
#cleanup(): void {
|
|
197
|
-
this.#callerSignal.removeEventListener("abort", this.#handleCallerAbort);
|
|
198
|
-
this.#watchdog.dispose();
|
|
199
|
-
if (this.#interruptDeadline !== undefined) clearTimeout(this.#interruptDeadline);
|
|
200
|
-
this.#interruptDeadline = undefined;
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
|
|
204
20
|
export function createEvalTool(options: CreateEvalToolOptions): ToolDefinition<EvalInputSchema, EvalToolDetails> {
|
|
205
21
|
const parameters = createEvalInputSchema(options.enabledLanguages);
|
|
206
22
|
const prompt = buildEvalPrompt(options.enabledLanguages, {
|
|
@@ -222,15 +38,15 @@ export function createEvalTool(options: CreateEvalToolOptions): ToolDefinition<E
|
|
|
222
38
|
...(options.renderers?.renderCall === undefined ? {} : { renderCall: options.renderers.renderCall }),
|
|
223
39
|
...(options.renderers?.renderResult === undefined ? {} : { renderResult: options.renderers.renderResult }),
|
|
224
40
|
async execute(toolCallId, params, signal, onUpdate, ctx) {
|
|
225
|
-
const request =
|
|
226
|
-
if (
|
|
41
|
+
const request = parseEvalRequest(params);
|
|
42
|
+
if (isEvalControlRequest(request)) return await executeEvalControl(cellManager, request);
|
|
227
43
|
if (options.proxyExecutor) return await options.proxyExecutor(request, signal);
|
|
228
44
|
if (!languages.includes(request.language))
|
|
229
45
|
throw new RangeError(
|
|
230
46
|
`Unsupported eval language "${request.language}". Enabled languages: ${languages.join(", ")}`,
|
|
231
47
|
);
|
|
232
48
|
const busy = cellManager.busyFor(request.language);
|
|
233
|
-
if (busy !== undefined) throw
|
|
49
|
+
if (busy !== undefined) throw detachedKernelBusyError(busy);
|
|
234
50
|
options.executionTracker?.assertEvalExecutionAllowed();
|
|
235
51
|
const lifecycleController = new AbortController();
|
|
236
52
|
const combinedSignal = signal
|
|
@@ -257,7 +73,7 @@ async function runEvalCell(
|
|
|
257
73
|
): Promise<AgentToolResult<EvalToolDetails>> {
|
|
258
74
|
if (invocation.signal.aborted) throw abortError(invocation.signal.reason);
|
|
259
75
|
const timeoutMs = Math.floor((invocation.input.timeout ?? options.cellTimeoutSeconds) * 1_000);
|
|
260
|
-
const timeoutBehavior =
|
|
76
|
+
const timeoutBehavior = evalTimeoutBehavior(invocation.input, invocation.ctx);
|
|
261
77
|
const bridgeAbortController = new AbortController();
|
|
262
78
|
const cellSignal = AbortSignal.any([invocation.signal, bridgeAbortController.signal]);
|
|
263
79
|
const bridgeContext: ExtensionContext = { ...invocation.ctx, signal: cellSignal };
|
|
@@ -317,7 +133,7 @@ async function runEvalCell(
|
|
|
317
133
|
finalized.then((result) => ({ kind: "result" as const, result })),
|
|
318
134
|
execution.detached.then(() => ({ kind: "detached" as const })),
|
|
319
135
|
]);
|
|
320
|
-
if (outcome.kind === "detached") return
|
|
136
|
+
if (outcome.kind === "detached") return resultAfterDetach(cellManager.peek(invocation.cellId), invocation.input);
|
|
321
137
|
return outcome.result;
|
|
322
138
|
}
|
|
323
139
|
|
|
@@ -351,7 +167,7 @@ async function executeCell(
|
|
|
351
167
|
}),
|
|
352
168
|
);
|
|
353
169
|
execution.setKernel(kernel);
|
|
354
|
-
|
|
170
|
+
const activeHandler = new CellHandler(kernel, state, {
|
|
355
171
|
executeTool: options.executeTool,
|
|
356
172
|
...(options.listTools === undefined ? {} : { listTools: options.listTools }),
|
|
357
173
|
settings: options.settings ?? defaultCodemodeSettings,
|
|
@@ -362,7 +178,8 @@ async function executeCell(
|
|
|
362
178
|
: { artifactPath: join(options.artifactsDir, `eval-${randomUUID()}.log`) }),
|
|
363
179
|
...(options.imageResizer === undefined ? {} : { imageResizer: options.imageResizer }),
|
|
364
180
|
});
|
|
365
|
-
|
|
181
|
+
handler = activeHandler;
|
|
182
|
+
cellManager.markRunning(cell, kernel, () => activeHandler.liveResult());
|
|
366
183
|
if ("setContext" in options.kernelManager && typeof options.kernelManager.setContext === "function") {
|
|
367
184
|
options.kernelManager.setContext(bridgeContext);
|
|
368
185
|
}
|
|
@@ -382,142 +199,3 @@ async function executeCell(
|
|
|
382
199
|
if (handler) await handler.flushOutput();
|
|
383
200
|
}
|
|
384
201
|
}
|
|
385
|
-
|
|
386
|
-
function requestFrom(params: unknown): EvalToolRequest {
|
|
387
|
-
if (typeof params !== "object" || params === null) throw new TypeError("eval parameters must be an object");
|
|
388
|
-
const value = params as Record<string, unknown>;
|
|
389
|
-
if (value.action === "peek" || value.action === "stop") {
|
|
390
|
-
if (typeof value.cell_id !== "string" || value.cell_id.length === 0)
|
|
391
|
-
throw new TypeError(`eval action "${value.action}" requires cell_id`);
|
|
392
|
-
return { action: value.action, cell_id: value.cell_id };
|
|
393
|
-
}
|
|
394
|
-
if (value.action !== undefined && value.action !== "run")
|
|
395
|
-
throw new TypeError(`Unknown eval action "${String(value.action)}"`);
|
|
396
|
-
if (!isEvalLanguage(value.language)) throw new TypeError("eval run requires language");
|
|
397
|
-
if (typeof value.code !== "string") throw new TypeError("eval run requires code");
|
|
398
|
-
if (value.on_timeout !== undefined && value.on_timeout !== "detach" && value.on_timeout !== "error")
|
|
399
|
-
throw new TypeError(`Unknown eval on_timeout value "${String(value.on_timeout)}"`);
|
|
400
|
-
return {
|
|
401
|
-
language: value.language,
|
|
402
|
-
code: value.code,
|
|
403
|
-
...(value.action === "run" ? { action: "run" as const } : {}),
|
|
404
|
-
...(typeof value.title === "string" ? { title: value.title } : {}),
|
|
405
|
-
...(typeof value.timeout === "number" ? { timeout: value.timeout } : {}),
|
|
406
|
-
...(value.on_timeout === "detach" || value.on_timeout === "error" ? { on_timeout: value.on_timeout } : {}),
|
|
407
|
-
...(typeof value.reset === "boolean" ? { reset: value.reset } : {}),
|
|
408
|
-
};
|
|
409
|
-
}
|
|
410
|
-
|
|
411
|
-
function isControlRequest(request: EvalToolRequest): request is EvalControlInput {
|
|
412
|
-
return request.action === "peek" || request.action === "stop";
|
|
413
|
-
}
|
|
414
|
-
|
|
415
|
-
function isEvalLanguage(value: unknown): value is EvalToolInput["language"] {
|
|
416
|
-
return value === "py" || value === "js" || value === "rb" || value === "jl";
|
|
417
|
-
}
|
|
418
|
-
|
|
419
|
-
async function executeControl(
|
|
420
|
-
cellManager: EvalDetachedCellManager,
|
|
421
|
-
request: EvalControlInput,
|
|
422
|
-
): Promise<AgentToolResult<EvalToolDetails>> {
|
|
423
|
-
const snapshot =
|
|
424
|
-
request.action === "stop" ? await cellManager.stop(request.cell_id) : cellManager.peek(request.cell_id);
|
|
425
|
-
return snapshotResult(snapshot);
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
function detachedResult(snapshot: EvalDetachedCellSnapshot, input: EvalToolInput): AgentToolResult<EvalToolDetails> {
|
|
429
|
-
return {
|
|
430
|
-
content: [
|
|
431
|
-
{
|
|
432
|
-
type: "text",
|
|
433
|
-
text: `Eval cell ${snapshot.cellId} detached and is still running in the ${input.language} kernel. Completion will arrive as a notification. Use eval({ action: "peek", cell_id: "${snapshot.cellId}" }) or eval({ action: "stop", cell_id: "${snapshot.cellId}" }).`,
|
|
434
|
-
},
|
|
435
|
-
],
|
|
436
|
-
details: {
|
|
437
|
-
language: input.language,
|
|
438
|
-
languages: [input.language],
|
|
439
|
-
...(input.title === undefined ? {} : { title: input.title }),
|
|
440
|
-
durationMs: 0,
|
|
441
|
-
toolCalls: [],
|
|
442
|
-
truncated: false,
|
|
443
|
-
statusEvents: [{ op: "detached", cellId: snapshot.cellId }],
|
|
444
|
-
cells: [
|
|
445
|
-
{
|
|
446
|
-
index: 0,
|
|
447
|
-
...(input.title === undefined ? {} : { title: input.title }),
|
|
448
|
-
code: input.code,
|
|
449
|
-
language: input.language,
|
|
450
|
-
output: snapshot.outputTail,
|
|
451
|
-
status: "detached",
|
|
452
|
-
statusEvents: [{ op: "detached", cellId: snapshot.cellId }],
|
|
453
|
-
},
|
|
454
|
-
],
|
|
455
|
-
},
|
|
456
|
-
};
|
|
457
|
-
}
|
|
458
|
-
|
|
459
|
-
function snapshotResult(snapshot: EvalDetachedCellSnapshot): AgentToolResult<EvalToolDetails> {
|
|
460
|
-
const terminationNote =
|
|
461
|
-
snapshot.state === "cancelled" ? interruptionStateNote(snapshot.language, snapshot.stateRetained) : undefined;
|
|
462
|
-
const text = [
|
|
463
|
-
`Eval cell ${snapshot.cellId} (${snapshot.language}) is ${snapshot.state}.`,
|
|
464
|
-
snapshot.outputTail.length === 0 ? "(no buffered output)" : snapshot.outputTail,
|
|
465
|
-
...(terminationNote === undefined ? [] : [terminationNote]),
|
|
466
|
-
].join("\n");
|
|
467
|
-
return {
|
|
468
|
-
content: [{ type: "text", text }],
|
|
469
|
-
details: {
|
|
470
|
-
language: snapshot.language,
|
|
471
|
-
languages: [snapshot.language],
|
|
472
|
-
durationMs: snapshot.result?.details.durationMs ?? 0,
|
|
473
|
-
toolCalls: snapshot.result?.details.toolCalls ?? [],
|
|
474
|
-
truncated: snapshot.result?.details.truncated ?? false,
|
|
475
|
-
...(snapshot.state === "failed" ? { isError: true } : {}),
|
|
476
|
-
statusEvents: [{ op: snapshot.state, cellId: snapshot.cellId }],
|
|
477
|
-
cells: [
|
|
478
|
-
{
|
|
479
|
-
index: 0,
|
|
480
|
-
code: "",
|
|
481
|
-
language: snapshot.language,
|
|
482
|
-
output: snapshot.outputTail,
|
|
483
|
-
status: cellStatus(snapshot.state),
|
|
484
|
-
statusEvents: [{ op: snapshot.state, cellId: snapshot.cellId }],
|
|
485
|
-
},
|
|
486
|
-
],
|
|
487
|
-
},
|
|
488
|
-
};
|
|
489
|
-
}
|
|
490
|
-
|
|
491
|
-
function cellStatus(state: EvalDetachedCellSnapshot["state"]): EvalCellResult["status"] {
|
|
492
|
-
switch (state) {
|
|
493
|
-
case "running":
|
|
494
|
-
return "running";
|
|
495
|
-
case "detached":
|
|
496
|
-
return "detached";
|
|
497
|
-
case "completed":
|
|
498
|
-
return "complete";
|
|
499
|
-
case "failed":
|
|
500
|
-
return "error";
|
|
501
|
-
case "cancelled":
|
|
502
|
-
return "cancelled";
|
|
503
|
-
}
|
|
504
|
-
}
|
|
505
|
-
|
|
506
|
-
function kernelBusyError(snapshot: EvalDetachedCellSnapshot): Error {
|
|
507
|
-
const tail = snapshot.outputTail.length === 0 ? "(no output yet)" : snapshot.outputTail;
|
|
508
|
-
return new Error(
|
|
509
|
-
`The ${snapshot.language} eval kernel is busy running detached cell ${snapshot.cellId}. Do not re-run it; use eval({ action: "peek", cell_id: "${snapshot.cellId}" }). Current output tail:\n${tail}`,
|
|
510
|
-
);
|
|
511
|
-
}
|
|
512
|
-
|
|
513
|
-
function timeoutBehaviorFor(input: EvalToolInput, ctx: ExtensionContext): "detach" | "error" {
|
|
514
|
-
if (input.on_timeout !== undefined) return input.on_timeout;
|
|
515
|
-
return NON_INTERACTIVE_MODES.has(ctx.mode) ? "error" : "detach";
|
|
516
|
-
}
|
|
517
|
-
|
|
518
|
-
function abortError(reason: unknown): Error {
|
|
519
|
-
if (reason instanceof Error && reason.name !== "AbortError") return reason;
|
|
520
|
-
const error = new Error(typeof reason === "string" ? reason : "Eval interrupted", { cause: reason });
|
|
521
|
-
error.name = "AbortError";
|
|
522
|
-
return error;
|
|
523
|
-
}
|