@oh-my-pi/pi-coding-agent 13.19.0 → 14.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (205) hide show
  1. package/CHANGELOG.md +277 -2
  2. package/package.json +86 -20
  3. package/scripts/format-prompts.ts +2 -2
  4. package/src/autoresearch/apply-contract-to-state.ts +24 -0
  5. package/src/autoresearch/contract.ts +0 -44
  6. package/src/autoresearch/dashboard.ts +1 -2
  7. package/src/autoresearch/git.ts +91 -0
  8. package/src/autoresearch/helpers.ts +49 -0
  9. package/src/autoresearch/index.ts +28 -187
  10. package/src/autoresearch/prompt.md +26 -9
  11. package/src/autoresearch/state.ts +0 -6
  12. package/src/autoresearch/tools/init-experiment.ts +202 -117
  13. package/src/autoresearch/tools/log-experiment.ts +83 -125
  14. package/src/autoresearch/tools/run-experiment.ts +48 -10
  15. package/src/autoresearch/types.ts +2 -2
  16. package/src/capability/index.ts +4 -2
  17. package/src/cli/file-processor.ts +3 -3
  18. package/src/cli/grep-cli.ts +8 -8
  19. package/src/cli/grievances-cli.ts +78 -0
  20. package/src/cli/read-cli.ts +67 -0
  21. package/src/cli/setup-cli.ts +4 -4
  22. package/src/cli/update-cli.ts +3 -3
  23. package/src/cli.ts +2 -0
  24. package/src/commands/grep.ts +6 -1
  25. package/src/commands/grievances.ts +20 -0
  26. package/src/commands/read.ts +33 -0
  27. package/src/commit/agentic/agent.ts +5 -5
  28. package/src/commit/agentic/index.ts +3 -4
  29. package/src/commit/agentic/tools/analyze-file.ts +3 -3
  30. package/src/commit/agentic/validation.ts +1 -1
  31. package/src/commit/analysis/conventional.ts +4 -4
  32. package/src/commit/analysis/summary.ts +3 -3
  33. package/src/commit/changelog/generate.ts +4 -4
  34. package/src/commit/map-reduce/map-phase.ts +4 -4
  35. package/src/commit/map-reduce/reduce-phase.ts +4 -4
  36. package/src/commit/pipeline.ts +3 -4
  37. package/src/config/model-registry.ts +17 -3
  38. package/src/config/prompt-templates.ts +44 -226
  39. package/src/config/resolve-config-value.ts +4 -2
  40. package/src/config/settings-schema.ts +54 -2
  41. package/src/config/settings.ts +25 -26
  42. package/src/dap/client.ts +674 -0
  43. package/src/dap/config.ts +150 -0
  44. package/src/dap/defaults.json +211 -0
  45. package/src/dap/index.ts +4 -0
  46. package/src/dap/session.ts +1255 -0
  47. package/src/dap/types.ts +600 -0
  48. package/src/debug/log-viewer.ts +3 -2
  49. package/src/discovery/builtin.ts +1 -2
  50. package/src/discovery/codex.ts +2 -2
  51. package/src/discovery/github.ts +2 -1
  52. package/src/discovery/helpers.ts +2 -2
  53. package/src/discovery/opencode.ts +2 -2
  54. package/src/edit/diff.ts +818 -0
  55. package/src/edit/index.ts +309 -0
  56. package/src/edit/line-hash.ts +67 -0
  57. package/src/edit/modes/chunk.ts +454 -0
  58. package/src/{patch → edit/modes}/hashline.ts +741 -361
  59. package/src/{patch/applicator.ts → edit/modes/patch.ts} +420 -117
  60. package/src/{patch/fuzzy.ts → edit/modes/replace.ts} +519 -197
  61. package/src/{patch → edit}/normalize.ts +97 -76
  62. package/src/{patch/shared.ts → edit/renderer.ts} +181 -108
  63. package/src/exec/bash-executor.ts +4 -2
  64. package/src/exec/idle-timeout-watchdog.ts +126 -0
  65. package/src/exec/non-interactive-env.ts +5 -0
  66. package/src/extensibility/custom-commands/bundled/ci-green/index.ts +2 -2
  67. package/src/extensibility/custom-commands/bundled/review/index.ts +36 -15
  68. package/src/extensibility/custom-commands/loader.ts +1 -2
  69. package/src/extensibility/custom-tools/loader.ts +34 -11
  70. package/src/extensibility/extensions/loader.ts +9 -4
  71. package/src/extensibility/extensions/runner.ts +24 -1
  72. package/src/extensibility/extensions/types.ts +1 -1
  73. package/src/extensibility/hooks/loader.ts +5 -6
  74. package/src/extensibility/hooks/types.ts +1 -1
  75. package/src/extensibility/plugins/doctor.ts +2 -1
  76. package/src/extensibility/slash-commands.ts +3 -7
  77. package/src/index.ts +2 -1
  78. package/src/internal-urls/docs-index.generated.ts +11 -11
  79. package/src/ipy/executor.ts +58 -17
  80. package/src/ipy/gateway-coordinator.ts +6 -4
  81. package/src/ipy/kernel.ts +45 -22
  82. package/src/ipy/runtime.ts +2 -2
  83. package/src/lsp/client.ts +7 -4
  84. package/src/lsp/clients/lsp-linter-client.ts +4 -4
  85. package/src/lsp/config.ts +20 -4
  86. package/src/lsp/defaults.json +688 -154
  87. package/src/lsp/index.ts +234 -45
  88. package/src/lsp/lspmux.ts +2 -2
  89. package/src/lsp/startup-events.ts +13 -0
  90. package/src/lsp/types.ts +12 -1
  91. package/src/lsp/utils.ts +8 -1
  92. package/src/main.ts +102 -46
  93. package/src/memories/index.ts +4 -5
  94. package/src/modes/acp/acp-agent.ts +563 -163
  95. package/src/modes/acp/acp-event-mapper.ts +9 -1
  96. package/src/modes/acp/acp-mode.ts +4 -2
  97. package/src/modes/components/agent-dashboard.ts +3 -4
  98. package/src/modes/components/diff.ts +6 -7
  99. package/src/modes/components/read-tool-group.ts +6 -12
  100. package/src/modes/components/session-observer-overlay.ts +21 -12
  101. package/src/modes/components/settings-defs.ts +5 -0
  102. package/src/modes/components/tool-execution.ts +1 -1
  103. package/src/modes/components/welcome.ts +1 -1
  104. package/src/modes/controllers/btw-controller.ts +2 -2
  105. package/src/modes/controllers/command-controller.ts +3 -2
  106. package/src/modes/controllers/input-controller.ts +12 -8
  107. package/src/modes/index.ts +20 -2
  108. package/src/modes/interactive-mode.ts +94 -37
  109. package/src/modes/rpc/host-tools.ts +186 -0
  110. package/src/modes/rpc/rpc-client.ts +178 -13
  111. package/src/modes/rpc/rpc-mode.ts +73 -3
  112. package/src/modes/rpc/rpc-types.ts +53 -1
  113. package/src/modes/theme/theme.ts +80 -8
  114. package/src/modes/types.ts +2 -2
  115. package/src/prompts/review-request.md +6 -0
  116. package/src/prompts/system/system-prompt.md +2 -1
  117. package/src/prompts/tools/chunk-edit.md +223 -0
  118. package/src/prompts/tools/debug.md +43 -0
  119. package/src/prompts/tools/grep.md +3 -0
  120. package/src/prompts/tools/lsp.md +5 -5
  121. package/src/prompts/tools/read-chunk.md +17 -0
  122. package/src/prompts/tools/read.md +19 -5
  123. package/src/sdk.ts +190 -154
  124. package/src/secrets/obfuscator.ts +1 -1
  125. package/src/session/agent-session.ts +306 -256
  126. package/src/session/agent-storage.ts +12 -12
  127. package/src/session/compaction/branch-summarization.ts +3 -3
  128. package/src/session/compaction/compaction.ts +5 -6
  129. package/src/session/compaction/utils.ts +3 -3
  130. package/src/session/history-storage.ts +62 -19
  131. package/src/session/messages.ts +3 -3
  132. package/src/session/session-dump-format.ts +203 -0
  133. package/src/session/session-storage.ts +4 -2
  134. package/src/session/streaming-output.ts +1 -1
  135. package/src/session/tool-choice-queue.ts +213 -0
  136. package/src/slash-commands/builtin-registry.ts +56 -8
  137. package/src/ssh/connection-manager.ts +2 -2
  138. package/src/ssh/sshfs-mount.ts +5 -5
  139. package/src/stt/downloader.ts +4 -4
  140. package/src/stt/recorder.ts +4 -4
  141. package/src/stt/transcriber.ts +2 -2
  142. package/src/system-prompt.ts +21 -13
  143. package/src/task/agents.ts +5 -6
  144. package/src/task/commands.ts +2 -5
  145. package/src/task/executor.ts +4 -4
  146. package/src/task/index.ts +3 -4
  147. package/src/task/template.ts +2 -2
  148. package/src/task/worktree.ts +4 -4
  149. package/src/tools/ask.ts +2 -3
  150. package/src/tools/ast-edit.ts +7 -7
  151. package/src/tools/ast-grep.ts +7 -7
  152. package/src/tools/auto-generated-guard.ts +36 -41
  153. package/src/tools/await-tool.ts +2 -2
  154. package/src/tools/bash.ts +5 -23
  155. package/src/tools/browser.ts +4 -5
  156. package/src/tools/calculator.ts +2 -3
  157. package/src/tools/cancel-job.ts +2 -2
  158. package/src/tools/checkpoint.ts +3 -3
  159. package/src/tools/debug.ts +1007 -0
  160. package/src/tools/exit-plan-mode.ts +2 -3
  161. package/src/tools/fetch.ts +67 -3
  162. package/src/tools/find.ts +4 -5
  163. package/src/tools/fs-cache-invalidation.ts +5 -0
  164. package/src/tools/gemini-image.ts +13 -5
  165. package/src/tools/gh.ts +10 -11
  166. package/src/tools/grep.ts +57 -9
  167. package/src/tools/index.ts +44 -22
  168. package/src/tools/inspect-image.ts +4 -4
  169. package/src/tools/output-meta.ts +1 -1
  170. package/src/tools/python.ts +19 -6
  171. package/src/tools/read.ts +198 -67
  172. package/src/tools/render-mermaid.ts +2 -3
  173. package/src/tools/render-utils.ts +20 -6
  174. package/src/tools/renderers.ts +3 -1
  175. package/src/tools/report-tool-issue.ts +80 -0
  176. package/src/tools/resolve.ts +70 -39
  177. package/src/tools/search-tool-bm25.ts +2 -2
  178. package/src/tools/ssh.ts +2 -2
  179. package/src/tools/todo-write.ts +2 -2
  180. package/src/tools/tool-timeouts.ts +1 -0
  181. package/src/tools/write.ts +5 -6
  182. package/src/tui/tree-list.ts +3 -1
  183. package/src/utils/clipboard.ts +80 -0
  184. package/src/utils/commit-message-generator.ts +2 -3
  185. package/src/utils/edit-mode.ts +49 -0
  186. package/src/utils/file-display-mode.ts +6 -5
  187. package/src/utils/file-mentions.ts +8 -7
  188. package/src/utils/git.ts +4 -4
  189. package/src/utils/image-loading.ts +98 -0
  190. package/src/utils/title-generator.ts +2 -3
  191. package/src/utils/tools-manager.ts +6 -6
  192. package/src/web/scrapers/choosealicense.ts +1 -1
  193. package/src/web/search/index.ts +3 -3
  194. package/src/autoresearch/command-initialize.md +0 -34
  195. package/src/patch/diff.ts +0 -433
  196. package/src/patch/index.ts +0 -888
  197. package/src/patch/parser.ts +0 -532
  198. package/src/patch/types.ts +0 -292
  199. package/src/prompts/agents/oracle.md +0 -77
  200. package/src/tools/pending-action.ts +0 -49
  201. package/src/utils/child-process.ts +0 -88
  202. package/src/utils/frontmatter.ts +0 -117
  203. package/src/utils/image-input.ts +0 -274
  204. package/src/utils/mime.ts +0 -53
  205. package/src/utils/prompt-format.ts +0 -170
@@ -0,0 +1,67 @@
1
+ /**
2
+ * Read CLI command handler.
3
+ *
4
+ * Handles `omp read` subcommand — emits chunk-mode read output for files,
5
+ * and delegates URL reads through the read tool pipeline.
6
+ */
7
+ import * as path from "node:path";
8
+ import chalk from "chalk";
9
+ import { Settings } from "../config/settings";
10
+ import { formatChunkedRead, resolveAnchorStyle } from "../edit/modes/chunk";
11
+ import { getLanguageFromPath } from "../modes/theme/theme";
12
+ import type { ToolSession } from "../tools";
13
+ import { parseReadUrlTarget } from "../tools/fetch";
14
+ import { ReadTool } from "../tools/read";
15
+
16
+ export interface ReadCommandArgs {
17
+ path: string;
18
+ sel?: string;
19
+ }
20
+
21
+ function createCliReadSession(cwd: string, settings: Settings): ToolSession {
22
+ return {
23
+ cwd,
24
+ hasUI: false,
25
+ hasEditTool: true,
26
+ getSessionFile: () => null,
27
+ getSessionSpawns: () => null,
28
+ settings,
29
+ };
30
+ }
31
+
32
+ export async function runReadCommand(cmd: ReadCommandArgs): Promise<void> {
33
+ const cwd = process.cwd();
34
+ const parsedUrlTarget = parseReadUrlTarget(cmd.path, cmd.sel);
35
+ if (parsedUrlTarget) {
36
+ const settings = await Settings.init({ cwd });
37
+ const tool = new ReadTool(createCliReadSession(cwd, settings));
38
+ const result = await tool.execute("cli-read", { path: cmd.path, sel: cmd.sel });
39
+ const text = result.content.find((content): content is { type: "text"; text: string } => content.type === "text");
40
+ console.log(text?.text ?? "");
41
+ return;
42
+ }
43
+
44
+ const filePath = path.resolve(cmd.path);
45
+ const file = Bun.file(filePath);
46
+ if (!(await file.exists())) {
47
+ console.error(chalk.red(`Error: File not found: ${cmd.path}`));
48
+ process.exit(1);
49
+ }
50
+
51
+ const readPath = cmd.sel ? `${filePath}:${cmd.sel}` : filePath;
52
+ const language = getLanguageFromPath(filePath);
53
+
54
+ try {
55
+ const result = await formatChunkedRead({
56
+ filePath,
57
+ readPath,
58
+ cwd,
59
+ language,
60
+ anchorStyle: resolveAnchorStyle(),
61
+ });
62
+ console.log(result.text);
63
+ } catch (err) {
64
+ console.error(chalk.red(`Error: ${err instanceof Error ? err.message : String(err)}`));
65
+ process.exit(1);
66
+ }
67
+ }
@@ -4,7 +4,7 @@
4
4
  * Handles `omp setup <component>` to install dependencies for optional features.
5
5
  */
6
6
  import * as path from "node:path";
7
- import { APP_NAME, getPythonEnvDir } from "@oh-my-pi/pi-utils";
7
+ import { $which, APP_NAME, getPythonEnvDir } from "@oh-my-pi/pi-utils";
8
8
  import { $ } from "bun";
9
9
  import chalk from "chalk";
10
10
  import { theme } from "../modes/theme/theme";
@@ -90,12 +90,12 @@ async function checkPythonSetup(): Promise<PythonCheckResult> {
90
90
  managedEnvPath: MANAGED_PYTHON_ENV,
91
91
  };
92
92
 
93
- const systemPythonPath = Bun.which("python") ?? Bun.which("python3");
93
+ const systemPythonPath = $which("python") ?? $which("python3");
94
94
  const managedPath = managedPythonPath();
95
95
  const hasManagedEnv = await Bun.file(managedPath).exists();
96
96
 
97
- result.uvPath = Bun.which("uv") ?? undefined;
98
- result.pipPath = Bun.which("pip3") ?? Bun.which("pip") ?? undefined;
97
+ result.uvPath = $which("uv") ?? undefined;
98
+ result.pipPath = $which("pip3") ?? $which("pip") ?? undefined;
99
99
 
100
100
  const candidates = [systemPythonPath, hasManagedEnv ? managedPath : undefined].filter(
101
101
  (candidate): candidate is string => !!candidate,
@@ -7,7 +7,7 @@
7
7
  import * as fs from "node:fs";
8
8
  import * as path from "node:path";
9
9
  import { pipeline } from "node:stream/promises";
10
- import { APP_NAME, isEnoent, VERSION } from "@oh-my-pi/pi-utils";
10
+ import { $which, APP_NAME, isEnoent, VERSION } from "@oh-my-pi/pi-utils";
11
11
  import { $ } from "bun";
12
12
  import chalk from "chalk";
13
13
  import { theme } from "../modes/theme/theme";
@@ -36,7 +36,7 @@ export function parseUpdateArgs(args: string[]): { force: boolean; check: boolea
36
36
  }
37
37
 
38
38
  async function getBunGlobalBinDir(): Promise<string | undefined> {
39
- if (!Bun.which("bun")) return undefined;
39
+ if (!$which("bun")) return undefined;
40
40
  try {
41
41
  const result = await $`bun pm bin -g`.quiet().nothrow();
42
42
  if (result.exitCode !== 0) return undefined;
@@ -167,7 +167,7 @@ function getBinaryName(): string {
167
167
  * Resolve the path that `omp` maps to in the user's PATH.
168
168
  */
169
169
  function resolveOmpPath(): string | undefined {
170
- return Bun.which(APP_NAME) ?? undefined;
170
+ return $which(APP_NAME) ?? undefined;
171
171
  }
172
172
 
173
173
  /**
package/src/cli.ts CHANGED
@@ -48,6 +48,8 @@ const commands: CommandEntry[] = [
48
48
  { name: "commit", load: () => import("./commands/commit").then(m => m.default) },
49
49
  { name: "config", load: () => import("./commands/config").then(m => m.default) },
50
50
  { name: "grep", load: () => import("./commands/grep").then(m => m.default) },
51
+ { name: "grievances", load: () => import("./commands/grievances").then(m => m.default) },
52
+ { name: "read", load: () => import("./commands/read").then(m => m.default) },
51
53
  { name: "jupyter", load: () => import("./commands/jupyter").then(m => m.default) },
52
54
  { name: "plugin", load: () => import("./commands/plugin").then(m => m.default) },
53
55
  { name: "setup", load: () => import("./commands/setup").then(m => m.default) },
@@ -1,6 +1,7 @@
1
1
  /**
2
2
  * Test grep tool.
3
3
  */
4
+ import { GrepOutputMode } from "@oh-my-pi/pi-natives";
4
5
  import { Args, Command, Flags } from "@oh-my-pi/pi-utils/cli";
5
6
  import { type GrepCommandArgs, runGrepCommand } from "../cli/grep-cli";
6
7
  import { initTheme } from "../modes/theme/theme";
@@ -25,7 +26,11 @@ export default class Grep extends Command {
25
26
  async run(): Promise<void> {
26
27
  const { args, flags } = await this.parse(Grep);
27
28
 
28
- const mode: GrepCommandArgs["mode"] = flags.count ? "count" : flags.files ? "filesWithMatches" : "content";
29
+ const mode: GrepCommandArgs["mode"] = flags.count
30
+ ? GrepOutputMode.Count
31
+ : flags.files
32
+ ? GrepOutputMode.FilesWithMatches
33
+ : GrepOutputMode.Content;
29
34
 
30
35
  const cmd: GrepCommandArgs = {
31
36
  pattern: args.pattern ?? "",
@@ -0,0 +1,20 @@
1
+ /**
2
+ * View recently reported tool issues from automated QA.
3
+ */
4
+ import { Command, Flags } from "@oh-my-pi/pi-utils/cli";
5
+ import { listGrievances } from "../cli/grievances-cli";
6
+
7
+ export default class Grievances extends Command {
8
+ static description = "View reported tool issues (auto-QA grievances)";
9
+
10
+ static flags = {
11
+ limit: Flags.integer({ char: "n", description: "Number of recent issues to show", default: 20 }),
12
+ tool: Flags.string({ char: "t", description: "Filter by tool name" }),
13
+ json: Flags.boolean({ char: "j", description: "Output as JSON", default: false }),
14
+ };
15
+
16
+ async run(): Promise<void> {
17
+ const { flags } = await this.parse(Grievances);
18
+ await listGrievances({ limit: flags.limit, tool: flags.tool, json: flags.json });
19
+ }
20
+ }
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Chunk-mode read tool.
3
+ */
4
+ import { Args, Command, Flags } from "@oh-my-pi/pi-utils/cli";
5
+ import { type ReadCommandArgs, runReadCommand } from "../cli/read-cli";
6
+ import { initTheme } from "../modes/theme/theme";
7
+
8
+ export default class Read extends Command {
9
+ static description = "Read a file as a chunk tree";
10
+
11
+ static args = {
12
+ path: Args.string({ description: "File path to read", required: true }),
13
+ };
14
+
15
+ static flags = {
16
+ sel: Flags.string({
17
+ char: "s",
18
+ description: "Chunk selector or line range (e.g. class_Foo.fn_bar, L10-L20)",
19
+ }),
20
+ };
21
+
22
+ async run(): Promise<void> {
23
+ const { args, flags } = await this.parse(Read);
24
+
25
+ const cmd: ReadCommandArgs = {
26
+ path: args.path ?? "",
27
+ sel: flags.sel,
28
+ };
29
+
30
+ await initTheme();
31
+ await runReadCommand(cmd);
32
+ }
33
+ }
@@ -1,10 +1,10 @@
1
1
  import { INTENT_FIELD, type ThinkingLevel } from "@oh-my-pi/pi-agent-core";
2
2
  import type { Api, Model } from "@oh-my-pi/pi-ai";
3
3
  import { Markdown } from "@oh-my-pi/pi-tui";
4
+ import { prompt } from "@oh-my-pi/pi-utils";
4
5
  import chalk from "chalk";
5
6
  import typesDescriptionPrompt from "../../commit/prompts/types-description.md" with { type: "text" };
6
7
  import type { ModelRegistry } from "../../config/model-registry";
7
- import { renderPromptTemplate } from "../../config/prompt-templates";
8
8
  import type { Settings } from "../../config/settings";
9
9
  import { getMarkdownTheme } from "../../modes/theme/theme";
10
10
  import { createAgentSession } from "../../sdk";
@@ -36,8 +36,8 @@ export interface ExistingChangelogEntries {
36
36
  }
37
37
 
38
38
  export async function runCommitAgentSession(input: CommitAgentInput): Promise<CommitAgentState> {
39
- const typesDescription = renderPromptTemplate(typesDescriptionPrompt);
40
- const systemPrompt = renderPromptTemplate(agentSystemPrompt, {
39
+ const typesDescription = prompt.render(typesDescriptionPrompt);
40
+ const systemPrompt = prompt.render(agentSystemPrompt, {
41
41
  types_description: typesDescription,
42
42
  });
43
43
  const state: CommitAgentState = { diffText: input.diffText };
@@ -149,7 +149,7 @@ export async function runCommitAgentSession(input: CommitAgentInput): Promise<Co
149
149
  });
150
150
 
151
151
  try {
152
- const prompt = renderPromptTemplate(agentUserPrompt, {
152
+ const agentUserMessage = prompt.render(agentUserPrompt, {
153
153
  user_context: input.userContext,
154
154
  changelog_targets: input.changelogTargets.length > 0 ? input.changelogTargets.join("\n") : undefined,
155
155
  existing_changelog_entries: input.existingChangelogEntries,
@@ -158,7 +158,7 @@ export async function runCommitAgentSession(input: CommitAgentInput): Promise<Co
158
158
  let retryCount = 0;
159
159
  const needsChangelog = input.requireChangelog && input.changelogTargets.length > 0;
160
160
 
161
- await session.prompt(prompt, {
161
+ await session.prompt(agentUserMessage, {
162
162
  attribution: "agent",
163
163
  expandPromptTemplates: false,
164
164
  });
@@ -1,6 +1,6 @@
1
1
  import * as path from "node:path";
2
2
  import { createInterface } from "node:readline/promises";
3
- import { $env, getProjectDir, isEnoent } from "@oh-my-pi/pi-utils";
3
+ import { $env, getProjectDir, isEnoent, prompt } from "@oh-my-pi/pi-utils";
4
4
  import { applyChangelogProposals } from "../../commit/changelog";
5
5
  import { detectChangelogBoundaries } from "../../commit/changelog/detect";
6
6
  import { parseUnreleasedSection } from "../../commit/changelog/parse";
@@ -8,7 +8,6 @@ import { formatCommitMessage } from "../../commit/message";
8
8
  import { resolvePrimaryModel, resolveSmolModel } from "../../commit/model-selection";
9
9
  import type { CommitCommandArgs, ConventionalAnalysis } from "../../commit/types";
10
10
  import { ModelRegistry } from "../../config/model-registry";
11
- import { renderPromptTemplate } from "../../config/prompt-templates";
12
11
  import { Settings } from "../../config/settings";
13
12
  import { discoverAuthStorage, discoverContextFiles } from "../../sdk";
14
13
  import * as git from "../../utils/git";
@@ -305,8 +304,8 @@ async function confirmSplitCommitPlan(plan: SplitCommitPlan): Promise<boolean> {
305
304
  }
306
305
  const rl = createInterface({ input: process.stdin, output: process.stdout });
307
306
  try {
308
- const prompt = renderPromptTemplate(splitConfirmPrompt, { count: plan.commits.length });
309
- const answer = await rl.question(prompt);
307
+ const splitConfirmQuestion = prompt.render(splitConfirmPrompt, { count: plan.commits.length });
308
+ const answer = await rl.question(splitConfirmQuestion);
310
309
  return ["y", "yes"].includes(answer.trim().toLowerCase());
311
310
  } finally {
312
311
  rl.close();
@@ -1,9 +1,9 @@
1
+ import { prompt } from "@oh-my-pi/pi-utils";
1
2
  import { Type } from "@sinclair/typebox";
2
3
  import analyzeFilePrompt from "../../../commit/agentic/prompts/analyze-file.md" with { type: "text" };
3
4
  import type { CommitAgentState } from "../../../commit/agentic/state";
4
5
  import type { NumstatEntry } from "../../../commit/types";
5
6
  import type { ModelRegistry } from "../../../config/model-registry";
6
- import { renderPromptTemplate } from "../../../config/prompt-templates";
7
7
  import type { Settings } from "../../../config/settings";
8
8
  import type { CustomTool, CustomToolContext } from "../../../extensibility/custom-tools/types";
9
9
  import type { AuthStorage } from "../../../session/auth-storage";
@@ -66,7 +66,7 @@ export function createAnalyzeFileTool(options: {
66
66
  const numstat = options.state.overview?.numstat ?? [];
67
67
  const tasks = params.files.map((file, index) => {
68
68
  const relatedFiles = formatRelatedFiles(params.files, file, numstat);
69
- const prompt = renderPromptTemplate(analyzeFilePrompt, {
69
+ const assignment = prompt.render(analyzeFilePrompt, {
70
70
  file,
71
71
  goal: params.goal,
72
72
  related_files: relatedFiles,
@@ -74,7 +74,7 @@ export function createAnalyzeFileTool(options: {
74
74
  return {
75
75
  id: `AnalyzeFile${index + 1}`,
76
76
  description: `Analyze ${file}`,
77
- assignment: prompt,
77
+ assignment,
78
78
  };
79
79
  });
80
80
  const taskParams: TaskParams = {
@@ -1,7 +1,7 @@
1
1
  import { stripTypePrefix } from "../../commit/analysis/summary";
2
2
  import { validateSummary } from "../../commit/analysis/validation";
3
3
  import type { CommitType, ConventionalDetail } from "../../commit/types";
4
- import { normalizeUnicode } from "../../patch/normalize";
4
+ import { normalizeUnicode } from "../../edit/normalize";
5
5
 
6
6
  export const SUMMARY_MAX_CHARS = 72;
7
7
  export const MAX_DETAIL_ITEMS = 6;
@@ -1,11 +1,11 @@
1
1
  import type { ThinkingLevel } from "@oh-my-pi/pi-agent-core";
2
2
  import type { Api, AssistantMessage, Model } from "@oh-my-pi/pi-ai";
3
3
  import { completeSimple, validateToolCall } from "@oh-my-pi/pi-ai";
4
+ import { prompt } from "@oh-my-pi/pi-utils";
4
5
  import { Type } from "@sinclair/typebox";
5
6
  import analysisSystemPrompt from "../../commit/prompts/analysis-system.md" with { type: "text" };
6
7
  import analysisUserPrompt from "../../commit/prompts/analysis-user.md" with { type: "text" };
7
8
  import type { ChangelogCategory, ConventionalAnalysis } from "../../commit/types";
8
- import { renderPromptTemplate } from "../../config/prompt-templates";
9
9
  import { toReasoningEffort } from "../../thinking";
10
10
  import { extractTextContent, extractToolCall, normalizeAnalysis, parseJsonPayload } from "../utils";
11
11
 
@@ -76,7 +76,7 @@ export async function generateConventionalAnalysis({
76
76
  stat,
77
77
  diff,
78
78
  }: ConventionalAnalysisInput): Promise<ConventionalAnalysis> {
79
- const prompt = renderPromptTemplate(analysisUserPrompt, {
79
+ const userContent = prompt.render(analysisUserPrompt, {
80
80
  context_files: contextFiles && contextFiles.length > 0 ? contextFiles : undefined,
81
81
  user_context: userContext,
82
82
  types_description: typesDescription,
@@ -89,8 +89,8 @@ export async function generateConventionalAnalysis({
89
89
  const response = await completeSimple(
90
90
  model,
91
91
  {
92
- systemPrompt: renderPromptTemplate(analysisSystemPrompt),
93
- messages: [{ role: "user", content: prompt, timestamp: Date.now() }],
92
+ systemPrompt: prompt.render(analysisSystemPrompt),
93
+ messages: [{ role: "user", content: userContent, timestamp: Date.now() }],
94
94
  tools: [ConventionalAnalysisTool],
95
95
  },
96
96
  { apiKey, maxTokens: 2400, reasoning: toReasoningEffort(thinkingLevel) },
@@ -1,11 +1,11 @@
1
1
  import type { ThinkingLevel } from "@oh-my-pi/pi-agent-core";
2
2
  import type { Api, AssistantMessage, Model } from "@oh-my-pi/pi-ai";
3
3
  import { completeSimple, validateToolCall } from "@oh-my-pi/pi-ai";
4
+ import { prompt } from "@oh-my-pi/pi-utils";
4
5
  import { Type } from "@sinclair/typebox";
5
6
  import summarySystemPrompt from "../../commit/prompts/summary-system.md" with { type: "text" };
6
7
  import summaryUserPrompt from "../../commit/prompts/summary-user.md" with { type: "text" };
7
8
  import type { CommitSummary } from "../../commit/types";
8
- import { renderPromptTemplate } from "../../config/prompt-templates";
9
9
  import { toReasoningEffort } from "../../thinking";
10
10
  import { extractTextContent, extractToolCall } from "../utils";
11
11
 
@@ -44,7 +44,7 @@ export async function generateSummary({
44
44
  userContext,
45
45
  }: SummaryInput): Promise<CommitSummary> {
46
46
  const systemPrompt = renderSummaryPrompt({ commitType, scope, maxChars });
47
- const userPrompt = renderPromptTemplate(summaryUserPrompt, {
47
+ const userPrompt = prompt.render(summaryUserPrompt, {
48
48
  user_context: userContext,
49
49
  details: details.join("\n"),
50
50
  stat,
@@ -73,7 +73,7 @@ function renderSummaryPrompt({
73
73
  maxChars: number;
74
74
  }): string {
75
75
  const scopePrefix = scope ? `(${scope})` : "";
76
- return renderPromptTemplate(summarySystemPrompt, {
76
+ return prompt.render(summarySystemPrompt, {
77
77
  commit_type: commitType,
78
78
  scope_prefix: scopePrefix,
79
79
  chars: String(maxChars),
@@ -1,11 +1,11 @@
1
1
  import type { ThinkingLevel } from "@oh-my-pi/pi-agent-core";
2
2
  import type { Api, AssistantMessage, Model } from "@oh-my-pi/pi-ai";
3
3
  import { completeSimple, validateToolCall } from "@oh-my-pi/pi-ai";
4
+ import { prompt } from "@oh-my-pi/pi-utils";
4
5
  import { type TSchema, Type } from "@sinclair/typebox";
5
6
  import changelogSystemPrompt from "../../commit/prompts/changelog-system.md" with { type: "text" };
6
7
  import changelogUserPrompt from "../../commit/prompts/changelog-user.md" with { type: "text" };
7
8
  import { CHANGELOG_CATEGORIES, type ChangelogCategory, type ChangelogGenerationResult } from "../../commit/types";
8
- import { renderPromptTemplate } from "../../config/prompt-templates";
9
9
  import { toReasoningEffort } from "../../thinking";
10
10
  import { extractTextContent, extractToolCall, parseJsonPayload } from "../utils";
11
11
 
@@ -48,7 +48,7 @@ export async function generateChangelogEntries({
48
48
  stat,
49
49
  diff,
50
50
  }: ChangelogPromptInput): Promise<ChangelogGenerationResult> {
51
- const prompt = renderPromptTemplate(changelogUserPrompt, {
51
+ const userContent = prompt.render(changelogUserPrompt, {
52
52
  changelog_path: changelogPath,
53
53
  is_package_changelog: isPackageChangelog,
54
54
  existing_entries: existingEntries,
@@ -58,8 +58,8 @@ export async function generateChangelogEntries({
58
58
  const response = await completeSimple(
59
59
  model,
60
60
  {
61
- systemPrompt: renderPromptTemplate(changelogSystemPrompt),
62
- messages: [{ role: "user", content: prompt, timestamp: Date.now() }],
61
+ systemPrompt: prompt.render(changelogSystemPrompt),
62
+ messages: [{ role: "user", content: userContent, timestamp: Date.now() }],
63
63
  tools: [changelogTool],
64
64
  },
65
65
  { apiKey, maxTokens: 1200, reasoning: toReasoningEffort(thinkingLevel) },
@@ -1,11 +1,11 @@
1
1
  import type { ThinkingLevel } from "@oh-my-pi/pi-agent-core";
2
2
  import type { Api, AssistantMessage, Message, Model } from "@oh-my-pi/pi-ai";
3
3
  import { completeSimple } from "@oh-my-pi/pi-ai";
4
+ import { prompt } from "@oh-my-pi/pi-utils";
4
5
  import fileObserverSystemPrompt from "../../commit/prompts/file-observer-system.md" with { type: "text" };
5
6
  import fileObserverUserPrompt from "../../commit/prompts/file-observer-user.md" with { type: "text" };
6
7
  import type { FileDiff, FileObservation } from "../../commit/types";
7
8
  import { isExcludedFile } from "../../commit/utils/exclusions";
8
- import { renderPromptTemplate } from "../../config/prompt-templates";
9
9
  import { toReasoningEffort } from "../../thinking";
10
10
  import { truncateToTokenLimit } from "./utils";
11
11
 
@@ -38,7 +38,7 @@ export async function runMapPhase({
38
38
  config,
39
39
  }: MapPhaseInput): Promise<FileObservation[]> {
40
40
  const filtered = files.filter(file => !isExcludedFile(file.filename));
41
- const systemPrompt = renderPromptTemplate(fileObserverSystemPrompt);
41
+ const systemPrompt = prompt.render(fileObserverSystemPrompt);
42
42
  const maxFileTokens = config?.maxFileTokens ?? MAX_FILE_TOKENS;
43
43
  const maxConcurrency = config?.maxConcurrency ?? MAX_CONCURRENCY;
44
44
  const timeoutMs = config?.timeoutMs ?? MAP_PHASE_TIMEOUT_MS;
@@ -56,14 +56,14 @@ export async function runMapPhase({
56
56
 
57
57
  const contextHeader = generateContextHeader(filtered, file.filename);
58
58
  const truncated = truncateToTokenLimit(file.content, maxFileTokens);
59
- const prompt = renderPromptTemplate(fileObserverUserPrompt, {
59
+ const userContent = prompt.render(fileObserverUserPrompt, {
60
60
  filename: file.filename,
61
61
  diff: truncated,
62
62
  context_header: contextHeader,
63
63
  });
64
64
  const request = {
65
65
  systemPrompt,
66
- messages: [{ role: "user", content: prompt, timestamp: Date.now() }] as Message[],
66
+ messages: [{ role: "user", content: userContent, timestamp: Date.now() }] as Message[],
67
67
  };
68
68
 
69
69
  const response = await withRetry(
@@ -1,11 +1,11 @@
1
1
  import type { ThinkingLevel } from "@oh-my-pi/pi-agent-core";
2
2
  import type { Api, AssistantMessage, Model } from "@oh-my-pi/pi-ai";
3
3
  import { completeSimple, validateToolCall } from "@oh-my-pi/pi-ai";
4
+ import { prompt } from "@oh-my-pi/pi-utils";
4
5
  import { Type } from "@sinclair/typebox";
5
6
  import reduceSystemPrompt from "../../commit/prompts/reduce-system.md" with { type: "text" };
6
7
  import reduceUserPrompt from "../../commit/prompts/reduce-user.md" with { type: "text" };
7
8
  import type { ChangelogCategory, ConventionalAnalysis, FileObservation } from "../../commit/types";
8
- import { renderPromptTemplate } from "../../config/prompt-templates";
9
9
  import { toReasoningEffort } from "../../thinking";
10
10
  import { extractTextContent, extractToolCall, normalizeAnalysis, parseJsonPayload } from "../utils";
11
11
 
@@ -67,7 +67,7 @@ export async function runReducePhase({
67
67
  scopeCandidates,
68
68
  typesDescription,
69
69
  }: ReducePhaseInput): Promise<ConventionalAnalysis> {
70
- const prompt = renderPromptTemplate(reduceUserPrompt, {
70
+ const userContent = prompt.render(reduceUserPrompt, {
71
71
  types_description: typesDescription,
72
72
  observations: observations.flatMap(obs => obs.observations.map(line => `- ${obs.file}: ${line}`)).join("\n"),
73
73
  stat,
@@ -76,8 +76,8 @@ export async function runReducePhase({
76
76
  const response = await completeSimple(
77
77
  model,
78
78
  {
79
- systemPrompt: renderPromptTemplate(reduceSystemPrompt),
80
- messages: [{ role: "user", content: prompt, timestamp: Date.now() }],
79
+ systemPrompt: prompt.render(reduceSystemPrompt),
80
+ messages: [{ role: "user", content: userContent, timestamp: Date.now() }],
81
81
  tools: [ReduceTool],
82
82
  },
83
83
  { apiKey, maxTokens: 2400, reasoning: toReasoningEffort(thinkingLevel) },
@@ -1,9 +1,8 @@
1
1
  import * as path from "node:path";
2
2
  import type { ThinkingLevel } from "@oh-my-pi/pi-agent-core";
3
3
  import type { Api, Model } from "@oh-my-pi/pi-ai";
4
- import { getProjectDir, logger } from "@oh-my-pi/pi-utils";
4
+ import { getProjectDir, logger, prompt } from "@oh-my-pi/pi-utils";
5
5
  import { ModelRegistry } from "../config/model-registry";
6
- import { renderPromptTemplate } from "../config/prompt-templates";
7
6
  import { Settings } from "../config/settings";
8
7
  import { discoverAuthStorage } from "../sdk";
9
8
  import { loadProjectContextFiles } from "../system-prompt";
@@ -26,7 +25,7 @@ import type { CommitCommandArgs, ConventionalAnalysis } from "./types";
26
25
 
27
26
  const SUMMARY_MAX_CHARS = 72;
28
27
  const RECENT_COMMITS_COUNT = 8;
29
- const TYPES_DESCRIPTION = renderPromptTemplate(typesDescriptionPrompt);
28
+ const TYPES_DESCRIPTION = prompt.render(typesDescriptionPrompt);
30
29
 
31
30
  /**
32
31
  * Execute the omp commit pipeline for staged changes.
@@ -236,7 +235,7 @@ async function generateSummaryWithRetry(input: {
236
235
  }
237
236
 
238
237
  function buildRetryContext(base: string | undefined, errors: string[]): string {
239
- return renderPromptTemplate(summaryRetryPrompt, {
238
+ return prompt.render(summaryRetryPrompt, {
240
239
  base_context: base,
241
240
  errors: errors.join("; "),
242
241
  });
@@ -947,7 +947,10 @@ export class ModelRegistry {
947
947
  }
948
948
  const models = this.#applyProviderModelOverrides(
949
949
  providerConfig.provider,
950
- this.#applyProviderCompat(providerConfig.compat, cache.models),
950
+ this.#normalizeDiscoverableModels(
951
+ providerConfig,
952
+ this.#applyProviderCompat(providerConfig.compat, cache.models),
953
+ ),
951
954
  );
952
955
  cachedModels.push(...models);
953
956
  this.#providerDiscoveryStates.set(providerConfig.provider, {
@@ -967,11 +970,19 @@ export class ModelRegistry {
967
970
  return models.map(model => ({ ...model, compat: mergeCompat(model.compat, compat) }));
968
971
  }
969
972
 
973
+ #normalizeDiscoverableModels(providerConfig: DiscoveryProviderConfig, models: Model<Api>[]): Model<Api>[] {
974
+ if (providerConfig.provider !== "ollama" || providerConfig.api !== "openai-responses") {
975
+ return models;
976
+ }
977
+
978
+ return models.map(model => (model.api === "openai-completions" ? { ...model, api: "openai-responses" } : model));
979
+ }
980
+
970
981
  #addImplicitDiscoverableProviders(configuredProviders: Set<string>): void {
971
982
  if (!configuredProviders.has("ollama")) {
972
983
  this.#discoverableProviders.push({
973
984
  provider: "ollama",
974
- api: "openai-completions",
985
+ api: "openai-responses",
975
986
  baseUrl: Bun.env.OLLAMA_BASE_URL || "http://127.0.0.1:11434",
976
987
  discovery: { type: "ollama" },
977
988
  optional: true,
@@ -1203,7 +1214,10 @@ export class ModelRegistry {
1203
1214
  }
1204
1215
  return this.#applyProviderModelOverrides(
1205
1216
  providerId,
1206
- this.#applyProviderCompat(providerConfig.compat, result.models),
1217
+ this.#normalizeDiscoverableModels(
1218
+ providerConfig,
1219
+ this.#applyProviderCompat(providerConfig.compat, result.models),
1220
+ ),
1207
1221
  );
1208
1222
  }
1209
1223