import type { ExtensionContext } from "@earendil-works/pi-coding-agent"; import type { AgentExecuteTool } from "../bridges/agent-bridge.ts"; import type { EvalSchemaToolInfo } from "../bridges/schema-bridge.ts"; import type { CompletionRequest, CompletionResult, } from "../completion/handler.ts"; import { type CodemodeSettings, loadCodemodeSettings, type ResolvedCodemodeSettings, resolveEnabledLanguages, resolveParallelPoolWidth, } from "../config/settings.ts"; import { assertActiveToolsUnique } from "../host/tool-execution.ts"; import { createInterpreterDetector, getInterpreterAvailability, type InterpreterAvailability, } from "../interpreters/detect.ts"; import { resolveSessionArtifactsDir } from "../output/streaming-output.ts"; import { SessionStore } from "../tool/session-store.ts"; import { captureToolSnapshot, type ToolSnapshot, } from "../tool/tool-snapshot.ts"; import type { EnabledEvalLanguages, EvalLanguage } from "../tool/types.ts"; import { type CodemodeSessionManager, type CreateCodemodeSessionManagerOptions, createCodemodeSessionManager, } from "./session-manager.ts"; export interface CodemodeRuntimeAPI { readonly executeTool: AgentExecuteTool; getActiveTools: () => string[]; getAllTools: () => readonly EvalSchemaToolInfo[]; } export interface RuntimeFactoryOptions { readonly createSessionManager?: ( options: CreateCodemodeSessionManagerOptions ) => CodemodeSessionManager | Promise; } export interface SessionRuntime { readonly artifactsDir: string; readonly artifactsTemp: boolean; readonly cwd: string; readonly enabledLanguages: EnabledEvalLanguages; readonly executeTool: AgentExecuteTool; readonly manager: CodemodeSessionManager; readonly parallelPoolWidth: number; readonly sessionId: string; /** One session value store per session runtime, shared by the manager bridge and the eval tool. */ readonly sessionStore: SessionStore; readonly settings: ResolvedCodemodeSettings; readonly spawns: boolean; readonly toolSnapshot: ToolSnapshot; } export async function createRuntime( pi: CodemodeRuntimeAPI, ctx: ExtensionContext, event: unknown, complete: ( request: CompletionRequest, ctx: ExtensionContext ) => Promise, options: RuntimeFactoryOptions ): Promise { const loaded = await loadCodemodeSettings({ cwd: ctx.cwd }); const settings: ResolvedCodemodeSettings = { ...loaded.settings, languages: resolveEnabledLanguages(loaded.settings), }; const availability = await getInterpreterAvailability( settings, createInterpreterDetector() ); const enabledLanguages = enabledLanguagesFrom(settings, availability); const artifacts = resolveSessionArtifactsDir( ctx.sessionManager.getSessionFile() ); const toolSnapshot = captureToolSnapshot(() => pi.getAllTools()); const hostActiveTools = pi.getActiveTools(); assertActiveToolsUnique(hostActiveTools); const activeTools = new Set(hostActiveTools); const executeTool = createExecuteTool(pi, activeTools); const sessionStore = new SessionStore(); const create = options.createSessionManager ?? createCodemodeSessionManager; const sessionId = sessionIdFrom(event, ctx); const parallelPoolWidth = resolveParallelPoolWidth( settings.parallelPoolWidth ); const manager = await create({ sessionId, cwd: ctx.cwd, settings, availability, artifactsDir: artifacts.dir, executeTool, listTools: () => toolSnapshot.tools, complete, sessionStore, }); return { sessionId, cwd: ctx.cwd, parallelPoolWidth, manager, enabledLanguages, settings, artifactsDir: artifacts.dir, artifactsTemp: artifacts.temp, executeTool, spawns: activeTools.has(settings.taskTools.task), sessionStore, toolSnapshot, }; } export function createExecuteTool( pi: CodemodeRuntimeAPI, activeTools?: ReadonlySet ): AgentExecuteTool { // A cell names tools directly and cannot run tool_search first, so eval opts in to // lazy activation. Eligibility still belongs to the extension that registered the tool. // pi 0.83.0 has no pi.executeTool: this delegates to the ToolExecutionShim. // that the extension factory wired into its CodemodeExtensionAPI. const executeTool: AgentExecuteTool = (toolName, params, executeOptions) => pi.executeTool(toolName, params, { ...executeOptions, activateInactiveTool: true, }); return Object.assign(executeTool, { isToolAvailable: (name: string): boolean => { if (activeTools !== undefined) { return activeTools.has(name); } const hostActiveTools = pi.getActiveTools(); assertActiveToolsUnique(hostActiveTools); return hostActiveTools.includes(name); }, }); } // pi 0.83.0's SessionStartEvent carries no sessionId (api-gaps G5): prefer the // session manager, falling back to the legacy event field, then a random id. function sessionIdFrom(event: unknown, ctx: ExtensionContext): string { const manager = ctx.sessionManager; if (typeof manager.getSessionId === "function") { const managed = manager.getSessionId(); if (managed !== undefined) { return managed; } } if ( typeof event === "object" && event !== null && "sessionId" in event && typeof event.sessionId === "string" ) { return event.sessionId; } return crypto.randomUUID(); } export function enabledLanguagesFrom( settings: CodemodeSettings, availability: InterpreterAvailability ): Record { return { py: settings.languages.py && availability.py.detected.ok, js: settings.languages.js && availability.js.detected.ok, rb: settings.languages.rb && availability.rb.detected.ok, ts: settings.languages.ts && availability.ts.detected.ok, }; }