@code-yeongyu/senpi 2026.7.22-2 → 2026.7.23
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 +20 -0
- package/dist/core/extensions/builtin/compaction/index.d.ts.map +1 -1
- package/dist/core/extensions/builtin/compaction/index.js +14 -6
- package/dist/core/extensions/builtin/compaction/index.js.map +1 -1
- package/dist/core/extensions/builtin/compaction/openai-remote-convert.d.ts +26 -0
- package/dist/core/extensions/builtin/compaction/openai-remote-convert.d.ts.map +1 -0
- package/dist/core/extensions/builtin/compaction/openai-remote-convert.js +194 -0
- package/dist/core/extensions/builtin/compaction/openai-remote-convert.js.map +1 -0
- package/dist/core/extensions/builtin/compaction/openai-remote-model.d.ts +22 -0
- package/dist/core/extensions/builtin/compaction/openai-remote-model.d.ts.map +1 -0
- package/dist/core/extensions/builtin/compaction/openai-remote-model.js +92 -0
- package/dist/core/extensions/builtin/compaction/openai-remote-model.js.map +1 -0
- package/dist/core/extensions/builtin/compaction/openai-remote-schema.d.ts +92 -0
- package/dist/core/extensions/builtin/compaction/openai-remote-schema.d.ts.map +1 -0
- package/dist/core/extensions/builtin/compaction/openai-remote-schema.js +56 -0
- package/dist/core/extensions/builtin/compaction/openai-remote-schema.js.map +1 -0
- package/dist/core/extensions/builtin/compaction/openai-remote.d.ts +7 -84
- package/dist/core/extensions/builtin/compaction/openai-remote.d.ts.map +1 -1
- package/dist/core/extensions/builtin/compaction/openai-remote.js +55 -271
- package/dist/core/extensions/builtin/compaction/openai-remote.js.map +1 -1
- package/dist/core/extensions/builtin/prompt-preset/gpt-5.2.d.ts.map +1 -1
- package/dist/core/extensions/builtin/prompt-preset/gpt-5.2.js +3 -0
- package/dist/core/extensions/builtin/prompt-preset/gpt-5.2.js.map +1 -1
- package/dist/core/extensions/builtin/prompt-preset/gpt-5.3-codex.d.ts.map +1 -1
- package/dist/core/extensions/builtin/prompt-preset/gpt-5.3-codex.js +3 -0
- package/dist/core/extensions/builtin/prompt-preset/gpt-5.3-codex.js.map +1 -1
- package/dist/core/extensions/builtin/prompt-preset/gpt-5.4.d.ts.map +1 -1
- package/dist/core/extensions/builtin/prompt-preset/gpt-5.4.js +3 -0
- package/dist/core/extensions/builtin/prompt-preset/gpt-5.4.js.map +1 -1
- package/dist/core/extensions/builtin/prompt-preset/gpt-5.5.d.ts.map +1 -1
- package/dist/core/extensions/builtin/prompt-preset/gpt-5.5.js +3 -0
- package/dist/core/extensions/builtin/prompt-preset/gpt-5.5.js.map +1 -1
- package/dist/core/extensions/builtin/prompt-preset/gpt-5.6.d.ts.map +1 -1
- package/dist/core/extensions/builtin/prompt-preset/gpt-5.6.js +3 -0
- package/dist/core/extensions/builtin/prompt-preset/gpt-5.6.js.map +1 -1
- package/dist/core/extensions/builtin/prompt-preset/gpt-5.d.ts.map +1 -1
- package/dist/core/extensions/builtin/prompt-preset/gpt-5.js +3 -0
- package/dist/core/extensions/builtin/prompt-preset/gpt-5.js.map +1 -1
- package/dist/core/extensions/builtin/prompt-preset/gpt-eval-routing.d.ts +3 -0
- package/dist/core/extensions/builtin/prompt-preset/gpt-eval-routing.d.ts.map +1 -0
- package/dist/core/extensions/builtin/prompt-preset/gpt-eval-routing.js +6 -0
- package/dist/core/extensions/builtin/prompt-preset/gpt-eval-routing.js.map +1 -0
- package/dist/modes/interactive/components/assistant-message.d.ts.map +1 -1
- package/dist/modes/interactive/components/assistant-message.js +34 -4
- package/dist/modes/interactive/components/assistant-message.js.map +1 -1
- package/dist/modes/interactive/tool-progress.js +1 -1
- package/dist/modes/interactive/tool-progress.js.map +1 -1
- package/dist/utils/duration.d.ts +2 -0
- package/dist/utils/duration.d.ts.map +1 -0
- package/dist/utils/duration.js +16 -0
- package/dist/utils/duration.js.map +1 -0
- package/docs/session-format.md +4 -0
- package/node_modules/@code-yeongyu/senpi-codemode/CHANGELOG.md +14 -0
- package/node_modules/@code-yeongyu/senpi-codemode/README.md +15 -6
- package/node_modules/@code-yeongyu/senpi-codemode/package.json +3 -3
- package/node_modules/@code-yeongyu/senpi-codemode/src/codemode/runtime.ts +258 -0
- package/node_modules/@code-yeongyu/senpi-codemode/src/codemode/tools.ts +106 -0
- package/node_modules/@code-yeongyu/senpi-codemode/src/extension/runtime-factory.ts +114 -0
- package/node_modules/@code-yeongyu/senpi-codemode/src/extension/session-manager-proxy.ts +116 -0
- package/node_modules/@code-yeongyu/senpi-codemode/src/index.ts +73 -204
- package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/worker-runtime.js +1 -0
- package/node_modules/@earendil-works/pi-agent-core/dist/agent-loop.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-agent-core/dist/agent-loop.js +34 -0
- package/node_modules/@earendil-works/pi-agent-core/dist/agent-loop.js.map +1 -1
- package/node_modules/@earendil-works/pi-agent-core/package.json +2 -2
- package/node_modules/@earendil-works/pi-ai/dist/models.generated.d.ts +20 -0
- package/node_modules/@earendil-works/pi-ai/dist/models.generated.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/google-vertex.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/huggingface.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/opencode-go.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/openrouter.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/vercel-ai-gateway.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/google-vertex.models.d.ts +8 -0
- package/node_modules/@earendil-works/pi-ai/dist/providers/google-vertex.models.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/google-vertex.models.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/huggingface.models.d.ts +4 -0
- package/node_modules/@earendil-works/pi-ai/dist/providers/huggingface.models.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/huggingface.models.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/opencode-go.models.d.ts +4 -0
- package/node_modules/@earendil-works/pi-ai/dist/providers/opencode-go.models.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/opencode-go.models.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/vercel-ai-gateway.models.d.ts +4 -0
- package/node_modules/@earendil-works/pi-ai/dist/providers/vercel-ai-gateway.models.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/vercel-ai-gateway.models.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/types.d.ts +4 -0
- package/node_modules/@earendil-works/pi-ai/dist/types.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/types.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/package.json +1 -1
- package/node_modules/@earendil-works/pi-pty/package.json +1 -1
- package/node_modules/@earendil-works/pi-tui/package.json +1 -1
- package/package.json +6 -6
|
@@ -0,0 +1,106 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import type { ExtensionContext } from "@code-yeongyu/senpi";
|
|
2
|
+
import type { AgentExecuteTool } from "../bridges/agent-bridge.ts";
|
|
3
|
+
import type { CompletionRequest, CompletionResult } from "../completion/handler.ts";
|
|
4
|
+
import {
|
|
5
|
+
type CodemodeSettings,
|
|
6
|
+
loadCodemodeSettings,
|
|
7
|
+
type ResolvedCodemodeSettings,
|
|
8
|
+
resolveEnabledLanguages,
|
|
9
|
+
} from "../config/settings.ts";
|
|
10
|
+
import {
|
|
11
|
+
createInterpreterDetector,
|
|
12
|
+
getInterpreterAvailability,
|
|
13
|
+
type InterpreterAvailability,
|
|
14
|
+
} from "../interpreters/detect.ts";
|
|
15
|
+
import { resolveSessionArtifactsDir } from "../output/streaming-output.ts";
|
|
16
|
+
import type { EnabledEvalLanguages, EvalLanguage } from "../tool/types.ts";
|
|
17
|
+
import {
|
|
18
|
+
type CodemodeSessionManager,
|
|
19
|
+
type CreateCodemodeSessionManagerOptions,
|
|
20
|
+
createCodemodeSessionManager,
|
|
21
|
+
} from "./session-manager.ts";
|
|
22
|
+
|
|
23
|
+
export interface CodemodeRuntimeAPI {
|
|
24
|
+
readonly executeTool: AgentExecuteTool;
|
|
25
|
+
getActiveTools(): string[];
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface RuntimeFactoryOptions {
|
|
29
|
+
readonly createSessionManager?: (
|
|
30
|
+
options: CreateCodemodeSessionManagerOptions,
|
|
31
|
+
) => CodemodeSessionManager | Promise<CodemodeSessionManager>;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export type SessionRuntime = {
|
|
35
|
+
readonly sessionId: string;
|
|
36
|
+
readonly cwd: string;
|
|
37
|
+
readonly parallelPoolWidth: number;
|
|
38
|
+
readonly manager: CodemodeSessionManager;
|
|
39
|
+
readonly enabledLanguages: EnabledEvalLanguages;
|
|
40
|
+
readonly settings: ResolvedCodemodeSettings;
|
|
41
|
+
readonly artifactsDir: string;
|
|
42
|
+
readonly executeTool: AgentExecuteTool;
|
|
43
|
+
readonly spawns: boolean;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
export async function createRuntime(
|
|
47
|
+
pi: CodemodeRuntimeAPI,
|
|
48
|
+
ctx: ExtensionContext,
|
|
49
|
+
event: unknown,
|
|
50
|
+
complete: (request: CompletionRequest, ctx: ExtensionContext) => Promise<CompletionResult>,
|
|
51
|
+
options: RuntimeFactoryOptions,
|
|
52
|
+
): Promise<SessionRuntime> {
|
|
53
|
+
const loaded = await loadCodemodeSettings({ cwd: ctx.cwd });
|
|
54
|
+
const settings: ResolvedCodemodeSettings = {
|
|
55
|
+
...loaded.settings,
|
|
56
|
+
languages: resolveEnabledLanguages(loaded.settings),
|
|
57
|
+
};
|
|
58
|
+
const availability = await getInterpreterAvailability(settings, createInterpreterDetector());
|
|
59
|
+
const enabledLanguages = enabledLanguagesFrom(settings, availability);
|
|
60
|
+
const artifacts = resolveSessionArtifactsDir(ctx.sessionManager.getSessionFile());
|
|
61
|
+
const activeTools = new Set(pi.getActiveTools());
|
|
62
|
+
const executeTool = createExecuteTool(pi, activeTools);
|
|
63
|
+
const create = options.createSessionManager ?? createCodemodeSessionManager;
|
|
64
|
+
const sessionId = sessionIdFrom(event);
|
|
65
|
+
const configuredPoolWidth = settings.parallelPoolWidth;
|
|
66
|
+
const parallelPoolWidth = Number.isFinite(configuredPoolWidth) ? Math.max(1, Math.trunc(configuredPoolWidth)) : 1;
|
|
67
|
+
const manager = await create({
|
|
68
|
+
sessionId,
|
|
69
|
+
cwd: ctx.cwd,
|
|
70
|
+
settings,
|
|
71
|
+
availability,
|
|
72
|
+
artifactsDir: artifacts.dir,
|
|
73
|
+
executeTool,
|
|
74
|
+
complete,
|
|
75
|
+
});
|
|
76
|
+
return {
|
|
77
|
+
sessionId,
|
|
78
|
+
cwd: ctx.cwd,
|
|
79
|
+
parallelPoolWidth,
|
|
80
|
+
manager,
|
|
81
|
+
enabledLanguages,
|
|
82
|
+
settings,
|
|
83
|
+
artifactsDir: artifacts.dir,
|
|
84
|
+
executeTool,
|
|
85
|
+
spawns: activeTools.has(settings.taskTools.task),
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export function createExecuteTool(pi: CodemodeRuntimeAPI, activeTools?: ReadonlySet<string>): AgentExecuteTool {
|
|
90
|
+
const executeTool: AgentExecuteTool = (toolName, params, executeOptions) =>
|
|
91
|
+
pi.executeTool(toolName, params, executeOptions);
|
|
92
|
+
return Object.assign(executeTool, {
|
|
93
|
+
isToolAvailable: (name: string): boolean => activeTools?.has(name) ?? pi.getActiveTools().includes(name),
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function sessionIdFrom(event: unknown): string {
|
|
98
|
+
if (typeof event === "object" && event !== null && "sessionId" in event && typeof event.sessionId === "string") {
|
|
99
|
+
return event.sessionId;
|
|
100
|
+
}
|
|
101
|
+
return crypto.randomUUID();
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export function enabledLanguagesFrom(
|
|
105
|
+
settings: CodemodeSettings,
|
|
106
|
+
availability: InterpreterAvailability,
|
|
107
|
+
): Record<EvalLanguage, boolean> {
|
|
108
|
+
return {
|
|
109
|
+
py: settings.languages.py && availability.py.detected.ok,
|
|
110
|
+
js: settings.languages.js && availability.js.detected.ok,
|
|
111
|
+
rb: settings.languages.rb && availability.rb.detected.ok,
|
|
112
|
+
jl: settings.languages.jl && availability.jl.detected.ok,
|
|
113
|
+
};
|
|
114
|
+
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import type { ExtensionContext } from "@code-yeongyu/senpi";
|
|
2
|
+
import type { KernelToHostMessage } from "../bridge/protocol.ts";
|
|
3
|
+
import type { CompletionRequest, CompletionResult } from "../completion/handler.ts";
|
|
4
|
+
import type { EvalKernel, EvalLanguage } from "../tool/types.ts";
|
|
5
|
+
import {
|
|
6
|
+
CodemodeSessionDisposedError,
|
|
7
|
+
type CodemodeSessionManager,
|
|
8
|
+
type EvalExecutionTracker,
|
|
9
|
+
} from "./session-manager.ts";
|
|
10
|
+
|
|
11
|
+
type TrackedExecution = {
|
|
12
|
+
readonly promise: Promise<unknown>;
|
|
13
|
+
readonly controller: AbortController;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export class CodemodeSessionNotStartedError extends Error {
|
|
17
|
+
readonly name = "CodemodeSessionNotStartedError";
|
|
18
|
+
|
|
19
|
+
constructor() {
|
|
20
|
+
super("codemode session has not started");
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export class SessionManagerProxy implements CodemodeSessionManager, EvalExecutionTracker {
|
|
25
|
+
#current: CodemodeSessionManager | undefined;
|
|
26
|
+
#generation = 0;
|
|
27
|
+
#started = false;
|
|
28
|
+
#acceptingExecutions = false;
|
|
29
|
+
readonly #executions = new Set<TrackedExecution>();
|
|
30
|
+
|
|
31
|
+
beginReplacement(): number {
|
|
32
|
+
this.#generation++;
|
|
33
|
+
this.#acceptingExecutions = false;
|
|
34
|
+
this.#abortExecutions();
|
|
35
|
+
return this.#generation;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
async replace(generation: number, next: CodemodeSessionManager): Promise<boolean> {
|
|
39
|
+
if (generation !== this.#generation) {
|
|
40
|
+
await next.dispose();
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
await this.#settleExecutions();
|
|
44
|
+
if (generation !== this.#generation) {
|
|
45
|
+
await next.dispose();
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
const current = this.#current;
|
|
49
|
+
this.#current = undefined;
|
|
50
|
+
await current?.dispose();
|
|
51
|
+
if (generation !== this.#generation) {
|
|
52
|
+
await next.dispose();
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
this.#current = next;
|
|
56
|
+
this.#started = true;
|
|
57
|
+
this.#acceptingExecutions = true;
|
|
58
|
+
return true;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
assertEvalExecutionAllowed(): void {
|
|
62
|
+
if (this.#acceptingExecutions && this.#current !== undefined) return;
|
|
63
|
+
if (this.#started) throw new CodemodeSessionDisposedError();
|
|
64
|
+
throw new CodemodeSessionNotStartedError();
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
async trackEvalExecution<Result>(execution: Promise<Result>, controller: AbortController): Promise<Result> {
|
|
68
|
+
this.assertEvalExecutionAllowed();
|
|
69
|
+
const tracked: TrackedExecution = { promise: execution, controller };
|
|
70
|
+
this.#executions.add(tracked);
|
|
71
|
+
try {
|
|
72
|
+
return await execution;
|
|
73
|
+
} finally {
|
|
74
|
+
this.#executions.delete(tracked);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
async getKernel(language: EvalLanguage, onMessage: (message: KernelToHostMessage) => void): Promise<EvalKernel> {
|
|
79
|
+
this.assertEvalExecutionAllowed();
|
|
80
|
+
const current = this.#current;
|
|
81
|
+
if (current === undefined) throw new CodemodeSessionNotStartedError();
|
|
82
|
+
return await current.getKernel(language, onMessage);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
async complete(request: CompletionRequest, ctx: ExtensionContext): Promise<CompletionResult> {
|
|
86
|
+
this.assertEvalExecutionAllowed();
|
|
87
|
+
const current = this.#current;
|
|
88
|
+
if (current === undefined) throw new CodemodeSessionNotStartedError();
|
|
89
|
+
return await current.complete(request, ctx);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
setContext(ctx: ExtensionContext): void {
|
|
93
|
+
this.#current?.setContext?.(ctx);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
async dispose(): Promise<void> {
|
|
97
|
+
this.#generation++;
|
|
98
|
+
this.#acceptingExecutions = false;
|
|
99
|
+
this.#abortExecutions();
|
|
100
|
+
await this.#settleExecutions();
|
|
101
|
+
const current = this.#current;
|
|
102
|
+
this.#current = undefined;
|
|
103
|
+
await current?.dispose();
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
#abortExecutions(): void {
|
|
107
|
+
if (this.#executions.size === 0) return;
|
|
108
|
+
const error = new CodemodeSessionDisposedError();
|
|
109
|
+
for (const execution of this.#executions) execution.controller.abort(error);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
async #settleExecutions(): Promise<void> {
|
|
113
|
+
if (this.#executions.size === 0) return;
|
|
114
|
+
await Promise.allSettled([...this.#executions].map((execution) => execution.promise));
|
|
115
|
+
}
|
|
116
|
+
}
|
|
@@ -1,31 +1,20 @@
|
|
|
1
1
|
import * as os from "node:os";
|
|
2
2
|
import type { ExtensionContext } from "@code-yeongyu/senpi";
|
|
3
|
-
import type { KernelToHostMessage } from "./bridge/protocol.ts";
|
|
4
3
|
import type { AgentExecuteTool } from "./bridges/agent-bridge.ts";
|
|
4
|
+
import { CodeModeSessionRuntime } from "./codemode/runtime.ts";
|
|
5
|
+
import { type CodeModeTool, createCodeModeTools, isGptCodeModeModel } from "./codemode/tools.ts";
|
|
5
6
|
import { type CompletionRequest, type CompletionResult, createCompletionHandler } from "./completion/handler.ts";
|
|
7
|
+
import { defaultCodemodeSettings } from "./config/settings.ts";
|
|
6
8
|
import {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
type
|
|
11
|
-
|
|
12
|
-
} from "./
|
|
13
|
-
import {
|
|
14
|
-
CodemodeSessionDisposedError,
|
|
15
|
-
type CodemodeSessionManager,
|
|
16
|
-
type CreateCodemodeSessionManagerOptions,
|
|
17
|
-
createCodemodeSessionManager,
|
|
18
|
-
type EvalExecutionTracker,
|
|
19
|
-
} from "./extension/session-manager.ts";
|
|
20
|
-
import {
|
|
21
|
-
createInterpreterDetector,
|
|
22
|
-
getInterpreterAvailability,
|
|
23
|
-
type InterpreterAvailability,
|
|
24
|
-
} from "./interpreters/detect.ts";
|
|
25
|
-
import { resolveSessionArtifactsDir } from "./output/streaming-output.ts";
|
|
9
|
+
createExecuteTool,
|
|
10
|
+
createRuntime,
|
|
11
|
+
enabledLanguagesFrom,
|
|
12
|
+
type SessionRuntime,
|
|
13
|
+
} from "./extension/runtime-factory.ts";
|
|
14
|
+
import type { CodemodeSessionManager, CreateCodemodeSessionManagerOptions } from "./extension/session-manager.ts";
|
|
15
|
+
import { SessionManagerProxy } from "./extension/session-manager-proxy.ts";
|
|
26
16
|
import { createEvalTool } from "./tool/eval-tool.ts";
|
|
27
17
|
import { renderEvalCall, renderEvalResult } from "./tool/render.ts";
|
|
28
|
-
import type { EnabledEvalLanguages, EvalKernel, EvalLanguage } from "./tool/types.ts";
|
|
29
18
|
|
|
30
19
|
const SESSION_LIFECYCLE_EVENTS = [
|
|
31
20
|
"session_start",
|
|
@@ -38,25 +27,13 @@ type SessionLifecycleEvent = (typeof SESSION_LIFECYCLE_EVENTS)[number];
|
|
|
38
27
|
|
|
39
28
|
type CodemodeEvent = SessionLifecycleEvent | "model_select";
|
|
40
29
|
|
|
41
|
-
type TrackedExecution = {
|
|
42
|
-
readonly promise: Promise<unknown>;
|
|
43
|
-
readonly controller: AbortController;
|
|
44
|
-
};
|
|
45
|
-
|
|
46
|
-
type SessionRuntime = {
|
|
47
|
-
readonly manager: CodemodeSessionManager;
|
|
48
|
-
readonly enabledLanguages: EnabledEvalLanguages;
|
|
49
|
-
readonly settings: ResolvedCodemodeSettings;
|
|
50
|
-
readonly artifactsDir: string;
|
|
51
|
-
readonly executeTool: AgentExecuteTool;
|
|
52
|
-
readonly spawns: boolean;
|
|
53
|
-
};
|
|
54
|
-
|
|
55
30
|
export interface CodemodeExtensionAPI {
|
|
56
31
|
registerTool(tool: ReturnType<typeof createEvalTool>): void;
|
|
57
32
|
on(event: CodemodeEvent, handler: (event: unknown, ctx: ExtensionContext) => Promise<void> | void): void;
|
|
58
33
|
executeTool: AgentExecuteTool;
|
|
59
34
|
getActiveTools(): string[];
|
|
35
|
+
setActiveTools?(toolNames: string[]): void | Promise<void>;
|
|
36
|
+
getAllTools?(): readonly { readonly name: string }[];
|
|
60
37
|
}
|
|
61
38
|
|
|
62
39
|
export interface SenpiCodemodeOptions {
|
|
@@ -66,19 +43,17 @@ export interface SenpiCodemodeOptions {
|
|
|
66
43
|
readonly complete?: (request: CompletionRequest, ctx: ExtensionContext) => Promise<CompletionResult>;
|
|
67
44
|
}
|
|
68
45
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
}
|
|
75
|
-
}
|
|
46
|
+
type DynamicCodeModeExtensionAPI = CodemodeExtensionAPI & {
|
|
47
|
+
registerTool(tool: CodeModeTool): void;
|
|
48
|
+
setActiveTools(toolNames: string[]): void | Promise<void>;
|
|
49
|
+
getAllTools(): readonly { readonly name: string }[];
|
|
50
|
+
};
|
|
76
51
|
|
|
77
52
|
export default function senpiCodemode(pi: CodemodeExtensionAPI, options: SenpiCodemodeOptions = {}): void {
|
|
78
53
|
const manager = new SessionManagerProxy();
|
|
79
54
|
const complete = options.complete ?? ((request, ctx) => createCompletionHandler()(ctx)(request));
|
|
80
55
|
const renderers = { renderCall: renderEvalCall, renderResult: renderEvalResult };
|
|
81
|
-
let activeRuntime: SessionRuntime | undefined;
|
|
56
|
+
let activeRuntime: (SessionRuntime & { readonly codeMode?: CodeModeSessionRuntime }) | undefined;
|
|
82
57
|
let activeModelId: string | undefined;
|
|
83
58
|
const registerEvalForRuntime = (runtime: SessionRuntime, modelId: string | undefined): void => {
|
|
84
59
|
pi.registerTool(
|
|
@@ -100,9 +75,21 @@ export default function senpiCodemode(pi: CodemodeExtensionAPI, options: SenpiCo
|
|
|
100
75
|
);
|
|
101
76
|
};
|
|
102
77
|
const dropRuntime = async (): Promise<void> => {
|
|
78
|
+
const codeMode = activeRuntime?.codeMode;
|
|
103
79
|
activeRuntime = undefined;
|
|
104
80
|
activeModelId = undefined;
|
|
105
|
-
await manager.dispose();
|
|
81
|
+
await Promise.all([manager.dispose(), codeMode?.dispose()]);
|
|
82
|
+
};
|
|
83
|
+
const activateCodeModeTools = async (runtime: CodeModeSessionRuntime): Promise<void> => {
|
|
84
|
+
if (!isDynamicCodeModeExtensionAPI(pi)) return;
|
|
85
|
+
const tools = createCodeModeTools({ runtime });
|
|
86
|
+
pi.registerTool(tools.exec);
|
|
87
|
+
pi.registerTool(tools.wait);
|
|
88
|
+
await pi.setActiveTools([...new Set([...pi.getActiveTools(), "exec", "wait"])]);
|
|
89
|
+
};
|
|
90
|
+
const deactivateCodeModeTools = async (): Promise<void> => {
|
|
91
|
+
if (!isDynamicCodeModeExtensionAPI(pi)) return;
|
|
92
|
+
await pi.setActiveTools(pi.getActiveTools().filter((name) => name !== "exec" && name !== "wait"));
|
|
106
93
|
};
|
|
107
94
|
pi.registerTool(
|
|
108
95
|
createEvalTool({
|
|
@@ -119,12 +106,25 @@ export default function senpiCodemode(pi: CodemodeExtensionAPI, options: SenpiCo
|
|
|
119
106
|
);
|
|
120
107
|
|
|
121
108
|
pi.on("session_start", async (event, ctx) => {
|
|
109
|
+
const previousCodeMode = activeRuntime?.codeMode;
|
|
122
110
|
const generation = manager.beginReplacement();
|
|
123
111
|
const runtime = await createRuntime(pi, ctx, event, complete, options);
|
|
124
|
-
|
|
125
|
-
|
|
112
|
+
const replaced = await manager.replace(generation, runtime.manager);
|
|
113
|
+
if (!replaced) return;
|
|
114
|
+
await previousCodeMode?.dispose();
|
|
115
|
+
const codeMode = isGptCodeModeModel(ctx.model?.id)
|
|
116
|
+
? new CodeModeSessionRuntime({
|
|
117
|
+
sessionId: runtime.sessionId,
|
|
118
|
+
cwd: runtime.cwd,
|
|
119
|
+
parallelPoolWidth: runtime.parallelPoolWidth,
|
|
120
|
+
executeTool: runtime.executeTool,
|
|
121
|
+
})
|
|
122
|
+
: undefined;
|
|
123
|
+
activeRuntime = { ...runtime, ...(codeMode === undefined ? {} : { codeMode }) };
|
|
126
124
|
activeModelId = ctx.model?.id;
|
|
127
|
-
registerEvalForRuntime(
|
|
125
|
+
registerEvalForRuntime(activeRuntime, activeModelId);
|
|
126
|
+
if (codeMode) await activateCodeModeTools(codeMode);
|
|
127
|
+
else await deactivateCodeModeTools();
|
|
128
128
|
});
|
|
129
129
|
pi.on("session_shutdown", async () => dropRuntime());
|
|
130
130
|
pi.on("session_before_switch", async () => dropRuntime());
|
|
@@ -136,9 +136,34 @@ export default function senpiCodemode(pi: CodemodeExtensionAPI, options: SenpiCo
|
|
|
136
136
|
if (modelId === undefined || modelId === activeModelId) return;
|
|
137
137
|
activeModelId = modelId;
|
|
138
138
|
registerEvalForRuntime(runtime, modelId);
|
|
139
|
+
if (!isGptCodeModeModel(modelId)) {
|
|
140
|
+
const { codeMode, ...nextRuntime } = runtime;
|
|
141
|
+
await codeMode?.dispose();
|
|
142
|
+
activeRuntime = nextRuntime;
|
|
143
|
+
await deactivateCodeModeTools();
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
if (runtime.codeMode) {
|
|
147
|
+
if (isDynamicCodeModeExtensionAPI(pi)) {
|
|
148
|
+
await pi.setActiveTools([...new Set([...pi.getActiveTools(), "exec", "wait"])]);
|
|
149
|
+
}
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
const codeMode = new CodeModeSessionRuntime({
|
|
153
|
+
sessionId: runtime.sessionId,
|
|
154
|
+
cwd: runtime.cwd,
|
|
155
|
+
parallelPoolWidth: runtime.parallelPoolWidth,
|
|
156
|
+
executeTool: runtime.executeTool,
|
|
157
|
+
});
|
|
158
|
+
activeRuntime = { ...runtime, codeMode };
|
|
159
|
+
await activateCodeModeTools(codeMode);
|
|
139
160
|
});
|
|
140
161
|
}
|
|
141
162
|
|
|
163
|
+
function isDynamicCodeModeExtensionAPI(pi: CodemodeExtensionAPI): pi is DynamicCodeModeExtensionAPI {
|
|
164
|
+
return typeof pi.setActiveTools === "function" && typeof pi.getAllTools === "function";
|
|
165
|
+
}
|
|
166
|
+
|
|
142
167
|
function hostLine(): string {
|
|
143
168
|
const cpu = os.cpus()[0]?.model?.trim();
|
|
144
169
|
return [`${os.platform()} ${os.arch()}`, cpu, `${os.availableParallelism()} cores`]
|
|
@@ -153,160 +178,4 @@ function modelIdFrom(event: unknown): string | undefined {
|
|
|
153
178
|
return typeof model.id === "string" ? model.id : undefined;
|
|
154
179
|
}
|
|
155
180
|
|
|
156
|
-
|
|
157
|
-
#current: CodemodeSessionManager | undefined;
|
|
158
|
-
#generation = 0;
|
|
159
|
-
#started = false;
|
|
160
|
-
#acceptingExecutions = false;
|
|
161
|
-
readonly #executions = new Set<TrackedExecution>();
|
|
162
|
-
|
|
163
|
-
beginReplacement(): number {
|
|
164
|
-
this.#generation++;
|
|
165
|
-
this.#acceptingExecutions = false;
|
|
166
|
-
this.#abortExecutions();
|
|
167
|
-
return this.#generation;
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
async replace(generation: number, next: CodemodeSessionManager): Promise<boolean> {
|
|
171
|
-
if (generation !== this.#generation) {
|
|
172
|
-
await next.dispose();
|
|
173
|
-
return false;
|
|
174
|
-
}
|
|
175
|
-
await this.#settleExecutions();
|
|
176
|
-
if (generation !== this.#generation) {
|
|
177
|
-
await next.dispose();
|
|
178
|
-
return false;
|
|
179
|
-
}
|
|
180
|
-
const current = this.#current;
|
|
181
|
-
this.#current = undefined;
|
|
182
|
-
await current?.dispose();
|
|
183
|
-
if (generation !== this.#generation) {
|
|
184
|
-
await next.dispose();
|
|
185
|
-
return false;
|
|
186
|
-
}
|
|
187
|
-
this.#current = next;
|
|
188
|
-
this.#started = true;
|
|
189
|
-
this.#acceptingExecutions = true;
|
|
190
|
-
return true;
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
assertEvalExecutionAllowed(): void {
|
|
194
|
-
if (this.#acceptingExecutions && this.#current !== undefined) return;
|
|
195
|
-
if (this.#started) throw new CodemodeSessionDisposedError();
|
|
196
|
-
throw new CodemodeSessionNotStartedError();
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
async trackEvalExecution<Result>(execution: Promise<Result>, controller: AbortController): Promise<Result> {
|
|
200
|
-
this.assertEvalExecutionAllowed();
|
|
201
|
-
const tracked: TrackedExecution = { promise: execution, controller };
|
|
202
|
-
this.#executions.add(tracked);
|
|
203
|
-
try {
|
|
204
|
-
return await execution;
|
|
205
|
-
} finally {
|
|
206
|
-
this.#executions.delete(tracked);
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
async getKernel(language: EvalLanguage, onMessage: (message: KernelToHostMessage) => void): Promise<EvalKernel> {
|
|
211
|
-
this.assertEvalExecutionAllowed();
|
|
212
|
-
const current = this.#current;
|
|
213
|
-
if (current === undefined) throw new CodemodeSessionNotStartedError();
|
|
214
|
-
return await current.getKernel(language, onMessage);
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
async complete(request: CompletionRequest, ctx: ExtensionContext): Promise<CompletionResult> {
|
|
218
|
-
this.assertEvalExecutionAllowed();
|
|
219
|
-
const current = this.#current;
|
|
220
|
-
if (current === undefined) throw new CodemodeSessionNotStartedError();
|
|
221
|
-
return await current.complete(request, ctx);
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
setContext(ctx: ExtensionContext): void {
|
|
225
|
-
this.#current?.setContext?.(ctx);
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
async dispose(): Promise<void> {
|
|
229
|
-
this.#generation++;
|
|
230
|
-
this.#acceptingExecutions = false;
|
|
231
|
-
this.#abortExecutions();
|
|
232
|
-
await this.#settleExecutions();
|
|
233
|
-
const current = this.#current;
|
|
234
|
-
this.#current = undefined;
|
|
235
|
-
await current?.dispose();
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
#abortExecutions(): void {
|
|
239
|
-
if (this.#executions.size === 0) return;
|
|
240
|
-
const error = new CodemodeSessionDisposedError();
|
|
241
|
-
for (const execution of this.#executions) execution.controller.abort(error);
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
async #settleExecutions(): Promise<void> {
|
|
245
|
-
if (this.#executions.size === 0) return;
|
|
246
|
-
await Promise.allSettled([...this.#executions].map((execution) => execution.promise));
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
async function createRuntime(
|
|
251
|
-
pi: CodemodeExtensionAPI,
|
|
252
|
-
ctx: ExtensionContext,
|
|
253
|
-
event: unknown,
|
|
254
|
-
complete: (request: CompletionRequest, ctx: ExtensionContext) => Promise<CompletionResult>,
|
|
255
|
-
options: SenpiCodemodeOptions,
|
|
256
|
-
): Promise<SessionRuntime> {
|
|
257
|
-
const loaded = await loadCodemodeSettings({ cwd: ctx.cwd });
|
|
258
|
-
const settings: ResolvedCodemodeSettings = {
|
|
259
|
-
...loaded.settings,
|
|
260
|
-
languages: resolveEnabledLanguages(loaded.settings),
|
|
261
|
-
};
|
|
262
|
-
const availability = await getInterpreterAvailability(settings, createInterpreterDetector());
|
|
263
|
-
const enabledLanguages = enabledLanguagesFrom(settings, availability);
|
|
264
|
-
const artifacts = resolveSessionArtifactsDir(ctx.sessionManager.getSessionFile());
|
|
265
|
-
const activeTools = new Set(pi.getActiveTools());
|
|
266
|
-
const executeTool = createExecuteTool(pi, activeTools);
|
|
267
|
-
const create = options.createSessionManager ?? createCodemodeSessionManager;
|
|
268
|
-
const manager = await create({
|
|
269
|
-
sessionId: sessionIdFrom(event),
|
|
270
|
-
cwd: ctx.cwd,
|
|
271
|
-
settings,
|
|
272
|
-
availability,
|
|
273
|
-
artifactsDir: artifacts.dir,
|
|
274
|
-
executeTool,
|
|
275
|
-
complete,
|
|
276
|
-
});
|
|
277
|
-
return {
|
|
278
|
-
manager,
|
|
279
|
-
enabledLanguages,
|
|
280
|
-
settings,
|
|
281
|
-
artifactsDir: artifacts.dir,
|
|
282
|
-
executeTool,
|
|
283
|
-
spawns: activeTools.has(settings.taskTools.task),
|
|
284
|
-
};
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
function createExecuteTool(pi: CodemodeExtensionAPI, activeTools?: ReadonlySet<string>): AgentExecuteTool {
|
|
288
|
-
const executeTool: AgentExecuteTool = (toolName, params, executeOptions) =>
|
|
289
|
-
pi.executeTool(toolName, params, executeOptions);
|
|
290
|
-
return Object.assign(executeTool, {
|
|
291
|
-
isToolAvailable: (name: string): boolean => activeTools?.has(name) ?? pi.getActiveTools().includes(name),
|
|
292
|
-
});
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
function sessionIdFrom(event: unknown): string {
|
|
296
|
-
if (typeof event === "object" && event !== null && "sessionId" in event && typeof event.sessionId === "string") {
|
|
297
|
-
return event.sessionId;
|
|
298
|
-
}
|
|
299
|
-
return crypto.randomUUID();
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
export function enabledLanguagesFrom(
|
|
303
|
-
settings: CodemodeSettings,
|
|
304
|
-
availability: InterpreterAvailability,
|
|
305
|
-
): Record<EvalLanguage, boolean> {
|
|
306
|
-
return {
|
|
307
|
-
py: settings.languages.py && availability.py.detected.ok,
|
|
308
|
-
js: settings.languages.js && availability.js.detected.ok,
|
|
309
|
-
rb: settings.languages.rb && availability.rb.detected.ok,
|
|
310
|
-
jl: settings.languages.jl && availability.jl.detected.ok,
|
|
311
|
-
};
|
|
312
|
-
}
|
|
181
|
+
export { enabledLanguagesFrom };
|