import type { AgentTool } from "@mariozechner/pi-agent-core"; import type { EmbeddedContextFile } from "./pi-embedded-helpers.js"; import type { WorkspaceBootstrapFile } from "./workspace.js"; import type { SessionSystemPromptReport } from "../config/sessions/types.js"; function extractBetween( input: string, startMarker: string, endMarker: string, ): { text: string; found: boolean } { const start = input.indexOf(startMarker); if (start === -1) return { text: "", found: false }; const end = input.indexOf(endMarker, start + startMarker.length); if (end === -1) return { text: input.slice(start), found: true }; return { text: input.slice(start, end), found: true }; } function parseSkillBlocks(skillsPrompt: string): Array<{ name: string; blockChars: number }> { const prompt = skillsPrompt.trim(); if (!prompt) return []; const blocks = Array.from(prompt.matchAll(/[\s\S]*?<\/skill>/gi)).map( (match) => match[0] ?? "", ); return blocks .map((block) => { const name = block.match(/\s*([^<]+?)\s*<\/name>/i)?.[1]?.trim() || "(unknown)"; return { name, blockChars: block.length }; }) .filter((b) => b.blockChars > 0); } function buildInjectedWorkspaceFiles(params: { bootstrapFiles: WorkspaceBootstrapFile[]; injectedFiles: EmbeddedContextFile[]; bootstrapMaxChars: number; }): SessionSystemPromptReport["injectedWorkspaceFiles"] { const injectedByName = new Map(params.injectedFiles.map((f) => [f.path, f.content])); return params.bootstrapFiles.map((file) => { const rawChars = file.missing ? 0 : (file.content ?? "").trimEnd().length; const injected = injectedByName.get(file.name); const injectedChars = injected ? injected.length : 0; const truncated = !file.missing && rawChars > params.bootstrapMaxChars; return { name: file.name, path: file.path, missing: file.missing, rawChars, injectedChars, truncated, }; }); } function buildToolsEntries(tools: AgentTool[]): SessionSystemPromptReport["tools"]["entries"] { return tools.map((tool) => { const name = tool.name; const summary = tool.description?.trim() || tool.label?.trim() || ""; const summaryChars = summary.length; const schemaChars = (() => { if (!tool.parameters || typeof tool.parameters !== "object") return 0; try { return JSON.stringify(tool.parameters).length; } catch { return 0; } })(); const propertiesCount = (() => { const schema = tool.parameters && typeof tool.parameters === "object" ? (tool.parameters as Record) : null; const props = schema && typeof schema.properties === "object" ? schema.properties : null; if (!props || typeof props !== "object") return null; return Object.keys(props as Record).length; })(); return { name, summaryChars, schemaChars, propertiesCount }; }); } function extractToolListText(systemPrompt: string): string { const markerA = "Tool names are case-sensitive. Call tools exactly as listed.\n"; const markerB = "\nTOOLS.md does not control tool availability; it is user guidance for how to use external tools."; const extracted = extractBetween(systemPrompt, markerA, markerB); if (!extracted.found) return ""; return extracted.text.replace(markerA, "").trim(); } export function buildSystemPromptReport(params: { source: SessionSystemPromptReport["source"]; generatedAt: number; sessionId?: string; sessionKey?: string; provider?: string; model?: string; workspaceDir?: string; bootstrapMaxChars: number; sandbox?: SessionSystemPromptReport["sandbox"]; systemPrompt: string; bootstrapFiles: WorkspaceBootstrapFile[]; injectedFiles: EmbeddedContextFile[]; skillsPrompt: string; tools: AgentTool[]; }): SessionSystemPromptReport { const systemPrompt = params.systemPrompt.trim(); const projectContext = extractBetween( systemPrompt, "\n# Project Context\n", "\n## Silent Replies\n", ); const projectContextChars = projectContext.text.length; const toolListText = extractToolListText(systemPrompt); const toolListChars = toolListText.length; const toolsEntries = buildToolsEntries(params.tools); const toolsSchemaChars = toolsEntries.reduce((sum, t) => sum + (t.schemaChars ?? 0), 0); const skillsEntries = parseSkillBlocks(params.skillsPrompt); return { source: params.source, generatedAt: params.generatedAt, sessionId: params.sessionId, sessionKey: params.sessionKey, provider: params.provider, model: params.model, workspaceDir: params.workspaceDir, bootstrapMaxChars: params.bootstrapMaxChars, sandbox: params.sandbox, systemPrompt: { chars: systemPrompt.length, projectContextChars, nonProjectContextChars: Math.max(0, systemPrompt.length - projectContextChars), }, injectedWorkspaceFiles: buildInjectedWorkspaceFiles({ bootstrapFiles: params.bootstrapFiles, injectedFiles: params.injectedFiles, bootstrapMaxChars: params.bootstrapMaxChars, }), skills: { promptChars: params.skillsPrompt.length, entries: skillsEntries, }, tools: { listChars: toolListChars, schemaChars: toolsSchemaChars, entries: toolsEntries, }, }; }