import type { SettingSource } from "@anthropic-ai/claude-agent-sdk"; import type { SystemPromptReplacements } from "./settings.js"; // "pi" mode isolates Claude Code's filesystem settings ([] = no setting sources); // every other mode keeps Claude Code's defaults (undefined). export function settingSourcesFor(systemPromptMode: string): SettingSource[] | undefined { return systemPromptMode === "pi" ? [] : undefined; } const PI_IDENTITY_PROMPT = `You are an expert coding assistant operating inside pi, a coding agent harness. You help users by reading files, executing commands, editing code, and writing new files.`; const PI_DOCUMENTATION_BLOCK_REGEX = /\n\nPi documentation \(read only when the user asks about pi itself, its SDK, extensions, themes, skills, or TUI\):\n[\s\S]*?\n- Always read pi \.md files completely and follow links to related docs \(e\.g\., tui\.md for TUI API details\)/; function rewritePiDocumentationBlock( systemPrompt: string, documentation: SystemPromptReplacements["documentation"], ): string { const match = PI_DOCUMENTATION_BLOCK_REGEX.exec(systemPrompt); if (!match) return systemPrompt; const pathLines = match[0] .split("\n") .filter( (line) => line.startsWith("- Main documentation:") || line.startsWith("- Additional docs:") || line.startsWith("- Examples:"), ); return systemPrompt.replace( PI_DOCUMENTATION_BLOCK_REGEX, [`\n\n${documentation.heading}`, ...pathLines, ...documentation.instructions].join("\n"), ); } function rewriteIdentityPrompt(systemPrompt: string, replacement: string): string { return systemPrompt.replace(PI_IDENTITY_PROMPT, replacement); } function insertToolNameNote(systemPrompt: string, replacement: string): string { return systemPrompt.replace("\n\nAvailable tools:", `\n\n${replacement}\n\nAvailable tools:`); } function insertRelocatedToolBlock(systemPrompt: string, block: string): string { const anchor = "\n\nIn addition to the tools above"; if (!systemPrompt.includes(anchor)) return `${block}\n\n${systemPrompt}`; return systemPrompt.replace(anchor, `\n\n${block}${anchor}`); } export type ClaudeSystemPrompt = | string | { type: "preset"; preset: "claude_code"; append?: string; }; export interface ToolDescriptionRelocation { name: string; description: string; } function relocatedToolBlock(relocations: ToolDescriptionRelocation[]): string | undefined { if (relocations.length === 0) return undefined; const descriptions = relocations .map( (relocation) => `${JSON.stringify(relocation)}`, ) .join("\n"); return `\n${descriptions}\n`; } export function rewritePiSystemPrompt( systemPrompt: string, replacements: SystemPromptReplacements, ): string { return rewritePiDocumentationBlock( insertToolNameNote( rewriteIdentityPrompt(systemPrompt, replacements.identity), replacements.toolNameNote, ), replacements.documentation, ); } export function buildClaudeSystemPrompt( piSystemPrompt: string, mode: "claude-code" | "pi" | "append", replacements: SystemPromptReplacements | undefined, relocations: ToolDescriptionRelocation[] = [], ): ClaudeSystemPrompt { const relocationBlock = relocatedToolBlock(relocations); if (mode === "claude-code") { return { type: "preset", preset: "claude_code", ...(relocationBlock ? { append: ` ${relocationBlock}` } : {}), }; } if (!replacements) { throw new Error("doppelclaude: system prompt replacements are required"); } const rewrittenPiPrompt = rewritePiSystemPrompt(piSystemPrompt, replacements); const promptWithRelocations = relocationBlock ? insertRelocatedToolBlock(rewrittenPiPrompt, relocationBlock) : rewrittenPiPrompt; // Claude Code puts its own identity in a separate system block right before ours and // joins nothing between them, so without this the two run together as "…Agent SDK.You are". const separatedPiPrompt = ` ${promptWithRelocations}`; return mode === "pi" ? separatedPiPrompt : { type: "preset", preset: "claude_code", append: separatedPiPrompt }; }