import { constants, existsSync } from "node:fs"; import { access, readFile, realpath, rename, stat, writeFile, } from "node:fs/promises"; import { homedir } from "node:os"; import { basename, delimiter, dirname, extname, join, resolve, sep, } from "node:path"; import { pathToFileURL } from "node:url"; import type { Agent, AgentMessage } from "@earendil-works/pi-agent-core"; import type { AgentSessionRuntime, ExtensionAPI, ExtensionContext, InlineExtension, LoadExtensionsResult, PromptTemplate, ResourceDiagnostic, SessionManager, SessionStartEvent, Skill, } from "@earendil-works/pi-coding-agent"; import { PROMPT, snapshot, summarize } from "./context-startup.ts"; interface ExtensionInventoryEntry { path: string; name: string; tools: string[]; hooks: string[]; providers: string[]; } interface SkillDescriptionEntry { name: string; description: string; tokens: number; } interface SourceEstimate { appendix: number; descriptions: SkillDescriptionEntry[]; piAgent: { tokens: number; count: number }; extensions: { tokens: number; count: number }; shared: number; ignoredProjectSkills: number; catalog: number; total: number; } interface SkillTokenizer { estimateTokens: (message: { role: "user"; content: string; timestamp: number; }) => number; formatSkillsForPrompt: (skills: Skill[]) => string; } interface ResourceOptionsContext { cwd: string; agentDir: string; projectTrusted: boolean; extensionPaths?: string[]; } interface RunOptions { kind: string; cwd: string; agentDir: string; sourceAgentDir: string; resultPath?: string; selection?: { provider: string; model: string; thinking?: string }; sourcesOnly?: boolean; skillDescriptions?: boolean; inspectExtensions?: boolean; extensionPaths?: string[]; } interface SessionResult { kind: string; error?: string; sources?: SourceEstimate; local?: ReturnType; requests?: number; } // Resolve the installed executable, not this repository's SDK dependency. export async function installedPiDist(): Promise { for (const directory of (process.env["PATH"] ?? "").split(delimiter)) { const candidate = resolve(directory, "pi"); try { await access(candidate, constants.X_OK); if (!(await stat(candidate)).isFile()) continue; } catch { continue; } let directoryPath = dirname(await realpath(candidate)); while (true) { try { const manifest = JSON.parse( await readFile(join(directoryPath, "package.json"), "utf8"), ); if (manifest.name === "@earendil-works/pi-coding-agent") return join(directoryPath, "dist"); } catch (error) { if ((error as NodeJS.ErrnoException).code !== "ENOENT") throw new Error("Could not locate installed Pi"); } const parent = dirname(directoryPath); if (parent === directoryPath) break; directoryPath = parent; } break; } throw new Error("Could not locate installed Pi"); } export async function estimateSourceTokens( agentDir: string, sdk: SkillTokenizer, { skills, diagnostics, }: { skills: Skill[]; diagnostics: ResourceDiagnostic[] }, ): Promise { const append = await readFile(join(agentDir, "APPEND_SYSTEM.md"), "utf8"); if (diagnostics.length) throw new Error( "Skill catalog has diagnostics; refusing a partial estimate", ); const tokens = (content: string) => sdk.estimateTokens({ role: "user", content, timestamp: 0 }); const groups: { piAgent: Skill[]; extensions: Skill[] } = { piAgent: [], extensions: [], }; let ignoredProjectSkills = 0; for (const skill of skills.filter((skill) => !skill.disableModelInvocation)) { const source = skill.sourceInfo; if ( source?.origin === "package" || source?.source?.startsWith("extension:") ) { groups.extensions.push(skill); } else if ( resolve(skill.filePath).startsWith(`${resolve(agentDir, "skills")}${sep}`) ) { groups.piAgent.push(skill); } else if (source?.scope === "project") { ignoredProjectSkills++; } else { throw new Error("Unattributed skill source; refusing a partial estimate"); } } const parts = (items: Skill[]) => { const text = sdk.formatSkillsForPrompt(items); if (!text) return { shared: "", entries: "" }; const start = text.indexOf(" "); const end = text.lastIndexOf(""); if (start < 0 || end <= start) throw new Error("Pi skill catalog format changed"); return { shared: text.slice(0, start) + text.slice(end), entries: text.slice(start, end), }; }; const piAgent = parts(groups.piAgent); const extensions = parts(groups.extensions); const shared = piAgent.shared || extensions.shared; const appendix = tokens(append); const catalog = tokens(shared + piAgent.entries + extensions.entries); return { appendix, descriptions: groups.piAgent .map(({ name, description }) => ({ name, description, tokens: tokens(description), })) .sort( (a, b) => b.description.length - a.description.length || a.name.localeCompare(b.name), ), piAgent: { tokens: tokens(piAgent.entries), count: groups.piAgent.length }, extensions: { tokens: tokens(extensions.entries), count: groups.extensions.length, }, shared: tokens(shared), ignoredProjectSkills, catalog, total: appendix + catalog, }; } export function inspectExtensionRegistrations( { extensions, runtime }: LoadExtensionsResult, previous: ExtensionInventoryEntry[] = [], ): ExtensionInventoryEntry[] { const providers = [ ...(runtime.pendingProviderRegistrations ?? []).map( ({ name, extensionPath }) => ({ name, path: extensionPath }), ), ...(runtime.pendingNativeProviderRegistrations ?? []).map( ({ provider, extensionPath }) => ({ name: provider.id, path: extensionPath, }), ), ]; return extensions .filter((extension) => !extension.path.startsWith(" { const path = extension.path; const file = basename(path, extname(path)); return { path, name: file === "index" ? basename(dirname(path)) : file, tools: [...extension.tools.keys()].sort(), hooks: [...extension.handlers.keys()].sort(), providers: [ ...new Set([ ...(previous.find((entry) => entry.path === path)?.providers ?? []), ...providers .filter((entry) => entry.path === path) .map((entry) => entry.name), ]), ].sort(), }; }); } interface ResourceOverrides { extensionFactories?: InlineExtension[]; noExtensions?: boolean; noSkills?: boolean; noPromptTemplates?: boolean; noThemes?: boolean; noContextFiles?: boolean; additionalExtensionPaths?: string[]; systemPrompt?: string; appendSystemPrompt?: string[]; agentsFilesOverride?: (base: { agentsFiles: Array<{ path: string; content: string }>; }) => { agentsFiles: Array<{ path: string; content: string }> }; skillsOverride?: (base: { skills: Skill[]; diagnostics: ResourceDiagnostic[]; }) => { skills: Skill[]; diagnostics: ResourceDiagnostic[] }; promptsOverride?: (base: { prompts: PromptTemplate[]; diagnostics: ResourceDiagnostic[]; }) => { prompts: PromptTemplate[]; diagnostics: ResourceDiagnostic[] }; } export function resourceOptions( kind: string, extensionFactories: InlineExtension[], { cwd, agentDir, projectTrusted, extensionPaths, }: ResourceOptionsContext = {} as ResourceOptionsContext, ): ResourceOverrides { if (kind === "headless+1msg+no_pi_config") { // Keep the real settings and extension discovery. Remove only agent-dir text resources. const root = resolve(agentDir); const outside = (path: string, directory: string) => !resolve(path).startsWith(`${join(root, directory)}${sep}`); const projectPrompt = (name: string) => { const path = join(cwd, ".pi", name); return projectTrusted && existsSync(path) ? path : ""; }; const append = projectPrompt("APPEND_SYSTEM.md"); return { extensionFactories, agentsFilesOverride: (base: { agentsFiles: Array<{ path: string; content: string }>; }) => ({ ...base, agentsFiles: base.agentsFiles.filter( (file) => dirname(resolve(file.path)) !== root, ), }), skillsOverride: (base: { skills: Skill[]; diagnostics: ResourceDiagnostic[]; }) => ({ ...base, skills: base.skills.filter((skill) => outside(skill.filePath, "skills"), ), }), promptsOverride: (base: { prompts: PromptTemplate[]; diagnostics: ResourceDiagnostic[]; }) => ({ ...base, prompts: base.prompts.filter((prompt) => outside(prompt.filePath, "prompts"), ), }), systemPrompt: projectPrompt("SYSTEM.md"), appendSystemPrompt: append ? [append] : [], }; } return kind === "baseline+1msg" ? { noExtensions: true, noSkills: true, noPromptTemplates: true, noThemes: true, noContextFiles: true, systemPrompt: "", appendSystemPrompt: [], extensionFactories, } : { extensionFactories, ...(extensionPaths === undefined ? {} : { noExtensions: true, additionalExtensionPaths: extensionPaths }), }; } export function observeRequests( agent: Agent, observe: (context: Parameters[1]) => void, ): void { const stream = agent.streamFunction; if (typeof stream !== "function") throw new Error("Pi stream API unavailable"); agent.streamFunction = (model, context, options) => { observe(context); return stream(model, context, options); }; } // Only counts and public run metadata cross the process boundary. export async function publish( path: string, value: SessionResult, ): Promise { await writeFile(`${path}.tmp`, JSON.stringify(value), { mode: 0o600 }); await rename(`${path}.tmp`, path); } export async function runSession(options: RunOptions): Promise { const { kind, cwd, agentDir, sourceAgentDir, resultPath, selection } = options; const baseline = kind === "baseline+1msg"; process.env["PI_CODING_AGENT_DIR"] = agentDir; process.env["PI_OFFLINE"] = "1"; process.env["AI_AGENT"] = "pi"; process.env["PI_CODING_AGENT"] = "true"; let runtime: AgentSessionRuntime | undefined; let inventory: ExtensionInventoryEntry[] = []; let stopThemeWatcher: (() => void) | undefined; let stage = "installed Pi loading"; let published = false; const finish = async (value: SessionResult) => { if (published) return; published = true; if (options.sourcesOnly) { if (value.error) { console.error(value.error); return; } const result = value.sources as SourceEstimate; const n = (value: number) => value.toLocaleString("en-US"); if (options.skillDescriptions) { const entries = result.descriptions; console.log( [ `pi/agent: ${entries.length} visible skills, largest descriptions first`, "~tok skill (raw description only, Pi chars/4)", ...entries.map( (entry) => `${String(entry.tokens).padStart(4)} ${entry.name}`, ), "", "Largest five descriptions:", ...entries .slice(0, 5) .map((entry) => `${entry.name}: ${entry.description}`), ].join("\n"), ); return; } console.log( [ `APPEND_SYSTEM.md: ~${n(result.appendix)} tok`, `pi/agent skills (${result.piAgent.count}): ~${n(result.piAgent.tokens)} tok`, `Extension/package skills (${result.extensions.count}): ~${n(result.extensions.tokens)} tok`, `Shared catalog instructions/wrapper: ~${n(result.shared)} tok`, `Total: ~${n(result.total)} tok (Pi chars/4; group rounding may differ)`, `Project-local skills excluded: ${result.ignoredProjectSkills}; skill bodies and prompt templates excluded.`, ].join("\n"), ); } else { await publish(resultPath as string, value); } }; try { const dist = await installedPiDist(); const load = (path: string) => import(pathToFileURL(join(dist, path)).href); const [ sdk, { ReadOnlyAuthStorage }, { InMemoryCodingAgentModelsStore }, { builtInExtensions }, trust, { initTheme, stopThemeWatcher: stopWatcher }, { convertToLlm }, ] = await Promise.all([ load("index.js"), load("core/auth-storage.js"), load("core/models-store.js"), load("extensions/index.js"), load("core/trust-manager.js"), load("modes/interactive/theme/theme.js"), load("core/messages.js"), ]); stopThemeWatcher = stopWatcher; const { version } = JSON.parse( await readFile(join(dist, "../package.json"), "utf8"), ); stage = "project trust resolution (use normal Pi to establish trust first)"; let projectTrusted = false; if (!baseline) { projectTrusted = !trust.hasTrustRequiringProjectResources(cwd) || new trust.ProjectTrustStore(agentDir).get(cwd); if (projectTrusted === null) { const policy = sdk.SettingsManager.create(cwd, agentDir, { projectTrusted: false, }).getDefaultProjectTrust(); if (policy === "ask") throw new Error(); projectTrusted = policy === "always"; } } const settingsManager = baseline ? sdk.SettingsManager.inMemory({}, { projectTrusted: false }) : sdk.SettingsManager.create(cwd, agentDir, { projectTrusted }); stage = "read-only authentication/model initialization"; const modelRuntime = await sdk.ModelRuntime.create({ credentials: new ReadOnlyAuthStorage(join(sourceAgentDir, "auth.json")), modelsPath: baseline ? null : join(agentDir, "models.json"), modelsStore: new InMemoryCodingAgentModelsStore(), allowModelNetwork: false, }); let contextMode: ExtensionContext["mode"] | undefined; let hasUI: boolean | undefined; let toolCalls = 0; let extensionErrors = 0; const observer = { name: "context-measurement", hidden: true, factory(pi: ExtensionAPI) { pi.on("session_start", (_event, ctx) => { contextMode = ctx.mode; hasUI = ctx.hasUI; }); // Keep schemas intact, but fail closed if the reply-only request tries tools. pi.on("tool_call", () => { toolCalls++; return { block: true, terminate: true, reason: "Context measurement permits no tool execution", }; }); }, }; stage = "resource/session initialization"; runtime = (await sdk.createAgentSessionRuntime( async ({ sessionManager, sessionStartEvent, }: { sessionManager: SessionManager; sessionStartEvent?: SessionStartEvent; }) => { const services = await sdk.createAgentSessionServices({ cwd, agentDir, settingsManager, modelRuntime, resourceLoaderOptions: { ...resourceOptions(kind, [...builtInExtensions, observer], { cwd, agentDir, projectTrusted, ...(options.extensionPaths !== undefined ? { extensionPaths: options.extensionPaths } : {}), }), ...(options.inspectExtensions ? { extensionsOverride(base: LoadExtensionsResult) { inventory = inspectExtensionRegistrations(base); return base; }, } : {}), }, }); if ( services.diagnostics.some( (d: { type: string }) => d.type === "error", ) || services.resourceLoader.getExtensions().errors.length || settingsManager.drainErrors().length || modelRuntime.getError() ) throw new Error(); const model = selection ? modelRuntime.getModel(selection.provider, selection.model) : undefined; if (selection && !model) { stage = baseline ? "selected model unavailable in stock Pi (no user model config imported)" : "configured model selection"; throw new Error(); } const created = await sdk.createAgentSessionFromServices({ services, sessionManager, sessionStartEvent, model, thinkingLevel: selection?.thinking, }); return { ...created, services, diagnostics: services.diagnostics }; }, { cwd, agentDir, sessionManager: sdk.SessionManager.inMemory(cwd) }, )) as AgentSessionRuntime; const { session } = runtime; stage = "startup model selection"; if (!session.model) throw new Error(); session.extensionRunner.onError(() => { extensionErrors++; }); let requests = 0; let local: ReturnType | undefined; const messages: AgentMessage[] = []; observeRequests(session.agent, (context) => { requests++; local = snapshot(context, sdk.estimateTokens); }); session.subscribe((event) => { if (event.type === "message_end" && event.message.role === "assistant") messages.push(event.message); }); const metadata = () => ({ kind, cwd, version, provider: session.model?.provider, model: session.model?.id, thinking: session.thinkingLevel, contextWindow: session.model?.contextWindow, mode: contextMode, hasUI, projectTrusted, ...(options.inspectExtensions ? { extensions: inventory } : {}), }); const result = () => { if (requests !== 1 || toolCalls || extensionErrors) { return { kind, error: `Invalid run: ${requests} requests, ${toolCalls} tool calls, ${extensionErrors} extension errors`, }; } try { const usage = summarize( messages .map((message) => JSON.stringify({ type: "message_end", message })) .join("\n"), ); return { ...metadata(), ...(local !== undefined ? { local } : {}), ...usage, requests, }; } catch { return { kind, error: "No valid single response/usage; check provider access and read-only credential validity", }; } }; initTheme(settingsManager.getTheme(), false); stage = "headless startup hooks"; await session.bindExtensions({ mode: "json", onError: () => { extensionErrors++; }, }); if (contextMode !== "json" || hasUI || extensionErrors) throw new Error(); if (options.inspectExtensions) { stage = "runtime extension inventory"; inventory = inspectExtensionRegistrations( runtime.services.resourceLoader.getExtensions(), inventory, ); if ( options.extensionPaths !== undefined && JSON.stringify(inventory.map((entry) => entry.path)) !== JSON.stringify(options.extensionPaths) ) { stage = "explicit extension set/order validation"; throw new Error(); } } if (kind === "headless+0msg") { if (requests || messages.length) throw new Error(); stage = "skill source attribution"; const sources = options.sourcesOnly ? await estimateSourceTokens( agentDir, sdk, runtime.services.resourceLoader.getSkills(), ) : undefined; await finish({ ...metadata(), ...(sources !== undefined ? { sources } : {}), local: snapshot( { ...session.agent.state, messages: convertToLlm(session.messages), }, sdk.estimateTokens, ), requests, }); } else { stage = "provider request (read-only credentials must already be valid)"; await session.prompt(PROMPT); stage = "single-response/usage validation"; await finish(result()); } } catch { await finish({ kind, error: `${stage} failed` }); process.exitCode = 1; } finally { for (const cleanup of [ () => runtime?.dispose(), () => stopThemeWatcher?.(), ]) { try { await cleanup(); } catch { process.exitCode = 1; } } } } const entryArg = process.argv[1]; if (entryArg && import.meta.url === pathToFileURL(resolve(entryArg)).href) { const mode = process.argv[2]; if ( mode !== undefined && ["--sources", "--skill-descriptions"].includes(mode) ) { const agentDir = process.env["PI_CODING_AGENT_DIR"] ?? join(homedir(), ".pi", "agent"); await runSession({ kind: "headless+0msg", cwd: process.cwd(), agentDir, sourceAgentDir: agentDir, sourcesOnly: true, skillDescriptions: mode === "--skill-descriptions", }); } else { if (mode === undefined) throw new Error("missing session argument"); await runSession(JSON.parse(await readFile(mode, "utf8"))); } process.exit(process.exitCode ?? 0); }