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

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 (43) hide show
  1. package/CHANGELOG.md +63 -1
  2. package/package.json +11 -8
  3. package/src/config/model-registry.ts +3 -2
  4. package/src/config/model-resolver.ts +33 -25
  5. package/src/config/settings.ts +9 -2
  6. package/src/dap/session.ts +31 -39
  7. package/src/debug/log-formatting.ts +2 -2
  8. package/src/edit/index.ts +2 -0
  9. package/src/edit/modes/chunk.ts +45 -16
  10. package/src/edit/modes/hashline.ts +2 -2
  11. package/src/ipy/executor.ts +3 -7
  12. package/src/ipy/kernel.ts +3 -3
  13. package/src/lsp/client.ts +4 -2
  14. package/src/lsp/index.ts +4 -9
  15. package/src/lsp/lspmux.ts +2 -2
  16. package/src/lsp/utils.ts +27 -143
  17. package/src/modes/components/diff.ts +1 -1
  18. package/src/modes/controllers/event-controller.ts +438 -426
  19. package/src/modes/theme/mermaid-cache.ts +5 -7
  20. package/src/modes/theme/theme.ts +2 -161
  21. package/src/priority.json +8 -0
  22. package/src/prompts/agents/designer.md +1 -2
  23. package/src/prompts/system/system-prompt.md +40 -2
  24. package/src/prompts/tools/chunk-edit.md +66 -38
  25. package/src/prompts/tools/read-chunk.md +10 -1
  26. package/src/sdk.ts +2 -1
  27. package/src/session/agent-session.ts +10 -0
  28. package/src/session/compaction/compaction.ts +1 -1
  29. package/src/tools/ast-edit.ts +2 -2
  30. package/src/tools/browser.ts +84 -21
  31. package/src/tools/fetch.ts +1 -1
  32. package/src/tools/find.ts +40 -94
  33. package/src/tools/gemini-image.ts +1 -0
  34. package/src/tools/index.ts +2 -3
  35. package/src/tools/read.ts +2 -0
  36. package/src/tools/render-utils.ts +1 -1
  37. package/src/tools/report-tool-issue.ts +2 -2
  38. package/src/utils/edit-mode.ts +2 -2
  39. package/src/utils/image-resize.ts +73 -37
  40. package/src/utils/lang-from-path.ts +239 -0
  41. package/src/utils/sixel.ts +2 -2
  42. package/src/web/scrapers/types.ts +50 -32
  43. package/src/web/search/providers/codex.ts +21 -2
@@ -0,0 +1,239 @@
1
+ import * as path from "node:path";
2
+
3
+ /**
4
+ * Extension segment → [highlight language id, LSP language id].
5
+ * Highlight ids match tree-sitter / native highlighter; LSP ids match Language Server Protocol.
6
+ */
7
+ const EXTENSION_LANG: Record<string, readonly [string, string]> = {
8
+ // TypeScript / JavaScript
9
+ ts: ["typescript", "typescript"],
10
+ cts: ["typescript", "typescript"],
11
+ mts: ["typescript", "typescript"],
12
+ tsx: ["tsx", "typescriptreact"],
13
+ js: ["javascript", "javascript"],
14
+ jsx: ["javascript", "javascriptreact"],
15
+ mjs: ["javascript", "javascript"],
16
+ cjs: ["javascript", "javascript"],
17
+
18
+ // Systems
19
+ rs: ["rust", "rust"],
20
+ go: ["go", "go"],
21
+ c: ["c", "c"],
22
+ h: ["c", "c"],
23
+ cpp: ["cpp", "cpp"],
24
+ cc: ["cpp", "cpp"],
25
+ cxx: ["cpp", "cpp"],
26
+ hh: ["cpp", "cpp"],
27
+ hpp: ["cpp", "cpp"],
28
+ hxx: ["cpp", "cpp"],
29
+ cu: ["cpp", "cpp"],
30
+ ino: ["cpp", "cpp"],
31
+ zig: ["zig", "zig"],
32
+
33
+ // Scripting
34
+ py: ["python", "python"],
35
+ pyi: ["python", "python"],
36
+ rb: ["ruby", "ruby"],
37
+ rbw: ["ruby", "ruby"],
38
+ gemspec: ["ruby", "ruby"],
39
+ lua: ["lua", "lua"],
40
+ sh: ["bash", "shellscript"],
41
+ bash: ["bash", "shellscript"],
42
+ zsh: ["bash", "shellscript"],
43
+ ksh: ["bash", "shellscript"],
44
+ bats: ["bash", "shellscript"],
45
+ tmux: ["bash", "shellscript"],
46
+ cgi: ["bash", "shellscript"],
47
+ fcgi: ["bash", "shellscript"],
48
+ command: ["bash", "shellscript"],
49
+ tool: ["bash", "shellscript"],
50
+ fish: ["fish", "fish"],
51
+ pl: ["perl", "perl"],
52
+ pm: ["perl", "perl"],
53
+ perl: ["perl", "perl"],
54
+ php: ["php", "php"],
55
+
56
+ // JVM
57
+ java: ["java", "java"],
58
+ kt: ["kotlin", "kotlin"],
59
+ ktm: ["kotlin", "kotlin"],
60
+ kts: ["kotlin", "kotlin"],
61
+ scala: ["scala", "scala"],
62
+ sc: ["scala", "scala"],
63
+ sbt: ["scala", "scala"],
64
+ groovy: ["groovy", "groovy"],
65
+ clj: ["clojure", "clojure"],
66
+ cljc: ["clojure", "clojure"],
67
+ cljs: ["clojure", "clojure"],
68
+ edn: ["clojure", "clojure"],
69
+
70
+ // .NET
71
+ cs: ["csharp", "csharp"],
72
+ fs: ["fsharp", "fsharp"],
73
+ vb: ["vb", "vb"],
74
+
75
+ // Web
76
+ html: ["html", "html"],
77
+ htm: ["html", "html"],
78
+ xhtml: ["html", "html"],
79
+ css: ["css", "css"],
80
+ scss: ["scss", "scss"],
81
+ sass: ["sass", "sass"],
82
+ less: ["less", "less"],
83
+ vue: ["vue", "vue"],
84
+ svelte: ["svelte", "svelte"],
85
+ astro: ["astro", "astro"],
86
+
87
+ // Data
88
+ json: ["json", "json"],
89
+ jsonc: ["jsonc", "jsonc"],
90
+ yaml: ["yaml", "yaml"],
91
+ yml: ["yaml", "yaml"],
92
+ toml: ["toml", "toml"],
93
+ xml: ["xml", "xml"],
94
+ xsl: ["xml", "xml"],
95
+ xslt: ["xml", "xml"],
96
+ svg: ["xml", "xml"],
97
+ plist: ["xml", "xml"],
98
+ ini: ["ini", "ini"],
99
+
100
+ // Docs
101
+ md: ["markdown", "markdown"],
102
+ markdown: ["markdown", "markdown"],
103
+ mdx: ["markdown", "markdown"],
104
+ rst: ["restructuredtext", "restructuredtext"],
105
+ adoc: ["asciidoc", "asciidoc"],
106
+ tex: ["latex", "latex"],
107
+
108
+ // Other languages
109
+ sql: ["sql", "sql"],
110
+ graphql: ["graphql", "graphql"],
111
+ gql: ["graphql", "graphql"],
112
+ proto: ["protobuf", "protobuf"],
113
+ dockerfile: ["dockerfile", "dockerfile"],
114
+ containerfile: ["dockerfile", "dockerfile"],
115
+ tf: ["hcl", "terraform"],
116
+ hcl: ["hcl", "hcl"],
117
+ tfvars: ["hcl", "hcl"],
118
+ nix: ["nix", "nix"],
119
+ ex: ["elixir", "elixir"],
120
+ exs: ["elixir", "elixir"],
121
+ erl: ["erlang", "erlang"],
122
+ hrl: ["erlang", "erlang"],
123
+ hs: ["haskell", "haskell"],
124
+ ml: ["ocaml", "ocaml"],
125
+ mli: ["ocaml", "ocaml"],
126
+ swift: ["swift", "swift"],
127
+ r: ["r", "r"],
128
+ jl: ["julia", "julia"],
129
+ dart: ["dart", "dart"],
130
+ elm: ["elm", "elm"],
131
+ v: ["verilog", "v"],
132
+ nim: ["nim", "nim"],
133
+ cr: ["crystal", "crystal"],
134
+ d: ["d", "d"],
135
+ pas: ["pascal", "pascal"],
136
+ pp: ["pascal", "pascal"],
137
+ lisp: ["lisp", "lisp"],
138
+ lsp: ["lisp", "lisp"],
139
+ rkt: ["racket", "racket"],
140
+ scm: ["scheme", "scheme"],
141
+ ps1: ["powershell", "powershell"],
142
+ psm1: ["powershell", "powershell"],
143
+ bat: ["bat", "bat"],
144
+ cmd: ["bat", "bat"],
145
+ tla: ["tlaplus", "tlaplus"],
146
+ tlaplus: ["tlaplus", "tlaplus"],
147
+ m: ["objc", "plaintext"],
148
+ mm: ["objc", "plaintext"],
149
+ sol: ["solidity", "plaintext"],
150
+ odin: ["odin", "plaintext"],
151
+ star: ["starlark", "plaintext"],
152
+ bzl: ["starlark", "plaintext"],
153
+ sv: ["verilog", "plaintext"],
154
+ svh: ["verilog", "plaintext"],
155
+ vh: ["verilog", "plaintext"],
156
+ vim: ["vim", "plaintext"],
157
+ ipynb: ["ipynb", "plaintext"],
158
+ hbs: ["handlebars", "plaintext"],
159
+ hsb: ["handlebars", "plaintext"],
160
+ handlebars: ["handlebars", "plaintext"],
161
+ diff: ["diff", "plaintext"],
162
+ patch: ["diff", "plaintext"],
163
+ makefile: ["make", "plaintext"],
164
+ mk: ["make", "plaintext"],
165
+ mak: ["make", "plaintext"],
166
+ cmake: ["cmake", "cmake"],
167
+ justfile: ["just", "plaintext"],
168
+ txt: ["text", "plaintext"],
169
+ text: ["text", "plaintext"],
170
+ log: ["log", "plaintext"],
171
+ csv: ["csv", "plaintext"],
172
+ tsv: ["tsv", "plaintext"],
173
+ cfg: ["conf", "plaintext"],
174
+ conf: ["conf", "plaintext"],
175
+ config: ["conf", "plaintext"],
176
+ properties: ["conf", "plaintext"],
177
+ env: ["env", "plaintext"],
178
+ gitignore: ["conf", "plaintext"],
179
+ gitattributes: ["conf", "plaintext"],
180
+ gitmodules: ["conf", "plaintext"],
181
+ editorconfig: ["conf", "plaintext"],
182
+ npmrc: ["conf", "plaintext"],
183
+ prettierrc: ["conf", "plaintext"],
184
+ eslintrc: ["conf", "plaintext"],
185
+ prettierignore: ["conf", "plaintext"],
186
+ eslintignore: ["conf", "plaintext"],
187
+ };
188
+
189
+ /** Final segment after the last `.` in the full path (prior theme behavior). */
190
+ function themeExtensionKey(filePath: string): string {
191
+ const extBeg = filePath.lastIndexOf(".");
192
+ return extBeg !== -1 ? filePath.slice(extBeg + 1).toLowerCase() : filePath.toLowerCase();
193
+ }
194
+
195
+ function lspExtensionKey(filePath: string): string {
196
+ const ext = path.extname(filePath).toLowerCase();
197
+ return ext.startsWith(".") ? ext.slice(1) : "";
198
+ }
199
+
200
+ /**
201
+ * Language id for syntax highlighting and UI (icons, read tool), or undefined if unknown.
202
+ */
203
+ export function getLanguageFromPath(filePath: string): string | undefined {
204
+ const pair = EXTENSION_LANG[themeExtensionKey(filePath)];
205
+ if (pair) return pair[0];
206
+
207
+ const baseName = path.basename(filePath).toLowerCase();
208
+ if (baseName.startsWith(".env.")) return "env";
209
+ if (baseName === "dockerfile" || baseName.startsWith("dockerfile.") || baseName === "containerfile") {
210
+ return "dockerfile";
211
+ }
212
+ if (baseName === "justfile") return "just";
213
+ if (baseName === "cmakelists.txt") return "cmake";
214
+
215
+ return undefined;
216
+ }
217
+
218
+ /**
219
+ * LSP language identifier; falls back to `plaintext`.
220
+ */
221
+ export function detectLanguageId(filePath: string): string {
222
+ const baseName = path.basename(filePath).toLowerCase();
223
+ if (baseName === "dockerfile" || baseName.startsWith("dockerfile.") || baseName === "containerfile") {
224
+ return "dockerfile";
225
+ }
226
+ if (baseName === "makefile" || baseName === "gnumakefile") {
227
+ return "makefile";
228
+ }
229
+ if (baseName === "justfile") {
230
+ return "just";
231
+ }
232
+
233
+ const lspExt = lspExtensionKey(filePath);
234
+ if (baseName === "cmakelists.txt" || lspExt === "cmake") {
235
+ return "cmake";
236
+ }
237
+
238
+ return EXTENSION_LANG[lspExt]?.[1] ?? "plaintext";
239
+ }
@@ -1,4 +1,4 @@
1
- import { $env } from "@oh-my-pi/pi-utils";
1
+ import { $env, $flag } from "@oh-my-pi/pi-utils";
2
2
 
3
3
  const SIXEL_START_REGEX = /\x1bP(?:[0-9;]*)q/u;
4
4
  const SIXEL_END_SEQUENCE = "\x1b\\";
@@ -15,7 +15,7 @@ const SIXEL_PLACEHOLDER_PREFIX = "__OMP_SIXEL_SEQUENCE_";
15
15
  */
16
16
  export function isSixelPassthroughEnabled(): boolean {
17
17
  const forcedProtocol = $env.PI_FORCE_IMAGE_PROTOCOL?.trim().toLowerCase();
18
- return forcedProtocol === "sixel" && $env.PI_ALLOW_SIXEL_PASSTHROUGH === "1";
18
+ return forcedProtocol === "sixel" && $flag("PI_ALLOW_SIXEL_PASSTHROUGH");
19
19
  }
20
20
  /** Returns true when the text contains a SIXEL start sequence. */
21
21
  export function containsSixelSequence(text: string): boolean {
@@ -2,6 +2,8 @@
2
2
  * Shared types and utilities for web-fetch handlers
3
3
  */
4
4
  import { ptree } from "@oh-my-pi/pi-utils";
5
+ import TurndownService from "turndown";
6
+ import { gfm } from "turndown-plugin-gfm";
5
7
  import { ToolAbortError } from "../../tools/tool-errors";
6
8
 
7
9
  export { formatNumber } from "@oh-my-pi/pi-utils";
@@ -153,41 +155,57 @@ export async function loadPage(url: string, options: LoadPageOptions = {}): Prom
153
155
  return { content: "", contentType: "", finalUrl: url, ok: false };
154
156
  }
155
157
 
158
+ /** Module-level Turndown instance — matches markit-ai's configuration. */
159
+ const turndown = new TurndownService({
160
+ headingStyle: "atx",
161
+ codeBlockStyle: "fenced",
162
+ bulletListMarker: "-",
163
+ });
164
+ turndown.use(gfm);
165
+ turndown.addRule("strikethrough", {
166
+ filter: ["del", "s", "strike"],
167
+ replacement(content) {
168
+ return `~~${content}~~`;
169
+ },
170
+ });
171
+ turndown.addRule("heading", {
172
+ filter: ["h1", "h2", "h3", "h4", "h5", "h6"],
173
+ replacement(content, node) {
174
+ const level = Number(node.nodeName.charAt(1));
175
+ const prefix = "#".repeat(level);
176
+ const cleaned = content.replace(/\\([.])/g, "$1").trim();
177
+ return `\n\n${prefix} ${cleaned}\n\n`;
178
+ },
179
+ });
180
+
181
+ type TurndownListParent = {
182
+ nodeName: string;
183
+ getAttribute(name: string): string | null;
184
+ children: ArrayLike<unknown>;
185
+ };
186
+
187
+ turndown.addRule("listItem", {
188
+ filter: "li",
189
+ replacement(content, node, options) {
190
+ content = content.replace(/^\n+/, "").replace(/\n+$/, "\n").replace(/\n/gm, "\n ");
191
+ const parent = node.parentNode as unknown as TurndownListParent | null;
192
+ let prefix = `${options.bulletListMarker} `;
193
+ if (parent?.nodeName === "OL") {
194
+ const start = parent.getAttribute("start");
195
+ const index = Array.prototype.indexOf.call(parent.children, node);
196
+ prefix = `${(start ? Number(start) : 1) + index}. `;
197
+ }
198
+ return prefix + content + (node.nextSibling ? "\n" : "");
199
+ },
200
+ });
201
+
156
202
  /**
157
- * Convert basic HTML to markdown
203
+ * Convert HTML to markdown using Turndown with GFM support.
204
+ * Strips script/style tags before conversion.
158
205
  */
159
206
  export function htmlToBasicMarkdown(html: string): string {
160
- const stripped = html
161
- .replace(/<pre[^>]*><code[^>]*>/g, "\n```\n")
162
- .replace(/<\/code><\/pre>/g, "\n```\n")
163
- .replace(/<code[^>]*>/g, "`")
164
- .replace(/<\/code>/g, "`")
165
- .replace(/<strong[^>]*>/g, "**")
166
- .replace(/<\/strong>/g, "**")
167
- .replace(/<b[^>]*>/g, "**")
168
- .replace(/<\/b>/g, "**")
169
- .replace(/<em[^>]*>/g, "*")
170
- .replace(/<\/em>/g, "*")
171
- .replace(/<i[^>]*>/g, "*")
172
- .replace(/<\/i>/g, "*")
173
- .replace(
174
- /<a[^>]*href="([^"]+)"[^>]*>([\s\S]*?)<\/a>/g,
175
- (_, href, text) => `[${text.replace(/<[^>]+>/g, "").trim()}](${href})`,
176
- )
177
- .replace(/<p[^>]*>/g, "\n\n")
178
- .replace(/<\/p>/g, "")
179
- .replace(/<br\s*\/?>/g, "\n")
180
- .replace(/<li[^>]*>/g, "- ")
181
- .replace(/<\/li>/g, "\n")
182
- .replace(/<\/?[uo]l[^>]*>/g, "\n")
183
- .replace(/<h(\d)[^>]*>/g, (_, n) => `\n${"#".repeat(parseInt(n, 10))} `)
184
- .replace(/<\/h\d>/g, "\n")
185
- .replace(/<blockquote[^>]*>/g, "\n> ")
186
- .replace(/<\/blockquote>/g, "\n")
187
- .replace(/<[^>]+>/g, "")
188
- .replace(/\n{3,}/g, "\n\n")
189
- .trim();
190
- return decodeHtmlEntities(stripped);
207
+ const cleaned = html.replace(/<script[\s\S]*?<\/script>/gi, "").replace(/<style[\s\S]*?<\/style>/gi, "");
208
+ return turndown.turndown(cleaned).trim();
191
209
  }
192
210
 
193
211
  /**
@@ -90,6 +90,10 @@ interface CodexResponse {
90
90
  usage?: CodexUsage;
91
91
  }
92
92
 
93
+ function isImagePlaceholderAnswer(text: string): boolean {
94
+ return text.trim().toLowerCase() === "(see attached image)";
95
+ }
96
+
93
97
  /**
94
98
  * Decodes a JWT token and extracts the payload.
95
99
  * @param token - JWT token string
@@ -232,6 +236,7 @@ async function callCodexSearch(
232
236
 
233
237
  // Parse SSE stream
234
238
  const answerParts: string[] = [];
239
+ const streamedAnswerParts: string[] = [];
235
240
  const sources: SearchSource[] = [];
236
241
  let model = requestedModel;
237
242
  let requestId = "";
@@ -241,7 +246,12 @@ async function callCodexSearch(
241
246
  const eventType = typeof rawEvent.type === "string" ? rawEvent.type : "";
242
247
  if (!eventType) continue;
243
248
 
244
- if (eventType === "response.output_item.done") {
249
+ if (eventType === "response.output_text.delta") {
250
+ const delta = typeof rawEvent.delta === "string" ? rawEvent.delta : "";
251
+ if (delta) {
252
+ streamedAnswerParts.push(delta);
253
+ }
254
+ } else if (eventType === "response.output_item.done") {
245
255
  const item = rawEvent.item as CodexResponseItem | undefined;
246
256
  if (!item) continue;
247
257
 
@@ -302,8 +312,17 @@ async function callCodexSearch(
302
312
  }
303
313
  }
304
314
 
315
+ const finalAnswer = answerParts.join("\n\n").trim();
316
+ const streamedAnswer = streamedAnswerParts.join("").trim();
317
+ const answer =
318
+ finalAnswer.length > 0 && !isImagePlaceholderAnswer(finalAnswer)
319
+ ? finalAnswer
320
+ : streamedAnswer.length > 0
321
+ ? streamedAnswer
322
+ : finalAnswer;
323
+
305
324
  return {
306
- answer: answerParts.join("\n\n"),
325
+ answer,
307
326
  sources,
308
327
  model,
309
328
  requestId,