@code-yeongyu/senpi-codemode 2026.7.25-2 → 2026.7.26
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 +23 -0
- package/README.md +39 -19
- package/package.json +3 -3
- package/src/bridge/http-server.ts +6 -1
- package/src/bridges/output-bridge.ts +0 -1
- package/src/extension/eval-notifier.ts +40 -0
- package/src/index.ts +46 -66
- package/src/kernels/js/context-manager.ts +5 -2
- package/src/kernels/py/kernel-contract.ts +2 -0
- package/src/kernels/py/kernel.ts +19 -4
- package/src/kernels/py/prelude.py +7 -0
- package/src/kernels/shared/subprocess-kernel.ts +8 -5
- package/src/prompt/eval-prompt.ts +23 -5
- package/src/tool/detached-cell-manager.ts +322 -0
- package/src/tool/eval-tool.ts +253 -18
- package/src/tool/interrupt-note.ts +58 -0
- package/src/tool/render.ts +28 -6
- package/src/tool/status-events.ts +20 -0
- package/src/tool/types.ts +65 -22
- package/src/codemode/runtime.ts +0 -258
- package/src/codemode/tools.ts +0 -106
package/src/tool/types.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { AgentToolResult, AgentToolUpdateCallback } from "@code-yeongyu/senpi";
|
|
2
|
-
import { Type } from "typebox";
|
|
2
|
+
import { type TUnsafe, Type } from "typebox";
|
|
3
3
|
import type { HostToKernelMessage, KernelToHostMessage } from "../bridge/protocol.ts";
|
|
4
4
|
import type { TruncationMeta } from "../output/output-meta.ts";
|
|
5
5
|
|
|
@@ -11,37 +11,75 @@ export function enabledLanguageList(enabled: EnabledEvalLanguages): EvalLanguage
|
|
|
11
11
|
return evalLanguageOrder.filter((language) => enabled[language]);
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
+
export interface EvalToolInput {
|
|
15
|
+
readonly language: EvalLanguage;
|
|
16
|
+
readonly code: string;
|
|
17
|
+
readonly action?: "run";
|
|
18
|
+
readonly title?: string;
|
|
19
|
+
readonly timeout?: number;
|
|
20
|
+
readonly on_timeout?: "detach" | "error";
|
|
21
|
+
readonly reset?: boolean;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface EvalControlInput {
|
|
25
|
+
readonly action: "peek" | "stop";
|
|
26
|
+
readonly cell_id: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export type EvalToolRequest = EvalToolInput | EvalControlInput;
|
|
30
|
+
|
|
14
31
|
const fullEvalInputSchema = Type.Object({
|
|
15
|
-
|
|
16
|
-
|
|
32
|
+
action: Type.Optional(
|
|
33
|
+
Type.Union([Type.Literal("run"), Type.Literal("peek"), Type.Literal("stop")], {
|
|
34
|
+
description: "Defaults to run. peek and stop require cell_id.",
|
|
35
|
+
}),
|
|
36
|
+
),
|
|
37
|
+
language: Type.Optional(
|
|
38
|
+
Type.Union([Type.Literal("py"), Type.Literal("js"), Type.Literal("rb"), Type.Literal("jl")]),
|
|
39
|
+
),
|
|
40
|
+
code: Type.Optional(Type.String({ description: "Cell body, verbatim." })),
|
|
17
41
|
title: Type.Optional(Type.String({ description: "Short transcript label." })),
|
|
18
42
|
timeout: Type.Optional(Type.Number({ minimum: 1, description: "Timeout in seconds." })),
|
|
43
|
+
on_timeout: Type.Optional(
|
|
44
|
+
Type.Union([Type.Literal("detach"), Type.Literal("error")], {
|
|
45
|
+
description: "Timeout behavior. Interactive sessions detach by default; print/json sessions error by default.",
|
|
46
|
+
}),
|
|
47
|
+
),
|
|
19
48
|
reset: Type.Optional(Type.Boolean({ description: "Reset this language kernel before running." })),
|
|
49
|
+
cell_id: Type.Optional(Type.String({ description: "Detached eval cell id for peek or stop." })),
|
|
20
50
|
});
|
|
21
51
|
|
|
22
|
-
|
|
52
|
+
/** Runtime accepts a discriminated run/control union. */
|
|
53
|
+
export type EvalInputSchema = TUnsafe<EvalToolRequest> & Pick<typeof fullEvalInputSchema, "properties">;
|
|
54
|
+
|
|
55
|
+
export function createEvalInputSchema(enabled: EnabledEvalLanguages): EvalInputSchema {
|
|
23
56
|
const languages = enabledLanguageList(enabled);
|
|
24
57
|
if (languages.length === 0) throw new Error("eval requires at least one enabled language");
|
|
25
58
|
const languageSchema =
|
|
26
59
|
languages.length === 1
|
|
27
60
|
? Type.Union([Type.Literal(languages[0])])
|
|
28
61
|
: Type.Union(languages.map((item) => Type.Literal(item)));
|
|
29
|
-
return Type.
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
62
|
+
return Type.Unsafe<EvalToolRequest>(
|
|
63
|
+
Type.Object({
|
|
64
|
+
action: Type.Optional(
|
|
65
|
+
Type.Union([Type.Literal("run"), Type.Literal("peek"), Type.Literal("stop")], {
|
|
66
|
+
description: "Defaults to run. peek and stop require cell_id.",
|
|
67
|
+
}),
|
|
68
|
+
),
|
|
69
|
+
language: Type.Optional(languageSchema),
|
|
70
|
+
code: Type.Optional(Type.String({ description: "Cell body, verbatim." })),
|
|
71
|
+
title: Type.Optional(Type.String({ description: "Short transcript label." })),
|
|
72
|
+
timeout: Type.Optional(Type.Number({ minimum: 1, description: "Timeout in seconds." })),
|
|
73
|
+
on_timeout: Type.Optional(
|
|
74
|
+
Type.Union([Type.Literal("detach"), Type.Literal("error")], {
|
|
75
|
+
description:
|
|
76
|
+
"Timeout behavior. Interactive sessions detach by default; print/json sessions error by default.",
|
|
77
|
+
}),
|
|
78
|
+
),
|
|
79
|
+
reset: Type.Optional(Type.Boolean({ description: "Reset this language kernel before running." })),
|
|
80
|
+
cell_id: Type.Optional(Type.String({ description: "Detached eval cell id for peek or stop." })),
|
|
81
|
+
}),
|
|
82
|
+
) as EvalInputSchema;
|
|
45
83
|
}
|
|
46
84
|
export type EvalKernelResult = Extract<KernelToHostMessage, { type: "result" }>;
|
|
47
85
|
export type EvalToolCallMessage = Extract<KernelToHostMessage, { type: "tool-call" }>;
|
|
@@ -52,9 +90,14 @@ export interface EvalKernelRunInput {
|
|
|
52
90
|
readonly timeoutMs?: number;
|
|
53
91
|
}
|
|
54
92
|
|
|
93
|
+
export interface KernelInterruptHandle {
|
|
94
|
+
/** Resolves once the kernel knows whether user state survived the interrupt. */
|
|
95
|
+
readonly stateRetained: Promise<boolean>;
|
|
96
|
+
}
|
|
97
|
+
|
|
55
98
|
export interface EvalKernel {
|
|
56
99
|
run(input: EvalKernelRunInput): Promise<EvalKernelResult>;
|
|
57
|
-
interrupt(reason?: string): Promise<
|
|
100
|
+
interrupt(reason?: string): Promise<KernelInterruptHandle>;
|
|
58
101
|
deliverToolReply(message: Extract<HostToKernelMessage, { type: "tool-reply" }>): void;
|
|
59
102
|
reset(): Promise<void>;
|
|
60
103
|
close(): Promise<void>;
|
|
@@ -90,7 +133,7 @@ export type EvalCellResult = {
|
|
|
90
133
|
readonly code: string;
|
|
91
134
|
readonly language: EvalLanguage;
|
|
92
135
|
readonly output: string;
|
|
93
|
-
readonly status: "pending" | "running" | "complete" | "error";
|
|
136
|
+
readonly status: "pending" | "running" | "detached" | "complete" | "error" | "cancelled";
|
|
94
137
|
readonly exitCode?: number;
|
|
95
138
|
readonly durationMs?: number;
|
|
96
139
|
readonly statusEvents?: readonly EvalStatusEvent[];
|
package/src/codemode/runtime.ts
DELETED
|
@@ -1,258 +0,0 @@
|
|
|
1
|
-
import type { KernelToHostMessage } from "../bridge/protocol.ts";
|
|
2
|
-
import type { AgentExecuteTool } from "../bridges/agent-bridge.ts";
|
|
3
|
-
import { JavaScriptKernel } from "../kernels/js/context-manager.ts";
|
|
4
|
-
import { marshalToolResult } from "../tool/image.ts";
|
|
5
|
-
|
|
6
|
-
const MAX_ACTIVE_CELLS = 4;
|
|
7
|
-
const MAX_PENDING_OUTPUT_CHARS = 100_000;
|
|
8
|
-
const MAX_TERMINAL_ERROR_CHARS = 4_096;
|
|
9
|
-
const RECURSIVE_TOOLS = new Set(["eval", "exec", "wait"]);
|
|
10
|
-
|
|
11
|
-
export type CodeModeCellState = "yielded" | "result" | "terminated" | "error" | "missing";
|
|
12
|
-
|
|
13
|
-
export interface CodeModeObservation {
|
|
14
|
-
readonly cellId: string;
|
|
15
|
-
readonly state: CodeModeCellState;
|
|
16
|
-
readonly output: string;
|
|
17
|
-
readonly error?: string;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export interface CodeModeRuntimeOptions {
|
|
21
|
-
readonly sessionId: string;
|
|
22
|
-
readonly cwd: string;
|
|
23
|
-
readonly parallelPoolWidth: number;
|
|
24
|
-
readonly executeTool: AgentExecuteTool;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
export class CodeModeCapacityError extends Error {
|
|
28
|
-
readonly name = "CodeModeCapacityError";
|
|
29
|
-
|
|
30
|
-
constructor() {
|
|
31
|
-
super(`Code Mode supports at most ${MAX_ACTIVE_CELLS} active cells`);
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
export class CodeModeSessionRuntime {
|
|
36
|
-
readonly #options: CodeModeRuntimeOptions;
|
|
37
|
-
readonly #cells = new Map<string, CodeModeCell>();
|
|
38
|
-
#nextCell = 0;
|
|
39
|
-
#disposed = false;
|
|
40
|
-
|
|
41
|
-
constructor(options: CodeModeRuntimeOptions) {
|
|
42
|
-
this.#options = options;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
async execute(code: string, yieldTimeMs: number, signal: AbortSignal | undefined): Promise<CodeModeObservation> {
|
|
46
|
-
this.#assertActive();
|
|
47
|
-
const cellId = `exec-${++this.#nextCell}`;
|
|
48
|
-
if (signal?.aborted) return { cellId, state: "terminated", output: "" };
|
|
49
|
-
if (this.#cells.size >= MAX_ACTIVE_CELLS) throw new CodeModeCapacityError();
|
|
50
|
-
const cell = new CodeModeCell(cellId, this.#options);
|
|
51
|
-
this.#cells.set(cellId, cell);
|
|
52
|
-
cell.start(code);
|
|
53
|
-
const observation = await cell.observe(yieldTimeMs, signal);
|
|
54
|
-
this.#releaseIfTerminal(observation);
|
|
55
|
-
return observation;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
async wait(
|
|
59
|
-
cellId: string,
|
|
60
|
-
yieldTimeMs: number,
|
|
61
|
-
terminate: boolean,
|
|
62
|
-
signal: AbortSignal | undefined,
|
|
63
|
-
): Promise<CodeModeObservation> {
|
|
64
|
-
const cell = this.#cells.get(cellId);
|
|
65
|
-
if (cell === undefined) return { cellId, state: "missing", output: "" };
|
|
66
|
-
const observation = terminate ? await cell.terminate() : await cell.observe(yieldTimeMs, signal);
|
|
67
|
-
this.#releaseIfTerminal(observation);
|
|
68
|
-
return observation;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
async dispose(): Promise<void> {
|
|
72
|
-
if (this.#disposed) return;
|
|
73
|
-
this.#disposed = true;
|
|
74
|
-
const cells = [...this.#cells.values()];
|
|
75
|
-
this.#cells.clear();
|
|
76
|
-
await Promise.all(cells.map((cell) => cell.terminate()));
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
#releaseIfTerminal(observation: CodeModeObservation): void {
|
|
80
|
-
if (observation.state !== "yielded") this.#cells.delete(observation.cellId);
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
#assertActive(): void {
|
|
84
|
-
if (this.#disposed) throw new Error("Code Mode session has been disposed");
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
class CodeModeCell {
|
|
89
|
-
readonly #id: string;
|
|
90
|
-
readonly #options: CodeModeRuntimeOptions;
|
|
91
|
-
readonly #kernel: JavaScriptKernel;
|
|
92
|
-
readonly #abort = new AbortController();
|
|
93
|
-
#output = "";
|
|
94
|
-
#outputTruncated = false;
|
|
95
|
-
#completion: Promise<void> | undefined;
|
|
96
|
-
#state: "running" | "result" | "terminated" | "error" = "running";
|
|
97
|
-
#error: string | undefined;
|
|
98
|
-
|
|
99
|
-
constructor(id: string, options: CodeModeRuntimeOptions) {
|
|
100
|
-
this.#id = id;
|
|
101
|
-
this.#options = options;
|
|
102
|
-
this.#kernel = new JavaScriptKernel({
|
|
103
|
-
sessionId: `${options.sessionId}:${id}`,
|
|
104
|
-
cwd: options.cwd,
|
|
105
|
-
parallelPoolWidth: options.parallelPoolWidth,
|
|
106
|
-
onMessage: (message) => this.#handleMessage(message),
|
|
107
|
-
});
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
start(code: string): void {
|
|
111
|
-
this.#completion = this.#kernel
|
|
112
|
-
.run({ cellId: this.#id, code })
|
|
113
|
-
.then((result) => {
|
|
114
|
-
if (this.#state !== "running") return;
|
|
115
|
-
if (result.ok) {
|
|
116
|
-
this.#state = "result";
|
|
117
|
-
if (result.valueRepr) this.#appendOutput(`${result.valueRepr}\n`);
|
|
118
|
-
return;
|
|
119
|
-
}
|
|
120
|
-
this.#state = "error";
|
|
121
|
-
this.#error = truncateTerminalError(result.error.message);
|
|
122
|
-
})
|
|
123
|
-
.catch((error: unknown) => {
|
|
124
|
-
if (this.#state !== "running") return;
|
|
125
|
-
this.#state = "error";
|
|
126
|
-
this.#error = truncateTerminalError(error instanceof Error ? error.message : String(error));
|
|
127
|
-
})
|
|
128
|
-
.finally(async () => {
|
|
129
|
-
if (this.#state !== "running") await this.#kernel.close();
|
|
130
|
-
});
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
async observe(yieldTimeMs: number, signal: AbortSignal | undefined): Promise<CodeModeObservation> {
|
|
134
|
-
if (signal?.aborted) return await this.terminate();
|
|
135
|
-
if (this.#state !== "running") return this.#observation();
|
|
136
|
-
const completion = this.#completion;
|
|
137
|
-
if (completion === undefined) throw new Error("Code Mode cell has not started");
|
|
138
|
-
const timeout = Math.max(1, Math.trunc(yieldTimeMs));
|
|
139
|
-
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
140
|
-
let abortListener: (() => void) | undefined;
|
|
141
|
-
const timedOut = new Promise<"yielded">((resolve) => {
|
|
142
|
-
timer = setTimeout(() => resolve("yielded"), timeout);
|
|
143
|
-
});
|
|
144
|
-
const completed = completion.then(() => "completed" as const);
|
|
145
|
-
const aborted = new Promise<"aborted">((resolve) => {
|
|
146
|
-
if (!signal) return;
|
|
147
|
-
abortListener = () => resolve("aborted");
|
|
148
|
-
signal.addEventListener("abort", abortListener, { once: true });
|
|
149
|
-
});
|
|
150
|
-
try {
|
|
151
|
-
const outcome = await Promise.race([completed, timedOut, aborted]);
|
|
152
|
-
if (outcome === "aborted") return await this.terminate();
|
|
153
|
-
return this.#observation();
|
|
154
|
-
} finally {
|
|
155
|
-
if (timer !== undefined) clearTimeout(timer);
|
|
156
|
-
if (abortListener && signal) signal.removeEventListener("abort", abortListener);
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
async terminate(): Promise<CodeModeObservation> {
|
|
161
|
-
if (this.#state === "terminated") return this.#observation();
|
|
162
|
-
if (this.#state === "result" || this.#state === "error") return this.#observation();
|
|
163
|
-
this.#state = "terminated";
|
|
164
|
-
this.#abort.abort(new Error("Code Mode cell terminated"));
|
|
165
|
-
try {
|
|
166
|
-
await this.#kernel.interrupt("Code Mode cell terminated");
|
|
167
|
-
} finally {
|
|
168
|
-
await this.#kernel.close();
|
|
169
|
-
}
|
|
170
|
-
return this.#observation();
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
#handleMessage(message: KernelToHostMessage): void {
|
|
174
|
-
if (message.type === "text") {
|
|
175
|
-
this.#appendOutput(message.data);
|
|
176
|
-
return;
|
|
177
|
-
}
|
|
178
|
-
if (message.type === "display") {
|
|
179
|
-
this.#appendOutput(`[display ${message.mimeType}]\n`);
|
|
180
|
-
return;
|
|
181
|
-
}
|
|
182
|
-
if (message.type === "tool-call") void this.#invokeTool(message);
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
async #invokeTool(message: Extract<KernelToHostMessage, { type: "tool-call" }>): Promise<void> {
|
|
186
|
-
if (RECURSIVE_TOOLS.has(message.toolName)) {
|
|
187
|
-
this.#kernel.deliverToolReply({
|
|
188
|
-
type: "tool-reply",
|
|
189
|
-
callId: message.callId,
|
|
190
|
-
ok: false,
|
|
191
|
-
error: { message: `recursive Code Mode tool "${message.toolName}" is not allowed` },
|
|
192
|
-
});
|
|
193
|
-
return;
|
|
194
|
-
}
|
|
195
|
-
if (this.#options.executeTool.isToolAvailable?.(message.toolName) === false) {
|
|
196
|
-
this.#kernel.deliverToolReply({
|
|
197
|
-
type: "tool-reply",
|
|
198
|
-
callId: message.callId,
|
|
199
|
-
ok: false,
|
|
200
|
-
error: { message: `nested tool "${message.toolName}" is not active` },
|
|
201
|
-
});
|
|
202
|
-
return;
|
|
203
|
-
}
|
|
204
|
-
try {
|
|
205
|
-
const result = await this.#options.executeTool(message.toolName, message.args, { signal: this.#abort.signal });
|
|
206
|
-
this.#kernel.deliverToolReply({
|
|
207
|
-
type: "tool-reply",
|
|
208
|
-
callId: message.callId,
|
|
209
|
-
ok: true,
|
|
210
|
-
value: marshalToolResult(result),
|
|
211
|
-
});
|
|
212
|
-
} catch (error) {
|
|
213
|
-
this.#kernel.deliverToolReply({
|
|
214
|
-
type: "tool-reply",
|
|
215
|
-
callId: message.callId,
|
|
216
|
-
ok: false,
|
|
217
|
-
error: { message: error instanceof Error ? error.message : String(error) },
|
|
218
|
-
});
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
#observation(): CodeModeObservation {
|
|
223
|
-
let output = this.#output;
|
|
224
|
-
this.#output = "";
|
|
225
|
-
if (this.#outputTruncated) {
|
|
226
|
-
output += "\n[output truncated]\n";
|
|
227
|
-
this.#outputTruncated = false;
|
|
228
|
-
}
|
|
229
|
-
if (this.#state === "running") return { cellId: this.#id, state: "yielded", output };
|
|
230
|
-
if (this.#state === "terminated") return { cellId: this.#id, state: "terminated", output };
|
|
231
|
-
if (this.#state === "error") {
|
|
232
|
-
const error = this.#error ?? "Code Mode cell failed";
|
|
233
|
-
if (output !== "") output += output.endsWith("\n") ? error : `\n${error}`;
|
|
234
|
-
else output = error;
|
|
235
|
-
return { cellId: this.#id, state: "error", output, error };
|
|
236
|
-
}
|
|
237
|
-
return { cellId: this.#id, state: "result", output };
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
#appendOutput(value: string): void {
|
|
241
|
-
const remaining = MAX_PENDING_OUTPUT_CHARS - this.#output.length;
|
|
242
|
-
if (remaining <= 0) {
|
|
243
|
-
this.#outputTruncated = true;
|
|
244
|
-
return;
|
|
245
|
-
}
|
|
246
|
-
if (value.length > remaining) {
|
|
247
|
-
this.#output += value.slice(0, remaining);
|
|
248
|
-
this.#outputTruncated = true;
|
|
249
|
-
return;
|
|
250
|
-
}
|
|
251
|
-
this.#output += value;
|
|
252
|
-
}
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
function truncateTerminalError(message: string): string {
|
|
256
|
-
if (message.length <= MAX_TERMINAL_ERROR_CHARS) return message;
|
|
257
|
-
return `${message.slice(0, MAX_TERMINAL_ERROR_CHARS)}\n[error truncated]`;
|
|
258
|
-
}
|
package/src/codemode/tools.ts
DELETED
|
@@ -1,106 +0,0 @@
|
|
|
1
|
-
import type { AgentToolResult, ToolDefinition } from "@code-yeongyu/senpi";
|
|
2
|
-
import { type Static, Type } from "typebox";
|
|
3
|
-
import type { CodeModeCellState, CodeModeObservation, CodeModeSessionRuntime } from "./runtime.ts";
|
|
4
|
-
|
|
5
|
-
const DEFAULT_YIELD_TIME_MS = 10_000;
|
|
6
|
-
|
|
7
|
-
const execInputSchema = Type.Object({
|
|
8
|
-
code: Type.String({ minLength: 1, description: "JavaScript program to execute in a dedicated Code Mode cell." }),
|
|
9
|
-
yield_time_ms: Type.Optional(
|
|
10
|
-
Type.Integer({ minimum: 1, maximum: 60_000, description: "Return a yielded cell after this many milliseconds." }),
|
|
11
|
-
),
|
|
12
|
-
});
|
|
13
|
-
|
|
14
|
-
const waitInputSchema = Type.Object({
|
|
15
|
-
cell_id: Type.String({ minLength: 1, description: "Code Mode cell id returned by exec." }),
|
|
16
|
-
yield_time_ms: Type.Optional(
|
|
17
|
-
Type.Integer({ minimum: 1, maximum: 60_000, description: "Return again after this many milliseconds." }),
|
|
18
|
-
),
|
|
19
|
-
terminate: Type.Optional(Type.Boolean({ description: "Interrupt and close the cell instead of waiting." })),
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
type ExecInput = Static<typeof execInputSchema>;
|
|
23
|
-
type WaitInput = Static<typeof waitInputSchema>;
|
|
24
|
-
|
|
25
|
-
export interface CodeModeToolDetails {
|
|
26
|
-
readonly cellId: string;
|
|
27
|
-
readonly state: CodeModeCellState;
|
|
28
|
-
readonly isError?: boolean;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
export type CodeModeTool =
|
|
32
|
-
| ToolDefinition<typeof execInputSchema, CodeModeToolDetails>
|
|
33
|
-
| ToolDefinition<typeof waitInputSchema, CodeModeToolDetails>;
|
|
34
|
-
|
|
35
|
-
export interface CreateCodeModeToolsOptions {
|
|
36
|
-
readonly runtime: CodeModeSessionRuntime;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
export function createCodeModeTools(options: CreateCodeModeToolsOptions): {
|
|
40
|
-
readonly exec: ToolDefinition<typeof execInputSchema, CodeModeToolDetails>;
|
|
41
|
-
readonly wait: ToolDefinition<typeof waitInputSchema, CodeModeToolDetails>;
|
|
42
|
-
} {
|
|
43
|
-
return {
|
|
44
|
-
exec: {
|
|
45
|
-
name: "exec",
|
|
46
|
-
label: "GPT Code Mode Exec",
|
|
47
|
-
description:
|
|
48
|
-
"Run JavaScript in a dedicated GPT Code Mode cell. Call active Senpi tools as `tools.<name>(args)`. " +
|
|
49
|
-
"If the cell yields, call wait with its cell_id.",
|
|
50
|
-
promptSnippet: "Execute JavaScript that composes active tools in a dedicated Code Mode cell.",
|
|
51
|
-
promptGuidelines: [
|
|
52
|
-
"Use exec for bounded JavaScript composition of active tools; use eval for persistent multi-language analysis.",
|
|
53
|
-
"Call wait only when exec reports a yielded cell, and terminate abandoned cells with wait({ cell_id, terminate: true }).",
|
|
54
|
-
],
|
|
55
|
-
parameters: execInputSchema,
|
|
56
|
-
executionMode: "sequential",
|
|
57
|
-
async execute(_toolCallId, params: ExecInput, signal) {
|
|
58
|
-
return resultFrom(
|
|
59
|
-
await options.runtime.execute(params.code, params.yield_time_ms ?? DEFAULT_YIELD_TIME_MS, signal),
|
|
60
|
-
);
|
|
61
|
-
},
|
|
62
|
-
},
|
|
63
|
-
wait: {
|
|
64
|
-
name: "wait",
|
|
65
|
-
label: "GPT Code Mode Wait",
|
|
66
|
-
description: "Observe a yielded GPT Code Mode cell or terminate it.",
|
|
67
|
-
promptSnippet: "Wait for, or terminate, a yielded GPT Code Mode cell.",
|
|
68
|
-
parameters: waitInputSchema,
|
|
69
|
-
executionMode: "sequential",
|
|
70
|
-
async execute(_toolCallId, params: WaitInput, signal) {
|
|
71
|
-
return resultFrom(
|
|
72
|
-
await options.runtime.wait(
|
|
73
|
-
params.cell_id,
|
|
74
|
-
params.yield_time_ms ?? DEFAULT_YIELD_TIME_MS,
|
|
75
|
-
params.terminate ?? false,
|
|
76
|
-
signal,
|
|
77
|
-
),
|
|
78
|
-
);
|
|
79
|
-
},
|
|
80
|
-
},
|
|
81
|
-
};
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
function resultFrom(observation: CodeModeObservation): AgentToolResult<CodeModeToolDetails> {
|
|
85
|
-
const text =
|
|
86
|
-
observation.output ||
|
|
87
|
-
(observation.state === "yielded"
|
|
88
|
-
? `Code Mode cell ${observation.cellId} is still running. Call wait with this cell_id.`
|
|
89
|
-
: observation.state === "missing"
|
|
90
|
-
? `Code Mode cell ${observation.cellId} does not exist.`
|
|
91
|
-
: observation.state === "terminated"
|
|
92
|
-
? `Code Mode cell ${observation.cellId} was terminated.`
|
|
93
|
-
: (observation.error ?? `Code Mode cell ${observation.cellId} completed.`));
|
|
94
|
-
return {
|
|
95
|
-
content: [{ type: "text", text }],
|
|
96
|
-
details: {
|
|
97
|
-
cellId: observation.cellId,
|
|
98
|
-
state: observation.state,
|
|
99
|
-
...(observation.state === "error" || observation.state === "missing" ? { isError: true } : {}),
|
|
100
|
-
},
|
|
101
|
-
};
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
export function isGptCodeModeModel(modelId: string | undefined): boolean {
|
|
105
|
-
return modelId !== undefined && /(^|[/.:])gpt[-.]/iu.test(modelId);
|
|
106
|
-
}
|