@code-yeongyu/senpi-codemode 2026.7.31 → 2026.8.3-3
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 +69 -0
- package/README.md +6 -3
- package/package.json +4 -4
- 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/cell-handler.ts
CHANGED
|
@@ -1,9 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
type AgentToolResult,
|
|
3
|
-
type AgentToolUpdateCallback,
|
|
4
|
-
type ExtensionContext,
|
|
5
|
-
sanitizeTerminalLabel,
|
|
6
|
-
} from "@code-yeongyu/senpi";
|
|
1
|
+
import { type AgentToolResult, type ExtensionContext, sanitizeTerminalLabel } from "@code-yeongyu/senpi";
|
|
7
2
|
import type { KernelToHostMessage } from "../bridge/protocol.ts";
|
|
8
3
|
import type { AgentExecuteTool } from "../bridges/agent-bridge.ts";
|
|
9
4
|
import { isReservedToolName, runReservedTool } from "../bridges/reserved-dispatch.ts";
|
|
@@ -13,15 +8,12 @@ import type { CompletionRequest, CompletionResult } from "../completion/handler.
|
|
|
13
8
|
import { handleCompletionToolCall } from "../completion/tool-bridge.ts";
|
|
14
9
|
import type { ResolvedCodemodeSettings } from "../config/settings.ts";
|
|
15
10
|
import { boundToolCallArgs, capCodePoints, MAX_ENRICHED_TOOL_CALLS, toolCallResultPreview } from "./call-capture.ts";
|
|
16
|
-
import {
|
|
17
|
-
|
|
18
|
-
EvalOutputCollector,
|
|
19
|
-
type EvalOutputResult,
|
|
20
|
-
marshalToolResult,
|
|
21
|
-
toolResultIsError,
|
|
22
|
-
} from "./image.ts";
|
|
11
|
+
import { CellResultBuilder, type CellState } from "./cell-runtime.ts";
|
|
12
|
+
import { type EvalImageResizer, marshalToolResult, toolResultIsError } from "./image.ts";
|
|
23
13
|
import { upsertStatusEvent } from "./status-events.ts";
|
|
24
|
-
import type { EvalKernel, EvalStatusEvent, EvalToolDetails
|
|
14
|
+
import type { EvalKernel, EvalStatusEvent, EvalToolDetails } from "./types.ts";
|
|
15
|
+
|
|
16
|
+
export type { CellState } from "./cell-runtime.ts";
|
|
25
17
|
|
|
26
18
|
type ResolvedToolReply = {
|
|
27
19
|
readonly value: unknown;
|
|
@@ -37,22 +29,6 @@ type ToolCallEnrichment = {
|
|
|
37
29
|
readonly argsTruncated?: true;
|
|
38
30
|
};
|
|
39
31
|
|
|
40
|
-
const LIVE_OUTPUT_PREVIEW_LINES = 8;
|
|
41
|
-
|
|
42
|
-
export interface CellState {
|
|
43
|
-
readonly input: EvalToolInput;
|
|
44
|
-
readonly signal: AbortSignal;
|
|
45
|
-
readonly onUpdate: AgentToolUpdateCallback<EvalToolDetails> | undefined;
|
|
46
|
-
readonly toolCalls: EvalToolDetails["toolCalls"] extends readonly (infer Item)[] ? Item[] : never;
|
|
47
|
-
readonly pendingBridgeCalls: Promise<void>[];
|
|
48
|
-
readonly statusEvents: EvalStatusEvent[];
|
|
49
|
-
active: boolean;
|
|
50
|
-
output: string;
|
|
51
|
-
phase: string | undefined;
|
|
52
|
-
durationMs: number;
|
|
53
|
-
status: "pending" | "running" | "complete" | "error";
|
|
54
|
-
}
|
|
55
|
-
|
|
56
32
|
export interface CellBridgeRuntime {
|
|
57
33
|
readonly executeTool: AgentExecuteTool;
|
|
58
34
|
readonly listTools?: () => readonly EvalSchemaToolInfo[];
|
|
@@ -67,46 +43,40 @@ export class CellHandler {
|
|
|
67
43
|
readonly #kernel: EvalKernel;
|
|
68
44
|
readonly #state: CellState;
|
|
69
45
|
readonly #runtime: CellBridgeRuntime;
|
|
70
|
-
readonly #
|
|
46
|
+
readonly #resultBuilder: CellResultBuilder;
|
|
71
47
|
|
|
72
48
|
constructor(kernel: EvalKernel, state: CellState, runtime: CellBridgeRuntime) {
|
|
73
49
|
this.#kernel = kernel;
|
|
74
50
|
this.#state = state;
|
|
75
51
|
this.#runtime = runtime;
|
|
76
52
|
const settings = runtime.settings.outputSink;
|
|
77
|
-
this.#
|
|
53
|
+
this.#resultBuilder = new CellResultBuilder({
|
|
54
|
+
state,
|
|
78
55
|
headBytes: settings.headBytes,
|
|
79
56
|
maxColumns: settings.maxColumns,
|
|
80
57
|
model: runtime.ctx.model,
|
|
81
58
|
...(runtime.artifactPath === undefined ? {} : { artifactPath: runtime.artifactPath }),
|
|
82
59
|
...(runtime.imageResizer === undefined ? {} : { imageResizer: runtime.imageResizer }),
|
|
83
|
-
onChunk: (_aggregate, cell) => {
|
|
84
|
-
state.output = cell;
|
|
85
|
-
this.#emitUpdate(false);
|
|
86
|
-
},
|
|
87
60
|
});
|
|
88
|
-
state.status = "running";
|
|
89
|
-
this.#emitUpdate(false);
|
|
90
61
|
}
|
|
91
62
|
|
|
92
63
|
async handle(message: KernelToHostMessage): Promise<void> {
|
|
93
64
|
if (!this.#state.active) return;
|
|
94
65
|
switch (message.type) {
|
|
95
66
|
case "text":
|
|
96
|
-
this.#
|
|
67
|
+
this.#resultBuilder.push(message.data);
|
|
97
68
|
return;
|
|
98
69
|
case "phase":
|
|
99
|
-
this.#
|
|
100
|
-
this.#emitUpdate(false);
|
|
70
|
+
this.#resultBuilder.setPhase(message.title);
|
|
101
71
|
return;
|
|
102
72
|
case "status":
|
|
103
73
|
this.#recordStatus(message.event);
|
|
104
74
|
return;
|
|
105
75
|
case "log":
|
|
106
|
-
this.#
|
|
76
|
+
this.#resultBuilder.push(`${message.message}\n`);
|
|
107
77
|
return;
|
|
108
78
|
case "display":
|
|
109
|
-
this.#
|
|
79
|
+
this.#resultBuilder.display(message);
|
|
110
80
|
return;
|
|
111
81
|
case "tool-call": {
|
|
112
82
|
const pending = this.#handleToolCall(message);
|
|
@@ -125,38 +95,19 @@ export class CellHandler {
|
|
|
125
95
|
}
|
|
126
96
|
|
|
127
97
|
async finalize(result: Extract<KernelToHostMessage, { type: "result" }>): Promise<AgentToolResult<EvalToolDetails>> {
|
|
128
|
-
this.#
|
|
129
|
-
if (result.ok) {
|
|
130
|
-
if (result.valueRepr) this.#output.push(`${result.valueRepr}\n`);
|
|
131
|
-
this.#state.status = "complete";
|
|
132
|
-
} else {
|
|
133
|
-
this.#output.push(`${result.error.message}\n`);
|
|
134
|
-
this.#state.status = "error";
|
|
135
|
-
}
|
|
136
|
-
return await this.#finish(!result.ok);
|
|
98
|
+
return await this.#resultBuilder.finalize(result);
|
|
137
99
|
}
|
|
138
100
|
|
|
139
101
|
async finalizeCancellation(error: Error): Promise<AgentToolResult<EvalToolDetails>> {
|
|
140
|
-
this.#
|
|
141
|
-
this.#state.status = "error";
|
|
142
|
-
return await this.#finish(true);
|
|
102
|
+
return await this.#resultBuilder.finalizeCancellation(error);
|
|
143
103
|
}
|
|
144
104
|
|
|
145
105
|
async flushOutput(): Promise<void> {
|
|
146
|
-
await this.#
|
|
106
|
+
await this.#resultBuilder.flushOutput();
|
|
147
107
|
}
|
|
148
108
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
this.#state.output = output.output;
|
|
152
|
-
const details = this.#details(output, isError);
|
|
153
|
-
this.#emitUpdate(isError);
|
|
154
|
-
const text =
|
|
155
|
-
output.output ||
|
|
156
|
-
(output.images.length > 0
|
|
157
|
-
? `(displayed ${output.images.length} image${output.images.length === 1 ? "" : "s"}; no text output)`
|
|
158
|
-
: "(no output)");
|
|
159
|
-
return { content: [{ type: "text", text }, ...output.images], details };
|
|
109
|
+
liveResult(): AgentToolResult<EvalToolDetails> {
|
|
110
|
+
return this.#resultBuilder.liveResult();
|
|
160
111
|
}
|
|
161
112
|
|
|
162
113
|
async #handleToolCall(message: Extract<KernelToHostMessage, { type: "tool-call" }>): Promise<void> {
|
|
@@ -202,7 +153,7 @@ export class CellHandler {
|
|
|
202
153
|
? { name: message.toolName, ok: true }
|
|
203
154
|
: { name: message.toolName, ok: false, error: result.error },
|
|
204
155
|
);
|
|
205
|
-
this.#emitUpdate(false);
|
|
156
|
+
this.#resultBuilder.emitUpdate(false);
|
|
206
157
|
return;
|
|
207
158
|
}
|
|
208
159
|
const capturedArgs = boundToolCallArgs(message.args);
|
|
@@ -268,7 +219,7 @@ export class CellHandler {
|
|
|
268
219
|
error: { message: text },
|
|
269
220
|
});
|
|
270
221
|
}
|
|
271
|
-
this.#emitUpdate(false);
|
|
222
|
+
this.#resultBuilder.emitUpdate(false);
|
|
272
223
|
}
|
|
273
224
|
|
|
274
225
|
#pushToolCall(
|
|
@@ -301,55 +252,6 @@ export class CellHandler {
|
|
|
301
252
|
#recordStatus(event: EvalStatusEvent): void {
|
|
302
253
|
if (!this.#runtime.settings.statusEvents) return;
|
|
303
254
|
upsertStatusEvent(this.#state.statusEvents, event);
|
|
304
|
-
this.#emitUpdate(false);
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
#details(output: EvalOutputResult | undefined, isError: boolean): EvalToolDetails {
|
|
308
|
-
const statusEvents = this.#state.statusEvents.length > 0 ? [...this.#state.statusEvents] : undefined;
|
|
309
|
-
return {
|
|
310
|
-
language: this.#state.input.language,
|
|
311
|
-
languages: [this.#state.input.language],
|
|
312
|
-
...(this.#state.input.title === undefined ? {} : { title: this.#state.input.title }),
|
|
313
|
-
durationMs: this.#state.durationMs,
|
|
314
|
-
toolCalls: [...this.#state.toolCalls],
|
|
315
|
-
truncated: output?.truncated ?? false,
|
|
316
|
-
...(isError ? { isError: true } : {}),
|
|
317
|
-
...(this.#state.phase === undefined ? {} : { phase: this.#state.phase }),
|
|
318
|
-
cells: [
|
|
319
|
-
{
|
|
320
|
-
index: 0,
|
|
321
|
-
...(this.#state.input.title === undefined ? {} : { title: this.#state.input.title }),
|
|
322
|
-
code: this.#state.input.code,
|
|
323
|
-
language: this.#state.input.language,
|
|
324
|
-
output: this.#state.output,
|
|
325
|
-
status: this.#state.status,
|
|
326
|
-
durationMs: this.#state.durationMs,
|
|
327
|
-
...(statusEvents === undefined ? {} : { statusEvents }),
|
|
328
|
-
...(output?.hasMarkdown ? { hasMarkdown: true } : {}),
|
|
329
|
-
},
|
|
330
|
-
],
|
|
331
|
-
...(statusEvents === undefined ? {} : { statusEvents }),
|
|
332
|
-
...(output === undefined || output.jsonOutputs.length === 0 ? {} : { jsonOutputs: output.jsonOutputs }),
|
|
333
|
-
...(output?.notice === undefined ? {} : { notice: output.notice }),
|
|
334
|
-
...(output?.meta === undefined ? {} : { meta: output.meta }),
|
|
335
|
-
};
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
#liveUpdateText(): string {
|
|
339
|
-
const title = this.#state.input.title === undefined ? "" : ` ${this.#state.input.title}`;
|
|
340
|
-
const aggregateOutput = this.#output.aggregateText();
|
|
341
|
-
const outputLines = aggregateOutput.split("\n");
|
|
342
|
-
const hasTrailingNewline = aggregateOutput.endsWith("\n");
|
|
343
|
-
if (hasTrailingNewline) outputLines.pop();
|
|
344
|
-
const output = `${outputLines.slice(-LIVE_OUTPUT_PREVIEW_LINES).join("\n")}${hasTrailingNewline ? "\n" : ""}`;
|
|
345
|
-
return `1/1 cells ${this.#state.status}\n[1] ${this.#state.input.language}${title} ${this.#state.status}${output.length === 0 ? "" : `\n${output}`}`;
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
#emitUpdate(isError: boolean): void {
|
|
349
|
-
if (!this.#state.active) return;
|
|
350
|
-
this.#state.onUpdate?.({
|
|
351
|
-
content: [{ type: "text", text: this.#liveUpdateText() }],
|
|
352
|
-
details: this.#details(undefined, isError),
|
|
353
|
-
});
|
|
255
|
+
this.#resultBuilder.emitUpdate(false);
|
|
354
256
|
}
|
|
355
257
|
}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import type { AgentToolResult, AgentToolUpdateCallback, ExtensionContext } from "@code-yeongyu/senpi";
|
|
2
|
+
import type { KernelToHostMessage } from "../bridge/protocol.ts";
|
|
3
|
+
import { type EvalImageResizer, EvalOutputCollector, type EvalOutputResult } from "./image.ts";
|
|
4
|
+
import type { EvalStatusEvent, EvalToolDetails, EvalToolInput } from "./types.ts";
|
|
5
|
+
|
|
6
|
+
type KernelResult = Extract<KernelToHostMessage, { type: "result" }>;
|
|
7
|
+
type DisplayMessage = Extract<KernelToHostMessage, { type: "display" }>;
|
|
8
|
+
type ToolCall = EvalToolDetails["toolCalls"] extends readonly (infer Item)[] ? Item : never;
|
|
9
|
+
|
|
10
|
+
export interface CellState {
|
|
11
|
+
readonly input: EvalToolInput;
|
|
12
|
+
readonly signal: AbortSignal;
|
|
13
|
+
readonly onUpdate: AgentToolUpdateCallback<EvalToolDetails> | undefined;
|
|
14
|
+
readonly toolCalls: ToolCall[];
|
|
15
|
+
readonly pendingBridgeCalls: Promise<void>[];
|
|
16
|
+
readonly statusEvents: EvalStatusEvent[];
|
|
17
|
+
active: boolean;
|
|
18
|
+
output: string;
|
|
19
|
+
phase: string | undefined;
|
|
20
|
+
durationMs: number;
|
|
21
|
+
status: "pending" | "running" | "complete" | "error";
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface CellResultBuilderOptions {
|
|
25
|
+
readonly artifactPath?: string;
|
|
26
|
+
readonly headBytes: number;
|
|
27
|
+
readonly imageResizer?: EvalImageResizer;
|
|
28
|
+
readonly maxColumns: number;
|
|
29
|
+
readonly model: ExtensionContext["model"];
|
|
30
|
+
readonly state: CellState;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export class CellResultBuilder {
|
|
34
|
+
readonly #output: EvalOutputCollector;
|
|
35
|
+
readonly #state: CellState;
|
|
36
|
+
|
|
37
|
+
constructor(options: CellResultBuilderOptions) {
|
|
38
|
+
this.#state = options.state;
|
|
39
|
+
this.#output = new EvalOutputCollector({
|
|
40
|
+
headBytes: options.headBytes,
|
|
41
|
+
maxColumns: options.maxColumns,
|
|
42
|
+
model: options.model,
|
|
43
|
+
...(options.artifactPath === undefined ? {} : { artifactPath: options.artifactPath }),
|
|
44
|
+
...(options.imageResizer === undefined ? {} : { imageResizer: options.imageResizer }),
|
|
45
|
+
onChunk: (_aggregate, cell) => {
|
|
46
|
+
options.state.output = cell;
|
|
47
|
+
this.emitUpdate(false);
|
|
48
|
+
},
|
|
49
|
+
});
|
|
50
|
+
options.state.status = "running";
|
|
51
|
+
this.emitUpdate(false);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
push(text: string): void {
|
|
55
|
+
this.#output.push(text);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
display(message: DisplayMessage): void {
|
|
59
|
+
this.#output.display(message);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
setPhase(title: string): void {
|
|
63
|
+
this.#state.phase = title;
|
|
64
|
+
this.emitUpdate(false);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
async finalize(result: KernelResult): Promise<AgentToolResult<EvalToolDetails>> {
|
|
68
|
+
this.#state.durationMs = result.durationMs;
|
|
69
|
+
if (result.ok) {
|
|
70
|
+
if (result.valueRepr) this.#output.push(`${result.valueRepr}\n`);
|
|
71
|
+
this.#state.status = "complete";
|
|
72
|
+
} else {
|
|
73
|
+
this.#output.push(`${result.error.message}\n`);
|
|
74
|
+
this.#state.status = "error";
|
|
75
|
+
}
|
|
76
|
+
return await this.#finish(!result.ok);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
async finalizeCancellation(error: Error): Promise<AgentToolResult<EvalToolDetails>> {
|
|
80
|
+
this.#output.push(`${error.message}\n`);
|
|
81
|
+
this.#state.status = "error";
|
|
82
|
+
return await this.#finish(true);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
async flushOutput(): Promise<void> {
|
|
86
|
+
await this.#output.flush();
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
liveResult(): AgentToolResult<EvalToolDetails> {
|
|
90
|
+
return {
|
|
91
|
+
content: [{ type: "text", text: this.#liveUpdateText() }],
|
|
92
|
+
details: this.#details(undefined, this.#state.status === "error"),
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
emitUpdate(isError: boolean): void {
|
|
97
|
+
if (!this.#state.active) return;
|
|
98
|
+
this.#state.onUpdate?.({
|
|
99
|
+
content: [{ type: "text", text: this.#liveUpdateText() }],
|
|
100
|
+
details: this.#details(undefined, isError),
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
async #finish(isError: boolean): Promise<AgentToolResult<EvalToolDetails>> {
|
|
105
|
+
const output = await this.#output.finish();
|
|
106
|
+
this.#state.output = output.output;
|
|
107
|
+
const details = this.#details(output, isError);
|
|
108
|
+
this.emitUpdate(isError);
|
|
109
|
+
const text =
|
|
110
|
+
output.output ||
|
|
111
|
+
(output.images.length > 0
|
|
112
|
+
? `(displayed ${output.images.length} image${output.images.length === 1 ? "" : "s"}; no text output)`
|
|
113
|
+
: "(no output)");
|
|
114
|
+
return { content: [{ type: "text", text }, ...output.images], details };
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
#details(output: EvalOutputResult | undefined, isError: boolean): EvalToolDetails {
|
|
118
|
+
const statusEvents = this.#state.statusEvents.length > 0 ? [...this.#state.statusEvents] : undefined;
|
|
119
|
+
return {
|
|
120
|
+
language: this.#state.input.language,
|
|
121
|
+
languages: [this.#state.input.language],
|
|
122
|
+
...(this.#state.input.title === undefined ? {} : { title: this.#state.input.title }),
|
|
123
|
+
durationMs: this.#state.durationMs,
|
|
124
|
+
toolCalls: [...this.#state.toolCalls],
|
|
125
|
+
truncated: output?.truncated ?? false,
|
|
126
|
+
...(isError ? { isError: true } : {}),
|
|
127
|
+
...(this.#state.phase === undefined ? {} : { phase: this.#state.phase }),
|
|
128
|
+
cells: [
|
|
129
|
+
{
|
|
130
|
+
index: 0,
|
|
131
|
+
...(this.#state.input.title === undefined ? {} : { title: this.#state.input.title }),
|
|
132
|
+
code: this.#state.input.code,
|
|
133
|
+
language: this.#state.input.language,
|
|
134
|
+
output: this.#state.output,
|
|
135
|
+
status: this.#state.status,
|
|
136
|
+
durationMs: this.#state.durationMs,
|
|
137
|
+
...(statusEvents === undefined ? {} : { statusEvents }),
|
|
138
|
+
...(output?.hasMarkdown ? { hasMarkdown: true } : {}),
|
|
139
|
+
},
|
|
140
|
+
],
|
|
141
|
+
...(statusEvents === undefined ? {} : { statusEvents }),
|
|
142
|
+
...(output === undefined || output.jsonOutputs.length === 0 ? {} : { jsonOutputs: output.jsonOutputs }),
|
|
143
|
+
...(output?.notice === undefined ? {} : { notice: output.notice }),
|
|
144
|
+
...(output?.meta === undefined ? {} : { meta: output.meta }),
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
#liveUpdateText(): string {
|
|
149
|
+
const title = this.#state.input.title === undefined ? "" : ` ${this.#state.input.title}`;
|
|
150
|
+
const aggregateOutput = this.#output.aggregateText();
|
|
151
|
+
const outputLines = aggregateOutput.split("\n");
|
|
152
|
+
const hasTrailingNewline = aggregateOutput.endsWith("\n");
|
|
153
|
+
if (hasTrailingNewline) outputLines.pop();
|
|
154
|
+
const output = `${outputLines.slice(-8).join("\n")}${hasTrailingNewline ? "\n" : ""}`;
|
|
155
|
+
return `1/1 cells ${this.#state.status}\n[1] ${this.#state.input.language}${title} ${this.#state.status}${output.length === 0 ? "" : `\n${output}`}`;
|
|
156
|
+
}
|
|
157
|
+
}
|