import * as fs from "node:fs"; import * as os from "node:os"; import * as path from "node:path"; import { fileURLToPath } from "node:url"; const THINKING_LEVELS = ["off", "minimal", "low", "medium", "high", "xhigh"]; const TASK_ARG_LIMIT = 8000; const PROMPT_RUNTIME_EXTENSION_PATH = path.join(path.dirname(fileURLToPath(import.meta.url)), "subagent-prompt-runtime.ts"); export const SUBAGENT_CHILD_ENV = "PI_SUBAGENT_CHILD"; export const SUBAGENT_ORCHESTRATOR_TARGET_ENV = "PI_SUBAGENT_ORCHESTRATOR_TARGET"; export const SUBAGENT_RUN_ID_ENV = "PI_SUBAGENT_RUN_ID"; export const SUBAGENT_CHILD_AGENT_ENV = "PI_SUBAGENT_CHILD_AGENT"; export const SUBAGENT_CHILD_INDEX_ENV = "PI_SUBAGENT_CHILD_INDEX"; interface BuildPiArgsInput { baseArgs: string[]; task: string; sessionEnabled: boolean; sessionDir?: string; sessionFile?: string; model?: string; thinking?: string; systemPromptMode?: "append" | "replace"; inheritProjectContext: boolean; inheritSkills: boolean; tools?: string[]; extensions?: string[]; systemPrompt?: string | null; mcpDirectTools?: string[]; promptFileStem?: string; intercomSessionName?: string; orchestratorIntercomTarget?: string; runId?: string; childAgentName?: string; childIndex?: number; } interface BuildPiArgsResult { args: string[]; env: Record; tempDir?: string; } export function applyThinkingSuffix(model: string | undefined, thinking: string | undefined): string | undefined { if (!model || !thinking || thinking === "off") return model; const colonIdx = model.lastIndexOf(":"); if (colonIdx !== -1 && THINKING_LEVELS.includes(model.substring(colonIdx + 1))) return model; return `${model}:${thinking}`; } export function buildPiArgs(input: BuildPiArgsInput): BuildPiArgsResult { const args = [...input.baseArgs]; if (input.sessionFile) { fs.mkdirSync(path.dirname(input.sessionFile), { recursive: true }); args.push("--session", input.sessionFile); } else { if (!input.sessionEnabled) { args.push("--no-session"); } if (input.sessionDir) { fs.mkdirSync(input.sessionDir, { recursive: true }); args.push("--session-dir", input.sessionDir); } } const modelArg = applyThinkingSuffix(input.model, input.thinking); if (modelArg) { args.push("--model", modelArg); } const toolExtensionPaths: string[] = []; if (input.tools?.length) { const builtinTools: string[] = []; for (const tool of input.tools) { if (tool.includes("/") || tool.endsWith(".ts") || tool.endsWith(".js")) { toolExtensionPaths.push(tool); } else { builtinTools.push(tool); } } if (builtinTools.length > 0) { args.push("--tools", builtinTools.join(",")); } } const runtimeExtensions = [PROMPT_RUNTIME_EXTENSION_PATH]; if (input.extensions !== undefined) { args.push("--no-extensions"); for (const extPath of [...new Set([...runtimeExtensions, ...toolExtensionPaths, ...input.extensions])]) { args.push("--extension", extPath); } } else { for (const extPath of [...new Set([...runtimeExtensions, ...toolExtensionPaths])]) { args.push("--extension", extPath); } } if (!input.inheritSkills) { args.push("--no-skills"); } let tempDir: string | undefined; if (input.systemPrompt !== undefined && input.systemPrompt !== null) { tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "pi-subagent-")); const stem = (input.promptFileStem ?? "prompt").replace(/[^\w.-]/g, "_"); const promptPath = path.join(tempDir, `${stem}.md`); fs.writeFileSync(promptPath, input.systemPrompt, { mode: 0o600 }); args.push(input.systemPromptMode === "replace" ? "--system-prompt" : "--append-system-prompt", promptPath); } if (input.task.length > TASK_ARG_LIMIT) { if (!tempDir) { tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "pi-subagent-")); } const taskFilePath = path.join(tempDir, "task.md"); fs.writeFileSync(taskFilePath, `Task: ${input.task}`, { mode: 0o600 }); args.push(`@${taskFilePath}`); } else { args.push(`Task: ${input.task}`); } const env: Record = {}; env[SUBAGENT_CHILD_ENV] = "1"; env.PI_SUBAGENT_INHERIT_PROJECT_CONTEXT = input.inheritProjectContext ? "1" : "0"; env.PI_SUBAGENT_INHERIT_SKILLS = input.inheritSkills ? "1" : "0"; if (input.intercomSessionName) { env.PI_SUBAGENT_INTERCOM_SESSION_NAME = input.intercomSessionName; } if (input.orchestratorIntercomTarget) { env[SUBAGENT_ORCHESTRATOR_TARGET_ENV] = input.orchestratorIntercomTarget; } if (input.runId) { env[SUBAGENT_RUN_ID_ENV] = input.runId; } if (input.childAgentName) { env[SUBAGENT_CHILD_AGENT_ENV] = input.childAgentName; } if (input.childIndex !== undefined) { env[SUBAGENT_CHILD_INDEX_ENV] = String(input.childIndex); } if (input.mcpDirectTools?.length) { env.MCP_DIRECT_TOOLS = input.mcpDirectTools.join(","); } else { env.MCP_DIRECT_TOOLS = "__none__"; } return { args, env, tempDir }; } export function cleanupTempDir(tempDir: string | null | undefined): void { if (!tempDir) return; try { fs.rmSync(tempDir, { recursive: true, force: true }); } catch { // Temp cleanup is best effort. } }