refactor(prompt-templates): migrated prompt utilities to pi-utils package
- Extracted prompt rendering and formatting utilities from coding-agent to centralized pi-utils package with new API surface (prompt.render, prompt.format, prompt.registerHelper). - Migrated parseFrontmatter utility from coding-agent to pi-utils package; updated 8 files to import from @oh-my-pi/pi-utils. - Removed 170-line prompt-format.ts module and consolidated 192 lines of Handlebars helper registrations into pi-utils prompt module. - Updated 60+ files across coding-agent and typescript-edit-benchmark to use new prompt.render() and prompt.format() API from pi-utils. - Simplified prompt-templates.ts by delegating core functionality to pi-utils while retaining custom helper registrations (jtdToTypeScript, jsonStringify, etc.).
This commit is contained in:
@@ -169,6 +169,7 @@
|
||||
"version": "13.19.0",
|
||||
"dependencies": {
|
||||
"beautiful-mermaid": "^1.1",
|
||||
"handlebars": "^4.7.9",
|
||||
"winston": "^3.19",
|
||||
"winston-daily-rotate-file": "^5.0",
|
||||
},
|
||||
|
||||
@@ -160,7 +160,7 @@ The Extensions dashboard also loads `slash-commands` capability and displays act
|
||||
If text still starts with `/`, attempt markdown command expansion.
|
||||
4. **Prompt templates** (`expandPromptTemplate`)
|
||||
Applied after slash/custom processing.
|
||||
5. **Delivery**
|
||||
5. **Delivery**
|
||||
- idle: prompt is sent immediately to agent
|
||||
- streaming: prompt is queued as steer/follow-up depending on `streamingBehavior`
|
||||
|
||||
@@ -177,7 +177,7 @@ This is why slash command expansion sits before prompt-template expansion, and w
|
||||
- if matched, applies:
|
||||
- positional replacement: `$1`, `$2`, ...
|
||||
- aggregate replacement: `$ARGUMENTS` and `$@`
|
||||
- then template rendering via `renderPromptTemplate` with `{ args, ARGUMENTS, arguments }`
|
||||
- then template rendering via `prompt.render` with `{ args, ARGUMENTS, arguments }`
|
||||
- if no match, returns original text unchanged
|
||||
|
||||
### `parseCommandArgs` caveats
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
# Changelog
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
### Breaking Changes
|
||||
|
||||
- Simplified chunk edit operations: removed `append_child`, `prepend_child`, `append_sibling`, `prepend_sibling`, and `replace_body` ops in favor of unified `replace`, `before`, `after`, `prepend`, and `append` with region targeting (`@container`, `@prologue`, `@body`, `@epilogue`)
|
||||
@@ -41,6 +40,9 @@
|
||||
|
||||
### Changed
|
||||
|
||||
- Moved prompt formatting and rendering utilities from `coding-agent` to `pi-utils` package; `renderPromptTemplate()` and `formatPromptContent()` now accessed via `prompt.render()` and `prompt.format()` from `@oh-my-pi/pi-utils`
|
||||
- Moved `parseFrontmatter()` utility from `coding-agent` to `pi-utils` package; now imported from `@oh-my-pi/pi-utils` instead of local utils
|
||||
- Consolidated prompt template handling: `TemplateContext` type now available as `prompt.TemplateContext` from `@oh-my-pi/pi-utils`
|
||||
- DAP initialization now advertises support for `runInTerminal` and `startDebugging` reverse requests, and memory references
|
||||
- Debug tool schema expanded with new parameters for instruction/data breakpoints, memory operations, and custom requests
|
||||
- DAP session state now tracks instruction and data breakpoints separately from source breakpoints
|
||||
@@ -78,6 +80,8 @@
|
||||
|
||||
### Removed
|
||||
|
||||
- Deleted `src/utils/prompt-format.ts` module; prompt formatting logic moved to `pi-utils`
|
||||
- Deleted `src/utils/frontmatter.ts` module; frontmatter parsing logic moved to `pi-utils`
|
||||
- Removed `waitForChildProcess` utility (child process termination now handled by native `killTree` from pi-natives)
|
||||
- `grep-chunk.md` (folded into unified grep template)
|
||||
- `startMacAppearanceObserver` export (use `MacAppearanceObserver.start()`)
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#!/usr/bin/env bun
|
||||
import { prompt } from "@oh-my-pi/pi-utils";
|
||||
/**
|
||||
* Format prompt files (mixed XML + Markdown + Handlebars).
|
||||
*
|
||||
@@ -14,7 +15,6 @@
|
||||
* 9. Bold RFC 2119 keywords (MUST, SHOULD, MAY, etc.) in prompt content
|
||||
*/
|
||||
import { Glob } from "bun";
|
||||
import { formatPromptContent } from "../src/utils/prompt-format";
|
||||
|
||||
const PROMPTS_DIR = new URL("../src/prompts/", import.meta.url).pathname;
|
||||
const COMMIT_PROMPTS_DIR = new URL("../src/commit/prompts/", import.meta.url).pathname;
|
||||
@@ -42,7 +42,7 @@ async function main() {
|
||||
|
||||
for (const fullPath of files) {
|
||||
const original = await Bun.file(fullPath).text();
|
||||
const formatted = formatPromptContent(original, PROMPT_FORMAT_OPTIONS);
|
||||
const formatted = prompt.format(original, PROMPT_FORMAT_OPTIONS);
|
||||
|
||||
if (original !== `${formatted}\n`) {
|
||||
if (check) {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import * as fs from "node:fs";
|
||||
import * as path from "node:path";
|
||||
import type { AutocompleteItem } from "@oh-my-pi/pi-tui";
|
||||
import { renderPromptTemplate } from "../config/prompt-templates";
|
||||
import { prompt } from "@oh-my-pi/pi-utils";
|
||||
import type { ExtensionContext, ExtensionFactory } from "../extensibility/extensions";
|
||||
import commandResumeTemplate from "./command-resume.md" with { type: "text" };
|
||||
import { pathMatchesContractPath } from "./contract";
|
||||
@@ -214,7 +214,7 @@ export const createAutoresearchExtension: ExtensionFactory = api => {
|
||||
dashboard.updateWidget(ctx, runtime);
|
||||
await api.setActiveTools([...new Set([...api.getActiveTools(), ...EXPERIMENT_TOOL_NAMES])]);
|
||||
api.sendUserMessage(
|
||||
renderPromptTemplate(commandResumeTemplate, {
|
||||
prompt.render(commandResumeTemplate, {
|
||||
autoresearch_md_path: autoresearchMdPath,
|
||||
branch_status_line: branchResult.created
|
||||
? `Created and checked out dedicated git branch \`${branchResult.branchName}\` before resuming.`
|
||||
@@ -303,7 +303,7 @@ export const createAutoresearchExtension: ExtensionFactory = api => {
|
||||
api.sendMessage(
|
||||
{
|
||||
customType: "autoresearch-resume",
|
||||
content: renderPromptTemplate(resumeMessageTemplate, {
|
||||
content: prompt.render(resumeMessageTemplate, {
|
||||
autoresearch_md_path: autoresearchMdPath,
|
||||
has_ideas: fs.existsSync(ideasPath),
|
||||
has_pending_run: Boolean(pendingRun),
|
||||
@@ -347,7 +347,7 @@ export const createAutoresearchExtension: ExtensionFactory = api => {
|
||||
});
|
||||
const hasAutoresearchMd = fs.existsSync(autoresearchMdPath);
|
||||
return {
|
||||
systemPrompt: renderPromptTemplate(promptTemplate, {
|
||||
systemPrompt: prompt.render(promptTemplate, {
|
||||
base_system_prompt: event.systemPrompt,
|
||||
has_goal: goal.trim().length > 0,
|
||||
goal,
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { INTENT_FIELD, type ThinkingLevel } from "@oh-my-pi/pi-agent-core";
|
||||
import type { Api, Model } from "@oh-my-pi/pi-ai";
|
||||
import { Markdown } from "@oh-my-pi/pi-tui";
|
||||
import { prompt } from "@oh-my-pi/pi-utils";
|
||||
import chalk from "chalk";
|
||||
import typesDescriptionPrompt from "../../commit/prompts/types-description.md" with { type: "text" };
|
||||
import type { ModelRegistry } from "../../config/model-registry";
|
||||
import { renderPromptTemplate } from "../../config/prompt-templates";
|
||||
import type { Settings } from "../../config/settings";
|
||||
import { getMarkdownTheme } from "../../modes/theme/theme";
|
||||
import { createAgentSession } from "../../sdk";
|
||||
@@ -36,8 +36,8 @@ export interface ExistingChangelogEntries {
|
||||
}
|
||||
|
||||
export async function runCommitAgentSession(input: CommitAgentInput): Promise<CommitAgentState> {
|
||||
const typesDescription = renderPromptTemplate(typesDescriptionPrompt);
|
||||
const systemPrompt = renderPromptTemplate(agentSystemPrompt, {
|
||||
const typesDescription = prompt.render(typesDescriptionPrompt);
|
||||
const systemPrompt = prompt.render(agentSystemPrompt, {
|
||||
types_description: typesDescription,
|
||||
});
|
||||
const state: CommitAgentState = { diffText: input.diffText };
|
||||
@@ -149,7 +149,7 @@ export async function runCommitAgentSession(input: CommitAgentInput): Promise<Co
|
||||
});
|
||||
|
||||
try {
|
||||
const prompt = renderPromptTemplate(agentUserPrompt, {
|
||||
const agentUserMessage = prompt.render(agentUserPrompt, {
|
||||
user_context: input.userContext,
|
||||
changelog_targets: input.changelogTargets.length > 0 ? input.changelogTargets.join("\n") : undefined,
|
||||
existing_changelog_entries: input.existingChangelogEntries,
|
||||
@@ -158,7 +158,7 @@ export async function runCommitAgentSession(input: CommitAgentInput): Promise<Co
|
||||
let retryCount = 0;
|
||||
const needsChangelog = input.requireChangelog && input.changelogTargets.length > 0;
|
||||
|
||||
await session.prompt(prompt, {
|
||||
await session.prompt(agentUserMessage, {
|
||||
attribution: "agent",
|
||||
expandPromptTemplates: false,
|
||||
});
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import * as path from "node:path";
|
||||
import { createInterface } from "node:readline/promises";
|
||||
import { $env, getProjectDir, isEnoent } from "@oh-my-pi/pi-utils";
|
||||
import { $env, getProjectDir, isEnoent, prompt } from "@oh-my-pi/pi-utils";
|
||||
import { applyChangelogProposals } from "../../commit/changelog";
|
||||
import { detectChangelogBoundaries } from "../../commit/changelog/detect";
|
||||
import { parseUnreleasedSection } from "../../commit/changelog/parse";
|
||||
@@ -8,7 +8,6 @@ import { formatCommitMessage } from "../../commit/message";
|
||||
import { resolvePrimaryModel, resolveSmolModel } from "../../commit/model-selection";
|
||||
import type { CommitCommandArgs, ConventionalAnalysis } from "../../commit/types";
|
||||
import { ModelRegistry } from "../../config/model-registry";
|
||||
import { renderPromptTemplate } from "../../config/prompt-templates";
|
||||
import { Settings } from "../../config/settings";
|
||||
import { discoverAuthStorage, discoverContextFiles } from "../../sdk";
|
||||
import * as git from "../../utils/git";
|
||||
@@ -305,8 +304,8 @@ async function confirmSplitCommitPlan(plan: SplitCommitPlan): Promise<boolean> {
|
||||
}
|
||||
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
||||
try {
|
||||
const prompt = renderPromptTemplate(splitConfirmPrompt, { count: plan.commits.length });
|
||||
const answer = await rl.question(prompt);
|
||||
const splitConfirmQuestion = prompt.render(splitConfirmPrompt, { count: plan.commits.length });
|
||||
const answer = await rl.question(splitConfirmQuestion);
|
||||
return ["y", "yes"].includes(answer.trim().toLowerCase());
|
||||
} finally {
|
||||
rl.close();
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { prompt } from "@oh-my-pi/pi-utils";
|
||||
import { Type } from "@sinclair/typebox";
|
||||
import analyzeFilePrompt from "../../../commit/agentic/prompts/analyze-file.md" with { type: "text" };
|
||||
import type { CommitAgentState } from "../../../commit/agentic/state";
|
||||
import type { NumstatEntry } from "../../../commit/types";
|
||||
import type { ModelRegistry } from "../../../config/model-registry";
|
||||
import { renderPromptTemplate } from "../../../config/prompt-templates";
|
||||
import type { Settings } from "../../../config/settings";
|
||||
import type { CustomTool, CustomToolContext } from "../../../extensibility/custom-tools/types";
|
||||
import type { AuthStorage } from "../../../session/auth-storage";
|
||||
@@ -66,7 +66,7 @@ export function createAnalyzeFileTool(options: {
|
||||
const numstat = options.state.overview?.numstat ?? [];
|
||||
const tasks = params.files.map((file, index) => {
|
||||
const relatedFiles = formatRelatedFiles(params.files, file, numstat);
|
||||
const prompt = renderPromptTemplate(analyzeFilePrompt, {
|
||||
const assignment = prompt.render(analyzeFilePrompt, {
|
||||
file,
|
||||
goal: params.goal,
|
||||
related_files: relatedFiles,
|
||||
@@ -74,7 +74,7 @@ export function createAnalyzeFileTool(options: {
|
||||
return {
|
||||
id: `AnalyzeFile${index + 1}`,
|
||||
description: `Analyze ${file}`,
|
||||
assignment: prompt,
|
||||
assignment,
|
||||
};
|
||||
});
|
||||
const taskParams: TaskParams = {
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import type { ThinkingLevel } from "@oh-my-pi/pi-agent-core";
|
||||
import type { Api, AssistantMessage, Model } from "@oh-my-pi/pi-ai";
|
||||
import { completeSimple, validateToolCall } from "@oh-my-pi/pi-ai";
|
||||
import { prompt } from "@oh-my-pi/pi-utils";
|
||||
import { Type } from "@sinclair/typebox";
|
||||
import analysisSystemPrompt from "../../commit/prompts/analysis-system.md" with { type: "text" };
|
||||
import analysisUserPrompt from "../../commit/prompts/analysis-user.md" with { type: "text" };
|
||||
import type { ChangelogCategory, ConventionalAnalysis } from "../../commit/types";
|
||||
import { renderPromptTemplate } from "../../config/prompt-templates";
|
||||
import { toReasoningEffort } from "../../thinking";
|
||||
import { extractTextContent, extractToolCall, normalizeAnalysis, parseJsonPayload } from "../utils";
|
||||
|
||||
@@ -76,7 +76,7 @@ export async function generateConventionalAnalysis({
|
||||
stat,
|
||||
diff,
|
||||
}: ConventionalAnalysisInput): Promise<ConventionalAnalysis> {
|
||||
const prompt = renderPromptTemplate(analysisUserPrompt, {
|
||||
const userContent = prompt.render(analysisUserPrompt, {
|
||||
context_files: contextFiles && contextFiles.length > 0 ? contextFiles : undefined,
|
||||
user_context: userContext,
|
||||
types_description: typesDescription,
|
||||
@@ -89,8 +89,8 @@ export async function generateConventionalAnalysis({
|
||||
const response = await completeSimple(
|
||||
model,
|
||||
{
|
||||
systemPrompt: renderPromptTemplate(analysisSystemPrompt),
|
||||
messages: [{ role: "user", content: prompt, timestamp: Date.now() }],
|
||||
systemPrompt: prompt.render(analysisSystemPrompt),
|
||||
messages: [{ role: "user", content: userContent, timestamp: Date.now() }],
|
||||
tools: [ConventionalAnalysisTool],
|
||||
},
|
||||
{ apiKey, maxTokens: 2400, reasoning: toReasoningEffort(thinkingLevel) },
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import type { ThinkingLevel } from "@oh-my-pi/pi-agent-core";
|
||||
import type { Api, AssistantMessage, Model } from "@oh-my-pi/pi-ai";
|
||||
import { completeSimple, validateToolCall } from "@oh-my-pi/pi-ai";
|
||||
import { prompt } from "@oh-my-pi/pi-utils";
|
||||
import { Type } from "@sinclair/typebox";
|
||||
import summarySystemPrompt from "../../commit/prompts/summary-system.md" with { type: "text" };
|
||||
import summaryUserPrompt from "../../commit/prompts/summary-user.md" with { type: "text" };
|
||||
import type { CommitSummary } from "../../commit/types";
|
||||
import { renderPromptTemplate } from "../../config/prompt-templates";
|
||||
import { toReasoningEffort } from "../../thinking";
|
||||
import { extractTextContent, extractToolCall } from "../utils";
|
||||
|
||||
@@ -44,7 +44,7 @@ export async function generateSummary({
|
||||
userContext,
|
||||
}: SummaryInput): Promise<CommitSummary> {
|
||||
const systemPrompt = renderSummaryPrompt({ commitType, scope, maxChars });
|
||||
const userPrompt = renderPromptTemplate(summaryUserPrompt, {
|
||||
const userPrompt = prompt.render(summaryUserPrompt, {
|
||||
user_context: userContext,
|
||||
details: details.join("\n"),
|
||||
stat,
|
||||
@@ -73,7 +73,7 @@ function renderSummaryPrompt({
|
||||
maxChars: number;
|
||||
}): string {
|
||||
const scopePrefix = scope ? `(${scope})` : "";
|
||||
return renderPromptTemplate(summarySystemPrompt, {
|
||||
return prompt.render(summarySystemPrompt, {
|
||||
commit_type: commitType,
|
||||
scope_prefix: scopePrefix,
|
||||
chars: String(maxChars),
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import type { ThinkingLevel } from "@oh-my-pi/pi-agent-core";
|
||||
import type { Api, AssistantMessage, Model } from "@oh-my-pi/pi-ai";
|
||||
import { completeSimple, validateToolCall } from "@oh-my-pi/pi-ai";
|
||||
import { prompt } from "@oh-my-pi/pi-utils";
|
||||
import { type TSchema, Type } from "@sinclair/typebox";
|
||||
import changelogSystemPrompt from "../../commit/prompts/changelog-system.md" with { type: "text" };
|
||||
import changelogUserPrompt from "../../commit/prompts/changelog-user.md" with { type: "text" };
|
||||
import { CHANGELOG_CATEGORIES, type ChangelogCategory, type ChangelogGenerationResult } from "../../commit/types";
|
||||
import { renderPromptTemplate } from "../../config/prompt-templates";
|
||||
import { toReasoningEffort } from "../../thinking";
|
||||
import { extractTextContent, extractToolCall, parseJsonPayload } from "../utils";
|
||||
|
||||
@@ -48,7 +48,7 @@ export async function generateChangelogEntries({
|
||||
stat,
|
||||
diff,
|
||||
}: ChangelogPromptInput): Promise<ChangelogGenerationResult> {
|
||||
const prompt = renderPromptTemplate(changelogUserPrompt, {
|
||||
const userContent = prompt.render(changelogUserPrompt, {
|
||||
changelog_path: changelogPath,
|
||||
is_package_changelog: isPackageChangelog,
|
||||
existing_entries: existingEntries,
|
||||
@@ -58,8 +58,8 @@ export async function generateChangelogEntries({
|
||||
const response = await completeSimple(
|
||||
model,
|
||||
{
|
||||
systemPrompt: renderPromptTemplate(changelogSystemPrompt),
|
||||
messages: [{ role: "user", content: prompt, timestamp: Date.now() }],
|
||||
systemPrompt: prompt.render(changelogSystemPrompt),
|
||||
messages: [{ role: "user", content: userContent, timestamp: Date.now() }],
|
||||
tools: [changelogTool],
|
||||
},
|
||||
{ apiKey, maxTokens: 1200, reasoning: toReasoningEffort(thinkingLevel) },
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import type { ThinkingLevel } from "@oh-my-pi/pi-agent-core";
|
||||
import type { Api, AssistantMessage, Message, Model } from "@oh-my-pi/pi-ai";
|
||||
import { completeSimple } from "@oh-my-pi/pi-ai";
|
||||
import { prompt } from "@oh-my-pi/pi-utils";
|
||||
import fileObserverSystemPrompt from "../../commit/prompts/file-observer-system.md" with { type: "text" };
|
||||
import fileObserverUserPrompt from "../../commit/prompts/file-observer-user.md" with { type: "text" };
|
||||
import type { FileDiff, FileObservation } from "../../commit/types";
|
||||
import { isExcludedFile } from "../../commit/utils/exclusions";
|
||||
import { renderPromptTemplate } from "../../config/prompt-templates";
|
||||
import { toReasoningEffort } from "../../thinking";
|
||||
import { truncateToTokenLimit } from "./utils";
|
||||
|
||||
@@ -38,7 +38,7 @@ export async function runMapPhase({
|
||||
config,
|
||||
}: MapPhaseInput): Promise<FileObservation[]> {
|
||||
const filtered = files.filter(file => !isExcludedFile(file.filename));
|
||||
const systemPrompt = renderPromptTemplate(fileObserverSystemPrompt);
|
||||
const systemPrompt = prompt.render(fileObserverSystemPrompt);
|
||||
const maxFileTokens = config?.maxFileTokens ?? MAX_FILE_TOKENS;
|
||||
const maxConcurrency = config?.maxConcurrency ?? MAX_CONCURRENCY;
|
||||
const timeoutMs = config?.timeoutMs ?? MAP_PHASE_TIMEOUT_MS;
|
||||
@@ -56,14 +56,14 @@ export async function runMapPhase({
|
||||
|
||||
const contextHeader = generateContextHeader(filtered, file.filename);
|
||||
const truncated = truncateToTokenLimit(file.content, maxFileTokens);
|
||||
const prompt = renderPromptTemplate(fileObserverUserPrompt, {
|
||||
const userContent = prompt.render(fileObserverUserPrompt, {
|
||||
filename: file.filename,
|
||||
diff: truncated,
|
||||
context_header: contextHeader,
|
||||
});
|
||||
const request = {
|
||||
systemPrompt,
|
||||
messages: [{ role: "user", content: prompt, timestamp: Date.now() }] as Message[],
|
||||
messages: [{ role: "user", content: userContent, timestamp: Date.now() }] as Message[],
|
||||
};
|
||||
|
||||
const response = await withRetry(
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import type { ThinkingLevel } from "@oh-my-pi/pi-agent-core";
|
||||
import type { Api, AssistantMessage, Model } from "@oh-my-pi/pi-ai";
|
||||
import { completeSimple, validateToolCall } from "@oh-my-pi/pi-ai";
|
||||
import { prompt } from "@oh-my-pi/pi-utils";
|
||||
import { Type } from "@sinclair/typebox";
|
||||
import reduceSystemPrompt from "../../commit/prompts/reduce-system.md" with { type: "text" };
|
||||
import reduceUserPrompt from "../../commit/prompts/reduce-user.md" with { type: "text" };
|
||||
import type { ChangelogCategory, ConventionalAnalysis, FileObservation } from "../../commit/types";
|
||||
import { renderPromptTemplate } from "../../config/prompt-templates";
|
||||
import { toReasoningEffort } from "../../thinking";
|
||||
import { extractTextContent, extractToolCall, normalizeAnalysis, parseJsonPayload } from "../utils";
|
||||
|
||||
@@ -67,7 +67,7 @@ export async function runReducePhase({
|
||||
scopeCandidates,
|
||||
typesDescription,
|
||||
}: ReducePhaseInput): Promise<ConventionalAnalysis> {
|
||||
const prompt = renderPromptTemplate(reduceUserPrompt, {
|
||||
const userContent = prompt.render(reduceUserPrompt, {
|
||||
types_description: typesDescription,
|
||||
observations: observations.flatMap(obs => obs.observations.map(line => `- ${obs.file}: ${line}`)).join("\n"),
|
||||
stat,
|
||||
@@ -76,8 +76,8 @@ export async function runReducePhase({
|
||||
const response = await completeSimple(
|
||||
model,
|
||||
{
|
||||
systemPrompt: renderPromptTemplate(reduceSystemPrompt),
|
||||
messages: [{ role: "user", content: prompt, timestamp: Date.now() }],
|
||||
systemPrompt: prompt.render(reduceSystemPrompt),
|
||||
messages: [{ role: "user", content: userContent, timestamp: Date.now() }],
|
||||
tools: [ReduceTool],
|
||||
},
|
||||
{ apiKey, maxTokens: 2400, reasoning: toReasoningEffort(thinkingLevel) },
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import * as path from "node:path";
|
||||
import type { ThinkingLevel } from "@oh-my-pi/pi-agent-core";
|
||||
import type { Api, Model } from "@oh-my-pi/pi-ai";
|
||||
import { getProjectDir, logger } from "@oh-my-pi/pi-utils";
|
||||
import { getProjectDir, logger, prompt } from "@oh-my-pi/pi-utils";
|
||||
import { ModelRegistry } from "../config/model-registry";
|
||||
import { renderPromptTemplate } from "../config/prompt-templates";
|
||||
import { Settings } from "../config/settings";
|
||||
import { discoverAuthStorage } from "../sdk";
|
||||
import { loadProjectContextFiles } from "../system-prompt";
|
||||
@@ -26,7 +25,7 @@ import type { CommitCommandArgs, ConventionalAnalysis } from "./types";
|
||||
|
||||
const SUMMARY_MAX_CHARS = 72;
|
||||
const RECENT_COMMITS_COUNT = 8;
|
||||
const TYPES_DESCRIPTION = renderPromptTemplate(typesDescriptionPrompt);
|
||||
const TYPES_DESCRIPTION = prompt.render(typesDescriptionPrompt);
|
||||
|
||||
/**
|
||||
* Execute the omp commit pipeline for staged changes.
|
||||
@@ -236,7 +235,7 @@ async function generateSummaryWithRetry(input: {
|
||||
}
|
||||
|
||||
function buildRetryContext(base: string | undefined, errors: string[]): string {
|
||||
return renderPromptTemplate(summaryRetryPrompt, {
|
||||
return prompt.render(summaryRetryPrompt, {
|
||||
base_context: base,
|
||||
errors: errors.join("; "),
|
||||
});
|
||||
|
||||
@@ -1,13 +1,17 @@
|
||||
import * as fs from "node:fs";
|
||||
import * as path from "node:path";
|
||||
import { type ChunkAnchorStyle, formatAnchor } from "@oh-my-pi/pi-natives";
|
||||
import { getProjectDir, getProjectPromptsDir, getPromptsDir, logger } from "@oh-my-pi/pi-utils";
|
||||
import Handlebars from "handlebars";
|
||||
import {
|
||||
getProjectDir,
|
||||
getProjectPromptsDir,
|
||||
getPromptsDir,
|
||||
logger,
|
||||
parseFrontmatter,
|
||||
prompt,
|
||||
} from "@oh-my-pi/pi-utils";
|
||||
import { computeLineHash } from "../edit/modes/hashline";
|
||||
import { jtdToTypeScript } from "../tools/jtd-to-typescript";
|
||||
import { parseCommandArgs, substituteArgs } from "../utils/command-args";
|
||||
import { parseFrontmatter } from "../utils/frontmatter";
|
||||
import { formatPromptContent } from "../utils/prompt-format";
|
||||
|
||||
/**
|
||||
* Represents a prompt template loaded from a markdown file
|
||||
@@ -19,215 +23,7 @@ export interface PromptTemplate {
|
||||
source: string; // e.g., "(user)", "(project)", "(project:frontend)"
|
||||
}
|
||||
|
||||
export interface TemplateContext extends Record<string, unknown> {
|
||||
args?: string[];
|
||||
ARGUMENTS?: string;
|
||||
arguments?: string;
|
||||
}
|
||||
|
||||
const handlebars = Handlebars.create();
|
||||
|
||||
handlebars.registerHelper("arg", function (this: TemplateContext, index: number | string): string {
|
||||
const args = this.args ?? [];
|
||||
const parsedIndex = typeof index === "number" ? index : Number.parseInt(index, 10);
|
||||
if (!Number.isFinite(parsedIndex)) return "";
|
||||
const zeroBased = parsedIndex - 1;
|
||||
if (zeroBased < 0) return "";
|
||||
return args[zeroBased] ?? "";
|
||||
});
|
||||
|
||||
/**
|
||||
* {{#list items prefix="- " suffix="" join="\n"}}{{this}}{{/list}}
|
||||
* Renders an array with customizable prefix, suffix, and join separator.
|
||||
* Note: Use \n in join for newlines (will be unescaped automatically).
|
||||
*/
|
||||
handlebars.registerHelper(
|
||||
"list",
|
||||
function (this: unknown, context: unknown[], options: Handlebars.HelperOptions): string {
|
||||
if (!Array.isArray(context) || context.length === 0) return "";
|
||||
const prefix = (options.hash.prefix as string) ?? "";
|
||||
const suffix = (options.hash.suffix as string) ?? "";
|
||||
const rawSeparator = (options.hash.join as string) ?? "\n";
|
||||
const separator = rawSeparator.replace(/\\n/g, "\n").replace(/\\t/g, "\t");
|
||||
return context.map(item => `${prefix}${options.fn(item)}${suffix}`).join(separator);
|
||||
},
|
||||
);
|
||||
|
||||
/**
|
||||
* {{join array ", "}}
|
||||
* Joins an array with a separator (default: ", ").
|
||||
*/
|
||||
handlebars.registerHelper("join", (context: unknown[], separator?: unknown): string => {
|
||||
if (!Array.isArray(context)) return "";
|
||||
const sep = typeof separator === "string" ? separator : ", ";
|
||||
return context.join(sep);
|
||||
});
|
||||
|
||||
/**
|
||||
* {{default value "fallback"}}
|
||||
* Returns the value if truthy, otherwise returns the fallback.
|
||||
*/
|
||||
handlebars.registerHelper("default", (value: unknown, defaultValue: unknown): unknown => value || defaultValue);
|
||||
|
||||
/**
|
||||
* {{pluralize count "item" "items"}}
|
||||
* Returns "1 item" or "5 items" based on count.
|
||||
*/
|
||||
handlebars.registerHelper(
|
||||
"pluralize",
|
||||
(count: number, singular: string, plural: string): string => `${count} ${count === 1 ? singular : plural}`,
|
||||
);
|
||||
|
||||
/**
|
||||
* {{#when value "==" compare}}...{{else}}...{{/when}}
|
||||
* Conditional block with comparison operators: ==, ===, !=, !==, >, <, >=, <=
|
||||
*/
|
||||
handlebars.registerHelper(
|
||||
"when",
|
||||
function (this: unknown, lhs: unknown, operator: string, rhs: unknown, options: Handlebars.HelperOptions): string {
|
||||
const ops: Record<string, (a: unknown, b: unknown) => boolean> = {
|
||||
"==": (a, b) => a === b,
|
||||
"===": (a, b) => a === b,
|
||||
"!=": (a, b) => a !== b,
|
||||
"!==": (a, b) => a !== b,
|
||||
">": (a, b) => (a as number) > (b as number),
|
||||
"<": (a, b) => (a as number) < (b as number),
|
||||
">=": (a, b) => (a as number) >= (b as number),
|
||||
"<=": (a, b) => (a as number) <= (b as number),
|
||||
};
|
||||
const fn = ops[operator];
|
||||
if (!fn) return options.inverse(this);
|
||||
return fn(lhs, rhs) ? options.fn(this) : options.inverse(this);
|
||||
},
|
||||
);
|
||||
|
||||
/**
|
||||
* {{#ifAny a b c}}...{{else}}...{{/ifAny}}
|
||||
* True if any argument is truthy.
|
||||
*/
|
||||
handlebars.registerHelper("ifAny", function (this: unknown, ...args: unknown[]): string {
|
||||
const options = args.pop() as Handlebars.HelperOptions;
|
||||
return args.some(Boolean) ? options.fn(this) : options.inverse(this);
|
||||
});
|
||||
|
||||
/**
|
||||
* {{#ifAll a b c}}...{{else}}...{{/ifAll}}
|
||||
* True if all arguments are truthy.
|
||||
*/
|
||||
handlebars.registerHelper("ifAll", function (this: unknown, ...args: unknown[]): string {
|
||||
const options = args.pop() as Handlebars.HelperOptions;
|
||||
return args.every(Boolean) ? options.fn(this) : options.inverse(this);
|
||||
});
|
||||
|
||||
/**
|
||||
* {{#table rows headers="Col1|Col2"}}{{col1}}|{{col2}}{{/table}}
|
||||
* Generates a markdown table from an array of objects.
|
||||
*/
|
||||
handlebars.registerHelper(
|
||||
"table",
|
||||
function (this: unknown, context: unknown[], options: Handlebars.HelperOptions): string {
|
||||
if (!Array.isArray(context) || context.length === 0) return "";
|
||||
const headersStr = options.hash.headers as string | undefined;
|
||||
const headers = headersStr?.split("|") ?? [];
|
||||
const separator = headers.map(() => "---").join(" | ");
|
||||
const headerRow = headers.length > 0 ? `| ${headers.join(" | ")} |\n| ${separator} |\n` : "";
|
||||
const rows = context.map(item => `| ${options.fn(item).trim()} |`).join("\n");
|
||||
return headerRow + rows;
|
||||
},
|
||||
);
|
||||
|
||||
/**
|
||||
* {{#codeblock lang="diff"}}...{{/codeblock}}
|
||||
* Wraps content in a fenced code block.
|
||||
*/
|
||||
handlebars.registerHelper("codeblock", function (this: unknown, options: Handlebars.HelperOptions): string {
|
||||
const lang = (options.hash.lang as string) ?? "";
|
||||
const content = options.fn(this).trim();
|
||||
return `\`\`\`${lang}\n${content}\n\`\`\``;
|
||||
});
|
||||
|
||||
/**
|
||||
* {{#xml "tag"}}content{{/xml}}
|
||||
* Wraps content in XML-style tags. Returns empty string if content is empty.
|
||||
*/
|
||||
handlebars.registerHelper("xml", function (this: unknown, tag: string, options: Handlebars.HelperOptions): string {
|
||||
const content = options.fn(this).trim();
|
||||
if (!content) return "";
|
||||
return `<${tag}>\n${content}\n</${tag}>`;
|
||||
});
|
||||
|
||||
/**
|
||||
* {{escapeXml value}}
|
||||
* Escapes XML special characters: & < > "
|
||||
*/
|
||||
handlebars.registerHelper("escapeXml", (value: unknown): string => {
|
||||
if (value == null) return "";
|
||||
return String(value).replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
||||
});
|
||||
|
||||
/**
|
||||
* {{len array}}
|
||||
* Returns the length of an array or string.
|
||||
*/
|
||||
handlebars.registerHelper("len", (value: unknown): number => {
|
||||
if (Array.isArray(value)) return value.length;
|
||||
if (typeof value === "string") return value.length;
|
||||
return 0;
|
||||
});
|
||||
|
||||
/**
|
||||
* {{add a b}}
|
||||
* Adds two numbers.
|
||||
*/
|
||||
handlebars.registerHelper("add", (a: number, b: number): number => (a ?? 0) + (b ?? 0));
|
||||
|
||||
/**
|
||||
* {{sub a b}}
|
||||
* Subtracts b from a.
|
||||
*/
|
||||
handlebars.registerHelper("sub", (a: number, b: number): number => (a ?? 0) - (b ?? 0));
|
||||
|
||||
/**
|
||||
* {{#has collection item}}...{{else}}...{{/has}}
|
||||
* Checks if an array includes an item or if a Set/Map has a key.
|
||||
*/
|
||||
handlebars.registerHelper(
|
||||
"has",
|
||||
function (this: unknown, collection: unknown, item: unknown, options: Handlebars.HelperOptions): string {
|
||||
let found = false;
|
||||
if (Array.isArray(collection)) {
|
||||
found = collection.includes(item);
|
||||
} else if (collection instanceof Set) {
|
||||
found = collection.has(item);
|
||||
} else if (collection instanceof Map) {
|
||||
found = collection.has(item);
|
||||
} else if (collection && typeof collection === "object") {
|
||||
if (typeof item === "string" || typeof item === "number" || typeof item === "symbol") {
|
||||
found = item in collection;
|
||||
}
|
||||
}
|
||||
return found ? options.fn(this) : options.inverse(this);
|
||||
},
|
||||
);
|
||||
|
||||
/**
|
||||
* {{includes array item}}
|
||||
* Returns true if array includes item. For use in other helpers.
|
||||
*/
|
||||
handlebars.registerHelper("includes", (collection: unknown, item: unknown): boolean => {
|
||||
if (Array.isArray(collection)) return collection.includes(item);
|
||||
if (collection instanceof Set) return collection.has(item);
|
||||
if (collection instanceof Map) return collection.has(item);
|
||||
return false;
|
||||
});
|
||||
|
||||
/**
|
||||
* {{not value}}
|
||||
* Returns logical NOT of value. For use in subexpressions.
|
||||
*/
|
||||
handlebars.registerHelper("not", (value: unknown): boolean => !value);
|
||||
|
||||
handlebars.registerHelper("jtdToTypeScript", (schema: unknown): string => {
|
||||
prompt.registerHelper("jtdToTypeScript", (schema: unknown): string => {
|
||||
try {
|
||||
return jtdToTypeScript(schema);
|
||||
} catch {
|
||||
@@ -235,7 +31,7 @@ handlebars.registerHelper("jtdToTypeScript", (schema: unknown): string => {
|
||||
}
|
||||
});
|
||||
|
||||
handlebars.registerHelper("jsonStringify", (value: unknown): string => JSON.stringify(value));
|
||||
prompt.registerHelper("jsonStringify", (value: unknown): string => JSON.stringify(value));
|
||||
|
||||
/**
|
||||
* Renders a section separator:
|
||||
@@ -248,7 +44,7 @@ export function sectionSeparator(name: string): string {
|
||||
return `\n\n═══════════${name}═══════════\n`;
|
||||
}
|
||||
|
||||
handlebars.registerHelper("SECTION_SEPERATOR", (name: unknown): string => sectionSeparator(String(name)));
|
||||
prompt.registerHelper("SECTION_SEPERATOR", (name: unknown): string => sectionSeparator(String(name)));
|
||||
|
||||
function formatHashlineRef(lineNum: unknown, content: unknown): { num: number; text: string; ref: string } {
|
||||
const num = typeof lineNum === "number" ? lineNum : Number.parseInt(String(lineNum), 10);
|
||||
@@ -262,7 +58,7 @@ function formatHashlineRef(lineNum: unknown, content: unknown): { num: number; t
|
||||
* {{href lineNum "content"}} — compute a real hashline ref for prompt examples.
|
||||
* Returns `"lineNum#hash"` using the actual hash algorithm.
|
||||
*/
|
||||
handlebars.registerHelper("href", (lineNum: unknown, content: unknown): string => {
|
||||
prompt.registerHelper("href", (lineNum: unknown, content: unknown): string => {
|
||||
const { ref } = formatHashlineRef(lineNum, content);
|
||||
return JSON.stringify(ref);
|
||||
});
|
||||
@@ -271,7 +67,7 @@ handlebars.registerHelper("href", (lineNum: unknown, content: unknown): string =
|
||||
* {{hline lineNum "content"}} — format a full read-style line with prefix.
|
||||
* Returns `"lineNum#hash:content"`.
|
||||
*/
|
||||
handlebars.registerHelper("hline", (lineNum: unknown, content: unknown): string => {
|
||||
prompt.registerHelper("hline", (lineNum: unknown, content: unknown): string => {
|
||||
const { ref, text } = formatHashlineRef(lineNum, content);
|
||||
return `${ref}:${text}`;
|
||||
});
|
||||
@@ -280,7 +76,7 @@ handlebars.registerHelper("hline", (lineNum: unknown, content: unknown): string
|
||||
* {{anchor name checksum}} — render a branch anchor tag using the current anchor style.
|
||||
* Style is resolved from the template context (`anchorStyle`) or defaults to "full".
|
||||
*/
|
||||
handlebars.registerHelper("anchor", function (this: TemplateContext, name: string, checksum: string): string {
|
||||
prompt.registerHelper("anchor", function (this: prompt.TemplateContext, name: string, checksum: string): string {
|
||||
const style = (this.anchorStyle as ChunkAnchorStyle) ?? "full";
|
||||
return formatAnchor(name, checksum, style);
|
||||
});
|
||||
@@ -291,7 +87,7 @@ handlebars.registerHelper("anchor", function (this: TemplateContext, name: strin
|
||||
* In `kind` style each segment is trimmed to its kind prefix (`class.fn`).
|
||||
* In `bare` style the path is omitted (the model uses only `crc` to identify chunks).
|
||||
*/
|
||||
handlebars.registerHelper("sel", function (this: TemplateContext, chunkPath: string): string {
|
||||
prompt.registerHelper("sel", function (this: prompt.TemplateContext, chunkPath: string): string {
|
||||
const style = (this.anchorStyle as ChunkAnchorStyle) ?? "full";
|
||||
if (style === "full") return chunkPath;
|
||||
if (style === "bare") return "";
|
||||
@@ -327,12 +123,6 @@ export function appendInlineArgsFallback(
|
||||
return `${rendered}\n\n${argsText}`;
|
||||
}
|
||||
|
||||
export function renderPromptTemplate(template: string, context: TemplateContext = {}): string {
|
||||
const compiled = handlebars.compile(template, { noEscape: true, strict: false });
|
||||
const rendered = compiled(context ?? {});
|
||||
return formatPromptContent(rendered, { renderPhase: "post-render" });
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively scan a directory for .md files (and symlinks to .md files) and load them as prompt templates
|
||||
*/
|
||||
@@ -459,7 +249,7 @@ export function expandPromptTemplate(text: string, templates: PromptTemplate[]):
|
||||
const argsText = args.join(" ");
|
||||
const usesInlineArgPlaceholders = templateUsesInlineArgPlaceholders(template.content);
|
||||
const substituted = substituteArgs(template.content, args);
|
||||
const rendered = renderPromptTemplate(substituted, { args, ARGUMENTS: argsText, arguments: argsText });
|
||||
const rendered = prompt.render(substituted, { args, ARGUMENTS: argsText, arguments: argsText });
|
||||
return appendInlineArgsFallback(rendered, argsText, usesInlineArgPlaceholders);
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
* Primary provider for OMP native configs. Supports all capabilities.
|
||||
*/
|
||||
import * as path from "node:path";
|
||||
import { logger, tryParseJson } from "@oh-my-pi/pi-utils";
|
||||
import { logger, parseFrontmatter, tryParseJson } from "@oh-my-pi/pi-utils";
|
||||
import { registerProvider } from "../capability";
|
||||
import { type ContextFile, contextFileCapability } from "../capability/context-file";
|
||||
import { type Extension, type ExtensionManifest, extensionCapability } from "../capability/extension";
|
||||
@@ -22,7 +22,6 @@ import { type SystemPrompt, systemPromptCapability } from "../capability/system-
|
||||
import { type CustomTool, toolCapability } from "../capability/tool";
|
||||
import type { LoadContext, LoadResult } from "../capability/types";
|
||||
import { expandTilde } from "../tools/path-utils";
|
||||
import { parseFrontmatter } from "../utils/frontmatter";
|
||||
import {
|
||||
buildRuleFromMarkdown,
|
||||
createSourceMeta,
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
* User directory: ~/.codex
|
||||
*/
|
||||
import * as path from "node:path";
|
||||
import { logger } from "@oh-my-pi/pi-utils";
|
||||
import { logger, parseFrontmatter } from "@oh-my-pi/pi-utils";
|
||||
import { registerProvider } from "../capability";
|
||||
import type { ContextFile } from "../capability/context-file";
|
||||
import { contextFileCapability } from "../capability/context-file";
|
||||
@@ -28,7 +28,7 @@ import { slashCommandCapability } from "../capability/slash-command";
|
||||
import type { CustomTool } from "../capability/tool";
|
||||
import { toolCapability } from "../capability/tool";
|
||||
import type { LoadContext, LoadResult, SourceMeta } from "../capability/types";
|
||||
import { parseFrontmatter } from "../utils/frontmatter";
|
||||
|
||||
import {
|
||||
createSourceMeta,
|
||||
discoverExtensionModulePaths,
|
||||
|
||||
@@ -12,12 +12,13 @@
|
||||
* - instructions: *.instructions.md in .github/instructions/ with applyTo frontmatter
|
||||
*/
|
||||
import * as path from "node:path";
|
||||
import { parseFrontmatter } from "@oh-my-pi/pi-utils";
|
||||
import { registerProvider } from "../capability";
|
||||
import { type ContextFile, contextFileCapability } from "../capability/context-file";
|
||||
import { readFile } from "../capability/fs";
|
||||
import { type Instruction, instructionCapability } from "../capability/instruction";
|
||||
import type { LoadContext, LoadResult, SourceMeta } from "../capability/types";
|
||||
import { parseFrontmatter } from "../utils/frontmatter";
|
||||
|
||||
import { calculateDepth, createSourceMeta, getProjectPath, loadFilesFromDir } from "./helpers";
|
||||
|
||||
const PROVIDER_ID = "github";
|
||||
|
||||
@@ -3,13 +3,13 @@ import * as os from "node:os";
|
||||
import * as path from "node:path";
|
||||
import type { ThinkingLevel } from "@oh-my-pi/pi-agent-core";
|
||||
import { FileType, glob } from "@oh-my-pi/pi-natives";
|
||||
import { CONFIG_DIR_NAME, getConfigDirName, getProjectDir, tryParseJson } from "@oh-my-pi/pi-utils";
|
||||
import { CONFIG_DIR_NAME, getConfigDirName, getProjectDir, parseFrontmatter, tryParseJson } from "@oh-my-pi/pi-utils";
|
||||
import { readDirEntries, readFile } from "../capability/fs";
|
||||
import { parseRuleConditionAndScope, type Rule, type RuleFrontmatter } from "../capability/rule";
|
||||
import type { Skill, SkillFrontmatter } from "../capability/skill";
|
||||
import type { LoadContext, LoadResult, SourceMeta } from "../capability/types";
|
||||
import { parseThinkingLevel } from "../thinking";
|
||||
import { parseFrontmatter } from "../utils/frontmatter";
|
||||
|
||||
import { buildPluginDirRoot } from "./plugin-dir-roots";
|
||||
|
||||
/**
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
* Priority: 55 (tool-specific provider)
|
||||
*/
|
||||
import * as path from "node:path";
|
||||
import { logger, tryParseJson } from "@oh-my-pi/pi-utils";
|
||||
import { logger, parseFrontmatter, tryParseJson } from "@oh-my-pi/pi-utils";
|
||||
import { registerProvider } from "../capability";
|
||||
import { type ContextFile, contextFileCapability } from "../capability/context-file";
|
||||
import { type ExtensionModule, extensionModuleCapability } from "../capability/extension-module";
|
||||
@@ -26,7 +26,7 @@ import { type Settings, settingsCapability } from "../capability/settings";
|
||||
import { type Skill, skillCapability } from "../capability/skill";
|
||||
import { type SlashCommand, slashCommandCapability } from "../capability/slash-command";
|
||||
import type { LoadContext, LoadResult, SourceMeta } from "../capability/types";
|
||||
import { parseFrontmatter } from "../utils/frontmatter";
|
||||
|
||||
import {
|
||||
createSourceMeta,
|
||||
discoverExtensionModulePaths,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { AgentTool, AgentToolContext, AgentToolResult, AgentToolUpdateCallback } from "@oh-my-pi/pi-agent-core";
|
||||
import { renderPromptTemplate } from "../config/prompt-templates";
|
||||
import { prompt } from "@oh-my-pi/pi-utils";
|
||||
import {
|
||||
createLspWritethrough,
|
||||
type FileDiagnosticsResult,
|
||||
@@ -194,7 +194,7 @@ export class EditTool implements AgentTool<TInput> {
|
||||
return {
|
||||
chunk: {
|
||||
description: (session: ToolSession) =>
|
||||
renderPromptTemplate(chunkEditDescription, {
|
||||
prompt.render(chunkEditDescription, {
|
||||
anchorStyle: resolveAnchorStyle(session.settings),
|
||||
}),
|
||||
parameters: chunkEditParamsSchema,
|
||||
@@ -212,7 +212,7 @@ export class EditTool implements AgentTool<TInput> {
|
||||
},
|
||||
},
|
||||
patch: {
|
||||
description: () => renderPromptTemplate(patchDescription),
|
||||
description: () => prompt.render(patchDescription),
|
||||
parameters: patchEditSchema,
|
||||
invalidParamsMessage: "Invalid edit parameters for patch mode.",
|
||||
validate: isPatchParams,
|
||||
@@ -230,7 +230,7 @@ export class EditTool implements AgentTool<TInput> {
|
||||
},
|
||||
},
|
||||
hashline: {
|
||||
description: () => renderPromptTemplate(hashlineDescription),
|
||||
description: () => prompt.render(hashlineDescription),
|
||||
parameters: hashlineEditParamsSchema,
|
||||
invalidParamsMessage: "Invalid edit parameters for hashline mode.",
|
||||
validate: isHashlineParams,
|
||||
@@ -246,7 +246,7 @@ export class EditTool implements AgentTool<TInput> {
|
||||
},
|
||||
},
|
||||
replace: {
|
||||
description: () => renderPromptTemplate(replaceDescription),
|
||||
description: () => prompt.render(replaceDescription),
|
||||
parameters: replaceEditSchema,
|
||||
invalidParamsMessage: "Invalid edit parameters for replace mode.",
|
||||
validate: isReplaceParams,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { renderPromptTemplate } from "../../../../config/prompt-templates";
|
||||
import { prompt } from "@oh-my-pi/pi-utils";
|
||||
import type { CustomCommand, CustomCommandAPI } from "../../../../extensibility/custom-commands/types";
|
||||
import type { HookCommandContext } from "../../../../extensibility/hooks/types";
|
||||
import ciGreenRequestTemplate from "../../../../prompts/ci-green-request.md" with { type: "text" };
|
||||
@@ -20,6 +20,6 @@ export class GreenCommand implements CustomCommand {
|
||||
|
||||
async execute(_args: string[], _ctx: HookCommandContext): Promise<string> {
|
||||
const headTag = await getHeadTag(this.api);
|
||||
return renderPromptTemplate(ciGreenRequestTemplate, { headTag });
|
||||
return prompt.render(ciGreenRequestTemplate, { headTag });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
* rich context for the orchestrating agent to distribute work across
|
||||
* multiple reviewer agents based on diff weight and locality.
|
||||
*/
|
||||
import { renderPromptTemplate } from "../../../../config/prompt-templates";
|
||||
import { prompt } from "@oh-my-pi/pi-utils";
|
||||
import type { CustomCommand, CustomCommandAPI } from "../../../../extensibility/custom-commands/types";
|
||||
import type { HookCommandContext } from "../../../../extensibility/hooks/types";
|
||||
import reviewRequestTemplate from "../../../../prompts/review-request.md" with { type: "text" };
|
||||
@@ -209,7 +209,7 @@ function buildReviewPrompt(mode: string, stats: DiffStats, rawDiff: string): str
|
||||
hunksPreview: skipDiff ? getDiffPreview(f.hunks, linesPerFile) : "",
|
||||
}));
|
||||
|
||||
return renderPromptTemplate(reviewRequestTemplate, {
|
||||
return prompt.render(reviewRequestTemplate, {
|
||||
mode,
|
||||
files: filesWithExt,
|
||||
excluded: stats.excluded,
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
import type { AutocompleteItem } from "@oh-my-pi/pi-tui";
|
||||
import { parseFrontmatter, prompt } from "@oh-my-pi/pi-utils";
|
||||
import { slashCommandCapability } from "../capability/slash-command";
|
||||
import {
|
||||
appendInlineArgsFallback,
|
||||
renderPromptTemplate,
|
||||
templateUsesInlineArgPlaceholders,
|
||||
} from "../config/prompt-templates";
|
||||
import { appendInlineArgsFallback, templateUsesInlineArgPlaceholders } from "../config/prompt-templates";
|
||||
import type { SlashCommand } from "../discovery";
|
||||
import { loadCapability } from "../discovery";
|
||||
import {
|
||||
@@ -14,7 +11,6 @@ import {
|
||||
} from "../slash-commands/builtin-registry";
|
||||
import { EMBEDDED_COMMAND_TEMPLATES } from "../task/commands";
|
||||
import { parseCommandArgs, substituteArgs } from "../utils/command-args";
|
||||
import { parseFrontmatter } from "../utils/frontmatter";
|
||||
|
||||
export type SlashCommandSource = "extension" | "prompt" | "skill";
|
||||
|
||||
@@ -223,7 +219,7 @@ export function expandSlashCommand(text: string, fileCommands: FileSlashCommand[
|
||||
const argsText = args.join(" ");
|
||||
const usesInlineArgPlaceholders = templateUsesInlineArgPlaceholders(fileCommand.content);
|
||||
const substituted = substituteArgs(fileCommand.content, args);
|
||||
const rendered = renderPromptTemplate(substituted, { args, ARGUMENTS: argsText, arguments: argsText });
|
||||
const rendered = prompt.render(substituted, { args, ARGUMENTS: argsText, arguments: argsText });
|
||||
return appendInlineArgsFallback(rendered, argsText, usesInlineArgPlaceholders);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import * as fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import type { AgentTool, AgentToolContext, AgentToolResult, AgentToolUpdateCallback } from "@oh-my-pi/pi-agent-core";
|
||||
import { logger, once, untilAborted } from "@oh-my-pi/pi-utils";
|
||||
import { logger, once, prompt, untilAborted } from "@oh-my-pi/pi-utils";
|
||||
import type { BunFile } from "bun";
|
||||
import { renderPromptTemplate } from "../config/prompt-templates";
|
||||
import { type Theme, theme } from "../modes/theme/theme";
|
||||
import lspDescription from "../prompts/tools/lsp.md" with { type: "text" };
|
||||
import type { ToolSession } from "../tools";
|
||||
@@ -1088,7 +1087,7 @@ export class LspTool implements AgentTool<typeof lspSchema, LspToolDetails, Them
|
||||
readonly inline = true;
|
||||
|
||||
constructor(private readonly session: ToolSession) {
|
||||
this.description = renderPromptTemplate(lspDescription);
|
||||
this.description = prompt.render(lspDescription);
|
||||
}
|
||||
|
||||
static createIf(session: ToolSession): LspTool | null {
|
||||
|
||||
@@ -4,10 +4,9 @@ import * as fs from "node:fs/promises";
|
||||
import * as path from "node:path";
|
||||
import type { AgentMessage } from "@oh-my-pi/pi-agent-core";
|
||||
import { completeSimple, Effort, type Model } from "@oh-my-pi/pi-ai";
|
||||
import { getAgentDbPath, getMemoriesDir, logger, parseJsonlLenient } from "@oh-my-pi/pi-utils";
|
||||
import { getAgentDbPath, getMemoriesDir, logger, parseJsonlLenient, prompt } from "@oh-my-pi/pi-utils";
|
||||
import type { ModelRegistry } from "../config/model-registry";
|
||||
import { parseModelString } from "../config/model-resolver";
|
||||
import { renderPromptTemplate } from "../config/prompt-templates";
|
||||
import type { Settings } from "../config/settings";
|
||||
import consolidationTemplate from "../prompts/memories/consolidation.md" with { type: "text" };
|
||||
import readPathTemplate from "../prompts/memories/read-path.md" with { type: "text" };
|
||||
@@ -166,7 +165,7 @@ export async function buildMemoryToolDeveloperInstructions(
|
||||
const truncated = truncateByApproxTokens(summary, cfg.summaryInjectionTokenLimit);
|
||||
if (!truncated.trim()) return undefined;
|
||||
|
||||
return renderPromptTemplate(readPathTemplate, {
|
||||
return prompt.render(readPathTemplate, {
|
||||
memory_summary: truncated,
|
||||
});
|
||||
}
|
||||
@@ -585,7 +584,7 @@ async function runStage1Job(options: {
|
||||
const serializedItems = JSON.stringify(persisted);
|
||||
const budgetTokens = Math.floor(modelMaxTokens * config.rolloutPayloadPercent);
|
||||
const truncatedItems = truncateByApproxTokens(serializedItems, budgetTokens);
|
||||
const inputPrompt = renderPromptTemplate(stageOneInputTemplate, {
|
||||
const inputPrompt = prompt.render(stageOneInputTemplate, {
|
||||
thread_id: claim.threadId,
|
||||
response_items_json: truncatedItems,
|
||||
});
|
||||
@@ -716,7 +715,7 @@ async function runConsolidationModel(options: { memoryRoot: string; model: Model
|
||||
const { memoryRoot, model, apiKey } = options;
|
||||
const rawMemories = await Bun.file(path.join(memoryRoot, "raw_memories.md")).text();
|
||||
const rolloutSummaries = await readRolloutSummaries(memoryRoot);
|
||||
const input = renderPromptTemplate(consolidationTemplate, {
|
||||
const input = prompt.render(consolidationTemplate, {
|
||||
raw_memories: truncateByApproxTokens(rawMemories, 20_000),
|
||||
rollout_summaries: truncateByApproxTokens(rolloutSummaries, 12_000),
|
||||
});
|
||||
|
||||
@@ -31,7 +31,7 @@ import {
|
||||
visibleWidth,
|
||||
wrapTextWithAnsi,
|
||||
} from "@oh-my-pi/pi-tui";
|
||||
import { isEnoent } from "@oh-my-pi/pi-utils";
|
||||
import { isEnoent, prompt } from "@oh-my-pi/pi-utils";
|
||||
import { YAML } from "bun";
|
||||
import { getConfigDirs } from "../../config";
|
||||
import type { ModelRegistry } from "../../config/model-registry";
|
||||
@@ -41,7 +41,6 @@ import {
|
||||
resolveConfiguredModelPatterns,
|
||||
resolveModelOverride,
|
||||
} from "../../config/model-resolver";
|
||||
import { renderPromptTemplate } from "../../config/prompt-templates";
|
||||
import { Settings } from "../../config/settings";
|
||||
import agentCreationArchitectPrompt from "../../prompts/system/agent-creation-architect.md" with { type: "text" };
|
||||
import agentCreationUserPrompt from "../../prompts/system/agent-creation-user.md" with { type: "text" };
|
||||
@@ -627,8 +626,8 @@ export class AgentDashboard extends Container {
|
||||
throw new Error("No available model to generate agent specification.");
|
||||
}
|
||||
|
||||
const systemPrompt = renderPromptTemplate(agentCreationArchitectPrompt, { TASK_TOOL_NAME: "task" });
|
||||
const userPrompt = renderPromptTemplate(agentCreationUserPrompt, { request: description });
|
||||
const systemPrompt = prompt.render(agentCreationArchitectPrompt, { TASK_TOOL_NAME: "task" });
|
||||
const userPrompt = prompt.render(agentCreationUserPrompt, { request: description });
|
||||
|
||||
const { session } = await createAgentSession({
|
||||
cwd: this.cwd,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { AgentMessage } from "@oh-my-pi/pi-agent-core";
|
||||
import { type AssistantMessage, type Context, streamSimple } from "@oh-my-pi/pi-ai";
|
||||
import { renderPromptTemplate } from "../../config/prompt-templates";
|
||||
import { prompt } from "@oh-my-pi/pi-utils";
|
||||
import btwUserPrompt from "../../prompts/system/btw-user.md" with { type: "text" };
|
||||
import { toReasoningEffort } from "../../thinking";
|
||||
import { BtwPanelComponent } from "../components/btw-panel";
|
||||
@@ -137,7 +137,7 @@ export class BtwController {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: renderPromptTemplate(btwUserPrompt, { question }),
|
||||
text: prompt.render(btwUserPrompt, { question }),
|
||||
},
|
||||
],
|
||||
attribution: "user",
|
||||
|
||||
@@ -15,10 +15,9 @@ import {
|
||||
} from "@oh-my-pi/pi-ai";
|
||||
import type { Component, SlashCommand } from "@oh-my-pi/pi-tui";
|
||||
import { Container, Loader, Markdown, ProcessTerminal, Spacer, Text, TUI, visibleWidth } from "@oh-my-pi/pi-tui";
|
||||
import { APP_NAME, getProjectDir, hsvToRgb, isEnoent, logger, postmortem } from "@oh-my-pi/pi-utils";
|
||||
import { APP_NAME, getProjectDir, hsvToRgb, isEnoent, logger, postmortem, prompt } from "@oh-my-pi/pi-utils";
|
||||
import chalk from "chalk";
|
||||
import { KeybindingsManager } from "../config/keybindings";
|
||||
import { renderPromptTemplate } from "../config/prompt-templates";
|
||||
import { type Settings, settings } from "../config/settings";
|
||||
import type {
|
||||
ExtensionUIContext,
|
||||
@@ -899,11 +898,11 @@ export class InteractiveMode implements InteractiveModeContext {
|
||||
}
|
||||
this.session.setPlanReferencePath(options.finalPlanFilePath);
|
||||
this.session.markPlanReferenceSent();
|
||||
const prompt = renderPromptTemplate(planModeApprovedPrompt, {
|
||||
const planModePrompt = prompt.render(planModeApprovedPrompt, {
|
||||
planContent,
|
||||
finalPlanFilePath: options.finalPlanFilePath,
|
||||
});
|
||||
await this.session.prompt(prompt, { synthetic: true });
|
||||
await this.session.prompt(planModePrompt, { synthetic: true });
|
||||
}
|
||||
|
||||
async handlePlanModeCommand(initialPrompt?: string): Promise<void> {
|
||||
|
||||
@@ -19,6 +19,7 @@ import {
|
||||
getSearchDbDir,
|
||||
logger,
|
||||
postmortem,
|
||||
prompt,
|
||||
} from "@oh-my-pi/pi-utils";
|
||||
import chalk from "chalk";
|
||||
import { AsyncJobManager } from "./async";
|
||||
@@ -27,11 +28,7 @@ import { loadCapability } from "./capability";
|
||||
import { type Rule, ruleCapability } from "./capability/rule";
|
||||
import { ModelRegistry } from "./config/model-registry";
|
||||
import { formatModelString, parseModelPattern, parseModelString, resolveModelRoleValue } from "./config/model-resolver";
|
||||
import {
|
||||
loadPromptTemplates as loadPromptTemplatesInternal,
|
||||
type PromptTemplate,
|
||||
renderPromptTemplate,
|
||||
} from "./config/prompt-templates";
|
||||
import { loadPromptTemplates as loadPromptTemplatesInternal, type PromptTemplate } from "./config/prompt-templates";
|
||||
import { Settings, type SkillsSettings } from "./config/settings";
|
||||
import { CursorExecHandlers } from "./cursor";
|
||||
import "./discovery";
|
||||
@@ -872,7 +869,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
||||
onJobComplete: async (jobId, result, job) => {
|
||||
if (!session) return;
|
||||
const formattedResult = await formatAsyncResultForFollowUp(result);
|
||||
const message = renderPromptTemplate(asyncResultTemplate, { jobId, result: formattedResult });
|
||||
const message = prompt.render(asyncResultTemplate, { jobId, result: formattedResult });
|
||||
const durationMs = job ? Math.max(0, Date.now() - job.startTime) : undefined;
|
||||
await session.sendCustomMessage(
|
||||
{
|
||||
|
||||
@@ -50,7 +50,7 @@ import {
|
||||
parseRateLimitReason,
|
||||
} from "@oh-my-pi/pi-ai";
|
||||
import { killTree, MacOSPowerAssertion, type SearchDb } from "@oh-my-pi/pi-natives";
|
||||
import { abortableSleep, getAgentDbPath, isEnoent, logger, setNativeKillTree } from "@oh-my-pi/pi-utils";
|
||||
import { abortableSleep, getAgentDbPath, isEnoent, logger, prompt, setNativeKillTree } from "@oh-my-pi/pi-utils";
|
||||
import type { AsyncJob, AsyncJobManager } from "../async";
|
||||
import type { Rule } from "../capability/rule";
|
||||
import { MODEL_ROLE_IDS, type ModelRegistry } from "../config/model-registry";
|
||||
@@ -61,7 +61,7 @@ import {
|
||||
type ResolvedModelRoleValue,
|
||||
resolveModelRoleValue,
|
||||
} from "../config/model-resolver";
|
||||
import { expandPromptTemplate, type PromptTemplate, renderPromptTemplate } from "../config/prompt-templates";
|
||||
import { expandPromptTemplate, type PromptTemplate } from "../config/prompt-templates";
|
||||
import type { Settings, SkillsSettings } from "../config/settings";
|
||||
import { normalizeDiff, normalizeToLF, ParseError, previewPatch, stripBom } from "../edit";
|
||||
import { type BashResult, executeBash as executeBashCommand } from "../exec/bash-executor";
|
||||
@@ -317,7 +317,7 @@ interface HandoffOptions {
|
||||
|
||||
/** Standard thinking levels */
|
||||
|
||||
const AUTO_HANDOFF_THRESHOLD_FOCUS = renderPromptTemplate(autoHandoffThresholdFocusPrompt);
|
||||
const AUTO_HANDOFF_THRESHOLD_FOCUS = prompt.render(autoHandoffThresholdFocusPrompt);
|
||||
|
||||
type RetryFallbackChains = Record<string, string[]>;
|
||||
|
||||
@@ -1139,7 +1139,7 @@ export class AgentSession {
|
||||
if (this.#pendingTtsrInjections.length === 0) return undefined;
|
||||
const rules = this.#pendingTtsrInjections;
|
||||
const content = rules
|
||||
.map(r => renderPromptTemplate(ttsrInterruptTemplate, { name: r.name, path: r.path, content: r.content }))
|
||||
.map(r => prompt.render(ttsrInterruptTemplate, { name: r.name, path: r.path, content: r.content }))
|
||||
.join("\n\n");
|
||||
this.#pendingTtsrInjections = [];
|
||||
return { content, rules };
|
||||
@@ -2210,7 +2210,7 @@ export class AgentSession {
|
||||
throw error;
|
||||
}
|
||||
|
||||
const content = renderPromptTemplate(planModeReferencePrompt, {
|
||||
const content = prompt.render(planModeReferencePrompt, {
|
||||
planFilePath,
|
||||
planContent,
|
||||
});
|
||||
@@ -2247,7 +2247,7 @@ export class AgentSession {
|
||||
: sessionPlanUrl;
|
||||
|
||||
const planExists = fs.existsSync(resolvedPlanPath);
|
||||
const content = renderPromptTemplate(planModeActivePrompt, {
|
||||
const content = prompt.render(planModeActivePrompt, {
|
||||
planFilePath: displayPlanPath,
|
||||
planExists,
|
||||
askToolName: "ask",
|
||||
@@ -3773,7 +3773,7 @@ export class AgentSession {
|
||||
}
|
||||
|
||||
// Build the handoff prompt
|
||||
const handoffPrompt = renderPromptTemplate(handoffDocumentPrompt, {
|
||||
const handoffPrompt = prompt.render(handoffDocumentPrompt, {
|
||||
additionalFocus: customInstructions,
|
||||
});
|
||||
|
||||
@@ -4041,7 +4041,7 @@ export class AgentSession {
|
||||
return;
|
||||
}
|
||||
|
||||
const reminder = renderPromptTemplate(planModeToolDecisionReminderPrompt, {
|
||||
const reminder = prompt.render(planModeToolDecisionReminderPrompt, {
|
||||
askToolName: "ask",
|
||||
exitToolName: "exit_plan_mode",
|
||||
});
|
||||
@@ -4088,7 +4088,7 @@ export class AgentSession {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const eagerTodoReminder = renderPromptTemplate(eagerTodoPrompt);
|
||||
const eagerTodoReminder = prompt.render(eagerTodoPrompt);
|
||||
|
||||
return {
|
||||
message: {
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
import type { AgentMessage } from "@oh-my-pi/pi-agent-core";
|
||||
import type { Model } from "@oh-my-pi/pi-ai";
|
||||
import { completeSimple } from "@oh-my-pi/pi-ai";
|
||||
import { renderPromptTemplate } from "../../config/prompt-templates";
|
||||
import { prompt } from "@oh-my-pi/pi-utils";
|
||||
import branchSummaryPrompt from "../../prompts/compaction/branch-summary.md" with { type: "text" };
|
||||
import branchSummaryPreamble from "../../prompts/compaction/branch-summary-preamble.md" with { type: "text" };
|
||||
import {
|
||||
@@ -244,9 +244,9 @@ export function prepareBranchEntries(entries: SessionEntry[], tokenBudget: numbe
|
||||
// Summary Generation
|
||||
// ============================================================================
|
||||
|
||||
const BRANCH_SUMMARY_PREAMBLE = renderPromptTemplate(branchSummaryPreamble);
|
||||
const BRANCH_SUMMARY_PREAMBLE = prompt.render(branchSummaryPreamble);
|
||||
|
||||
const BRANCH_SUMMARY_PROMPT = renderPromptTemplate(branchSummaryPrompt);
|
||||
const BRANCH_SUMMARY_PROMPT = prompt.render(branchSummaryPrompt);
|
||||
|
||||
/**
|
||||
* Generate a summary of abandoned branch entries.
|
||||
|
||||
@@ -26,8 +26,7 @@ import {
|
||||
getOpenAIResponsesHistoryPayload,
|
||||
normalizeResponsesToolCallId,
|
||||
} from "@oh-my-pi/pi-ai/utils";
|
||||
import { logger } from "@oh-my-pi/pi-utils";
|
||||
import { renderPromptTemplate } from "../../config/prompt-templates";
|
||||
import { logger, prompt } from "@oh-my-pi/pi-utils";
|
||||
import compactionShortSummaryPrompt from "../../prompts/compaction/compaction-short-summary.md" with { type: "text" };
|
||||
import compactionSummaryPrompt from "../../prompts/compaction/compaction-summary.md" with { type: "text" };
|
||||
import compactionTurnPrefixPrompt from "../../prompts/compaction/compaction-turn-prefix.md" with { type: "text" };
|
||||
@@ -474,11 +473,11 @@ export function findCutPoint(
|
||||
// Summarization
|
||||
// ============================================================================
|
||||
|
||||
const SUMMARIZATION_PROMPT = renderPromptTemplate(compactionSummaryPrompt);
|
||||
const SUMMARIZATION_PROMPT = prompt.render(compactionSummaryPrompt);
|
||||
|
||||
const UPDATE_SUMMARIZATION_PROMPT = renderPromptTemplate(compactionUpdateSummaryPrompt);
|
||||
const UPDATE_SUMMARIZATION_PROMPT = prompt.render(compactionUpdateSummaryPrompt);
|
||||
|
||||
const SHORT_SUMMARY_PROMPT = renderPromptTemplate(compactionShortSummaryPrompt);
|
||||
const SHORT_SUMMARY_PROMPT = prompt.render(compactionShortSummaryPrompt);
|
||||
|
||||
function formatAdditionalContext(context: string[] | undefined): string {
|
||||
if (!context || context.length === 0) return "";
|
||||
@@ -1201,7 +1200,7 @@ export function prepareCompaction(
|
||||
// Main compaction function
|
||||
// ============================================================================
|
||||
|
||||
const TURN_PREFIX_SUMMARIZATION_PROMPT = renderPromptTemplate(compactionTurnPrefixPrompt);
|
||||
const TURN_PREFIX_SUMMARIZATION_PROMPT = prompt.render(compactionTurnPrefixPrompt);
|
||||
|
||||
/**
|
||||
* Generate summaries for compaction using prepared data.
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
*/
|
||||
import type { AgentMessage } from "@oh-my-pi/pi-agent-core";
|
||||
import type { Message } from "@oh-my-pi/pi-ai";
|
||||
import { renderPromptTemplate } from "../../config/prompt-templates";
|
||||
import { prompt } from "@oh-my-pi/pi-utils";
|
||||
import fileOperationsTemplate from "../../prompts/system/file-operations.md" with { type: "text" };
|
||||
import summarizationSystemPrompt from "../../prompts/system/summarization-system.md" with { type: "text" };
|
||||
|
||||
@@ -86,7 +86,7 @@ function stripFileOperationTags(summary: string): string {
|
||||
}
|
||||
export function formatFileOperations(readFiles: string[], modifiedFiles: string[]): string {
|
||||
if (readFiles.length === 0 && modifiedFiles.length === 0) return "";
|
||||
return renderPromptTemplate(fileOperationsTemplate, {
|
||||
return prompt.render(fileOperationsTemplate, {
|
||||
readFiles: truncateFileList(readFiles),
|
||||
modifiedFiles: truncateFileList(modifiedFiles),
|
||||
});
|
||||
@@ -181,4 +181,4 @@ export function serializeConversation(messages: Message[]): string {
|
||||
// Summarization System Prompt
|
||||
// ============================================================================
|
||||
|
||||
export const SUMMARIZATION_SYSTEM_PROMPT = renderPromptTemplate(summarizationSystemPrompt);
|
||||
export const SUMMARIZATION_SYSTEM_PROMPT = prompt.render(summarizationSystemPrompt);
|
||||
|
||||
@@ -14,7 +14,7 @@ import type {
|
||||
TextContent,
|
||||
ToolResultMessage,
|
||||
} from "@oh-my-pi/pi-ai";
|
||||
import { renderPromptTemplate } from "../config/prompt-templates";
|
||||
import { prompt } from "@oh-my-pi/pi-utils";
|
||||
import branchSummaryContextPrompt from "../prompts/compaction/branch-summary-context.md" with { type: "text" };
|
||||
import compactionSummaryContextPrompt from "../prompts/compaction/compaction-summary-context.md" with { type: "text" };
|
||||
import type { OutputMeta } from "../tools/output-meta";
|
||||
@@ -310,7 +310,7 @@ export function convertToLlm(messages: AgentMessage[]): Message[] {
|
||||
content: [
|
||||
{
|
||||
type: "text" as const,
|
||||
text: renderPromptTemplate(BRANCH_SUMMARY_TEMPLATE, { summary: m.summary }),
|
||||
text: prompt.render(BRANCH_SUMMARY_TEMPLATE, { summary: m.summary }),
|
||||
},
|
||||
],
|
||||
attribution: "agent",
|
||||
@@ -322,7 +322,7 @@ export function convertToLlm(messages: AgentMessage[]): Message[] {
|
||||
content: [
|
||||
{
|
||||
type: "text" as const,
|
||||
text: renderPromptTemplate(COMPACTION_SUMMARY_TEMPLATE, { summary: m.summary }),
|
||||
text: prompt.render(COMPACTION_SUMMARY_TEMPLATE, { summary: m.summary }),
|
||||
},
|
||||
],
|
||||
attribution: "agent",
|
||||
|
||||
@@ -6,17 +6,15 @@ import * as fs from "node:fs";
|
||||
import * as os from "node:os";
|
||||
import * as path from "node:path";
|
||||
import type { AgentTool } from "@oh-my-pi/pi-agent-core";
|
||||
import { $env, getGpuCachePath, getProjectDir, hasFsCode, isEnoent, logger } from "@oh-my-pi/pi-utils";
|
||||
import { $env, getGpuCachePath, getProjectDir, hasFsCode, isEnoent, logger, prompt } from "@oh-my-pi/pi-utils";
|
||||
import { $ } from "bun";
|
||||
import { contextFileCapability } from "./capability/context-file";
|
||||
import { systemPromptCapability } from "./capability/system-prompt";
|
||||
import { renderPromptTemplate } from "./config/prompt-templates";
|
||||
import type { SkillsSettings } from "./config/settings";
|
||||
import { type ContextFile, loadCapability, type SystemPrompt as SystemPromptFile } from "./discovery";
|
||||
import { loadSkills, type Skill } from "./extensibility/skills";
|
||||
import customSystemPromptTemplate from "./prompts/system/custom-system-prompt.md" with { type: "text" };
|
||||
import systemPromptTemplate from "./prompts/system/system-prompt.md" with { type: "text" };
|
||||
import { formatPromptContent } from "./utils/prompt-format";
|
||||
|
||||
interface AlwaysApplyRule {
|
||||
name: string;
|
||||
@@ -25,7 +23,7 @@ interface AlwaysApplyRule {
|
||||
}
|
||||
|
||||
function normalizePromptBlock(content: string): string {
|
||||
return formatPromptContent(content, { renderPhase: "post-render" }).trim();
|
||||
return prompt.format(content, { renderPhase: "post-render" }).trim();
|
||||
}
|
||||
|
||||
function splitComparablePromptBlocks(content: string | null | undefined): string[] {
|
||||
@@ -608,5 +606,5 @@ export async function buildSystemPrompt(options: BuildSystemPromptOptions = {}):
|
||||
eagerTasks,
|
||||
secretsEnabled,
|
||||
};
|
||||
return renderPromptTemplate(resolvedCustomPrompt ? customSystemPromptTemplate : systemPromptTemplate, data);
|
||||
return prompt.render(resolvedCustomPrompt ? customSystemPromptTemplate : systemPromptTemplate, data);
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
* Agents are embedded at build time via Bun's import with { type: "text" }.
|
||||
*/
|
||||
import { Effort } from "@oh-my-pi/pi-ai";
|
||||
import { renderPromptTemplate } from "../config/prompt-templates";
|
||||
import { parseFrontmatter, prompt } from "@oh-my-pi/pi-utils";
|
||||
import { parseAgentFields } from "../discovery/helpers";
|
||||
import designerMd from "../prompts/agents/designer.md" with { type: "text" };
|
||||
import exploreMd from "../prompts/agents/explore.md" with { type: "text" };
|
||||
@@ -15,7 +15,7 @@ import oracleMd from "../prompts/agents/oracle.md" with { type: "text" };
|
||||
import planMd from "../prompts/agents/plan.md" with { type: "text" };
|
||||
import reviewerMd from "../prompts/agents/reviewer.md" with { type: "text" };
|
||||
import taskMd from "../prompts/agents/task.md" with { type: "text" };
|
||||
import { parseFrontmatter } from "../utils/frontmatter";
|
||||
|
||||
import type { AgentDefinition, AgentSource } from "./types";
|
||||
|
||||
interface AgentFrontmatter {
|
||||
@@ -35,9 +35,9 @@ interface EmbeddedAgentDef {
|
||||
}
|
||||
|
||||
function buildAgentContent(def: EmbeddedAgentDef): string {
|
||||
const body = renderPromptTemplate(def.template);
|
||||
const body = prompt.render(def.template);
|
||||
if (!def.frontmatter) return body;
|
||||
return renderPromptTemplate(agentFrontmatterTemplate, { ...def.frontmatter, body });
|
||||
return prompt.render(agentFrontmatterTemplate, { ...def.frontmatter, body });
|
||||
}
|
||||
|
||||
const EMBEDDED_AGENT_DEFS: EmbeddedAgentDef[] = [
|
||||
|
||||
@@ -4,16 +4,13 @@
|
||||
* Commands are embedded at build time via Bun's import with { type: "text" }.
|
||||
*/
|
||||
import * as path from "node:path";
|
||||
import { parseFrontmatter, prompt } from "@oh-my-pi/pi-utils";
|
||||
import { type SlashCommand, slashCommandCapability } from "../capability/slash-command";
|
||||
import { renderPromptTemplate } from "../config/prompt-templates";
|
||||
import { loadCapability } from "../discovery";
|
||||
// Embed command markdown files at build time
|
||||
import initMd from "../prompts/agents/init.md" with { type: "text" };
|
||||
import { parseFrontmatter } from "../utils/frontmatter";
|
||||
|
||||
const EMBEDDED_COMMANDS: { name: string; content: string }[] = [
|
||||
{ name: "init.md", content: renderPromptTemplate(initMd) },
|
||||
];
|
||||
const EMBEDDED_COMMANDS: { name: string; content: string }[] = [{ name: "init.md", content: prompt.render(initMd) }];
|
||||
|
||||
export const EMBEDDED_COMMAND_TEMPLATES: ReadonlyArray<{ name: string; content: string }> = EMBEDDED_COMMANDS;
|
||||
|
||||
|
||||
@@ -6,12 +6,12 @@
|
||||
import path from "node:path";
|
||||
import type { AgentEvent, ThinkingLevel } from "@oh-my-pi/pi-agent-core";
|
||||
import type { SearchDb } from "@oh-my-pi/pi-natives";
|
||||
import { logger, untilAborted } from "@oh-my-pi/pi-utils";
|
||||
import { logger, prompt, untilAborted } from "@oh-my-pi/pi-utils";
|
||||
import type { TSchema } from "@sinclair/typebox";
|
||||
import Ajv, { type ValidateFunction } from "ajv";
|
||||
import { ModelRegistry } from "../config/model-registry";
|
||||
import { resolveModelOverride } from "../config/model-resolver";
|
||||
import { type PromptTemplate, renderPromptTemplate } from "../config/prompt-templates";
|
||||
import type { PromptTemplate } from "../config/prompt-templates";
|
||||
import { Settings } from "../config/settings";
|
||||
import { SETTINGS_SCHEMA, type SettingPath } from "../config/settings-schema";
|
||||
import type { CustomTool } from "../extensibility/custom-tools/types";
|
||||
@@ -965,7 +965,7 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
|
||||
skills: options.skills,
|
||||
promptTemplates: options.promptTemplates,
|
||||
systemPrompt: defaultPrompt =>
|
||||
renderPromptTemplate(subagentSystemPromptTemplate, {
|
||||
prompt.render(subagentSystemPromptTemplate, {
|
||||
base: defaultPrompt,
|
||||
agent: agent.systemPrompt,
|
||||
worktree: worktree ?? "",
|
||||
@@ -1106,7 +1106,7 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
|
||||
while (!submitResultCalled && retryCount < MAX_SUBMIT_RESULT_RETRIES && !abortSignal.aborted) {
|
||||
try {
|
||||
retryCount++;
|
||||
const reminder = renderPromptTemplate(submitReminderTemplate, {
|
||||
const reminder = prompt.render(submitReminderTemplate, {
|
||||
retryCount,
|
||||
maxRetries: MAX_SUBMIT_RESULT_RETRIES,
|
||||
});
|
||||
|
||||
@@ -17,10 +17,9 @@ import * as os from "node:os";
|
||||
import path from "node:path";
|
||||
import type { AgentTool, AgentToolResult, AgentToolUpdateCallback } from "@oh-my-pi/pi-agent-core";
|
||||
import type { Usage } from "@oh-my-pi/pi-ai";
|
||||
import { $env, Snowflake } from "@oh-my-pi/pi-utils";
|
||||
import { $env, prompt, Snowflake } from "@oh-my-pi/pi-utils";
|
||||
import type { ToolSession } from "..";
|
||||
import { resolveAgentModelPatterns } from "../config/model-resolver";
|
||||
import { renderPromptTemplate } from "../config/prompt-templates";
|
||||
import type { Theme } from "../modes/theme/theme";
|
||||
import planModeSubagentPrompt from "../prompts/system/plan-mode-subagent.md" with { type: "text" };
|
||||
import taskDescriptionTemplate from "../prompts/tools/task.md" with { type: "text" };
|
||||
@@ -136,7 +135,7 @@ function renderDescription(
|
||||
disabledAgents: string[],
|
||||
): string {
|
||||
const filteredAgents = disabledAgents.length > 0 ? agents.filter(a => !disabledAgents.includes(a.name)) : agents;
|
||||
return renderPromptTemplate(taskDescriptionTemplate, {
|
||||
return prompt.render(taskDescriptionTemplate, {
|
||||
agents: filteredAgents,
|
||||
MAX_CONCURRENCY: maxConcurrency,
|
||||
isolationEnabled,
|
||||
@@ -1157,7 +1156,7 @@ export class TaskTool implements AgentTool<TaskSchema, TaskToolDetails, Theme> {
|
||||
|
||||
const outputIds = results.filter(r => !r.aborted || r.output.trim()).map(r => `agent://${r.id}`);
|
||||
const backendSummaryPrefix = isolationBackendWarning ? `\n\n${isolationBackendWarning}` : "";
|
||||
const summary = renderPromptTemplate(taskSummaryTemplate, {
|
||||
const summary = prompt.render(taskSummaryTemplate, {
|
||||
successCount,
|
||||
totalCount: results.length,
|
||||
cancelledCount,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { renderPromptTemplate } from "../config/prompt-templates";
|
||||
import { prompt } from "@oh-my-pi/pi-utils";
|
||||
import subagentUserPromptTemplate from "../prompts/system/subagent-user-prompt.md" with { type: "text" };
|
||||
import type { TaskItem } from "./types";
|
||||
|
||||
@@ -25,7 +25,7 @@ export function renderTemplate(context: string | undefined, task: TaskItem): Ren
|
||||
return { task: assignment || context!, assignment: assignment || context!, id, description };
|
||||
}
|
||||
return {
|
||||
task: renderPromptTemplate(subagentUserPromptTemplate, { context, assignment }),
|
||||
task: prompt.render(subagentUserPromptTemplate, { context, assignment }),
|
||||
assignment,
|
||||
id,
|
||||
description,
|
||||
|
||||
@@ -17,9 +17,8 @@
|
||||
|
||||
import type { AgentTool, AgentToolContext, AgentToolResult, AgentToolUpdateCallback } from "@oh-my-pi/pi-agent-core";
|
||||
import { type Component, Container, Markdown, renderInlineMarkdown, TERMINAL, Text } from "@oh-my-pi/pi-tui";
|
||||
import { untilAborted } from "@oh-my-pi/pi-utils";
|
||||
import { prompt, untilAborted } from "@oh-my-pi/pi-utils";
|
||||
import { type Static, Type } from "@sinclair/typebox";
|
||||
import { renderPromptTemplate } from "../config/prompt-templates";
|
||||
import type { RenderResultOptions } from "../extensibility/custom-tools/types";
|
||||
import { getMarkdownTheme, type Theme, theme } from "../modes/theme/theme";
|
||||
import askDescription from "../prompts/tools/ask.md" with { type: "text" };
|
||||
@@ -385,7 +384,7 @@ export class AskTool implements AgentTool<typeof askSchema, AskToolDetails> {
|
||||
readonly strict = true;
|
||||
|
||||
constructor(private readonly session: ToolSession) {
|
||||
this.description = renderPromptTemplate(askDescription);
|
||||
this.description = prompt.render(askDescription);
|
||||
}
|
||||
|
||||
static createIf(session: ToolSession): AskTool | null {
|
||||
|
||||
@@ -3,9 +3,8 @@ import type { AgentTool, AgentToolContext, AgentToolResult, AgentToolUpdateCallb
|
||||
import { type AstReplaceChange, astEdit } from "@oh-my-pi/pi-natives";
|
||||
import type { Component } from "@oh-my-pi/pi-tui";
|
||||
import { Text } from "@oh-my-pi/pi-tui";
|
||||
import { untilAborted } from "@oh-my-pi/pi-utils";
|
||||
import { prompt, untilAborted } from "@oh-my-pi/pi-utils";
|
||||
import { type Static, Type } from "@sinclair/typebox";
|
||||
import { renderPromptTemplate } from "../config/prompt-templates";
|
||||
import { computeLineHash } from "../edit/modes/hashline";
|
||||
import type { RenderResultOptions } from "../extensibility/custom-tools/types";
|
||||
import type { Theme } from "../modes/theme/theme";
|
||||
@@ -71,7 +70,7 @@ export class AstEditTool implements AgentTool<typeof astEditSchema, AstEditToolD
|
||||
readonly strict = true;
|
||||
readonly deferrable = true;
|
||||
constructor(private readonly session: ToolSession) {
|
||||
this.description = renderPromptTemplate(astEditDescription);
|
||||
this.description = prompt.render(astEditDescription);
|
||||
}
|
||||
|
||||
async execute(
|
||||
|
||||
@@ -3,9 +3,8 @@ import type { AgentTool, AgentToolContext, AgentToolResult, AgentToolUpdateCallb
|
||||
import { type AstFindMatch, astGrep } from "@oh-my-pi/pi-natives";
|
||||
import type { Component } from "@oh-my-pi/pi-tui";
|
||||
import { Text } from "@oh-my-pi/pi-tui";
|
||||
import { untilAborted } from "@oh-my-pi/pi-utils";
|
||||
import { prompt, untilAborted } from "@oh-my-pi/pi-utils";
|
||||
import { type Static, Type } from "@sinclair/typebox";
|
||||
import { renderPromptTemplate } from "../config/prompt-templates";
|
||||
import { computeLineHash } from "../edit/modes/hashline";
|
||||
import type { RenderResultOptions } from "../extensibility/custom-tools/types";
|
||||
import type { Theme } from "../modes/theme/theme";
|
||||
@@ -65,7 +64,7 @@ export class AstGrepTool implements AgentTool<typeof astGrepSchema, AstGrepToolD
|
||||
readonly strict = true;
|
||||
|
||||
constructor(private readonly session: ToolSession) {
|
||||
this.description = renderPromptTemplate(astGrepDescription);
|
||||
this.description = prompt.render(astGrepDescription);
|
||||
}
|
||||
|
||||
async execute(
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { AgentTool, AgentToolContext, AgentToolResult, AgentToolUpdateCallback } from "@oh-my-pi/pi-agent-core";
|
||||
import { prompt } from "@oh-my-pi/pi-utils";
|
||||
import { type Static, Type } from "@sinclair/typebox";
|
||||
import { renderPromptTemplate } from "../config/prompt-templates";
|
||||
import awaitDescription from "../prompts/tools/await.md" with { type: "text" };
|
||||
import type { ToolSession } from "./index";
|
||||
|
||||
@@ -36,7 +36,7 @@ export class AwaitTool implements AgentTool<typeof awaitSchema, AwaitToolDetails
|
||||
readonly strict = true;
|
||||
|
||||
constructor(private readonly session: ToolSession) {
|
||||
this.description = renderPromptTemplate(awaitDescription);
|
||||
this.description = prompt.render(awaitDescription);
|
||||
}
|
||||
|
||||
static createIf(session: ToolSession): AwaitTool | null {
|
||||
|
||||
@@ -3,9 +3,8 @@ import * as path from "node:path";
|
||||
import type { AgentTool, AgentToolContext, AgentToolResult, AgentToolUpdateCallback } from "@oh-my-pi/pi-agent-core";
|
||||
import type { Component } from "@oh-my-pi/pi-tui";
|
||||
import { ImageProtocol, TERMINAL, Text } from "@oh-my-pi/pi-tui";
|
||||
import { $env, getProjectDir, isEnoent } from "@oh-my-pi/pi-utils";
|
||||
import { $env, getProjectDir, isEnoent, prompt } from "@oh-my-pi/pi-utils";
|
||||
import { Type } from "@sinclair/typebox";
|
||||
import { renderPromptTemplate } from "../config/prompt-templates";
|
||||
import { type BashResult, executeBash } from "../exec/bash-executor";
|
||||
import type { RenderResultOptions } from "../extensibility/custom-tools/types";
|
||||
import { truncateToVisualLines } from "../modes/components/visual-truncate";
|
||||
@@ -218,7 +217,7 @@ export class BashTool implements AgentTool<BashToolSchema, BashToolDetails> {
|
||||
constructor(private readonly session: ToolSession) {
|
||||
this.#asyncEnabled = this.session.settings.get("async.enabled");
|
||||
this.parameters = this.#asyncEnabled ? bashSchemaWithAsync : bashSchemaBase;
|
||||
this.description = renderPromptTemplate(bashDescription, {
|
||||
this.description = prompt.render(bashDescription, {
|
||||
asyncEnabled: this.#asyncEnabled,
|
||||
hasAstGrep: this.session.settings.get("astGrep.enabled"),
|
||||
hasAstEdit: this.session.settings.get("astEdit.enabled"),
|
||||
|
||||
@@ -4,7 +4,7 @@ import * as path from "node:path";
|
||||
import { Readability } from "@mozilla/readability";
|
||||
import type { AgentTool, AgentToolContext, AgentToolResult, AgentToolUpdateCallback } from "@oh-my-pi/pi-agent-core";
|
||||
import { StringEnum } from "@oh-my-pi/pi-ai";
|
||||
import { $which, getPuppeteerDir, logger, Snowflake, untilAborted } from "@oh-my-pi/pi-utils";
|
||||
import { $which, getPuppeteerDir, logger, prompt, Snowflake, untilAborted } from "@oh-my-pi/pi-utils";
|
||||
import { type Static, Type } from "@sinclair/typebox";
|
||||
import { type HTMLElement, parseHTML } from "linkedom";
|
||||
import type {
|
||||
@@ -16,7 +16,6 @@ import type {
|
||||
default as Puppeteer,
|
||||
SerializedAXNode,
|
||||
} from "puppeteer";
|
||||
import { renderPromptTemplate } from "../config/prompt-templates";
|
||||
import browserDescription from "../prompts/tools/browser.md" with { type: "text" };
|
||||
import type { ToolSession } from "../sdk";
|
||||
import { resizeImage } from "../utils/image-resize";
|
||||
@@ -539,7 +538,7 @@ export class BrowserTool implements AgentTool<typeof browserSchema, BrowserToolD
|
||||
readonly #patchedClients = new WeakSet<object>();
|
||||
|
||||
constructor(private readonly session: ToolSession) {
|
||||
this.description = renderPromptTemplate(browserDescription, {});
|
||||
this.description = prompt.render(browserDescription, {});
|
||||
}
|
||||
|
||||
async #closeBrowser(): Promise<void> {
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import type { AgentTool, AgentToolResult } from "@oh-my-pi/pi-agent-core";
|
||||
import type { Component } from "@oh-my-pi/pi-tui";
|
||||
import { Text } from "@oh-my-pi/pi-tui";
|
||||
import { untilAborted } from "@oh-my-pi/pi-utils";
|
||||
import { prompt, untilAborted } from "@oh-my-pi/pi-utils";
|
||||
import { type Static, Type } from "@sinclair/typebox";
|
||||
import { renderPromptTemplate } from "../config/prompt-templates";
|
||||
import type { RenderResultOptions } from "../extensibility/custom-tools/types";
|
||||
import type { Theme } from "../modes/theme/theme";
|
||||
import calculatorDescription from "../prompts/tools/calculator.md" with { type: "text" };
|
||||
@@ -402,7 +401,7 @@ export class CalculatorTool implements AgentTool<typeof calculatorSchema, Calcul
|
||||
readonly strict = true;
|
||||
|
||||
constructor(_session: ToolSession) {
|
||||
this.description = renderPromptTemplate(calculatorDescription);
|
||||
this.description = prompt.render(calculatorDescription);
|
||||
}
|
||||
|
||||
async execute(
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { AgentTool, AgentToolContext, AgentToolResult, AgentToolUpdateCallback } from "@oh-my-pi/pi-agent-core";
|
||||
import { prompt } from "@oh-my-pi/pi-utils";
|
||||
import { type Static, Type } from "@sinclair/typebox";
|
||||
import { renderPromptTemplate } from "../config/prompt-templates";
|
||||
import cancelJobDescription from "../prompts/tools/cancel-job.md" with { type: "text" };
|
||||
import type { ToolSession } from "./index";
|
||||
|
||||
@@ -23,7 +23,7 @@ export class CancelJobTool implements AgentTool<typeof cancelJobSchema, CancelJo
|
||||
readonly strict = true;
|
||||
|
||||
constructor(private readonly session: ToolSession) {
|
||||
this.description = renderPromptTemplate(cancelJobDescription);
|
||||
this.description = prompt.render(cancelJobDescription);
|
||||
}
|
||||
|
||||
static createIf(session: ToolSession): CancelJobTool | null {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { AgentTool, AgentToolContext, AgentToolResult, AgentToolUpdateCallback } from "@oh-my-pi/pi-agent-core";
|
||||
import { prompt } from "@oh-my-pi/pi-utils";
|
||||
import { type Static, Type } from "@sinclair/typebox";
|
||||
import { renderPromptTemplate } from "../config/prompt-templates";
|
||||
import checkpointDescription from "../prompts/tools/checkpoint.md" with { type: "text" };
|
||||
import rewindDescription from "../prompts/tools/rewind.md" with { type: "text" };
|
||||
import type { ToolSession } from ".";
|
||||
@@ -54,7 +54,7 @@ export class CheckpointTool implements AgentTool<typeof checkpointSchema, Checkp
|
||||
readonly strict = true;
|
||||
|
||||
constructor(private readonly session: ToolSession) {
|
||||
this.description = renderPromptTemplate(checkpointDescription);
|
||||
this.description = prompt.render(checkpointDescription);
|
||||
}
|
||||
|
||||
static createIf(session: ToolSession): CheckpointTool | null {
|
||||
@@ -96,7 +96,7 @@ export class RewindTool implements AgentTool<typeof rewindSchema, RewindToolDeta
|
||||
readonly strict = true;
|
||||
|
||||
constructor(private readonly session: ToolSession) {
|
||||
this.description = renderPromptTemplate(rewindDescription);
|
||||
this.description = prompt.render(rewindDescription);
|
||||
}
|
||||
|
||||
static createIf(session: ToolSession): RewindTool | null {
|
||||
|
||||
@@ -7,8 +7,8 @@ import type {
|
||||
} from "@oh-my-pi/pi-agent-core";
|
||||
import { StringEnum } from "@oh-my-pi/pi-ai";
|
||||
import { type Component, Text } from "@oh-my-pi/pi-tui";
|
||||
import { prompt } from "@oh-my-pi/pi-utils";
|
||||
import { type Static, Type } from "@sinclair/typebox";
|
||||
import { renderPromptTemplate } from "../config/prompt-templates";
|
||||
import {
|
||||
type DapBreakpointRecord,
|
||||
type DapCapabilities,
|
||||
@@ -612,7 +612,7 @@ export class DebugTool implements AgentTool<typeof debugSchema, DebugToolDetails
|
||||
readonly concurrency = "exclusive";
|
||||
|
||||
constructor(private readonly session: ToolSession) {
|
||||
this.description = renderPromptTemplate(debugDescription);
|
||||
this.description = prompt.render(debugDescription);
|
||||
}
|
||||
|
||||
static createIf(session: ToolSession): DebugTool | null {
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import * as fs from "node:fs/promises";
|
||||
import type { AgentTool, AgentToolContext, AgentToolResult, AgentToolUpdateCallback } from "@oh-my-pi/pi-agent-core";
|
||||
import { isEnoent } from "@oh-my-pi/pi-utils";
|
||||
import { isEnoent, prompt } from "@oh-my-pi/pi-utils";
|
||||
import { type Static, Type } from "@sinclair/typebox";
|
||||
import { renderPromptTemplate } from "../config/prompt-templates";
|
||||
import exitPlanModeDescription from "../prompts/tools/exit-plan-mode.md" with { type: "text" };
|
||||
import type { ToolSession } from ".";
|
||||
import { resolvePlanPath } from "./plan-mode-guard";
|
||||
@@ -49,7 +48,7 @@ export class ExitPlanModeTool implements AgentTool<typeof exitPlanModeSchema, Ex
|
||||
readonly concurrency = "exclusive";
|
||||
|
||||
constructor(private readonly session: ToolSession) {
|
||||
this.description = renderPromptTemplate(exitPlanModeDescription);
|
||||
this.description = prompt.render(exitPlanModeDescription);
|
||||
}
|
||||
|
||||
async execute(
|
||||
|
||||
@@ -4,10 +4,9 @@ import type { AgentTool, AgentToolContext, AgentToolResult, AgentToolUpdateCallb
|
||||
import { FileType, type GlobMatch, glob } from "@oh-my-pi/pi-natives";
|
||||
import type { Component } from "@oh-my-pi/pi-tui";
|
||||
import { Text } from "@oh-my-pi/pi-tui";
|
||||
import { isEnoent, untilAborted } from "@oh-my-pi/pi-utils";
|
||||
import { isEnoent, prompt, untilAborted } from "@oh-my-pi/pi-utils";
|
||||
import type { Static } from "@sinclair/typebox";
|
||||
import { Type } from "@sinclair/typebox";
|
||||
import { renderPromptTemplate } from "../config/prompt-templates";
|
||||
import type { RenderResultOptions } from "../extensibility/custom-tools/types";
|
||||
import type { Theme } from "../modes/theme/theme";
|
||||
import findDescription from "../prompts/tools/find.md" with { type: "text" };
|
||||
@@ -86,7 +85,7 @@ export class FindTool implements AgentTool<typeof findSchema, FindToolDetails> {
|
||||
options?: FindToolOptions,
|
||||
) {
|
||||
this.#customOps = options?.operations;
|
||||
this.description = renderPromptTemplate(findDescription);
|
||||
this.description = prompt.render(findDescription);
|
||||
}
|
||||
|
||||
async execute(
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import * as os from "node:os";
|
||||
import * as path from "node:path";
|
||||
import { getAntigravityHeaders, getEnvApiKey, StringEnum } from "@oh-my-pi/pi-ai";
|
||||
import { $env, isEnoent, ptree, readSseJson, Snowflake, untilAborted } from "@oh-my-pi/pi-utils";
|
||||
import { $env, isEnoent, prompt, ptree, readSseJson, Snowflake, untilAborted } from "@oh-my-pi/pi-utils";
|
||||
import { type Static, Type } from "@sinclair/typebox";
|
||||
import type { ModelRegistry } from "../config/model-registry";
|
||||
import { renderPromptTemplate } from "../config/prompt-templates";
|
||||
import type { CustomTool } from "../extensibility/custom-tools/types";
|
||||
import geminiImageDescription from "../prompts/tools/gemini-image.md" with { type: "text" };
|
||||
import { detectSupportedImageMimeTypeFromFile } from "../utils/mime";
|
||||
@@ -599,7 +598,7 @@ async function parseAntigravitySseForImage(response: Response, signal?: AbortSig
|
||||
export const geminiImageTool: CustomTool<typeof geminiImageSchema, GeminiImageToolDetails> = {
|
||||
name: "generate_image",
|
||||
label: "GenerateImage",
|
||||
description: renderPromptTemplate(geminiImageDescription),
|
||||
description: prompt.render(geminiImageDescription),
|
||||
parameters: geminiImageSchema,
|
||||
async execute(_toolCallId, params, _onUpdate, ctx, signal) {
|
||||
return untilAborted(signal, async () => {
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import * as fs from "node:fs/promises";
|
||||
import * as path from "node:path";
|
||||
import type { AgentTool, AgentToolContext, AgentToolResult, AgentToolUpdateCallback } from "@oh-my-pi/pi-agent-core";
|
||||
import { abortableSleep, isEnoent, untilAborted } from "@oh-my-pi/pi-utils";
|
||||
import { abortableSleep, isEnoent, prompt, untilAborted } from "@oh-my-pi/pi-utils";
|
||||
import { type Static, Type } from "@sinclair/typebox";
|
||||
import { renderPromptTemplate } from "../config/prompt-templates";
|
||||
import ghIssueViewDescription from "../prompts/tools/gh-issue-view.md" with { type: "text" };
|
||||
import ghPrCheckoutDescription from "../prompts/tools/gh-pr-checkout.md" with { type: "text" };
|
||||
import ghPrDiffDescription from "../prompts/tools/gh-pr-diff.md" with { type: "text" };
|
||||
@@ -1902,7 +1901,7 @@ function buildTextResult(
|
||||
export class GhRepoViewTool implements AgentTool<typeof ghRepoViewSchema, GhToolDetails> {
|
||||
readonly name = "gh_repo_view";
|
||||
readonly label = "GitHub Repo";
|
||||
readonly description = renderPromptTemplate(ghRepoViewDescription);
|
||||
readonly description = prompt.render(ghRepoViewDescription);
|
||||
readonly parameters = ghRepoViewSchema;
|
||||
readonly strict = true;
|
||||
|
||||
@@ -1943,7 +1942,7 @@ export class GhRepoViewTool implements AgentTool<typeof ghRepoViewSchema, GhTool
|
||||
export class GhIssueViewTool implements AgentTool<typeof ghIssueViewSchema, GhToolDetails> {
|
||||
readonly name = "gh_issue_view";
|
||||
readonly label = "GitHub Issue";
|
||||
readonly description = renderPromptTemplate(ghIssueViewDescription);
|
||||
readonly description = prompt.render(ghIssueViewDescription);
|
||||
readonly parameters = ghIssueViewSchema;
|
||||
readonly strict = true;
|
||||
|
||||
@@ -1980,7 +1979,7 @@ export class GhIssueViewTool implements AgentTool<typeof ghIssueViewSchema, GhTo
|
||||
export class GhPrViewTool implements AgentTool<typeof ghPrViewSchema, GhToolDetails> {
|
||||
readonly name = "gh_pr_view";
|
||||
readonly label = "GitHub PR";
|
||||
readonly description = renderPromptTemplate(ghPrViewDescription);
|
||||
readonly description = prompt.render(ghPrViewDescription);
|
||||
readonly parameters = ghPrViewSchema;
|
||||
readonly strict = true;
|
||||
|
||||
@@ -2024,7 +2023,7 @@ export class GhPrViewTool implements AgentTool<typeof ghPrViewSchema, GhToolDeta
|
||||
export class GhPrDiffTool implements AgentTool<typeof ghPrDiffSchema, GhToolDetails> {
|
||||
readonly name = "gh_pr_diff";
|
||||
readonly label = "GitHub PR Diff";
|
||||
readonly description = renderPromptTemplate(ghPrDiffDescription);
|
||||
readonly description = prompt.render(ghPrDiffDescription);
|
||||
readonly parameters = ghPrDiffSchema;
|
||||
readonly strict = true;
|
||||
|
||||
@@ -2073,7 +2072,7 @@ export class GhPrDiffTool implements AgentTool<typeof ghPrDiffSchema, GhToolDeta
|
||||
export class GhPrCheckoutTool implements AgentTool<typeof ghPrCheckoutSchema, GhToolDetails> {
|
||||
readonly name = "gh_pr_checkout";
|
||||
readonly label = "GitHub PR Checkout";
|
||||
readonly description = renderPromptTemplate(ghPrCheckoutDescription);
|
||||
readonly description = prompt.render(ghPrCheckoutDescription);
|
||||
readonly parameters = ghPrCheckoutSchema;
|
||||
readonly strict = true;
|
||||
|
||||
@@ -2204,7 +2203,7 @@ export class GhPrCheckoutTool implements AgentTool<typeof ghPrCheckoutSchema, Gh
|
||||
export class GhPrPushTool implements AgentTool<typeof ghPrPushSchema, GhToolDetails> {
|
||||
readonly name = "gh_pr_push";
|
||||
readonly label = "GitHub PR Push";
|
||||
readonly description = renderPromptTemplate(ghPrPushDescription);
|
||||
readonly description = prompt.render(ghPrPushDescription);
|
||||
readonly parameters = ghPrPushSchema;
|
||||
readonly strict = true;
|
||||
|
||||
@@ -2265,7 +2264,7 @@ export class GhPrPushTool implements AgentTool<typeof ghPrPushSchema, GhToolDeta
|
||||
export class GhSearchIssuesTool implements AgentTool<typeof ghSearchIssuesSchema, GhToolDetails> {
|
||||
readonly name = "gh_search_issues";
|
||||
readonly label = "GitHub Issue Search";
|
||||
readonly description = renderPromptTemplate(ghSearchIssuesDescription);
|
||||
readonly description = prompt.render(ghSearchIssuesDescription);
|
||||
readonly parameters = ghSearchIssuesSchema;
|
||||
readonly strict = true;
|
||||
|
||||
@@ -2300,7 +2299,7 @@ export class GhSearchIssuesTool implements AgentTool<typeof ghSearchIssuesSchema
|
||||
export class GhSearchPrsTool implements AgentTool<typeof ghSearchPrsSchema, GhToolDetails> {
|
||||
readonly name = "gh_search_prs";
|
||||
readonly label = "GitHub PR Search";
|
||||
readonly description = renderPromptTemplate(ghSearchPrsDescription);
|
||||
readonly description = prompt.render(ghSearchPrsDescription);
|
||||
readonly parameters = ghSearchPrsSchema;
|
||||
readonly strict = true;
|
||||
|
||||
@@ -2335,7 +2334,7 @@ export class GhSearchPrsTool implements AgentTool<typeof ghSearchPrsSchema, GhTo
|
||||
export class GhRunWatchTool implements AgentTool<typeof ghRunWatchSchema, GhToolDetails> {
|
||||
readonly name = "gh_run_watch";
|
||||
readonly label = "GitHub Run Watch";
|
||||
readonly description = renderPromptTemplate(ghRunWatchDescription);
|
||||
readonly description = prompt.render(ghRunWatchDescription);
|
||||
readonly parameters = ghRunWatchSchema;
|
||||
readonly strict = true;
|
||||
|
||||
|
||||
@@ -4,9 +4,8 @@ import type { AgentTool, AgentToolContext, AgentToolResult, AgentToolUpdateCallb
|
||||
import { type GrepMatch, GrepOutputMode, type GrepResult, grep } from "@oh-my-pi/pi-natives";
|
||||
import type { Component } from "@oh-my-pi/pi-tui";
|
||||
import { Text } from "@oh-my-pi/pi-tui";
|
||||
import { untilAborted } from "@oh-my-pi/pi-utils";
|
||||
import { prompt, untilAborted } from "@oh-my-pi/pi-utils";
|
||||
import { type Static, Type } from "@sinclair/typebox";
|
||||
import { renderPromptTemplate } from "../config/prompt-templates";
|
||||
import { formatChunkedGrepLine } from "../edit/modes/chunk";
|
||||
import { computeLineHash } from "../edit/modes/hashline";
|
||||
import type { RenderResultOptions } from "../extensibility/custom-tools/types";
|
||||
@@ -74,7 +73,7 @@ export class GrepTool implements AgentTool<typeof grepSchema, GrepToolDetails> {
|
||||
|
||||
constructor(private readonly session: ToolSession) {
|
||||
const displayMode = resolveFileDisplayMode(session);
|
||||
this.description = renderPromptTemplate(grepDescription, {
|
||||
this.description = prompt.render(grepDescription, {
|
||||
IS_HASHLINE_MODE: displayMode.hashLines,
|
||||
IS_LINE_NUMBER_MODE: !displayMode.hashLines && displayMode.lineNumbers,
|
||||
IS_CHUNK_MODE: displayMode.chunked,
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import type { AgentTool, AgentToolContext, AgentToolResult, AgentToolUpdateCallback } from "@oh-my-pi/pi-agent-core";
|
||||
import { type Api, type AssistantMessage, completeSimple, type Model } from "@oh-my-pi/pi-ai";
|
||||
import { prompt } from "@oh-my-pi/pi-utils";
|
||||
import { type Static, Type } from "@sinclair/typebox";
|
||||
import { expandRoleAlias, resolveModelFromString } from "../config/model-resolver";
|
||||
import { renderPromptTemplate } from "../config/prompt-templates";
|
||||
import inspectImageDescription from "../prompts/tools/inspect-image.md" with { type: "text" };
|
||||
import inspectImageSystemPromptTemplate from "../prompts/tools/inspect-image-system.md" with { type: "text" };
|
||||
import {
|
||||
@@ -49,7 +49,7 @@ export class InspectImageTool implements AgentTool<typeof inspectImageSchema, In
|
||||
private readonly session: ToolSession,
|
||||
private readonly completeImageRequest: typeof completeSimple = completeSimple,
|
||||
) {
|
||||
this.description = renderPromptTemplate(inspectImageDescription);
|
||||
this.description = prompt.render(inspectImageDescription);
|
||||
}
|
||||
|
||||
async execute(
|
||||
@@ -127,7 +127,7 @@ export class InspectImageTool implements AgentTool<typeof inspectImageSchema, In
|
||||
const response = await this.completeImageRequest(
|
||||
model,
|
||||
{
|
||||
systemPrompt: renderPromptTemplate(inspectImageSystemPromptTemplate),
|
||||
systemPrompt: prompt.render(inspectImageSystemPromptTemplate),
|
||||
messages: [
|
||||
{
|
||||
role: "user",
|
||||
|
||||
@@ -4,9 +4,8 @@ import type { AgentTool, AgentToolContext, AgentToolResult, AgentToolUpdateCallb
|
||||
import type { ImageContent } from "@oh-my-pi/pi-ai";
|
||||
import type { Component } from "@oh-my-pi/pi-tui";
|
||||
import { Markdown, Text } from "@oh-my-pi/pi-tui";
|
||||
import { getProjectDir } from "@oh-my-pi/pi-utils";
|
||||
import { getProjectDir, prompt } from "@oh-my-pi/pi-utils";
|
||||
import { type Static, Type } from "@sinclair/typebox";
|
||||
import { renderPromptTemplate } from "../config/prompt-templates";
|
||||
import type { RenderResultOptions } from "../extensibility/custom-tools/types";
|
||||
import { executePython, getPreludeDocs, type PythonExecutorOptions } from "../ipy/executor";
|
||||
import type { PreludeHelper, PythonStatusEvent } from "../ipy/kernel";
|
||||
@@ -136,7 +135,7 @@ function renderJsonTree(value: unknown, theme: Theme, expanded: boolean, maxDept
|
||||
export function getPythonToolDescription(): string {
|
||||
const helpers = getPreludeDocs();
|
||||
const categories = groupPreludeHelpers(helpers);
|
||||
return renderPromptTemplate(pythonDescription, { categories });
|
||||
return prompt.render(pythonDescription, { categories });
|
||||
}
|
||||
|
||||
export interface PythonToolOptions {
|
||||
|
||||
@@ -5,9 +5,8 @@ import type { ImageContent, TextContent } from "@oh-my-pi/pi-ai";
|
||||
import { glob } from "@oh-my-pi/pi-natives";
|
||||
import type { Component } from "@oh-my-pi/pi-tui";
|
||||
import { Text } from "@oh-my-pi/pi-tui";
|
||||
import { getRemoteDir, untilAborted } from "@oh-my-pi/pi-utils";
|
||||
import { getRemoteDir, prompt, untilAborted } from "@oh-my-pi/pi-utils";
|
||||
import { type Static, Type } from "@sinclair/typebox";
|
||||
import { renderPromptTemplate } from "../config/prompt-templates";
|
||||
import {
|
||||
type ChunkReadTarget,
|
||||
formatChunkedRead,
|
||||
@@ -452,10 +451,10 @@ export class ReadTool implements AgentTool<typeof readSchema, ReadToolDetails> {
|
||||
this.#inspectImageEnabled = session.settings.get("inspect_image.enabled");
|
||||
this.description =
|
||||
resolveEditMode(session) === "chunk"
|
||||
? renderPromptTemplate(readChunkDescription, {
|
||||
? prompt.render(readChunkDescription, {
|
||||
anchorStyle: resolveAnchorStyle(session.settings),
|
||||
})
|
||||
: renderPromptTemplate(readDescription, {
|
||||
: prompt.render(readDescription, {
|
||||
DEFAULT_LIMIT: String(this.#defaultLimit),
|
||||
DEFAULT_MAX_LINES: String(DEFAULT_MAX_LINES),
|
||||
IS_HASHLINE_MODE: displayMode.hashLines,
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import type { AgentTool, AgentToolContext, AgentToolResult, AgentToolUpdateCallback } from "@oh-my-pi/pi-agent-core";
|
||||
import { type MermaidAsciiRenderOptions, renderMermaidAscii } from "@oh-my-pi/pi-utils";
|
||||
import { type MermaidAsciiRenderOptions, prompt, renderMermaidAscii } from "@oh-my-pi/pi-utils";
|
||||
import { type Static, Type } from "@sinclair/typebox";
|
||||
import { renderPromptTemplate } from "../config/prompt-templates";
|
||||
import renderMermaidDescription from "../prompts/tools/render-mermaid.md" with { type: "text" };
|
||||
import type { ToolSession } from "./index";
|
||||
|
||||
@@ -41,7 +40,7 @@ export class RenderMermaidTool implements AgentTool<typeof renderMermaidSchema,
|
||||
readonly strict = true;
|
||||
|
||||
constructor(private readonly session: ToolSession) {
|
||||
this.description = renderPromptTemplate(renderMermaidDescription);
|
||||
this.description = prompt.render(renderMermaidDescription);
|
||||
}
|
||||
|
||||
async execute(
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import type { AgentTool, AgentToolContext, AgentToolResult, AgentToolUpdateCallback } from "@oh-my-pi/pi-agent-core";
|
||||
import type { Component } from "@oh-my-pi/pi-tui";
|
||||
import { Text } from "@oh-my-pi/pi-tui";
|
||||
import { untilAborted } from "@oh-my-pi/pi-utils";
|
||||
import { prompt, untilAborted } from "@oh-my-pi/pi-utils";
|
||||
import { type Static, Type } from "@sinclair/typebox";
|
||||
import { renderPromptTemplate } from "../config/prompt-templates";
|
||||
import type { RenderResultOptions } from "../extensibility/custom-tools/types";
|
||||
import type { Theme } from "../modes/theme/theme";
|
||||
import resolveDescription from "../prompts/tools/resolve.md" with { type: "text" };
|
||||
@@ -42,7 +41,7 @@ export class ResolveTool implements AgentTool<typeof resolveSchema, ResolveToolD
|
||||
readonly strict = true;
|
||||
|
||||
constructor(private readonly session: ToolSession) {
|
||||
this.description = renderPromptTemplate(resolveDescription);
|
||||
this.description = prompt.render(resolveDescription);
|
||||
}
|
||||
|
||||
async execute(
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { AgentTool, AgentToolContext, AgentToolResult, AgentToolUpdateCallback } from "@oh-my-pi/pi-agent-core";
|
||||
import { type Component, Text } from "@oh-my-pi/pi-tui";
|
||||
import { prompt } from "@oh-my-pi/pi-utils";
|
||||
import { type Static, Type } from "@sinclair/typebox";
|
||||
import { renderPromptTemplate } from "../config/prompt-templates";
|
||||
import type { RenderResultOptions } from "../extensibility/custom-tools/types";
|
||||
import {
|
||||
buildDiscoverableMCPSearchIndex,
|
||||
@@ -105,7 +105,7 @@ function supportsMCPToolDiscoveryExecution(session: ToolSession): session is MCP
|
||||
|
||||
export function renderSearchToolBm25Description(discoverableTools: DiscoverableMCPTool[] = []): string {
|
||||
const summary = summarizeDiscoverableMCPTools(discoverableTools);
|
||||
return renderPromptTemplate(searchToolBm25Description, {
|
||||
return prompt.render(searchToolBm25Description, {
|
||||
discoverableMCPToolCount: summary.toolCount,
|
||||
discoverableMCPServerSummaries: summary.servers.map(formatDiscoverableMCPToolServerSummary),
|
||||
hasDiscoverableMCPServers: summary.servers.length > 0,
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import type { AgentTool, AgentToolContext, AgentToolResult, AgentToolUpdateCallback } from "@oh-my-pi/pi-agent-core";
|
||||
import type { Component } from "@oh-my-pi/pi-tui";
|
||||
import { Text } from "@oh-my-pi/pi-tui";
|
||||
import { prompt } from "@oh-my-pi/pi-utils";
|
||||
import { type Static, Type } from "@sinclair/typebox";
|
||||
import type { SSHHost } from "../capability/ssh";
|
||||
import { sshCapability } from "../capability/ssh";
|
||||
import { renderPromptTemplate } from "../config/prompt-templates";
|
||||
import { loadCapability } from "../discovery";
|
||||
import type { RenderResultOptions } from "../extensibility/custom-tools/types";
|
||||
import type { Theme } from "../modes/theme/theme";
|
||||
@@ -59,7 +59,7 @@ async function formatHostEntry(host: SSHHost): Promise<string> {
|
||||
}
|
||||
|
||||
async function formatDescription(hosts: SSHHost[]): Promise<string> {
|
||||
const baseDescription = renderPromptTemplate(sshDescriptionBase);
|
||||
const baseDescription = prompt.render(sshDescriptionBase);
|
||||
if (hosts.length === 0) {
|
||||
return baseDescription;
|
||||
}
|
||||
|
||||
@@ -2,9 +2,9 @@ import type { AgentTool, AgentToolContext, AgentToolResult, AgentToolUpdateCallb
|
||||
import { StringEnum } from "@oh-my-pi/pi-ai";
|
||||
import type { Component } from "@oh-my-pi/pi-tui";
|
||||
import { Text } from "@oh-my-pi/pi-tui";
|
||||
import { prompt } from "@oh-my-pi/pi-utils";
|
||||
import { type Static, Type } from "@sinclair/typebox";
|
||||
import chalk from "chalk";
|
||||
import { renderPromptTemplate } from "../config/prompt-templates";
|
||||
import type { RenderResultOptions } from "../extensibility/custom-tools/types";
|
||||
import type { Theme } from "../modes/theme/theme";
|
||||
import todoWriteDescription from "../prompts/tools/todo-write.md" with { type: "text" };
|
||||
@@ -347,7 +347,7 @@ export class TodoWriteTool implements AgentTool<typeof todoWriteSchema, TodoWrit
|
||||
readonly strict = true;
|
||||
|
||||
constructor(private readonly session: ToolSession) {
|
||||
this.description = renderPromptTemplate(todoWriteDescription);
|
||||
this.description = prompt.render(todoWriteDescription);
|
||||
}
|
||||
|
||||
async execute(
|
||||
|
||||
@@ -9,10 +9,9 @@ import type {
|
||||
} from "@oh-my-pi/pi-agent-core";
|
||||
import type { Component } from "@oh-my-pi/pi-tui";
|
||||
import { Text } from "@oh-my-pi/pi-tui";
|
||||
import { isEnoent, untilAborted } from "@oh-my-pi/pi-utils";
|
||||
import { isEnoent, prompt, untilAborted } from "@oh-my-pi/pi-utils";
|
||||
import { type Static, Type } from "@sinclair/typebox";
|
||||
import { unzipSync, zipSync } from "fflate";
|
||||
import { renderPromptTemplate } from "../config/prompt-templates";
|
||||
import { stripHashlinePrefixes } from "../edit";
|
||||
import type { RenderResultOptions } from "../extensibility/custom-tools/types";
|
||||
import { createLspWritethrough, type FileDiagnosticsResult, type WritethroughCallback, writethroughNoop } from "../lsp";
|
||||
@@ -149,7 +148,7 @@ export class WriteTool implements AgentTool<typeof writeSchema, WriteToolDetails
|
||||
this.#writethrough = enableLsp
|
||||
? createLspWritethrough(session.cwd, { enableFormat, enableDiagnostics })
|
||||
: writethroughNoop;
|
||||
this.description = renderPromptTemplate(writeDescription);
|
||||
this.description = prompt.render(writeDescription);
|
||||
}
|
||||
|
||||
async #resolveArchiveWritePath(writePath: string): Promise<ResolvedArchiveWritePath | null> {
|
||||
|
||||
@@ -5,16 +5,15 @@
|
||||
import type { ThinkingLevel } from "@oh-my-pi/pi-agent-core";
|
||||
import type { Api, Model } from "@oh-my-pi/pi-ai";
|
||||
import { completeSimple } from "@oh-my-pi/pi-ai";
|
||||
import { logger } from "@oh-my-pi/pi-utils";
|
||||
import { logger, prompt } from "@oh-my-pi/pi-utils";
|
||||
import type { ModelRegistry } from "../config/model-registry";
|
||||
import { resolveModelRoleValue } from "../config/model-resolver";
|
||||
import { renderPromptTemplate } from "../config/prompt-templates";
|
||||
import type { Settings } from "../config/settings";
|
||||
import MODEL_PRIO from "../priority.json" with { type: "json" };
|
||||
import commitSystemPrompt from "../prompts/system/commit-message-system.md" with { type: "text" };
|
||||
import { toReasoningEffort } from "../thinking";
|
||||
|
||||
const COMMIT_SYSTEM_PROMPT = renderPromptTemplate(commitSystemPrompt);
|
||||
const COMMIT_SYSTEM_PROMPT = prompt.render(commitSystemPrompt);
|
||||
const MAX_DIFF_CHARS = 4000;
|
||||
|
||||
/** File patterns that should be excluded from commit message generation diffs. */
|
||||
|
||||
@@ -1,170 +0,0 @@
|
||||
export type PromptRenderPhase = "pre-render" | "post-render";
|
||||
|
||||
export interface PromptFormatOptions {
|
||||
renderPhase?: PromptRenderPhase;
|
||||
replaceAsciiSymbols?: boolean;
|
||||
boldRfc2119Keywords?: boolean;
|
||||
}
|
||||
|
||||
// Opening XML tag (not self-closing, not closing)
|
||||
const OPENING_XML = /^<([a-z_-]+)(?:\s+[^>]*)?>$/;
|
||||
// Closing XML tag
|
||||
const CLOSING_XML = /^<\/([a-z_-]+)>$/;
|
||||
// Handlebars block start: {{#if}}, {{#has}}, {{#list}}, etc.
|
||||
const OPENING_HBS = /^\{\{#/;
|
||||
// Handlebars block end: {{/if}}, {{/has}}, {{/list}}, etc.
|
||||
const CLOSING_HBS = /^\{\{\//;
|
||||
// List item (- or * or 1.)
|
||||
const LIST_ITEM = /^(?:[-*]\s|\d+\.\s)/;
|
||||
// Code fence
|
||||
const CODE_FENCE = /^```/;
|
||||
// Table row
|
||||
const TABLE_ROW = /^\|.*\|$/;
|
||||
// Table separator (|---|---|)
|
||||
const TABLE_SEP = /^\|[-:\s|]+\|$/;
|
||||
|
||||
/** RFC 2119 keywords used in prompts. */
|
||||
const RFC2119_KEYWORDS = /\b(?:MUST NOT|SHOULD NOT|SHALL NOT|RECOMMENDED|REQUIRED|OPTIONAL|SHOULD|SHALL|MUST|MAY)\b/g;
|
||||
|
||||
function boldRfc2119Keywords(line: string): string {
|
||||
return line.replace(RFC2119_KEYWORDS, (match, offset, source) => {
|
||||
const isAlreadyBold =
|
||||
source[offset - 2] === "*" &&
|
||||
source[offset - 1] === "*" &&
|
||||
source[offset + match.length] === "*" &&
|
||||
source[offset + match.length + 1] === "*";
|
||||
if (isAlreadyBold) {
|
||||
return match;
|
||||
}
|
||||
return `**${match}**`;
|
||||
});
|
||||
}
|
||||
|
||||
/** Compact a table row by trimming cell padding */
|
||||
function compactTableRow(line: string): string {
|
||||
const cells = line.split("|");
|
||||
return cells.map(c => c.trim()).join("|");
|
||||
}
|
||||
|
||||
/** Compact a table separator row */
|
||||
function compactTableSep(line: string): string {
|
||||
const cells = line.split("|").filter(c => c.trim());
|
||||
const normalized = cells.map(c => {
|
||||
const trimmed = c.trim();
|
||||
const left = trimmed.startsWith(":");
|
||||
const right = trimmed.endsWith(":");
|
||||
if (left && right) return ":---:";
|
||||
if (left) return ":---";
|
||||
if (right) return "---:";
|
||||
return "---";
|
||||
});
|
||||
return `|${normalized.join("|")}|`;
|
||||
}
|
||||
|
||||
function replaceCommonAsciiSymbols(line: string): string {
|
||||
return line
|
||||
.replace(/\.{3}/g, "…")
|
||||
.replace(/<->/g, "↔")
|
||||
.replace(/->/g, "→")
|
||||
.replace(/<-/g, "←")
|
||||
.replace(/!=/g, "≠")
|
||||
.replace(/<=/g, "≤")
|
||||
.replace(/>=/g, "≥");
|
||||
}
|
||||
|
||||
export function formatPromptContent(content: string, options: PromptFormatOptions = {}): string {
|
||||
const {
|
||||
renderPhase = "post-render",
|
||||
replaceAsciiSymbols = false,
|
||||
boldRfc2119Keywords: shouldBoldRfc2119 = false,
|
||||
} = options;
|
||||
const isPreRender = renderPhase === "pre-render";
|
||||
const lines = content.split("\n");
|
||||
const result: string[] = [];
|
||||
let inCodeBlock = false;
|
||||
const topLevelTags: string[] = [];
|
||||
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
let line = lines[i].trimEnd();
|
||||
let trimmedStart = line.trimStart();
|
||||
if (CODE_FENCE.test(trimmedStart)) {
|
||||
inCodeBlock = !inCodeBlock;
|
||||
result.push(line);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (inCodeBlock) {
|
||||
result.push(line);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (replaceAsciiSymbols) {
|
||||
line = replaceCommonAsciiSymbols(line);
|
||||
}
|
||||
trimmedStart = line.trimStart();
|
||||
const trimmed = line.trim();
|
||||
|
||||
const isOpeningXml = OPENING_XML.test(trimmedStart) && !trimmedStart.endsWith("/>");
|
||||
if (isOpeningXml && line.length === trimmedStart.length) {
|
||||
const match = OPENING_XML.exec(trimmedStart);
|
||||
if (match) topLevelTags.push(match[1]);
|
||||
}
|
||||
|
||||
const closingMatch = CLOSING_XML.exec(trimmedStart);
|
||||
if (closingMatch) {
|
||||
const tagName = closingMatch[1];
|
||||
if (topLevelTags.length > 0 && topLevelTags[topLevelTags.length - 1] === tagName) {
|
||||
topLevelTags.pop();
|
||||
}
|
||||
} else if (isPreRender && trimmedStart.startsWith("{{")) {
|
||||
/* keep indentation as-is in pre-render for Handlebars markers */
|
||||
} else if (TABLE_SEP.test(trimmedStart)) {
|
||||
const leadingWhitespace = line.slice(0, line.length - trimmedStart.length);
|
||||
line = `${leadingWhitespace}${compactTableSep(trimmedStart)}`;
|
||||
} else if (TABLE_ROW.test(trimmedStart)) {
|
||||
const leadingWhitespace = line.slice(0, line.length - trimmedStart.length);
|
||||
line = `${leadingWhitespace}${compactTableRow(trimmedStart)}`;
|
||||
}
|
||||
|
||||
if (shouldBoldRfc2119) {
|
||||
line = boldRfc2119Keywords(line);
|
||||
}
|
||||
|
||||
const isBlank = trimmed === "";
|
||||
if (isBlank) {
|
||||
const prevLine = result[result.length - 1]?.trim() ?? "";
|
||||
const nextLine = lines[i + 1]?.trim() ?? "";
|
||||
|
||||
if (LIST_ITEM.test(nextLine)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (OPENING_XML.test(prevLine) || (isPreRender && OPENING_HBS.test(prevLine))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (CLOSING_XML.test(nextLine) || (isPreRender && CLOSING_HBS.test(nextLine))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const prevIsBlank = prevLine === "";
|
||||
if (prevIsBlank) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (CLOSING_XML.test(trimmed) || (isPreRender && CLOSING_HBS.test(trimmed))) {
|
||||
while (result.length > 0 && result[result.length - 1].trim() === "") {
|
||||
result.pop();
|
||||
}
|
||||
}
|
||||
|
||||
result.push(line);
|
||||
}
|
||||
|
||||
while (result.length > 0 && result[result.length - 1].trim() === "") {
|
||||
result.pop();
|
||||
}
|
||||
|
||||
return result.join("\n");
|
||||
}
|
||||
@@ -5,15 +5,14 @@ import * as path from "node:path";
|
||||
import type { ThinkingLevel } from "@oh-my-pi/pi-agent-core";
|
||||
import type { Api, Model } from "@oh-my-pi/pi-ai";
|
||||
import { completeSimple } from "@oh-my-pi/pi-ai";
|
||||
import { logger } from "@oh-my-pi/pi-utils";
|
||||
import { logger, prompt } from "@oh-my-pi/pi-utils";
|
||||
import type { ModelRegistry } from "../config/model-registry";
|
||||
import { resolveRoleSelection } from "../config/model-resolver";
|
||||
import { renderPromptTemplate } from "../config/prompt-templates";
|
||||
import type { Settings } from "../config/settings";
|
||||
import titleSystemPrompt from "../prompts/system/title-system.md" with { type: "text" };
|
||||
import { toReasoningEffort } from "../thinking";
|
||||
|
||||
const TITLE_SYSTEM_PROMPT = renderPromptTemplate(titleSystemPrompt);
|
||||
const TITLE_SYSTEM_PROMPT = prompt.render(titleSystemPrompt);
|
||||
|
||||
const DEFAULT_TERMINAL_TITLE = "π";
|
||||
const TERMINAL_TITLE_CONTROL_CHARS = /[\u0000-\u001f\u007f-\u009f]/g;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { parseFrontmatter } from "../../utils/frontmatter";
|
||||
import { parseFrontmatter } from "@oh-my-pi/pi-utils";
|
||||
import type { RenderResult, SpecialHandler } from "./types";
|
||||
import { buildResult, loadPage } from "./types";
|
||||
import { asString } from "./utils";
|
||||
|
||||
@@ -7,8 +7,8 @@
|
||||
*/
|
||||
import type { AgentTool, AgentToolContext, AgentToolResult, AgentToolUpdateCallback } from "@oh-my-pi/pi-agent-core";
|
||||
import { StringEnum } from "@oh-my-pi/pi-ai";
|
||||
import { prompt } from "@oh-my-pi/pi-utils";
|
||||
import { Type } from "@sinclair/typebox";
|
||||
import { renderPromptTemplate } from "../../config/prompt-templates";
|
||||
import type { CustomTool, CustomToolContext, RenderResultOptions } from "../../extensibility/custom-tools/types";
|
||||
import type { Theme } from "../../modes/theme/theme";
|
||||
import webSearchSystemPrompt from "../../prompts/system/web-search.md" with { type: "text" };
|
||||
@@ -213,7 +213,7 @@ export class SearchTool implements AgentTool<typeof webSearchSchema, SearchRende
|
||||
readonly strict = true;
|
||||
|
||||
constructor(_session: ToolSession) {
|
||||
this.description = renderPromptTemplate(webSearchDescription);
|
||||
this.description = prompt.render(webSearchDescription);
|
||||
}
|
||||
|
||||
async execute(
|
||||
@@ -231,7 +231,7 @@ export class SearchTool implements AgentTool<typeof webSearchSchema, SearchRende
|
||||
export const webSearchCustomTool: CustomTool<typeof webSearchSchema, SearchRenderDetails> = {
|
||||
name: "web_search",
|
||||
label: "Web Search",
|
||||
description: renderPromptTemplate(webSearchDescription),
|
||||
description: prompt.render(webSearchDescription),
|
||||
parameters: webSearchSchema,
|
||||
|
||||
async execute(
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { describe, expect, test } from "bun:test";
|
||||
import { parseFrontmatter } from "@oh-my-pi/pi-coding-agent/utils/frontmatter";
|
||||
import { parseFrontmatter } from "@oh-my-pi/pi-utils";
|
||||
|
||||
describe("parseFrontmatter", () => {
|
||||
const parse = (content: string) => parseFrontmatter(content, { source: "tests:frontmatter", level: "off" });
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import { describe, expect, it } from "bun:test";
|
||||
import { renderPromptTemplate } from "@oh-my-pi/pi-coding-agent/config/prompt-templates";
|
||||
import planModeApprovedPrompt from "@oh-my-pi/pi-coding-agent/prompts/system/plan-mode-approved.md" with {
|
||||
type: "text",
|
||||
};
|
||||
import { prompt } from "@oh-my-pi/pi-utils";
|
||||
|
||||
describe("plan-mode-approved prompt", () => {
|
||||
it("includes final plan artifact path in injected execution prompt", () => {
|
||||
const rendered = renderPromptTemplate(planModeApprovedPrompt, {
|
||||
const rendered = prompt.render(planModeApprovedPrompt, {
|
||||
planContent: "1. Do work",
|
||||
finalPlanFilePath: "local://WP_MIGRATION_PLAN.md",
|
||||
});
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { describe, expect, test } from "bun:test";
|
||||
import { formatPromptContent } from "@oh-my-pi/pi-coding-agent/utils/prompt-format";
|
||||
import { prompt } from "@oh-my-pi/pi-utils";
|
||||
|
||||
describe("formatPromptContent renderPhase", () => {
|
||||
describe("prompt.format renderPhase", () => {
|
||||
test("pre-render preserves indentation on Handlebars block lines", () => {
|
||||
const input = "<root>\n {{#if ok}}\n value\n {{/if}}\n</root>";
|
||||
|
||||
const output = formatPromptContent(input, { renderPhase: "pre-render" });
|
||||
const output = prompt.format(input, { renderPhase: "pre-render" });
|
||||
|
||||
expect(output).toBe("<root>\n {{#if ok}}\n value\n {{/if}}\n</root>");
|
||||
});
|
||||
@@ -13,7 +13,7 @@ describe("formatPromptContent renderPhase", () => {
|
||||
test("pre-render preserves leading tabs", () => {
|
||||
const input = "\t<root>\n\t {{#if ok}}\n\t value\n\t {{/if}}\n</root>";
|
||||
|
||||
const output = formatPromptContent(input, { renderPhase: "pre-render" });
|
||||
const output = prompt.format(input, { renderPhase: "pre-render" });
|
||||
|
||||
expect(output).toBe(input);
|
||||
});
|
||||
@@ -21,7 +21,7 @@ describe("formatPromptContent renderPhase", () => {
|
||||
test("pre-render trims trailing whitespace", () => {
|
||||
const input = "\t<root> \n\t {{#if ok}}\t\n\t value \n\t {{/if}} \n</root>";
|
||||
|
||||
const output = formatPromptContent(input, { renderPhase: "pre-render" });
|
||||
const output = prompt.format(input, { renderPhase: "pre-render" });
|
||||
|
||||
expect(output).toBe("\t<root>\n\t {{#if ok}}\n\t value\n\t {{/if}}\n</root>");
|
||||
});
|
||||
@@ -29,7 +29,7 @@ describe("formatPromptContent renderPhase", () => {
|
||||
test("post-render mode preserves indentation on Handlebars-like lines", () => {
|
||||
const input = "<root>\n {{#if ok}}\n value\n {{/if}}\n</root>";
|
||||
|
||||
const output = formatPromptContent(input, { renderPhase: "post-render" });
|
||||
const output = prompt.format(input, { renderPhase: "post-render" });
|
||||
|
||||
expect(output).toBe("<root>\n {{#if ok}}\n value\n {{/if}}\n</root>");
|
||||
});
|
||||
@@ -37,8 +37,8 @@ describe("formatPromptContent renderPhase", () => {
|
||||
test("pre-render removes blank line before closing Handlebars block while post-render keeps it", () => {
|
||||
const input = "<root>\n{{#if ok}}\nvalue\n\n{{/if}}\n</root>";
|
||||
|
||||
const preRender = formatPromptContent(input, { renderPhase: "pre-render" });
|
||||
const postRender = formatPromptContent(input, { renderPhase: "post-render" });
|
||||
const preRender = prompt.format(input, { renderPhase: "pre-render" });
|
||||
const postRender = prompt.format(input, { renderPhase: "post-render" });
|
||||
|
||||
expect(preRender).toBe("<root>\n{{#if ok}}\nvalue\n{{/if}}\n</root>");
|
||||
expect(postRender).toBe("<root>\n{{#if ok}}\nvalue\n\n{{/if}}\n</root>");
|
||||
@@ -46,7 +46,7 @@ describe("formatPromptContent renderPhase", () => {
|
||||
test("pre-render compacts table rows and does not duplicate content when replacing ascii", () => {
|
||||
const input =
|
||||
'|`cat <<\'EOF\' > file`|`write(path="file", content="...")`|\n|`sed -i \'s/old/new/\' file`|`edit(path="file", edits=[...])`|';
|
||||
const output = formatPromptContent(input, {
|
||||
const output = prompt.format(input, {
|
||||
renderPhase: "pre-render",
|
||||
replaceAsciiSymbols: true,
|
||||
});
|
||||
|
||||
@@ -2,8 +2,8 @@ import { describe, expect, test } from "bun:test";
|
||||
import * as fs from "node:fs/promises";
|
||||
import * as os from "node:os";
|
||||
import * as path from "node:path";
|
||||
import { renderPromptTemplate, type TemplateContext } from "@oh-my-pi/pi-coding-agent/config/prompt-templates";
|
||||
import { buildSystemPrompt } from "@oh-my-pi/pi-coding-agent/system-prompt";
|
||||
import { prompt } from "@oh-my-pi/pi-utils";
|
||||
import Handlebars from "handlebars";
|
||||
|
||||
const baseGitContext = {
|
||||
@@ -16,7 +16,7 @@ const baseGitContext = {
|
||||
|
||||
const systemPromptsDir = path.resolve(import.meta.dir, "../src/prompts/system");
|
||||
|
||||
const baseRenderContext: TemplateContext = {
|
||||
const baseRenderContext: prompt.TemplateContext = {
|
||||
TASK_TOOL_NAME: "task",
|
||||
ARGUMENTS: "alpha beta",
|
||||
agent: "You are a delegated worker",
|
||||
@@ -101,7 +101,7 @@ describe("system Handlebars prompt templates", () => {
|
||||
const templatePath = path.join(systemPromptsDir, "custom-system-prompt.md");
|
||||
const template = await Bun.file(templatePath).text();
|
||||
|
||||
const both = renderPromptTemplate(template, {
|
||||
const both = prompt.render(template, {
|
||||
...baseRenderContext,
|
||||
contextFiles: [{ path: "a.txt", content: "A" }],
|
||||
git: { ...baseGitContext, isRepo: true },
|
||||
@@ -110,7 +110,7 @@ describe("system Handlebars prompt templates", () => {
|
||||
expect(both).toContain("## Context");
|
||||
expect(both).toContain("## Version Control");
|
||||
|
||||
const contextOnly = renderPromptTemplate(template, {
|
||||
const contextOnly = prompt.render(template, {
|
||||
...baseRenderContext,
|
||||
contextFiles: [{ path: "a.txt", content: "A" }],
|
||||
git: { isRepo: false },
|
||||
@@ -119,7 +119,7 @@ describe("system Handlebars prompt templates", () => {
|
||||
expect(contextOnly).toContain("## Context");
|
||||
expect(contextOnly).not.toContain("## Version Control");
|
||||
|
||||
const gitOnly = renderPromptTemplate(template, {
|
||||
const gitOnly = prompt.render(template, {
|
||||
...baseRenderContext,
|
||||
contextFiles: [],
|
||||
git: {
|
||||
@@ -134,7 +134,7 @@ describe("system Handlebars prompt templates", () => {
|
||||
expect(gitOnly).not.toContain("## Context");
|
||||
expect(gitOnly).toContain("## Version Control");
|
||||
|
||||
const neither = renderPromptTemplate(template, {
|
||||
const neither = prompt.render(template, {
|
||||
...baseRenderContext,
|
||||
contextFiles: [],
|
||||
git: { isRepo: false },
|
||||
@@ -149,7 +149,7 @@ describe("system Handlebars prompt templates", () => {
|
||||
const template = await Bun.file(templatePath).text();
|
||||
|
||||
const baseTools = baseRenderContext.tools as string[];
|
||||
const withInspectImage = renderPromptTemplate(template, {
|
||||
const withInspectImage = prompt.render(template, {
|
||||
...baseRenderContext,
|
||||
tools: [...baseTools, "inspect_image"],
|
||||
});
|
||||
@@ -157,7 +157,7 @@ describe("system Handlebars prompt templates", () => {
|
||||
expect(withInspectImage).toContain("**MUST** use `inspect_image` over `read`");
|
||||
expect(withInspectImage).toContain("Write a specific `question` for `inspect_image`");
|
||||
|
||||
const withoutInspectImage = renderPromptTemplate(template, {
|
||||
const withoutInspectImage = prompt.render(template, {
|
||||
...baseRenderContext,
|
||||
tools: baseTools.filter((tool: string) => tool !== "inspect_image"),
|
||||
});
|
||||
@@ -168,7 +168,7 @@ describe("system Handlebars prompt templates", () => {
|
||||
const templatePath = path.join(systemPromptsDir, "system-prompt.md");
|
||||
const template = await Bun.file(templatePath).text();
|
||||
|
||||
const rendered = renderPromptTemplate(template, {
|
||||
const rendered = prompt.render(template, {
|
||||
...baseRenderContext,
|
||||
mcpDiscoveryMode: true,
|
||||
hasMCPDiscoveryServers: true,
|
||||
|
||||
@@ -10,8 +10,8 @@ import * as path from "node:path";
|
||||
import type { AgentMessage, ResolvedThinkingLevel, ThinkingLevel } from "@oh-my-pi/pi-agent-core";
|
||||
import type { Model } from "@oh-my-pi/pi-ai";
|
||||
|
||||
import { computeLineHash, formatSessionDumpText, RpcClient, renderPromptTemplate } from "@oh-my-pi/pi-coding-agent";
|
||||
import { Snowflake } from "@oh-my-pi/pi-utils";
|
||||
import { computeLineHash, formatSessionDumpText, RpcClient } from "@oh-my-pi/pi-coding-agent";
|
||||
import { prompt, Snowflake } from "@oh-my-pi/pi-utils";
|
||||
import { diffLines } from "diff";
|
||||
import { formatDirectory } from "./formatter";
|
||||
import { discoverSharedInfra, InProcessClient, type SharedInfra } from "./in-process-client";
|
||||
@@ -612,21 +612,21 @@ type BenchmarkPromptDelivery = {
|
||||
};
|
||||
|
||||
function buildBenchmarkSystemPrompt(params: { multiFile: boolean; config: BenchmarkConfig }): string {
|
||||
return renderPromptTemplate(benchmarkSystemPrompt, {
|
||||
return prompt.render(benchmarkSystemPrompt, {
|
||||
multiFile: params.multiFile,
|
||||
instructions: buildInstructions(params.config),
|
||||
});
|
||||
}
|
||||
|
||||
function buildInitialBenchmarkPrompt(params: { taskPrompt: string; guidedContext?: string | null }): string {
|
||||
return renderPromptTemplate(benchmarkTaskPrompt, {
|
||||
return prompt.render(benchmarkTaskPrompt, {
|
||||
task_prompt: params.taskPrompt,
|
||||
guided_context: params.guidedContext ?? undefined,
|
||||
});
|
||||
}
|
||||
|
||||
function buildRetryBenchmarkPrompt(params: { retryContext: string; guidedContext?: string | null }): string {
|
||||
return renderPromptTemplate(benchmarkRetryPrompt, {
|
||||
return prompt.render(benchmarkRetryPrompt, {
|
||||
retry_context: params.retryContext,
|
||||
guided_context: params.guidedContext ?? undefined,
|
||||
});
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"beautiful-mermaid": "^1.1",
|
||||
"handlebars": "^4.7.9",
|
||||
"winston": "^3.19",
|
||||
"winston-daily-rotate-file": "^5.0"
|
||||
},
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { logger, truncate } from "@oh-my-pi/pi-utils";
|
||||
import { YAML } from "bun";
|
||||
import { truncate } from "./format";
|
||||
import * as logger from "./logger";
|
||||
|
||||
function stripHtmlComments(content: string): string {
|
||||
return content.replace(/<!--[\s\S]*?-->/g, "");
|
||||
@@ -4,6 +4,7 @@ export * from "./color";
|
||||
export * from "./dirs";
|
||||
export * from "./env";
|
||||
export * from "./format";
|
||||
export * from "./frontmatter";
|
||||
export * from "./fs-error";
|
||||
export * from "./glob";
|
||||
export * from "./hook-fetch";
|
||||
@@ -13,6 +14,7 @@ export * from "./mermaid-ascii";
|
||||
export * as postmortem from "./postmortem";
|
||||
export * as procmgr from "./procmgr";
|
||||
export { setNativeKillTree } from "./procmgr";
|
||||
export * as prompt from "./prompt";
|
||||
export * as ptree from "./ptree";
|
||||
export { AbortError, ChildProcess, Exception, NonZeroExitError } from "./ptree";
|
||||
export * from "./snowflake";
|
||||
|
||||
@@ -0,0 +1,401 @@
|
||||
import type { HelperDelegate, HelperOptions, Template, TemplateDelegate } from "handlebars";
|
||||
import Handlebars from "handlebars";
|
||||
|
||||
export type { HelperDelegate, HelperOptions, Template, TemplateDelegate };
|
||||
|
||||
export type PromptRenderPhase = "pre-render" | "post-render";
|
||||
|
||||
export interface PromptFormatOptions {
|
||||
renderPhase?: PromptRenderPhase;
|
||||
replaceAsciiSymbols?: boolean;
|
||||
boldRfc2119Keywords?: boolean;
|
||||
}
|
||||
|
||||
// Opening XML tag (not self-closing, not closing)
|
||||
const OPENING_XML = /^<([a-z_-]+)(?:\s+[^>]*)?>$/;
|
||||
// Closing XML tag
|
||||
const CLOSING_XML = /^<\/([a-z_-]+)>$/;
|
||||
// Handlebars block start: {{#if}}, {{#has}}, {{#list}}, etc.
|
||||
const OPENING_HBS = /^\{\{#/;
|
||||
// Handlebars block end: {{/if}}, {{/has}}, {{/list}}, etc.
|
||||
const CLOSING_HBS = /^\{\{\//;
|
||||
// List item (- or * or 1.)
|
||||
const LIST_ITEM = /^(?:[-*]\s|\d+\.\s)/;
|
||||
// Code fence
|
||||
const CODE_FENCE = /^```/;
|
||||
// Table row
|
||||
const TABLE_ROW = /^\|.*\|$/;
|
||||
// Table separator (|---|---|)
|
||||
const TABLE_SEP = /^\|[-:\s|]+\|$/;
|
||||
|
||||
/** RFC 2119 keywords used in prompts. */
|
||||
const RFC2119_KEYWORDS = /\b(?:MUST NOT|SHOULD NOT|SHALL NOT|RECOMMENDED|REQUIRED|OPTIONAL|SHOULD|SHALL|MUST|MAY)\b/g;
|
||||
|
||||
function boldRfc2119Keywords(line: string): string {
|
||||
return line.replace(RFC2119_KEYWORDS, (match, offset, source) => {
|
||||
const isAlreadyBold =
|
||||
source[offset - 2] === "*" &&
|
||||
source[offset - 1] === "*" &&
|
||||
source[offset + match.length] === "*" &&
|
||||
source[offset + match.length + 1] === "*";
|
||||
if (isAlreadyBold) {
|
||||
return match;
|
||||
}
|
||||
return `**${match}**`;
|
||||
});
|
||||
}
|
||||
|
||||
/** Compact a table row by trimming cell padding */
|
||||
function compactTableRow(line: string): string {
|
||||
const cells = line.split("|");
|
||||
return cells.map(c => c.trim()).join("|");
|
||||
}
|
||||
|
||||
/** Compact a table separator row */
|
||||
function compactTableSep(line: string): string {
|
||||
const cells = line.split("|").filter(c => c.trim());
|
||||
const normalized = cells.map(c => {
|
||||
const trimmed = c.trim();
|
||||
const left = trimmed.startsWith(":");
|
||||
const right = trimmed.endsWith(":");
|
||||
if (left && right) return ":---:";
|
||||
if (left) return ":---";
|
||||
if (right) return "---:";
|
||||
return "---";
|
||||
});
|
||||
return `|${normalized.join("|")}|`;
|
||||
}
|
||||
|
||||
function replaceCommonAsciiSymbols(line: string): string {
|
||||
return line
|
||||
.replace(/\.{3}/g, "…")
|
||||
.replace(/<->/g, "↔")
|
||||
.replace(/->/g, "→")
|
||||
.replace(/<-/g, "←")
|
||||
.replace(/!=/g, "≠")
|
||||
.replace(/<=/g, "≤")
|
||||
.replace(/>=/g, "≥");
|
||||
}
|
||||
|
||||
export function format(content: string, options: PromptFormatOptions = {}): string {
|
||||
const {
|
||||
renderPhase = "post-render",
|
||||
replaceAsciiSymbols = false,
|
||||
boldRfc2119Keywords: shouldBoldRfc2119 = false,
|
||||
} = options;
|
||||
const isPreRender = renderPhase === "pre-render";
|
||||
const lines = content.split("\n");
|
||||
const result: string[] = [];
|
||||
let inCodeBlock = false;
|
||||
const topLevelTags: string[] = [];
|
||||
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
let line = lines[i].trimEnd();
|
||||
let trimmedStart = line.trimStart();
|
||||
if (CODE_FENCE.test(trimmedStart)) {
|
||||
inCodeBlock = !inCodeBlock;
|
||||
result.push(line);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (inCodeBlock) {
|
||||
result.push(line);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (replaceAsciiSymbols) {
|
||||
line = replaceCommonAsciiSymbols(line);
|
||||
}
|
||||
trimmedStart = line.trimStart();
|
||||
const trimmed = line.trim();
|
||||
|
||||
const isOpeningXml = OPENING_XML.test(trimmedStart) && !trimmedStart.endsWith("/>");
|
||||
if (isOpeningXml && line.length === trimmedStart.length) {
|
||||
const match = OPENING_XML.exec(trimmedStart);
|
||||
if (match) topLevelTags.push(match[1]);
|
||||
}
|
||||
|
||||
const closingMatch = CLOSING_XML.exec(trimmedStart);
|
||||
if (closingMatch) {
|
||||
const tagName = closingMatch[1];
|
||||
if (topLevelTags.length > 0 && topLevelTags[topLevelTags.length - 1] === tagName) {
|
||||
topLevelTags.pop();
|
||||
}
|
||||
} else if (isPreRender && trimmedStart.startsWith("{{")) {
|
||||
/* keep indentation as-is in pre-render for Handlebars markers */
|
||||
} else if (TABLE_SEP.test(trimmedStart)) {
|
||||
const leadingWhitespace = line.slice(0, line.length - trimmedStart.length);
|
||||
line = `${leadingWhitespace}${compactTableSep(trimmedStart)}`;
|
||||
} else if (TABLE_ROW.test(trimmedStart)) {
|
||||
const leadingWhitespace = line.slice(0, line.length - trimmedStart.length);
|
||||
line = `${leadingWhitespace}${compactTableRow(trimmedStart)}`;
|
||||
}
|
||||
|
||||
if (shouldBoldRfc2119) {
|
||||
line = boldRfc2119Keywords(line);
|
||||
}
|
||||
|
||||
const isBlank = trimmed === "";
|
||||
if (isBlank) {
|
||||
const prevLine = result[result.length - 1]?.trim() ?? "";
|
||||
const nextLine = lines[i + 1]?.trim() ?? "";
|
||||
|
||||
if (LIST_ITEM.test(nextLine)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (OPENING_XML.test(prevLine) || (isPreRender && OPENING_HBS.test(prevLine))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (CLOSING_XML.test(nextLine) || (isPreRender && CLOSING_HBS.test(nextLine))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const prevIsBlank = prevLine === "";
|
||||
if (prevIsBlank) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (CLOSING_XML.test(trimmed) || (isPreRender && CLOSING_HBS.test(trimmed))) {
|
||||
while (result.length > 0 && result[result.length - 1].trim() === "") {
|
||||
result.pop();
|
||||
}
|
||||
}
|
||||
|
||||
result.push(line);
|
||||
}
|
||||
|
||||
while (result.length > 0 && result[result.length - 1].trim() === "") {
|
||||
result.pop();
|
||||
}
|
||||
|
||||
return result.join("\n");
|
||||
}
|
||||
|
||||
export interface TemplateContext extends Record<string, unknown> {
|
||||
args?: string[];
|
||||
ARGUMENTS?: string;
|
||||
arguments?: string;
|
||||
}
|
||||
|
||||
const handlebars = Handlebars.create();
|
||||
|
||||
handlebars.registerHelper("arg", function (this: TemplateContext, index: number | string): string {
|
||||
const args = this.args ?? [];
|
||||
const parsedIndex = typeof index === "number" ? index : Number.parseInt(index, 10);
|
||||
if (!Number.isFinite(parsedIndex)) return "";
|
||||
const zeroBased = parsedIndex - 1;
|
||||
if (zeroBased < 0) return "";
|
||||
return args[zeroBased] ?? "";
|
||||
});
|
||||
|
||||
/**
|
||||
* {{#list items prefix="- " suffix="" join="\n"}}{{this}}{{/list}}
|
||||
* Renders an array with customizable prefix, suffix, and join separator.
|
||||
* Note: Use \n in join for newlines (will be unescaped automatically).
|
||||
*/
|
||||
handlebars.registerHelper(
|
||||
"list",
|
||||
function (this: unknown, context: unknown[], options: Handlebars.HelperOptions): string {
|
||||
if (!Array.isArray(context) || context.length === 0) return "";
|
||||
const prefix = (options.hash.prefix as string) ?? "";
|
||||
const suffix = (options.hash.suffix as string) ?? "";
|
||||
const rawSeparator = (options.hash.join as string) ?? "\n";
|
||||
const separator = rawSeparator.replace(/\\n/g, "\n").replace(/\\t/g, "\t");
|
||||
return context.map(item => `${prefix}${options.fn(item)}${suffix}`).join(separator);
|
||||
},
|
||||
);
|
||||
|
||||
/**
|
||||
* {{join array ", "}}
|
||||
* Joins an array with a separator (default: ", ").
|
||||
*/
|
||||
handlebars.registerHelper("join", (context: unknown[], separator?: unknown): string => {
|
||||
if (!Array.isArray(context)) return "";
|
||||
const sep = typeof separator === "string" ? separator : ", ";
|
||||
return context.join(sep);
|
||||
});
|
||||
|
||||
/**
|
||||
* {{default value "fallback"}}
|
||||
* Returns the value if truthy, otherwise returns the fallback.
|
||||
*/
|
||||
handlebars.registerHelper("default", (value: unknown, defaultValue: unknown): unknown => value || defaultValue);
|
||||
|
||||
/**
|
||||
* {{pluralize count "item" "items"}}
|
||||
* Returns "1 item" or "5 items" based on count.
|
||||
*/
|
||||
handlebars.registerHelper(
|
||||
"pluralize",
|
||||
(count: number, singular: string, plural: string): string => `${count} ${count === 1 ? singular : plural}`,
|
||||
);
|
||||
|
||||
/**
|
||||
* {{#when value "==" compare}}...{{else}}...{{/when}}
|
||||
* Conditional block with comparison operators: ==, ===, !=, !==, >, <, >=, <=
|
||||
*/
|
||||
handlebars.registerHelper(
|
||||
"when",
|
||||
function (this: unknown, lhs: unknown, operator: string, rhs: unknown, options: Handlebars.HelperOptions): string {
|
||||
const ops: Record<string, (a: unknown, b: unknown) => boolean> = {
|
||||
"==": (a, b) => a === b,
|
||||
"===": (a, b) => a === b,
|
||||
"!=": (a, b) => a !== b,
|
||||
"!==": (a, b) => a !== b,
|
||||
">": (a, b) => (a as number) > (b as number),
|
||||
"<": (a, b) => (a as number) < (b as number),
|
||||
">=": (a, b) => (a as number) >= (b as number),
|
||||
"<=": (a, b) => (a as number) <= (b as number),
|
||||
};
|
||||
const fn = ops[operator];
|
||||
if (!fn) return options.inverse(this);
|
||||
return fn(lhs, rhs) ? options.fn(this) : options.inverse(this);
|
||||
},
|
||||
);
|
||||
|
||||
/**
|
||||
* {{#ifAny a b c}}...{{else}}...{{/ifAny}}
|
||||
* True if any argument is truthy.
|
||||
*/
|
||||
handlebars.registerHelper("ifAny", function (this: unknown, ...args: unknown[]): string {
|
||||
const options = args.pop() as Handlebars.HelperOptions;
|
||||
return args.some(Boolean) ? options.fn(this) : options.inverse(this);
|
||||
});
|
||||
|
||||
/**
|
||||
* {{#ifAll a b c}}...{{else}}...{{/ifAll}}
|
||||
* True if all arguments are truthy.
|
||||
*/
|
||||
handlebars.registerHelper("ifAll", function (this: unknown, ...args: unknown[]): string {
|
||||
const options = args.pop() as Handlebars.HelperOptions;
|
||||
return args.every(Boolean) ? options.fn(this) : options.inverse(this);
|
||||
});
|
||||
|
||||
/**
|
||||
* {{#table rows headers="Col1|Col2"}}{{col1}}|{{col2}}{{/table}}
|
||||
* Generates a markdown table from an array of objects.
|
||||
*/
|
||||
handlebars.registerHelper(
|
||||
"table",
|
||||
function (this: unknown, context: unknown[], options: Handlebars.HelperOptions): string {
|
||||
if (!Array.isArray(context) || context.length === 0) return "";
|
||||
const headersStr = options.hash.headers as string | undefined;
|
||||
const headers = headersStr?.split("|") ?? [];
|
||||
const separator = headers.map(() => "---").join(" | ");
|
||||
const headerRow = headers.length > 0 ? `| ${headers.join(" | ")} |\n| ${separator} |\n` : "";
|
||||
const rows = context.map(item => `| ${options.fn(item).trim()} |`).join("\n");
|
||||
return headerRow + rows;
|
||||
},
|
||||
);
|
||||
|
||||
/**
|
||||
* {{#codeblock lang="diff"}}...{{/codeblock}}
|
||||
* Wraps content in a fenced code block.
|
||||
*/
|
||||
handlebars.registerHelper("codeblock", function (this: unknown, options: Handlebars.HelperOptions): string {
|
||||
const lang = (options.hash.lang as string) ?? "";
|
||||
const content = options.fn(this).trim();
|
||||
return `\`\`\`${lang}\n${content}\n\`\`\``;
|
||||
});
|
||||
|
||||
/**
|
||||
* {{#xml "tag"}}content{{/xml}}
|
||||
* Wraps content in XML-style tags. Returns empty string if content is empty.
|
||||
*/
|
||||
handlebars.registerHelper("xml", function (this: unknown, tag: string, options: Handlebars.HelperOptions): string {
|
||||
const content = options.fn(this).trim();
|
||||
if (!content) return "";
|
||||
return `<${tag}>\n${content}\n</${tag}>`;
|
||||
});
|
||||
|
||||
/**
|
||||
* {{escapeXml value}}
|
||||
* Escapes XML special characters: & < > "
|
||||
*/
|
||||
handlebars.registerHelper("escapeXml", (value: unknown): string => {
|
||||
if (value == null) return "";
|
||||
return String(value).replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
||||
});
|
||||
|
||||
/**
|
||||
* {{len array}}
|
||||
* Returns the length of an array or string.
|
||||
*/
|
||||
handlebars.registerHelper("len", (value: unknown): number => {
|
||||
if (Array.isArray(value)) return value.length;
|
||||
if (typeof value === "string") return value.length;
|
||||
return 0;
|
||||
});
|
||||
|
||||
/**
|
||||
* {{add a b}}
|
||||
* Adds two numbers.
|
||||
*/
|
||||
handlebars.registerHelper("add", (a: number, b: number): number => (a ?? 0) + (b ?? 0));
|
||||
|
||||
/**
|
||||
* {{sub a b}}
|
||||
* Subtracts b from a.
|
||||
*/
|
||||
handlebars.registerHelper("sub", (a: number, b: number): number => (a ?? 0) - (b ?? 0));
|
||||
|
||||
/**
|
||||
* {{#has collection item}}...{{else}}...{{/has}}
|
||||
* Checks if an array includes an item or if a Set/Map has a key.
|
||||
*/
|
||||
handlebars.registerHelper(
|
||||
"has",
|
||||
function (this: unknown, collection: unknown, item: unknown, options: Handlebars.HelperOptions): string {
|
||||
let found = false;
|
||||
if (Array.isArray(collection)) {
|
||||
found = collection.includes(item);
|
||||
} else if (collection instanceof Set) {
|
||||
found = collection.has(item);
|
||||
} else if (collection instanceof Map) {
|
||||
found = collection.has(item);
|
||||
} else if (collection && typeof collection === "object") {
|
||||
if (typeof item === "string" || typeof item === "number" || typeof item === "symbol") {
|
||||
found = item in collection;
|
||||
}
|
||||
}
|
||||
return found ? options.fn(this) : options.inverse(this);
|
||||
},
|
||||
);
|
||||
|
||||
/**
|
||||
* {{includes array item}}
|
||||
* Returns true if array includes item. For use in other helpers.
|
||||
*/
|
||||
handlebars.registerHelper("includes", (collection: unknown, item: unknown): boolean => {
|
||||
if (Array.isArray(collection)) return collection.includes(item);
|
||||
if (collection instanceof Set) return collection.has(item);
|
||||
if (collection instanceof Map) return collection.has(item);
|
||||
return false;
|
||||
});
|
||||
|
||||
/**
|
||||
* {{not value}}
|
||||
* Returns logical NOT of value. For use in subexpressions.
|
||||
*/
|
||||
handlebars.registerHelper("not", (value: unknown): boolean => !value);
|
||||
|
||||
export function registerHelper(name: string, fn: HelperDelegate): void {
|
||||
handlebars.registerHelper(name, fn);
|
||||
}
|
||||
|
||||
export function registerPartial(name: string, fn: Template): void {
|
||||
handlebars.registerPartial(name, fn);
|
||||
}
|
||||
|
||||
export function compile(template: string): (context: TemplateContext) => string {
|
||||
return handlebars.compile(template, { noEscape: true, strict: false });
|
||||
}
|
||||
|
||||
export function render(template: string, context: TemplateContext = {}): string {
|
||||
const compiled = compile(template);
|
||||
const rendered = compiled(context ?? {});
|
||||
return format(rendered, { renderPhase: "post-render" });
|
||||
}
|
||||
Reference in New Issue
Block a user