@oh-my-pi/pi-coding-agent 13.2.0 → 13.3.0

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 (243) hide show
  1. package/CHANGELOG.md +54 -1
  2. package/package.json +7 -7
  3. package/scripts/format-prompts.ts +33 -14
  4. package/scripts/generate-docs-index.ts +2 -2
  5. package/src/capability/index.ts +1 -2
  6. package/src/cli/args.ts +3 -3
  7. package/src/cli/config-cli.ts +1 -1
  8. package/src/cli/file-processor.ts +1 -2
  9. package/src/cli/grep-cli.ts +1 -1
  10. package/src/cli/jupyter-cli.ts +1 -1
  11. package/src/cli/plugin-cli.ts +1 -1
  12. package/src/cli/setup-cli.ts +1 -1
  13. package/src/cli/shell-cli.ts +1 -1
  14. package/src/cli/ssh-cli.ts +1 -1
  15. package/src/cli/stats-cli.ts +1 -2
  16. package/src/cli/update-cli.ts +1 -2
  17. package/src/cli/web-search-cli.ts +1 -1
  18. package/src/cli.ts +1 -1
  19. package/src/commands/launch.ts +2 -1
  20. package/src/commit/agentic/agent.ts +2 -1
  21. package/src/commit/agentic/index.ts +1 -2
  22. package/src/commit/agentic/prompts/system.md +3 -3
  23. package/src/commit/agentic/tools/propose-changelog.ts +30 -19
  24. package/src/commit/changelog/generate.ts +16 -6
  25. package/src/commit/changelog/index.ts +2 -1
  26. package/src/commit/pipeline.ts +1 -2
  27. package/src/commit/prompts/reduce-system.md +1 -1
  28. package/src/commit/types.ts +10 -1
  29. package/src/config/keybindings.ts +1 -2
  30. package/src/config/model-registry.ts +1 -1
  31. package/src/config/prompt-templates.ts +14 -2
  32. package/src/config/settings-schema.ts +36 -4
  33. package/src/config/settings.ts +19 -2
  34. package/src/config.ts +1 -2
  35. package/src/debug/index.ts +1 -1
  36. package/src/debug/report-bundle.ts +1 -2
  37. package/src/debug/system-info.ts +1 -2
  38. package/src/discovery/agents.ts +2 -2
  39. package/src/discovery/builtin.ts +8 -9
  40. package/src/discovery/claude-plugins.ts +2 -2
  41. package/src/discovery/claude.ts +30 -12
  42. package/src/discovery/codex.ts +3 -3
  43. package/src/discovery/cursor.ts +5 -4
  44. package/src/discovery/gemini.ts +5 -5
  45. package/src/discovery/helpers.ts +47 -69
  46. package/src/discovery/mcp-json.ts +3 -3
  47. package/src/discovery/opencode.ts +7 -8
  48. package/src/discovery/ssh.ts +3 -3
  49. package/src/discovery/vscode.ts +3 -2
  50. package/src/discovery/windsurf.ts +3 -2
  51. package/src/exa/company.ts +1 -1
  52. package/src/exa/factory.ts +1 -6
  53. package/src/exa/linkedin.ts +1 -1
  54. package/src/exa/mcp-client.ts +19 -8
  55. package/src/exa/search.ts +2 -2
  56. package/src/exa/types.ts +3 -3
  57. package/src/exec/bash-executor.ts +2 -1
  58. package/src/exec/non-interactive-env.ts +43 -0
  59. package/src/export/custom-share.ts +1 -1
  60. package/src/export/html/index.ts +1 -2
  61. package/src/extensibility/custom-commands/loader.ts +1 -2
  62. package/src/extensibility/plugins/installer.ts +1 -2
  63. package/src/extensibility/plugins/loader.ts +1 -2
  64. package/src/extensibility/plugins/manager.ts +3 -2
  65. package/src/extensibility/skills.ts +59 -115
  66. package/src/index.ts +1 -3
  67. package/src/internal-urls/docs-index.generated.ts +1 -1
  68. package/src/ipy/executor.ts +1 -2
  69. package/src/ipy/gateway-coordinator.ts +1 -2
  70. package/src/ipy/modules.ts +1 -1
  71. package/src/ipy/runtime.ts +2 -3
  72. package/src/main.ts +1 -2
  73. package/src/mcp/config.ts +2 -2
  74. package/src/mcp/transports/stdio.ts +1 -2
  75. package/src/memories/index.ts +1 -2
  76. package/src/modes/components/extensions/extension-dashboard.ts +1 -1
  77. package/src/modes/components/extensions/inspector-panel.ts +8 -2
  78. package/src/modes/components/footer.ts +1 -2
  79. package/src/modes/components/settings-defs.ts +17 -1
  80. package/src/modes/components/status-line/segments.ts +1 -2
  81. package/src/modes/components/status-line.ts +7 -5
  82. package/src/modes/components/tool-execution.ts +3 -10
  83. package/src/modes/components/welcome.ts +1 -1
  84. package/src/modes/controllers/command-controller.ts +1 -2
  85. package/src/modes/controllers/mcp-command-controller.ts +5 -4
  86. package/src/modes/controllers/selector-controller.ts +22 -1
  87. package/src/modes/controllers/ssh-command-controller.ts +1 -1
  88. package/src/modes/interactive-mode.ts +11 -3
  89. package/src/modes/oauth-manual-input.ts +42 -0
  90. package/src/modes/shared.ts +1 -2
  91. package/src/modes/theme/theme.ts +1 -2
  92. package/src/modes/types.ts +2 -0
  93. package/src/patch/hashline.ts +19 -1
  94. package/src/patch/index.ts +1 -25
  95. package/src/prompts/agents/designer.md +7 -10
  96. package/src/prompts/agents/explore.md +15 -23
  97. package/src/prompts/agents/init.md +23 -23
  98. package/src/prompts/agents/plan.md +14 -77
  99. package/src/prompts/agents/reviewer.md +6 -5
  100. package/src/prompts/agents/task.md +13 -11
  101. package/src/prompts/compaction/branch-summary.md +3 -3
  102. package/src/prompts/compaction/compaction-short-summary.md +7 -7
  103. package/src/prompts/compaction/compaction-summary-context.md +1 -1
  104. package/src/prompts/compaction/compaction-summary.md +5 -5
  105. package/src/prompts/compaction/compaction-turn-prefix.md +3 -3
  106. package/src/prompts/compaction/compaction-update-summary.md +11 -11
  107. package/src/prompts/memories/consolidation.md +5 -5
  108. package/src/prompts/memories/read-path.md +6 -6
  109. package/src/prompts/memories/stage_one_input.md +1 -1
  110. package/src/prompts/memories/stage_one_system.md +5 -5
  111. package/src/prompts/review-request.md +4 -4
  112. package/src/prompts/system/agent-creation-architect.md +17 -17
  113. package/src/prompts/system/agent-creation-user.md +2 -2
  114. package/src/prompts/system/commit-message-system.md +2 -0
  115. package/src/prompts/system/custom-system-prompt.md +4 -4
  116. package/src/prompts/system/plan-mode-active.md +20 -20
  117. package/src/prompts/system/plan-mode-approved.md +7 -7
  118. package/src/prompts/system/plan-mode-reference.md +2 -2
  119. package/src/prompts/system/plan-mode-subagent.md +8 -8
  120. package/src/prompts/system/subagent-submit-reminder.md +5 -5
  121. package/src/prompts/system/subagent-system-prompt.md +29 -22
  122. package/src/prompts/system/subagent-user-prompt.md +7 -3
  123. package/src/prompts/system/summarization-system.md +1 -1
  124. package/src/prompts/system/system-prompt.md +214 -226
  125. package/src/prompts/system/title-system.md +2 -2
  126. package/src/prompts/system/ttsr-interrupt.md +1 -1
  127. package/src/prompts/system/web-search.md +16 -16
  128. package/src/prompts/tools/ask.md +1 -3
  129. package/src/prompts/tools/await.md +2 -4
  130. package/src/prompts/tools/bash.md +5 -7
  131. package/src/prompts/tools/browser.md +4 -6
  132. package/src/prompts/tools/calculator.md +1 -3
  133. package/src/prompts/tools/cancel-job.md +2 -4
  134. package/src/prompts/tools/exit-plan-mode.md +7 -7
  135. package/src/prompts/tools/fetch.md +0 -2
  136. package/src/prompts/tools/find.md +3 -5
  137. package/src/prompts/tools/gemini-image.md +6 -22
  138. package/src/prompts/tools/grep.md +4 -6
  139. package/src/prompts/tools/hashline.md +56 -15
  140. package/src/prompts/tools/lsp.md +1 -3
  141. package/src/prompts/tools/patch.md +7 -9
  142. package/src/prompts/tools/python.md +10 -14
  143. package/src/prompts/tools/read.md +0 -2
  144. package/src/prompts/tools/replace.md +5 -7
  145. package/src/prompts/tools/ssh.md +3 -5
  146. package/src/prompts/tools/task-summary.md +4 -4
  147. package/src/prompts/tools/task.md +7 -9
  148. package/src/prompts/tools/todo-write.md +7 -9
  149. package/src/prompts/tools/web-search.md +3 -5
  150. package/src/prompts/tools/write.md +3 -5
  151. package/src/sdk.ts +4 -2
  152. package/src/session/agent-session.ts +10 -26
  153. package/src/session/agent-storage.ts +1 -2
  154. package/src/session/history-storage.ts +1 -2
  155. package/src/session/session-manager.ts +10 -2
  156. package/src/slash-commands/builtin-registry.ts +26 -1
  157. package/src/ssh/connection-manager.ts +11 -2
  158. package/src/ssh/sshfs-mount.ts +7 -1
  159. package/src/system-prompt.ts +29 -103
  160. package/src/task/agents.ts +1 -1
  161. package/src/task/index.ts +211 -70
  162. package/src/task/render.ts +24 -8
  163. package/src/task/types.ts +6 -1
  164. package/src/task/worktree.ts +394 -32
  165. package/src/tools/ask.ts +0 -1
  166. package/src/tools/bash-interactive.ts +2 -45
  167. package/src/tools/bash.ts +5 -5
  168. package/src/tools/browser.ts +1 -2
  169. package/src/tools/gemini-image.ts +8 -28
  170. package/src/tools/json-tree.ts +2 -1
  171. package/src/tools/python.ts +1 -1
  172. package/src/tools/read.ts +1 -2
  173. package/src/tools/submit-result.ts +22 -23
  174. package/src/utils/commit-message-generator.ts +132 -0
  175. package/src/utils/tools-manager.ts +1 -2
  176. package/src/web/scrapers/artifacthub.ts +2 -1
  177. package/src/web/scrapers/aur.ts +2 -1
  178. package/src/web/scrapers/biorxiv.ts +2 -1
  179. package/src/web/scrapers/bluesky.ts +2 -1
  180. package/src/web/scrapers/chocolatey.ts +2 -1
  181. package/src/web/scrapers/cisa-kev.ts +2 -1
  182. package/src/web/scrapers/clojars.ts +2 -1
  183. package/src/web/scrapers/coingecko.ts +2 -1
  184. package/src/web/scrapers/crates-io.ts +2 -1
  185. package/src/web/scrapers/crossref.ts +2 -1
  186. package/src/web/scrapers/discogs.ts +3 -1
  187. package/src/web/scrapers/discourse.ts +2 -1
  188. package/src/web/scrapers/dockerhub.ts +2 -1
  189. package/src/web/scrapers/fdroid.ts +2 -1
  190. package/src/web/scrapers/firefox-addons.ts +2 -1
  191. package/src/web/scrapers/flathub.ts +2 -1
  192. package/src/web/scrapers/gitlab.ts +1 -1
  193. package/src/web/scrapers/go-pkg.ts +2 -1
  194. package/src/web/scrapers/hackage.ts +2 -1
  195. package/src/web/scrapers/hackernews.ts +2 -1
  196. package/src/web/scrapers/hex.ts +2 -1
  197. package/src/web/scrapers/huggingface.ts +2 -1
  198. package/src/web/scrapers/jetbrains-marketplace.ts +2 -1
  199. package/src/web/scrapers/lemmy.ts +2 -1
  200. package/src/web/scrapers/lobsters.ts +2 -1
  201. package/src/web/scrapers/mastodon.ts +2 -1
  202. package/src/web/scrapers/maven.ts +2 -1
  203. package/src/web/scrapers/mdn.ts +2 -1
  204. package/src/web/scrapers/metacpan.ts +2 -1
  205. package/src/web/scrapers/musicbrainz.ts +3 -1
  206. package/src/web/scrapers/npm.ts +2 -1
  207. package/src/web/scrapers/nuget.ts +2 -1
  208. package/src/web/scrapers/nvd.ts +2 -1
  209. package/src/web/scrapers/ollama.ts +2 -1
  210. package/src/web/scrapers/open-vsx.ts +2 -1
  211. package/src/web/scrapers/opencorporates.ts +2 -1
  212. package/src/web/scrapers/openlibrary.ts +2 -1
  213. package/src/web/scrapers/orcid.ts +3 -1
  214. package/src/web/scrapers/osv.ts +2 -1
  215. package/src/web/scrapers/packagist.ts +2 -1
  216. package/src/web/scrapers/pub-dev.ts +2 -1
  217. package/src/web/scrapers/pubmed.ts +2 -1
  218. package/src/web/scrapers/pypi.ts +2 -1
  219. package/src/web/scrapers/rawg.ts +2 -8
  220. package/src/web/scrapers/reddit.ts +2 -1
  221. package/src/web/scrapers/repology.ts +2 -1
  222. package/src/web/scrapers/rfc.ts +2 -1
  223. package/src/web/scrapers/rubygems.ts +2 -1
  224. package/src/web/scrapers/searchcode.ts +2 -1
  225. package/src/web/scrapers/sec-edgar.ts +2 -1
  226. package/src/web/scrapers/semantic-scholar.ts +2 -1
  227. package/src/web/scrapers/snapcraft.ts +2 -1
  228. package/src/web/scrapers/sourcegraph.ts +2 -1
  229. package/src/web/scrapers/spdx.ts +2 -1
  230. package/src/web/scrapers/stackoverflow.ts +2 -1
  231. package/src/web/scrapers/terraform.ts +2 -1
  232. package/src/web/scrapers/types.ts +0 -11
  233. package/src/web/scrapers/vimeo.ts +2 -1
  234. package/src/web/scrapers/vscode-marketplace.ts +2 -1
  235. package/src/web/scrapers/w3c.ts +2 -1
  236. package/src/web/scrapers/wikidata.ts +2 -1
  237. package/src/web/search/index.ts +10 -14
  238. package/src/web/search/provider.ts +2 -2
  239. package/src/web/search/providers/codex.ts +1 -2
  240. package/src/web/search/providers/exa.ts +42 -10
  241. package/src/web/search/providers/gemini.ts +1 -1
  242. package/src/web/search/providers/perplexity.ts +20 -9
  243. package/src/web/search/providers/utils.ts +1 -1
@@ -1,8 +1,10 @@
1
1
  /**
2
2
  * MusicBrainz URL handler for artists, releases, and recordings
3
3
  */
4
+
5
+ import { tryParseJson } from "@oh-my-pi/pi-utils";
4
6
  import type { RenderResult, SpecialHandler } from "./types";
5
- import { buildResult, formatMediaDuration, loadPage, tryParseJson } from "./types";
7
+ import { buildResult, formatMediaDuration, loadPage } from "./types";
6
8
 
7
9
  type MusicBrainzEntity = "artist" | "release" | "recording";
8
10
 
@@ -1,5 +1,6 @@
1
+ import { tryParseJson } from "@oh-my-pi/pi-utils";
1
2
  import type { RenderResult, SpecialHandler } from "./types";
2
- import { buildResult, formatNumber, loadPage, tryParseJson } from "./types";
3
+ import { buildResult, formatNumber, loadPage } from "./types";
3
4
 
4
5
  /**
5
6
  * Handle npm URLs via registry API
@@ -1,5 +1,6 @@
1
+ import { tryParseJson } from "@oh-my-pi/pi-utils";
1
2
  import type { RenderResult, SpecialHandler } from "./types";
2
- import { buildResult, formatIsoDate, formatNumber, loadPage, tryParseJson } from "./types";
3
+ import { buildResult, formatIsoDate, formatNumber, loadPage } from "./types";
3
4
 
4
5
  interface NuGetCatalogEntry {
5
6
  id: string;
@@ -1,5 +1,6 @@
1
+ import { tryParseJson } from "@oh-my-pi/pi-utils";
1
2
  import type { RenderResult, SpecialHandler } from "./types";
2
- import { buildResult, formatIsoDate, loadPage, tryParseJson } from "./types";
3
+ import { buildResult, formatIsoDate, loadPage } from "./types";
3
4
 
4
5
  interface CvssV31 {
5
6
  baseScore: number;
@@ -1,6 +1,7 @@
1
+ import { tryParseJson } from "@oh-my-pi/pi-utils";
1
2
  import { formatBytes } from "../../tools/render-utils";
2
3
  import type { RenderResult, SpecialHandler } from "./types";
3
- import { buildResult, decodeHtmlEntities, loadPage, tryParseJson } from "./types";
4
+ import { buildResult, decodeHtmlEntities, loadPage } from "./types";
4
5
 
5
6
  interface OllamaTagDetails {
6
7
  parent_model?: string;
@@ -1,5 +1,6 @@
1
+ import { tryParseJson } from "@oh-my-pi/pi-utils";
1
2
  import type { RenderResult, SpecialHandler } from "./types";
2
- import { buildResult, formatNumber, loadPage, tryParseJson } from "./types";
3
+ import { buildResult, formatNumber, loadPage } from "./types";
3
4
 
4
5
  interface OpenVsxFileLinks {
5
6
  readme?: string;
@@ -1,5 +1,6 @@
1
+ import { tryParseJson } from "@oh-my-pi/pi-utils";
1
2
  import type { RenderResult, SpecialHandler } from "./types";
2
- import { buildResult, loadPage, tryParseJson } from "./types";
3
+ import { buildResult, loadPage } from "./types";
3
4
 
4
5
  interface Officer {
5
6
  id: number;
@@ -1,5 +1,6 @@
1
+ import { tryParseJson } from "@oh-my-pi/pi-utils";
1
2
  import type { RenderResult, SpecialHandler } from "./types";
2
- import { buildResult, loadPage, tryParseJson } from "./types";
3
+ import { buildResult, loadPage } from "./types";
3
4
 
4
5
  interface OpenLibraryAuthor {
5
6
  name?: string;
@@ -1,8 +1,10 @@
1
1
  /**
2
2
  * ORCID handler for web-fetch
3
3
  */
4
+
5
+ import { tryParseJson } from "@oh-my-pi/pi-utils";
4
6
  import type { RenderResult, SpecialHandler } from "./types";
5
- import { buildResult, loadPage, tryParseJson } from "./types";
7
+ import { buildResult, loadPage } from "./types";
6
8
 
7
9
  const MAX_WORKS = 50;
8
10
  const ORCID_PATTERN = /\/(\d{4}-\d{4}-\d{4}-\d{3}[\dXx])(?:\/|$)/;
@@ -1,5 +1,6 @@
1
+ import { tryParseJson } from "@oh-my-pi/pi-utils";
1
2
  import type { RenderResult, SpecialHandler } from "./types";
2
- import { buildResult, formatIsoDate, loadPage, tryParseJson } from "./types";
3
+ import { buildResult, formatIsoDate, loadPage } from "./types";
3
4
 
4
5
  interface OsvSeverity {
5
6
  type: string;
@@ -1,5 +1,6 @@
1
+ import { tryParseJson } from "@oh-my-pi/pi-utils";
1
2
  import type { RenderResult, SpecialHandler } from "./types";
2
- import { buildResult, formatNumber, loadPage, tryParseJson } from "./types";
3
+ import { buildResult, formatNumber, loadPage } from "./types";
3
4
 
4
5
  /**
5
6
  * Handle Packagist URLs via JSON API
@@ -1,4 +1,5 @@
1
- import { buildResult, formatNumber, htmlToBasicMarkdown, loadPage, type SpecialHandler, tryParseJson } from "./types";
1
+ import { tryParseJson } from "@oh-my-pi/pi-utils";
2
+ import { buildResult, formatNumber, htmlToBasicMarkdown, loadPage, type SpecialHandler } from "./types";
2
3
 
3
4
  /**
4
5
  * Handle pub.dev URLs via API
@@ -1,7 +1,8 @@
1
1
  /**
2
2
  * PubMed handler for web-fetch
3
3
  */
4
- import { buildResult, loadPage, type RenderResult, type SpecialHandler, tryParseJson } from "./types";
4
+ import { tryParseJson } from "@oh-my-pi/pi-utils";
5
+ import { buildResult, loadPage, type RenderResult, type SpecialHandler } from "./types";
5
6
 
6
7
  const NCBI_HEADERS = {
7
8
  Accept: "application/json, text/plain;q=0.9, */*;q=0.8",
@@ -1,4 +1,5 @@
1
- import { buildResult, formatNumber, loadPage, type RenderResult, type SpecialHandler, tryParseJson } from "./types";
1
+ import { tryParseJson } from "@oh-my-pi/pi-utils";
2
+ import { buildResult, formatNumber, loadPage, type RenderResult, type SpecialHandler } from "./types";
2
3
 
3
4
  /**
4
5
  * Handle PyPI URLs via JSON API
@@ -1,11 +1,5 @@
1
- import {
2
- buildResult,
3
- htmlToBasicMarkdown,
4
- loadPage,
5
- type RenderResult,
6
- type SpecialHandler,
7
- tryParseJson,
8
- } from "./types";
1
+ import { tryParseJson } from "@oh-my-pi/pi-utils";
2
+ import { buildResult, htmlToBasicMarkdown, loadPage, type RenderResult, type SpecialHandler } from "./types";
9
3
 
10
4
  interface RawgPlatformEntry {
11
5
  platform?: {
@@ -1,4 +1,5 @@
1
- import { buildResult, formatIsoDate, loadPage, type RenderResult, type SpecialHandler, tryParseJson } from "./types";
1
+ import { tryParseJson } from "@oh-my-pi/pi-utils";
2
+ import { buildResult, formatIsoDate, loadPage, type RenderResult, type SpecialHandler } from "./types";
2
3
 
3
4
  interface RedditPost {
4
5
  title: string;
@@ -1,4 +1,5 @@
1
- import { buildResult, loadPage, type RenderResult, type SpecialHandler, tryParseJson } from "./types";
1
+ import { tryParseJson } from "@oh-my-pi/pi-utils";
2
+ import { buildResult, loadPage, type RenderResult, type SpecialHandler } from "./types";
2
3
 
3
4
  interface RepologyPackage {
4
5
  repo: string;
@@ -1,4 +1,5 @@
1
- import { buildResult, loadPage, type RenderResult, type SpecialHandler, tryParseJson } from "./types";
1
+ import { tryParseJson } from "@oh-my-pi/pi-utils";
2
+ import { buildResult, loadPage, type RenderResult, type SpecialHandler } from "./types";
2
3
 
3
4
  interface RfcMetadata {
4
5
  doc_id: string;
@@ -1,4 +1,5 @@
1
- import { buildResult, formatNumber, loadPage, type RenderResult, type SpecialHandler, tryParseJson } from "./types";
1
+ import { tryParseJson } from "@oh-my-pi/pi-utils";
2
+ import { buildResult, formatNumber, loadPage, type RenderResult, type SpecialHandler } from "./types";
2
3
 
3
4
  interface RubyGemsDependency {
4
5
  name: string;
@@ -1,5 +1,6 @@
1
+ import { tryParseJson } from "@oh-my-pi/pi-utils";
1
2
  import type { RenderResult, SpecialHandler } from "./types";
2
- import { buildResult, formatNumber, loadPage, tryParseJson } from "./types";
3
+ import { buildResult, formatNumber, loadPage } from "./types";
3
4
 
4
5
  interface SearchcodeResult {
5
6
  id?: number | string;
@@ -1,5 +1,6 @@
1
+ import { tryParseJson } from "@oh-my-pi/pi-utils";
1
2
  import type { RenderResult, SpecialHandler } from "./types";
2
- import { buildResult, loadPage, tryParseJson } from "./types";
3
+ import { buildResult, loadPage } from "./types";
3
4
 
4
5
  interface SecFiling {
5
6
  accessionNumber: string;
@@ -1,5 +1,6 @@
1
+ import { tryParseJson } from "@oh-my-pi/pi-utils";
1
2
  import type { SpecialHandler } from "./types";
2
- import { buildResult, formatNumber, loadPage, tryParseJson } from "./types";
3
+ import { buildResult, formatNumber, loadPage } from "./types";
3
4
 
4
5
  interface SemanticScholarAuthor {
5
6
  name: string;
@@ -1,5 +1,6 @@
1
+ import { tryParseJson } from "@oh-my-pi/pi-utils";
1
2
  import type { RenderResult, SpecialHandler } from "./types";
2
- import { buildResult, formatNumber, loadPage, tryParseJson } from "./types";
3
+ import { buildResult, formatNumber, loadPage } from "./types";
3
4
 
4
5
  interface SnapcraftPublisher {
5
6
  "display-name"?: string;
@@ -1,5 +1,6 @@
1
+ import { tryParseJson } from "@oh-my-pi/pi-utils";
1
2
  import type { RenderResult, SpecialHandler } from "./types";
2
- import { buildResult, loadPage, tryParseJson } from "./types";
3
+ import { buildResult, loadPage } from "./types";
3
4
 
4
5
  const GRAPHQL_ENDPOINT = "https://sourcegraph.com/.api/graphql";
5
6
  const GRAPHQL_HEADERS = {
@@ -1,5 +1,6 @@
1
+ import { tryParseJson } from "@oh-my-pi/pi-utils";
1
2
  import type { RenderResult, SpecialHandler } from "./types";
2
- import { buildResult, htmlToBasicMarkdown, loadPage, tryParseJson } from "./types";
3
+ import { buildResult, htmlToBasicMarkdown, loadPage } from "./types";
3
4
 
4
5
  interface SpdxCrossRef {
5
6
  url?: string;
@@ -1,5 +1,6 @@
1
+ import { tryParseJson } from "@oh-my-pi/pi-utils";
1
2
  import type { RenderResult, SpecialHandler } from "./types";
2
- import { buildResult, formatIsoDate, htmlToBasicMarkdown, loadPage, tryParseJson } from "./types";
3
+ import { buildResult, formatIsoDate, htmlToBasicMarkdown, loadPage } from "./types";
3
4
 
4
5
  interface SOQuestion {
5
6
  title: string;
@@ -1,5 +1,6 @@
1
+ import { tryParseJson } from "@oh-my-pi/pi-utils";
1
2
  import type { RenderResult, SpecialHandler } from "./types";
2
- import { buildResult, formatNumber, loadPage, tryParseJson } from "./types";
3
+ import { buildResult, formatNumber, loadPage } from "./types";
3
4
 
4
5
  interface TerraformModule {
5
6
  id: string;
@@ -190,17 +190,6 @@ export function htmlToBasicMarkdown(html: string): string {
190
190
  return decodeHtmlEntities(stripped);
191
191
  }
192
192
 
193
- /**
194
- * Try to parse JSON, returning null on failure.
195
- */
196
- export function tryParseJson<T = unknown>(content: string): T | null {
197
- try {
198
- return JSON.parse(content) as T;
199
- } catch {
200
- return null;
201
- }
202
- }
203
-
204
193
  /**
205
194
  * Build a RenderResult from markdown content. Calls finalizeOutput internally.
206
195
  */
@@ -1,5 +1,6 @@
1
+ import { tryParseJson } from "@oh-my-pi/pi-utils";
1
2
  import type { SpecialHandler } from "./types";
2
- import { buildResult, formatMediaDuration, loadPage, tryParseJson } from "./types";
3
+ import { buildResult, formatMediaDuration, loadPage } from "./types";
3
4
 
4
5
  interface VimeoOEmbed {
5
6
  title: string;
@@ -1,5 +1,6 @@
1
+ import { tryParseJson } from "@oh-my-pi/pi-utils";
1
2
  import type { RenderResult, SpecialHandler } from "./types";
2
- import { buildResult, formatNumber, loadPage, tryParseJson } from "./types";
3
+ import { buildResult, formatNumber, loadPage } from "./types";
3
4
 
4
5
  interface MarketplaceProperty {
5
6
  key?: string;
@@ -1,5 +1,6 @@
1
+ import { tryParseJson } from "@oh-my-pi/pi-utils";
1
2
  import type { RenderResult, SpecialHandler } from "./types";
2
- import { buildResult, htmlToBasicMarkdown, loadPage, tryParseJson } from "./types";
3
+ import { buildResult, htmlToBasicMarkdown, loadPage } from "./types";
3
4
  import { asRecord } from "./utils";
4
5
 
5
6
  type JsonRecord = Record<string, unknown>;
@@ -1,5 +1,6 @@
1
+ import { tryParseJson } from "@oh-my-pi/pi-utils";
1
2
  import type { RenderResult, SpecialHandler } from "./types";
2
- import { buildResult, formatNumber, loadPage, tryParseJson } from "./types";
3
+ import { buildResult, formatNumber, loadPage } from "./types";
3
4
 
4
5
  /**
5
6
  * Common Wikidata property IDs mapped to human-readable names
@@ -353,13 +353,6 @@ async function executeExaTool(
353
353
  ): Promise<{ content: Array<{ type: "text"; text: string }>; details: ExaRenderDetails }> {
354
354
  try {
355
355
  const apiKey = await findExaKey();
356
- if (!apiKey) {
357
- return {
358
- content: [{ type: "text" as const, text: "Error: EXA_API_KEY not found" }],
359
- details: { error: "EXA_API_KEY not found", toolName },
360
- };
361
- }
362
-
363
356
  const response = await callExaTool(mcpToolName, params, apiKey);
364
357
 
365
358
  if (isSearchResponse(response)) {
@@ -402,7 +395,7 @@ Parameters:
402
395
 
403
396
  async execute(_toolCallId, params, _onUpdate, _ctx, _signal) {
404
397
  const { num_results, ...rest } = params as Record<string, unknown>;
405
- const args = { ...rest, type: "deep", numResults: num_results ?? 10 };
398
+ const args = { ...rest, type: "auto", numResults: num_results ?? 10 };
406
399
  return executeExaTool("web_search_exa", args, "web_search_deep");
407
400
  },
408
401
 
@@ -462,7 +455,7 @@ Parameters:
462
455
  parameters: webSearchCrawlSchema,
463
456
 
464
457
  async execute(_toolCallId, params, _onUpdate, _ctx, _signal) {
465
- return executeExaTool("crawling", params as Record<string, unknown>, "web_search_crawl");
458
+ return executeExaTool("crawling_exa", params as Record<string, unknown>, "web_search_crawl");
466
459
  },
467
460
 
468
461
  renderCall(args, _options, theme) {
@@ -493,7 +486,7 @@ Parameters:
493
486
  parameters: webSearchLinkedinSchema,
494
487
 
495
488
  async execute(_toolCallId, params, _onUpdate, _ctx, _signal) {
496
- return executeExaTool("linkedin_search", params as Record<string, unknown>, "web_search_linkedin");
489
+ return executeExaTool("linkedin_search_exa", params as Record<string, unknown>, "web_search_linkedin");
497
490
  },
498
491
 
499
492
  renderCall(args, _options, theme) {
@@ -523,7 +516,7 @@ Parameters:
523
516
  parameters: webSearchCompanySchema,
524
517
 
525
518
  async execute(_toolCallId, params, _onUpdate, _ctx, _signal) {
526
- return executeExaTool("company_research", params as Record<string, unknown>, "web_search_company");
519
+ return executeExaTool("company_research_exa", params as Record<string, unknown>, "web_search_company");
527
520
  },
528
521
 
529
522
  renderCall(args, _options, theme) {
@@ -561,17 +554,20 @@ export interface SearchToolsOptions {
561
554
  *
562
555
  * Returns:
563
556
  * - Always: web_search (unified, works with Anthropic/Perplexity/Exa)
564
- * - With EXA_API_KEY: web_search_deep, web_search_code_context, web_search_crawl
557
+ * - Always: web_search_deep, web_search_code_context (public Exa MCP tools)
558
+ * - With EXA_API_KEY: web_search_crawl
565
559
  * - With EXA_API_KEY + options.enableLinkedin: web_search_linkedin
566
560
  * - With EXA_API_KEY + options.enableCompany: web_search_company
567
561
  */
568
562
  export async function getSearchTools(options: SearchToolsOptions = {}): Promise<CustomTool<any, any>[]> {
569
563
  const tools: CustomTool<any, any>[] = [webSearchCustomTool];
570
564
 
571
- // Check for Exa API key
565
+ tools.push(webSearchDeepTool, webSearchCodeContextTool);
566
+
567
+ // Advanced/add-on tools remain key-gated to avoid exposing known unauthenticated failures
572
568
  const exaKey = await findExaKey();
573
569
  if (exaKey) {
574
- tools.push(...exaSearchTools);
570
+ tools.push(webSearchCrawlTool);
575
571
 
576
572
  if (options.enableLinkedin) {
577
573
  tools.push(...linkedinSearchTools);
@@ -29,7 +29,6 @@ const SEARCH_PROVIDERS: Record<SearchProviderId, SearchProvider> = {
29
29
 
30
30
  export const SEARCH_PROVIDER_ORDER: SearchProviderId[] = [
31
31
  "perplexity",
32
- "exa",
33
32
  "brave",
34
33
  "jina",
35
34
  "kimi",
@@ -37,6 +36,7 @@ export const SEARCH_PROVIDER_ORDER: SearchProviderId[] = [
37
36
  "gemini",
38
37
  "codex",
39
38
  "zai",
39
+ "exa",
40
40
  "synthetic",
41
41
  ];
42
42
 
@@ -52,7 +52,7 @@ export function setPreferredSearchProvider(provider: SearchProviderId | "auto"):
52
52
  preferredProvId = provider;
53
53
  }
54
54
 
55
- /** Determine which providers are configured (priority: Perplexity → Exa → Brave → Jina → Kimi → Anthropic → Gemini → Codex → Z.AI → Synthetic) */
55
+ /** Determine which providers are configured (priority: Perplexity → Brave → Jina → Kimi → Anthropic → Gemini → Codex → Z.AI → Exa → Synthetic) */
56
56
  export async function resolveProviderChain(
57
57
  preferredProvider: SearchProviderId | "auto" = preferredProvId,
58
58
  ): Promise<SearchProvider[]> {
@@ -6,8 +6,7 @@
6
6
  * Returns synthesized answers with web search sources.
7
7
  */
8
8
  import * as os from "node:os";
9
- import { readSseJson } from "@oh-my-pi/pi-utils";
10
- import { getAgentDbPath } from "@oh-my-pi/pi-utils/dirs";
9
+ import { getAgentDbPath, readSseJson } from "@oh-my-pi/pi-utils";
11
10
  import packageJson from "../../../../package.json" with { type: "json" };
12
11
  import { AgentStorage } from "../../../session/agent-storage";
13
12
  import type { SearchResponse, SearchSource } from "../../../web/search/types";
@@ -3,9 +3,10 @@
3
3
  *
4
4
  * High-quality neural search via Exa Search API.
5
5
  * Returns structured search results with optional content extraction.
6
+ * Requests per-result summaries via `contents.summary` and synthesizes
7
+ * them into a combined `answer` string on the SearchResponse.
6
8
  */
7
9
  import { getEnvApiKey } from "@oh-my-pi/pi-ai";
8
- import { findApiKey as findExaKey } from "../../../exa/mcp-client";
9
10
  import type { SearchResponse, SearchSource } from "../../../web/search/types";
10
11
  import { SearchProviderError } from "../../../web/search/types";
11
12
  import { dateToAgeSeconds } from "../utils";
@@ -35,6 +36,7 @@ interface ExaSearchResult {
35
36
  publishedDate?: string | null;
36
37
  text?: string | null;
37
38
  highlights?: string[] | null;
39
+ summary?: string | null;
38
40
  }
39
41
 
40
42
  interface ExaSearchResponse {
@@ -45,18 +47,41 @@ interface ExaSearchResponse {
45
47
  searchTime?: number;
46
48
  }
47
49
 
48
- function normalizeSearchType(type: ExaSearchParamType | undefined): ExaSearchType {
50
+ export function normalizeSearchType(type: ExaSearchParamType | undefined): ExaSearchType {
49
51
  if (!type) return "auto";
50
52
  if (type === "keyword") return "fast";
51
53
  return type;
52
54
  }
53
55
 
54
- /** Call Exa Search API */
55
- async function callExaSearch(apiKey: string, params: ExaSearchParams): Promise<ExaSearchResponse> {
56
+ /** Maximum number of per-result summaries to include in the synthesized answer. */
57
+ const MAX_ANSWER_SUMMARIES = 3;
58
+
59
+ /**
60
+ * Synthesize an answer string from per-result summaries returned by Exa.
61
+ * Returns `undefined` when no non-empty summaries are available so callers
62
+ * can leave `SearchResponse.answer` unset (matching other providers).
63
+ */
64
+ export function synthesizeAnswer(results: ExaSearchResult[]): string | undefined {
65
+ const parts: string[] = [];
66
+ for (const r of results) {
67
+ if (parts.length >= MAX_ANSWER_SUMMARIES) break;
68
+ const summary = r.summary?.trim();
69
+ if (!summary) continue;
70
+ const title = r.title?.trim() || r.url || "Untitled";
71
+ parts.push(`**${title}**: ${summary}`);
72
+ }
73
+ return parts.length > 0 ? parts.join("\n\n") : undefined;
74
+ }
75
+
76
+ /** Build the request body for `callExaSearch`. Exported for testing. */
77
+ export function buildExaRequestBody(params: ExaSearchParams): Record<string, unknown> {
56
78
  const body: Record<string, unknown> = {
57
79
  query: params.query,
58
80
  numResults: params.num_results ?? 10,
59
81
  type: normalizeSearchType(params.type),
82
+ contents: {
83
+ summary: { query: params.query },
84
+ },
60
85
  };
61
86
 
62
87
  if (params.include_domains?.length) {
@@ -72,6 +97,13 @@ async function callExaSearch(apiKey: string, params: ExaSearchParams): Promise<E
72
97
  body.endPublishedDate = params.end_published_date;
73
98
  }
74
99
 
100
+ return body;
101
+ }
102
+
103
+ /** Call Exa Search API */
104
+ async function callExaSearch(apiKey: string, params: ExaSearchParams): Promise<ExaSearchResponse> {
105
+ const body = buildExaRequestBody(params);
106
+
75
107
  const response = await fetch(EXA_API_URL, {
76
108
  method: "POST",
77
109
  headers: {
@@ -107,7 +139,7 @@ export async function searchExa(params: ExaSearchParams): Promise<SearchResponse
107
139
  sources.push({
108
140
  title: result.title ?? result.url,
109
141
  url: result.url,
110
- snippet: result.text ?? result.highlights?.join(" ") ?? undefined,
142
+ snippet: result.summary || result.text || result.highlights?.join(" ") || undefined,
111
143
  publishedDate: result.publishedDate ?? undefined,
112
144
  ageSeconds: dateToAgeSeconds(result.publishedDate ?? undefined),
113
145
  author: result.author ?? undefined,
@@ -118,8 +150,12 @@ export async function searchExa(params: ExaSearchParams): Promise<SearchResponse
118
150
  // Apply num_results limit if specified
119
151
  const limitedSources = params.num_results ? sources.slice(0, params.num_results) : sources;
120
152
 
153
+ // Synthesize answer only from results that have a URL (same guard as sources loop)
154
+ const answer = response.results ? synthesizeAnswer(response.results.filter(r => !!r.url)) : undefined;
155
+
121
156
  return {
122
157
  provider: "exa",
158
+ answer,
123
159
  sources: limitedSources,
124
160
  requestId: response.requestId,
125
161
  };
@@ -131,11 +167,7 @@ export class ExaProvider extends SearchProvider {
131
167
  readonly label = "Exa";
132
168
 
133
169
  isAvailable(): boolean {
134
- try {
135
- return !!findExaKey();
136
- } catch {
137
- return false;
138
- }
170
+ return true;
139
171
  }
140
172
 
141
173
  search(params: SearchParams): Promise<SearchResponse> {
@@ -6,7 +6,7 @@
6
6
  * Returns synthesized answers with citations and source metadata from grounding chunks.
7
7
  */
8
8
  import { getAntigravityHeaders, getGeminiCliHeaders, refreshGoogleCloudToken } from "@oh-my-pi/pi-ai";
9
- import { getAgentDbPath } from "@oh-my-pi/pi-utils/dirs";
9
+ import { getAgentDbPath } from "@oh-my-pi/pi-utils";
10
10
  import { AgentStorage } from "../../../session/agent-storage";
11
11
  import type { SearchCitation, SearchResponse, SearchSource } from "../../../web/search/types";
12
12
  import { SearchProviderError } from "../../../web/search/types";
@@ -1,14 +1,14 @@
1
1
  /**
2
2
  * Perplexity Web Search Provider
3
3
  *
4
- * Supports two auth modes:
5
- * - API key (`PERPLEXITY_API_KEY`) via `api.perplexity.ai/chat/completions`
4
+ * Supports three auth modes:
5
+ * - Cookies (`PERPLEXITY_COOKIES`) via `www.perplexity.ai/rest/sse/perplexity_ask`
6
6
  * - OAuth JWT (stored in `agent.db`) via `www.perplexity.ai/rest/sse/perplexity_ask`
7
+ * - API key (`PERPLEXITY_API_KEY`) via `api.perplexity.ai/chat/completions`
7
8
  */
8
9
 
9
10
  import { getEnvApiKey } from "@oh-my-pi/pi-ai";
10
- import { readSseJson } from "@oh-my-pi/pi-utils";
11
- import { getAgentDbPath } from "@oh-my-pi/pi-utils/dirs";
11
+ import { $env, getAgentDbPath, readSseJson } from "@oh-my-pi/pi-utils";
12
12
  import { AgentStorage } from "../../../session/agent-storage";
13
13
  import type {
14
14
  PerplexityMessageOutput,
@@ -47,6 +47,10 @@ type PerplexityAuth =
47
47
  | {
48
48
  type: "oauth";
49
49
  token: string;
50
+ }
51
+ | {
52
+ type: "cookies";
53
+ cookies: string;
50
54
  };
51
55
 
52
56
  interface PerplexityOAuthStreamMarkdownBlock {
@@ -189,10 +193,17 @@ async function findOAuthToken(): Promise<string | null> {
189
193
  }
190
194
 
191
195
  async function findPerplexityAuth(): Promise<PerplexityAuth | null> {
196
+ // 1. PERPLEXITY_COOKIES env var
197
+ const cookies = $env.PERPLEXITY_COOKIES?.trim();
198
+ if (cookies) {
199
+ return { type: "cookies", cookies };
200
+ }
201
+ // 2. OAuth token from agent.db
192
202
  const oauthToken = await findOAuthToken();
193
203
  if (oauthToken) {
194
204
  return { type: "oauth", token: oauthToken };
195
205
  }
206
+ // 3. PERPLEXITY_API_KEY env var
196
207
  const apiKey = findApiKey();
197
208
  if (apiKey) {
198
209
  return { type: "api_key", token: apiKey };
@@ -290,7 +301,7 @@ function buildOAuthAnswer(event: PerplexityOAuthStreamEvent): string {
290
301
  }
291
302
 
292
303
  async function callPerplexityOAuth(
293
- oauthToken: string,
304
+ auth: { type: "oauth"; token: string } | { type: "cookies"; cookies: string },
294
305
  params: PerplexitySearchParams,
295
306
  ): Promise<{ answer: string; sources: SearchSource[]; model?: string; requestId?: string }> {
296
307
  const requestId = crypto.randomUUID();
@@ -299,7 +310,7 @@ async function callPerplexityOAuth(
299
310
  const response = await fetch(PERPLEXITY_OAUTH_ASK_URL, {
300
311
  method: "POST",
301
312
  headers: {
302
- Authorization: `Bearer ${oauthToken}`,
313
+ ...(auth.type === "cookies" ? { Cookie: auth.cookies } : { Authorization: `Bearer ${auth.token}` }),
303
314
  "Content-Type": "application/json",
304
315
  Accept: "text/event-stream",
305
316
  Origin: "https://www.perplexity.ai",
@@ -456,11 +467,11 @@ function applySourceLimit(result: SearchResponse, limit?: number): SearchRespons
456
467
  export async function searchPerplexity(params: PerplexitySearchParams): Promise<SearchResponse> {
457
468
  const auth = await findPerplexityAuth();
458
469
  if (!auth) {
459
- throw new Error("Perplexity auth not found. Set PERPLEXITY_API_KEY or login via OAuth.");
470
+ throw new Error("Perplexity auth not found. Set PERPLEXITY_COOKIES, PERPLEXITY_API_KEY, or login via OAuth.");
460
471
  }
461
472
 
462
- if (auth.type === "oauth") {
463
- const oauthResult = await callPerplexityOAuth(auth.token, params);
473
+ if (auth.type === "oauth" || auth.type === "cookies") {
474
+ const oauthResult = await callPerplexityOAuth(auth, params);
464
475
  return applySourceLimit(
465
476
  {
466
477
  provider: "perplexity",
@@ -1,4 +1,4 @@
1
- import { getAgentDbPath } from "@oh-my-pi/pi-utils/dirs";
1
+ import { getAgentDbPath } from "@oh-my-pi/pi-utils";
2
2
  import { AgentStorage } from "../../../session/agent-storage";
3
3
 
4
4
  /**