@oh-my-pi/pi-coding-agent 15.10.10 → 15.10.12

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 (415) hide show
  1. package/CHANGELOG.md +142 -7
  2. package/dist/cli.js +23108 -0
  3. package/dist/tokenizers.linux-x64-gnu-xcjh3jwk.node +0 -0
  4. package/dist/types/async/job-manager.d.ts +18 -0
  5. package/dist/types/cli/args.d.ts +2 -1
  6. package/dist/types/cli/dry-balance-cli.d.ts +1 -1
  7. package/dist/types/cli/gallery-cli.d.ts +1 -1
  8. package/dist/types/cli/gallery-fixtures/types.d.ts +1 -1
  9. package/dist/types/cli/usage-cli.d.ts +72 -0
  10. package/dist/types/cli-commands.d.ts +12 -0
  11. package/dist/types/commands/launch.d.ts +5 -1
  12. package/dist/types/commands/read.d.ts +1 -1
  13. package/dist/types/commands/usage.d.ts +25 -0
  14. package/dist/types/config/api-key-resolver.d.ts +3 -0
  15. package/dist/types/config/append-only-context-mode.d.ts +2 -1
  16. package/dist/types/config/model-discovery.d.ts +55 -0
  17. package/dist/types/config/model-registry.d.ts +8 -219
  18. package/dist/types/config/model-resolver.d.ts +34 -10
  19. package/dist/types/config/model-roles.d.ts +28 -0
  20. package/dist/types/config/models-config-schema.d.ts +523 -42
  21. package/dist/types/config/models-config.d.ts +385 -0
  22. package/dist/types/config/settings-schema.d.ts +41 -8
  23. package/dist/types/config/settings.d.ts +8 -1
  24. package/dist/types/debug/log-viewer.d.ts +1 -1
  25. package/dist/types/debug/raw-sse.d.ts +1 -1
  26. package/dist/types/edit/hashline/noop-loop-guard.d.ts +72 -0
  27. package/dist/types/eval/backend.d.ts +0 -2
  28. package/dist/types/eval/idle-timeout.d.ts +0 -4
  29. package/dist/types/eval/js/shared/rewrite-imports.d.ts +6 -6
  30. package/dist/types/eval/py/executor.d.ts +5 -0
  31. package/dist/types/eval/py/kernel.d.ts +6 -1
  32. package/dist/types/eval/py/runtime.d.ts +9 -0
  33. package/dist/types/exec/bash-executor.d.ts +2 -0
  34. package/dist/types/export/html/template.generated.d.ts +1 -1
  35. package/dist/types/extensibility/extensions/runner.d.ts +3 -2
  36. package/dist/types/extensibility/extensions/types.d.ts +6 -3
  37. package/dist/types/hindsight/mental-models.d.ts +17 -8
  38. package/dist/types/internal-urls/artifact-protocol.d.ts +2 -2
  39. package/dist/types/internal-urls/types.d.ts +1 -1
  40. package/dist/types/lsp/edits.d.ts +9 -0
  41. package/dist/types/lsp/index.d.ts +2 -2
  42. package/dist/types/lsp/types.d.ts +2 -0
  43. package/dist/types/lsp/utils.d.ts +3 -0
  44. package/dist/types/mcp/json-rpc.d.ts +5 -0
  45. package/dist/types/memory-backend/index.d.ts +1 -0
  46. package/dist/types/memory-backend/runtime.d.ts +4 -0
  47. package/dist/types/memory-backend/types.d.ts +66 -1
  48. package/dist/types/mnemopi/state.d.ts +11 -1
  49. package/dist/types/modes/components/agent-dashboard.d.ts +1 -1
  50. package/dist/types/modes/components/assistant-message.d.ts +3 -1
  51. package/dist/types/modes/components/bash-execution.d.ts +1 -1
  52. package/dist/types/modes/components/copy-selector.d.ts +1 -1
  53. package/dist/types/modes/components/dynamic-border.d.ts +1 -1
  54. package/dist/types/modes/components/extensions/extension-dashboard.d.ts +1 -1
  55. package/dist/types/modes/components/extensions/extension-list.d.ts +1 -1
  56. package/dist/types/modes/components/extensions/inspector-panel.d.ts +1 -1
  57. package/dist/types/modes/components/footer.d.ts +1 -1
  58. package/dist/types/modes/components/hook-editor.d.ts +5 -0
  59. package/dist/types/modes/components/hook-input.d.ts +4 -0
  60. package/dist/types/modes/components/hook-selector.d.ts +1 -1
  61. package/dist/types/modes/components/model-selector.d.ts +1 -1
  62. package/dist/types/modes/components/plan-review-overlay.d.ts +1 -1
  63. package/dist/types/modes/components/session-observer-overlay.d.ts +1 -1
  64. package/dist/types/modes/components/session-selector.d.ts +1 -1
  65. package/dist/types/modes/components/status-line/component.d.ts +1 -1
  66. package/dist/types/modes/components/tiny-title-download-progress.d.ts +1 -1
  67. package/dist/types/modes/components/transcript-container.d.ts +25 -6
  68. package/dist/types/modes/components/tree-selector.d.ts +1 -1
  69. package/dist/types/modes/components/user-message-selector.d.ts +1 -1
  70. package/dist/types/modes/components/user-message.d.ts +2 -1
  71. package/dist/types/modes/components/visual-truncate.d.ts +1 -1
  72. package/dist/types/modes/components/welcome.d.ts +19 -3
  73. package/dist/types/modes/controllers/mcp-command-controller.d.ts +1 -1
  74. package/dist/types/modes/controllers/streaming-reveal.d.ts +1 -1
  75. package/dist/types/modes/index.d.ts +3 -3
  76. package/dist/types/modes/interactive-mode.d.ts +8 -3
  77. package/dist/types/modes/oauth-manual-input.d.ts +7 -0
  78. package/dist/types/modes/rpc/rpc-client.d.ts +39 -2
  79. package/dist/types/modes/rpc/rpc-mode.d.ts +31 -2
  80. package/dist/types/modes/rpc/rpc-subagents.d.ts +24 -0
  81. package/dist/types/modes/rpc/rpc-types.d.ts +75 -1
  82. package/dist/types/modes/setup-wizard/index.d.ts +5 -1
  83. package/dist/types/modes/setup-wizard/lazy.d.ts +2 -0
  84. package/dist/types/modes/setup-wizard/scenes/sign-in.d.ts +1 -1
  85. package/dist/types/modes/setup-wizard/scenes/types.d.ts +1 -1
  86. package/dist/types/modes/setup-wizard/scenes/web-search.d.ts +1 -1
  87. package/dist/types/modes/setup-wizard/wizard-overlay.d.ts +1 -1
  88. package/dist/types/modes/types.d.ts +4 -1
  89. package/dist/types/secrets/index.d.ts +1 -1
  90. package/dist/types/secrets/obfuscator.d.ts +8 -2
  91. package/dist/types/session/agent-session.d.ts +15 -3
  92. package/dist/types/session/auth-broker-config.d.ts +4 -0
  93. package/dist/types/session/session-manager.d.ts +1 -1
  94. package/dist/types/session/streaming-output.d.ts +23 -0
  95. package/dist/types/slash-commands/acp-builtins.d.ts +16 -0
  96. package/dist/types/slash-commands/builtin-registry.d.ts +1 -0
  97. package/dist/types/slash-commands/helpers/stats-dashboard.d.ts +13 -0
  98. package/dist/types/slash-commands/types.d.ts +1 -1
  99. package/dist/types/ssh/connection-manager.d.ts +8 -0
  100. package/dist/types/system-prompt.d.ts +2 -0
  101. package/dist/types/task/executor.d.ts +1 -0
  102. package/dist/types/task/index.d.ts +2 -2
  103. package/dist/types/task/parallel.d.ts +2 -2
  104. package/dist/types/task/types.d.ts +8 -0
  105. package/dist/types/task/worktree.d.ts +2 -0
  106. package/dist/types/thinking.d.ts +4 -0
  107. package/dist/types/tiny/title-client.d.ts +11 -0
  108. package/dist/types/tiny/title-protocol.d.ts +1 -0
  109. package/dist/types/tools/ask.d.ts +4 -0
  110. package/dist/types/tools/conflict-detect.d.ts +16 -0
  111. package/dist/types/tools/github-cache.d.ts +7 -0
  112. package/dist/types/tools/index.d.ts +6 -0
  113. package/dist/types/tools/sqlite-reader.d.ts +3 -0
  114. package/dist/types/tui/output-block.d.ts +3 -3
  115. package/dist/types/utils/changelog.d.ts +8 -0
  116. package/dist/types/utils/git.d.ts +15 -2
  117. package/dist/types/utils/title-generator.d.ts +3 -2
  118. package/dist/types/web/scrapers/readthedocs.d.ts +3 -0
  119. package/dist/types/web/scrapers/types.d.ts +12 -0
  120. package/dist/types/web/search/providers/codex.d.ts +1 -1
  121. package/dist/types/web/search/providers/gemini.d.ts +1 -1
  122. package/examples/extensions/tools.ts +5 -4
  123. package/package.json +14 -11
  124. package/scripts/build-binary.ts +18 -23
  125. package/scripts/bundle-dist.ts +81 -0
  126. package/scripts/{dev-launch → omp} +1 -1
  127. package/scripts/{dev-launch-preload.ts → omp.ts} +1 -1
  128. package/src/async/job-manager.ts +57 -3
  129. package/src/auto-thinking/classifier.ts +1 -0
  130. package/src/autoresearch/dashboard.ts +1 -1
  131. package/src/autoresearch/prompt-setup.md +6 -6
  132. package/src/autoresearch/prompt.md +6 -6
  133. package/src/capability/fs.ts +10 -0
  134. package/src/cli/args.ts +4 -1
  135. package/src/cli/auth-gateway-cli.ts +1 -3
  136. package/src/cli/dry-balance-cli.ts +1 -1
  137. package/src/cli/gallery-cli.ts +1 -1
  138. package/src/cli/gallery-fixtures/fs.ts +1 -1
  139. package/src/cli/gallery-fixtures/types.ts +5 -1
  140. package/src/cli/list-models.ts +2 -1
  141. package/src/cli/usage-cli.ts +603 -0
  142. package/src/cli-commands.ts +30 -0
  143. package/src/cli.ts +76 -13
  144. package/src/commands/complete.ts +1 -1
  145. package/src/commands/launch.ts +5 -1
  146. package/src/commands/read.ts +6 -3
  147. package/src/commands/usage.ts +35 -0
  148. package/src/commit/agentic/agent.ts +1 -1
  149. package/src/commit/model-selection.ts +4 -3
  150. package/src/config/api-key-resolver.ts +8 -6
  151. package/src/config/append-only-context-mode.ts +6 -12
  152. package/src/config/model-discovery.ts +554 -0
  153. package/src/config/model-registry.ts +320 -1041
  154. package/src/config/model-resolver.ts +173 -156
  155. package/src/config/model-roles.ts +74 -0
  156. package/src/config/models-config-schema.ts +57 -8
  157. package/src/config/models-config.ts +129 -0
  158. package/src/config/settings-schema.ts +61 -19
  159. package/src/config/settings.ts +98 -4
  160. package/src/dap/client.ts +124 -37
  161. package/src/dap/session.ts +259 -158
  162. package/src/debug/log-viewer.ts +1 -1
  163. package/src/debug/raw-sse.ts +1 -1
  164. package/src/edit/diff.ts +47 -3
  165. package/src/edit/hashline/block-resolver.ts +20 -1
  166. package/src/edit/hashline/diff.ts +36 -1
  167. package/src/edit/hashline/execute.ts +47 -4
  168. package/src/edit/hashline/noop-loop-guard.ts +99 -0
  169. package/src/edit/index.ts +16 -1
  170. package/src/edit/modes/patch.ts +52 -0
  171. package/src/edit/modes/replace.ts +56 -22
  172. package/src/edit/notebook.ts +22 -2
  173. package/src/edit/renderer.ts +36 -10
  174. package/src/eval/__tests__/completion-bridge.test.ts +1 -1
  175. package/src/eval/backend.ts +0 -2
  176. package/src/eval/completion-bridge.ts +3 -1
  177. package/src/eval/idle-timeout.ts +2 -9
  178. package/src/eval/js/context-manager.ts +6 -8
  179. package/src/eval/js/executor.ts +6 -2
  180. package/src/eval/js/index.ts +0 -2
  181. package/src/eval/js/shared/helpers.ts +5 -6
  182. package/src/eval/js/shared/local-module-loader.ts +1 -1
  183. package/src/eval/js/shared/prelude.txt +62 -1
  184. package/src/eval/js/shared/rewrite-imports.ts +40 -22
  185. package/src/eval/js/shared/runtime.ts +1 -1
  186. package/src/eval/py/executor.ts +29 -7
  187. package/src/eval/py/index.ts +6 -3
  188. package/src/eval/py/kernel.ts +43 -4
  189. package/src/eval/py/runner.py +107 -3
  190. package/src/eval/py/runtime.ts +37 -0
  191. package/src/exec/bash-executor.ts +85 -4
  192. package/src/export/html/template.generated.ts +1 -1
  193. package/src/export/html/template.js +3 -1
  194. package/src/extensibility/extensions/get-commands-handler.ts +2 -1
  195. package/src/extensibility/extensions/runner.ts +6 -1
  196. package/src/extensibility/extensions/types.ts +6 -2
  197. package/src/extensibility/plugins/legacy-pi-compat.ts +20 -3
  198. package/src/hindsight/bank.ts +17 -2
  199. package/src/hindsight/mental-models.ts +59 -12
  200. package/src/hindsight/state.ts +6 -1
  201. package/src/internal-urls/artifact-protocol.ts +11 -2
  202. package/src/internal-urls/docs-index.generated.ts +11 -11
  203. package/src/internal-urls/issue-pr-protocol.ts +12 -5
  204. package/src/internal-urls/router.ts +1 -1
  205. package/src/internal-urls/types.ts +1 -1
  206. package/src/lib/xai-http.ts +1 -1
  207. package/src/lsp/client.ts +118 -38
  208. package/src/lsp/clients/biome-client.ts +101 -39
  209. package/src/lsp/edits.ts +143 -95
  210. package/src/lsp/index.ts +31 -22
  211. package/src/lsp/render.ts +1 -1
  212. package/src/lsp/types.ts +2 -0
  213. package/src/lsp/utils.ts +28 -10
  214. package/src/main.ts +183 -23
  215. package/src/mcp/json-rpc.ts +35 -5
  216. package/src/mcp/transports/stdio.ts +7 -1
  217. package/src/memories/index.ts +4 -1
  218. package/src/memory-backend/index.ts +1 -0
  219. package/src/memory-backend/local-backend.ts +9 -0
  220. package/src/memory-backend/off-backend.ts +9 -0
  221. package/src/memory-backend/runtime.ts +66 -0
  222. package/src/memory-backend/types.ts +81 -1
  223. package/src/mnemopi/backend.ts +176 -7
  224. package/src/mnemopi/state.ts +38 -2
  225. package/src/modes/acp/acp-agent.ts +119 -11
  226. package/src/modes/components/agent-dashboard.ts +10 -7
  227. package/src/modes/components/assistant-message.ts +32 -28
  228. package/src/modes/components/bash-execution.ts +1 -1
  229. package/src/modes/components/copy-selector.ts +1 -1
  230. package/src/modes/components/diff.ts +13 -2
  231. package/src/modes/components/dynamic-border.ts +12 -3
  232. package/src/modes/components/extensions/extension-dashboard.ts +8 -5
  233. package/src/modes/components/extensions/extension-list.ts +1 -1
  234. package/src/modes/components/extensions/inspector-panel.ts +1 -1
  235. package/src/modes/components/footer.ts +4 -2
  236. package/src/modes/components/history-search.ts +1 -1
  237. package/src/modes/components/hook-editor.ts +8 -0
  238. package/src/modes/components/hook-input.ts +8 -0
  239. package/src/modes/components/hook-selector.ts +2 -2
  240. package/src/modes/components/model-selector.ts +4 -2
  241. package/src/modes/components/plan-review-overlay.ts +1 -1
  242. package/src/modes/components/session-observer-overlay.ts +2 -2
  243. package/src/modes/components/session-selector.ts +1 -1
  244. package/src/modes/components/settings-selector.ts +5 -1
  245. package/src/modes/components/status-line/component.ts +119 -35
  246. package/src/modes/components/tiny-title-download-progress.ts +1 -1
  247. package/src/modes/components/transcript-container.ts +258 -53
  248. package/src/modes/components/tree-selector.ts +3 -3
  249. package/src/modes/components/user-message-selector.ts +1 -1
  250. package/src/modes/components/user-message.ts +17 -5
  251. package/src/modes/components/visual-truncate.ts +1 -1
  252. package/src/modes/components/welcome.ts +108 -26
  253. package/src/modes/controllers/command-controller.ts +11 -4
  254. package/src/modes/controllers/event-controller.ts +73 -4
  255. package/src/modes/controllers/input-controller.ts +2 -1
  256. package/src/modes/controllers/mcp-command-controller.ts +39 -4
  257. package/src/modes/controllers/selector-controller.ts +1 -1
  258. package/src/modes/controllers/streaming-reveal.ts +85 -18
  259. package/src/modes/index.ts +3 -21
  260. package/src/modes/interactive-mode.ts +42 -18
  261. package/src/modes/oauth-manual-input.ts +30 -3
  262. package/src/modes/rpc/rpc-client.ts +154 -3
  263. package/src/modes/rpc/rpc-mode.ts +97 -12
  264. package/src/modes/rpc/rpc-subagents.ts +265 -0
  265. package/src/modes/rpc/rpc-types.ts +81 -1
  266. package/src/modes/setup-wizard/index.ts +12 -2
  267. package/src/modes/setup-wizard/lazy.ts +16 -0
  268. package/src/modes/setup-wizard/scenes/glyph.ts +1 -1
  269. package/src/modes/setup-wizard/scenes/providers.ts +1 -1
  270. package/src/modes/setup-wizard/scenes/sign-in.ts +1 -1
  271. package/src/modes/setup-wizard/scenes/theme.ts +1 -1
  272. package/src/modes/setup-wizard/scenes/types.ts +1 -1
  273. package/src/modes/setup-wizard/scenes/web-search.ts +1 -1
  274. package/src/modes/setup-wizard/wizard-overlay.ts +1 -1
  275. package/src/modes/types.ts +4 -1
  276. package/src/prompts/agents/explore.md +2 -2
  277. package/src/prompts/agents/librarian.md +1 -2
  278. package/src/prompts/agents/oracle.md +1 -1
  279. package/src/prompts/agents/plan.md +5 -5
  280. package/src/prompts/agents/task.md +5 -5
  281. package/src/prompts/ci-green-request.md +5 -7
  282. package/src/prompts/goals/goal-budget-limit.md +2 -2
  283. package/src/prompts/goals/goal-continuation.md +4 -4
  284. package/src/prompts/goals/goal-mode-active.md +1 -1
  285. package/src/prompts/memories/read-path.md +1 -1
  286. package/src/prompts/memories/stage_one_system.md +2 -2
  287. package/src/prompts/review-custom-request.md +1 -1
  288. package/src/prompts/system/agent-creation-architect.md +2 -2
  289. package/src/prompts/system/auto-continue.md +1 -1
  290. package/src/prompts/system/background-tan-dispatch.md +1 -1
  291. package/src/prompts/system/btw-user.md +2 -2
  292. package/src/prompts/system/commit-message-system.md +13 -1
  293. package/src/prompts/system/custom-system-prompt.md +1 -1
  294. package/src/prompts/system/eager-todo.md +2 -2
  295. package/src/prompts/system/irc-incoming.md +1 -1
  296. package/src/prompts/system/manual-continue.md +1 -1
  297. package/src/prompts/system/omfg-user.md +3 -4
  298. package/src/prompts/system/orchestrate-notice.md +9 -9
  299. package/src/prompts/system/plan-mode-active.md +4 -4
  300. package/src/prompts/system/plan-mode-subagent.md +4 -5
  301. package/src/prompts/system/plan-mode-tool-decision-reminder.md +1 -1
  302. package/src/prompts/system/project-prompt.md +2 -2
  303. package/src/prompts/system/subagent-system-prompt.md +4 -4
  304. package/src/prompts/system/system-prompt.md +13 -24
  305. package/src/prompts/system/title-system.md +2 -2
  306. package/src/prompts/system/ttsr-tool-reminder.md +1 -1
  307. package/src/prompts/system/workflow-notice.md +1 -1
  308. package/src/prompts/tools/ast-edit.md +1 -1
  309. package/src/prompts/tools/ast-grep.md +2 -2
  310. package/src/prompts/tools/bash.md +5 -7
  311. package/src/prompts/tools/browser.md +7 -7
  312. package/src/prompts/tools/debug.md +1 -1
  313. package/src/prompts/tools/eval.md +3 -3
  314. package/src/prompts/tools/find.md +0 -1
  315. package/src/prompts/tools/github.md +8 -7
  316. package/src/prompts/tools/goal.md +1 -1
  317. package/src/prompts/tools/image-gen.md +1 -1
  318. package/src/prompts/tools/inspect-image-system.md +1 -1
  319. package/src/prompts/tools/irc.md +15 -15
  320. package/src/prompts/tools/lsp.md +2 -2
  321. package/src/prompts/tools/patch.md +2 -2
  322. package/src/prompts/tools/read.md +3 -4
  323. package/src/prompts/tools/recall.md +1 -1
  324. package/src/prompts/tools/reflect.md +1 -1
  325. package/src/prompts/tools/render-mermaid.md +2 -2
  326. package/src/prompts/tools/replace.md +4 -10
  327. package/src/prompts/tools/rewind.md +2 -2
  328. package/src/prompts/tools/search-tool-bm25.md +1 -9
  329. package/src/prompts/tools/search.md +0 -1
  330. package/src/prompts/tools/ssh.md +0 -4
  331. package/src/prompts/tools/task.md +2 -3
  332. package/src/prompts/tools/todo.md +1 -1
  333. package/src/sdk.ts +31 -11
  334. package/src/secrets/index.ts +8 -1
  335. package/src/secrets/obfuscator.ts +39 -18
  336. package/src/session/agent-session.ts +223 -64
  337. package/src/session/auth-broker-config.ts +30 -1
  338. package/src/session/session-manager.ts +2 -2
  339. package/src/session/streaming-output.ts +188 -11
  340. package/src/slash-commands/acp-builtins.ts +24 -0
  341. package/src/slash-commands/builtin-registry.ts +40 -0
  342. package/src/slash-commands/helpers/stats-dashboard.ts +85 -0
  343. package/src/slash-commands/types.ts +1 -1
  344. package/src/ssh/connection-manager.ts +27 -0
  345. package/src/system-prompt.ts +14 -0
  346. package/src/task/commands.ts +2 -1
  347. package/src/task/executor.ts +74 -65
  348. package/src/task/index.ts +146 -68
  349. package/src/task/parallel.ts +3 -3
  350. package/src/task/render.ts +20 -5
  351. package/src/task/types.ts +9 -0
  352. package/src/task/worktree.ts +64 -56
  353. package/src/thinking.ts +9 -1
  354. package/src/tiny/title-client.ts +60 -16
  355. package/src/tiny/title-protocol.ts +1 -1
  356. package/src/tiny/worker.ts +6 -4
  357. package/src/tools/archive-reader.ts +30 -2
  358. package/src/tools/ask.ts +104 -21
  359. package/src/tools/ast-edit.ts +25 -5
  360. package/src/tools/auto-generated-guard.ts +20 -3
  361. package/src/tools/bash-interactive.ts +27 -7
  362. package/src/tools/bash.ts +100 -18
  363. package/src/tools/browser/launch.ts +11 -2
  364. package/src/tools/browser/readable.ts +19 -2
  365. package/src/tools/browser/registry.ts +4 -1
  366. package/src/tools/browser/render.ts +2 -2
  367. package/src/tools/browser/tab-supervisor.ts +55 -16
  368. package/src/tools/conflict-detect.ts +50 -4
  369. package/src/tools/debug.ts +1 -1
  370. package/src/tools/eval-render.ts +5 -5
  371. package/src/tools/eval.ts +0 -2
  372. package/src/tools/fetch.ts +33 -10
  373. package/src/tools/gh-cache-invalidation.ts +63 -8
  374. package/src/tools/gh-renderer.ts +1 -1
  375. package/src/tools/gh.ts +172 -29
  376. package/src/tools/github-cache.ts +70 -6
  377. package/src/tools/image-gen.ts +14 -13
  378. package/src/tools/index.ts +13 -1
  379. package/src/tools/inspect-image.ts +1 -0
  380. package/src/tools/irc.ts +5 -1
  381. package/src/tools/job.ts +1 -1
  382. package/src/tools/read.ts +202 -61
  383. package/src/tools/render-utils.ts +3 -3
  384. package/src/tools/resolve.ts +1 -1
  385. package/src/tools/search.ts +92 -29
  386. package/src/tools/sqlite-reader.ts +17 -5
  387. package/src/tools/ssh.ts +8 -8
  388. package/src/tools/todo.ts +38 -8
  389. package/src/tools/write.ts +118 -18
  390. package/src/tui/output-block.ts +4 -4
  391. package/src/utils/changelog.ts +27 -1
  392. package/src/utils/commit-message-generator.ts +1 -0
  393. package/src/utils/file-mentions.ts +2 -1
  394. package/src/utils/git.ts +267 -13
  395. package/src/utils/title-generator.ts +24 -5
  396. package/src/web/scrapers/arxiv.ts +1 -1
  397. package/src/web/scrapers/go-pkg.ts +1 -1
  398. package/src/web/scrapers/iacr.ts +1 -1
  399. package/src/web/scrapers/readthedocs.ts +1 -1
  400. package/src/web/scrapers/twitter.ts +2 -1
  401. package/src/web/scrapers/types.ts +87 -8
  402. package/src/web/scrapers/wikipedia.ts +1 -1
  403. package/src/web/scrapers/youtube.ts +6 -1
  404. package/src/web/search/index.ts +1 -1
  405. package/src/web/search/providers/codex.ts +2 -1
  406. package/src/web/search/providers/gemini.ts +2 -3
  407. package/src/web/search/render.ts +8 -6
  408. package/dist/types/config/model-equivalence.d.ts +0 -24
  409. package/dist/types/config/model-id-affixes.d.ts +0 -12
  410. package/dist/types/config/model-provider-priority.d.ts +0 -1
  411. package/dist/types/exec/idle-timeout-watchdog.d.ts +0 -18
  412. package/src/config/model-equivalence.ts +0 -875
  413. package/src/config/model-id-affixes.ts +0 -81
  414. package/src/config/model-provider-priority.ts +0 -56
  415. package/src/exec/idle-timeout-watchdog.ts +0 -126
@@ -0,0 +1,554 @@
1
+ /**
2
+ * HTTP discovery protocols for configured and implicit providers — ollama,
3
+ * llama.cpp, lm-studio, openai-models-list, and new-api/one-api-style proxies.
4
+ * `ModelRegistry` owns the orchestration (status, state, caching) and calls
5
+ * `discoverModelsByProviderType` with a `DiscoveryContext`; built-in provider
6
+ * discovery lives in pi-catalog's provider-models.
7
+ */
8
+ import type { FetchImpl } from "@oh-my-pi/pi-ai";
9
+ import type { Api, Model } from "@oh-my-pi/pi-ai/types";
10
+ import { buildModel } from "@oh-my-pi/pi-catalog/build";
11
+ import {
12
+ getBundledModelReferenceIndex,
13
+ resolveModelReference,
14
+ stripBracketedModelIdAffixes,
15
+ } from "@oh-my-pi/pi-catalog/identity";
16
+ import type { ModelSpec } from "@oh-my-pi/pi-catalog/types";
17
+ import { isRecord } from "@oh-my-pi/pi-utils";
18
+ import type { ProviderDiscovery } from "./models-config-schema";
19
+
20
+ // Default cap on `max_tokens` for auto-discovered models that do not advertise
21
+ // their own output limit (OpenAI-models-list, Ollama, llama.cpp, new-api/
22
+ // one-api proxies). 32K matches the upper end of what mainstream
23
+ // OpenAI-compatible providers (DeepSeek, MiMo, OpenRouter, etc.) actually
24
+ // accept and keeps `min(contextWindow, …)` honoring smaller local windows.
25
+ // Conservative caps below this caused providers to drop the connection
26
+ // mid-stream when models hit the cap on legitimate large tool calls (see
27
+ // issue #1528: `write` payloads >~5KB on deepseek-v4-pro surfaced as
28
+ // "socket connection was closed unexpectedly").
29
+ export const DISCOVERY_DEFAULT_MAX_TOKENS = 32_768;
30
+
31
+ const DEFAULT_OLLAMA_BASE_URL = "http://127.0.0.1:11434";
32
+ const OLLAMA_HOST_DEFAULT_PORT = "11434";
33
+
34
+ function normalizeOllamaHostEnv(value: string | undefined): string | undefined {
35
+ const trimmed = value?.trim();
36
+ if (!trimmed) return undefined;
37
+ const candidate = trimmed.includes("://")
38
+ ? trimmed
39
+ : trimmed.startsWith("//")
40
+ ? `http:${trimmed}`
41
+ : trimmed.startsWith(":")
42
+ ? `http://127.0.0.1${trimmed}`
43
+ : `http://${trimmed}`;
44
+ try {
45
+ const parsed = new URL(candidate);
46
+ if (!parsed.hostname || (parsed.protocol !== "http:" && parsed.protocol !== "https:")) {
47
+ return undefined;
48
+ }
49
+ if (!parsed.port && parsed.protocol === "http:") {
50
+ parsed.port = OLLAMA_HOST_DEFAULT_PORT;
51
+ }
52
+ return `${parsed.protocol}//${parsed.host}`;
53
+ } catch {
54
+ return undefined;
55
+ }
56
+ }
57
+
58
+ export function getImplicitOllamaBaseUrl(): string {
59
+ const baseUrl = Bun.env.OLLAMA_BASE_URL?.trim();
60
+ return baseUrl || normalizeOllamaHostEnv(Bun.env.OLLAMA_HOST) || DEFAULT_OLLAMA_BASE_URL;
61
+ }
62
+
63
+ export function getOllamaContextLengthOverride(): number | undefined {
64
+ const value = Bun.env.OLLAMA_CONTEXT_LENGTH?.trim();
65
+ if (!value) return undefined;
66
+ const parsed = Number(value);
67
+ return Number.isSafeInteger(parsed) && parsed > 0 ? parsed : undefined;
68
+ }
69
+
70
+ // Anthropic-safe variant of the discovery cap. The Anthropic stream converter
71
+ // in `packages/ai/src/providers/anthropic.ts` derives the request limit as
72
+ // `(model.maxTokens / 3) | 0`, so the 32K default would surface as 10,922
73
+ // requested output tokens — above the 8,192 hard cap on classic Claude 3.x
74
+ // Sonnet/Haiku/Opus endpoints. Discovered models routed through
75
+ // `anthropic-messages` (proxy `supported_endpoint_types: ["anthropic"]` or a
76
+ // custom provider with `api: anthropic-messages` + openai-models-list
77
+ // discovery) fall back to this conservative value.
78
+ const DISCOVERY_DEFAULT_MAX_TOKENS_ANTHROPIC = 8_192;
79
+
80
+ /** Routes discovered-model `maxTokens` defaults around Anthropic's 3× output divisor. */
81
+ export function discoveryDefaultMaxTokens(api: Api | undefined): number {
82
+ return api === "anthropic-messages" ? DISCOVERY_DEFAULT_MAX_TOKENS_ANTHROPIC : DISCOVERY_DEFAULT_MAX_TOKENS;
83
+ }
84
+
85
+ export interface DiscoveryProviderConfig {
86
+ provider: string;
87
+ api: Api;
88
+ baseUrl?: string;
89
+ headers?: Record<string, string>;
90
+ compat?: ModelSpec<Api>["compat"];
91
+ discovery: ProviderDiscovery;
92
+ optional?: boolean;
93
+ }
94
+
95
+ /** Registry-provided capabilities the protocol probes need; never the registry itself. */
96
+ export interface DiscoveryContext {
97
+ /** Injected fetch implementation (tests stub this). */
98
+ fetch: FetchImpl;
99
+ /**
100
+ * Resolve a provider's API key for `Authorization: Bearer …`. Returns
101
+ * undefined when no key is stored or it is a local/no-auth sentinel.
102
+ */
103
+ getBearerApiKey(provider: string): Promise<string | undefined>;
104
+ }
105
+
106
+ type OllamaDiscoveredModelMetadata = {
107
+ reasoning: boolean;
108
+ input: ("text" | "image")[];
109
+ contextWindow?: number;
110
+ };
111
+
112
+ type LlamaCppDiscoveredServerMetadata = {
113
+ contextWindow?: number;
114
+ input?: ("text" | "image")[];
115
+ };
116
+
117
+ function toPositiveNumberOrUndefined(value: unknown): number | undefined {
118
+ if (typeof value === "number" && Number.isFinite(value) && value > 0) {
119
+ return value;
120
+ }
121
+ if (typeof value === "string" && value.trim()) {
122
+ const parsed = Number(value);
123
+ if (Number.isFinite(parsed) && parsed > 0) {
124
+ return parsed;
125
+ }
126
+ }
127
+ return undefined;
128
+ }
129
+
130
+ function extractOllamaContextWindow(payload: Record<string, unknown>): number | undefined {
131
+ const modelInfo = payload.model_info;
132
+ if (isRecord(modelInfo)) {
133
+ for (const [key, value] of Object.entries(modelInfo)) {
134
+ if (key === "context_length" || key.endsWith(".context_length")) {
135
+ const contextWindow = toPositiveNumberOrUndefined(value);
136
+ if (contextWindow !== undefined) {
137
+ return contextWindow;
138
+ }
139
+ }
140
+ }
141
+ }
142
+
143
+ const parameters = payload.parameters;
144
+ if (typeof parameters !== "string") {
145
+ return undefined;
146
+ }
147
+ const match = parameters.match(/(?:^|\n)\s*num_ctx\s+(\d+)\s*(?:$|\n)/m);
148
+ return match ? toPositiveNumberOrUndefined(match[1]) : undefined;
149
+ }
150
+
151
+ function extractLlamaCppContextWindow(payload: Record<string, unknown>): number | undefined {
152
+ const generationSettings = payload.default_generation_settings;
153
+ if (isRecord(generationSettings)) {
154
+ const contextWindow = toPositiveNumberOrUndefined(generationSettings.n_ctx);
155
+ if (contextWindow !== undefined) {
156
+ return contextWindow;
157
+ }
158
+ }
159
+ return toPositiveNumberOrUndefined(payload.n_ctx);
160
+ }
161
+
162
+ function extractLlamaCppInputCapabilities(payload: Record<string, unknown>): ("text" | "image")[] | undefined {
163
+ const modalities = payload.modalities;
164
+ if (!isRecord(modalities)) {
165
+ return undefined;
166
+ }
167
+ return modalities.vision === true ? ["text", "image"] : ["text"];
168
+ }
169
+
170
+ export function discoverModelsByProviderType(
171
+ providerConfig: DiscoveryProviderConfig,
172
+ ctx: DiscoveryContext,
173
+ ): Promise<Model<Api>[]> {
174
+ switch (providerConfig.discovery.type) {
175
+ case "ollama":
176
+ return discoverOllamaModels(providerConfig, ctx);
177
+ case "llama.cpp":
178
+ return discoverLlamaCppModels(providerConfig, ctx);
179
+ case "lm-studio":
180
+ case "openai-models-list":
181
+ return discoverOpenAIModelsList(providerConfig, ctx);
182
+ case "proxy":
183
+ return discoverProxyModels(providerConfig, ctx);
184
+ }
185
+ }
186
+
187
+ async function discoverOllamaModelMetadata(
188
+ ctx: DiscoveryContext,
189
+ endpoint: string,
190
+ modelId: string,
191
+ headers: Record<string, string> | undefined,
192
+ ): Promise<OllamaDiscoveredModelMetadata | null> {
193
+ const showUrl = `${endpoint}/api/show`;
194
+ try {
195
+ const response = await ctx.fetch(showUrl, {
196
+ method: "POST",
197
+ headers: { ...(headers ?? {}), "Content-Type": "application/json" },
198
+ body: JSON.stringify({ model: modelId }),
199
+ signal: AbortSignal.timeout(150),
200
+ });
201
+ if (!response.ok) {
202
+ return null;
203
+ }
204
+ const payload = (await response.json()) as unknown;
205
+ if (!isRecord(payload)) {
206
+ return null;
207
+ }
208
+ const contextWindow = extractOllamaContextWindow(payload);
209
+ const capabilities = payload.capabilities;
210
+ if (Array.isArray(capabilities)) {
211
+ const normalized = new Set(
212
+ capabilities.flatMap(capability => (typeof capability === "string" ? [capability.toLowerCase()] : [])),
213
+ );
214
+ const supportsVision = normalized.has("vision") || normalized.has("image");
215
+ return {
216
+ reasoning: normalized.has("thinking"),
217
+ input: supportsVision ? ["text", "image"] : ["text"],
218
+ contextWindow,
219
+ };
220
+ }
221
+ if (!isRecord(capabilities)) {
222
+ return {
223
+ reasoning: false,
224
+ input: ["text"],
225
+ contextWindow,
226
+ };
227
+ }
228
+ const supportsVision = capabilities.vision === true || capabilities.image === true;
229
+ return {
230
+ reasoning: capabilities.thinking === true,
231
+ input: supportsVision ? ["text", "image"] : ["text"],
232
+ contextWindow,
233
+ };
234
+ } catch {
235
+ return null;
236
+ }
237
+ }
238
+
239
+ export async function discoverOllamaModels(
240
+ providerConfig: DiscoveryProviderConfig,
241
+ ctx: DiscoveryContext,
242
+ ): Promise<Model<Api>[]> {
243
+ const endpoint = normalizeOllamaBaseUrl(providerConfig.baseUrl);
244
+ const tagsUrl = `${endpoint}/api/tags`;
245
+ const headers = { ...(providerConfig.headers ?? {}) };
246
+ const response = await ctx.fetch(tagsUrl, {
247
+ headers,
248
+ signal: AbortSignal.timeout(250),
249
+ });
250
+ if (!response.ok) {
251
+ throw new Error(`HTTP ${response.status} from ${tagsUrl}`);
252
+ }
253
+ const payload = (await response.json()) as { models?: Array<{ name?: string; model?: string }> };
254
+ const entries = (payload.models ?? []).flatMap(item => {
255
+ const id = item.model || item.name;
256
+ return id ? [{ id, name: item.name || id }] : [];
257
+ });
258
+ const metadataById = new Map(
259
+ await Promise.all(
260
+ entries.map(
261
+ async entry => [entry.id, await discoverOllamaModelMetadata(ctx, endpoint, entry.id, headers)] as const,
262
+ ),
263
+ ),
264
+ );
265
+ return entries.map(entry => {
266
+ const metadata = metadataById.get(entry.id);
267
+ return buildModel({
268
+ id: entry.id,
269
+ name: entry.name,
270
+ api: providerConfig.api,
271
+ provider: providerConfig.provider,
272
+ baseUrl: `${endpoint}/v1`,
273
+ reasoning: metadata?.reasoning ?? false,
274
+ input: metadata?.input ?? ["text"],
275
+ cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
276
+ contextWindow: metadata?.contextWindow ?? 128000,
277
+ maxTokens: Math.min(metadata?.contextWindow ?? Number.POSITIVE_INFINITY, DISCOVERY_DEFAULT_MAX_TOKENS),
278
+ headers: providerConfig.headers,
279
+ } as ModelSpec<Api>);
280
+ });
281
+ }
282
+
283
+ async function discoverLlamaCppServerMetadata(
284
+ ctx: DiscoveryContext,
285
+ baseUrl: string,
286
+ headers: Record<string, string> | undefined,
287
+ ): Promise<LlamaCppDiscoveredServerMetadata | null> {
288
+ const propsUrl = `${toLlamaCppNativeBaseUrl(baseUrl)}/props`;
289
+ try {
290
+ const response = await ctx.fetch(propsUrl, {
291
+ headers,
292
+ signal: AbortSignal.timeout(150),
293
+ });
294
+ if (!response.ok) {
295
+ return null;
296
+ }
297
+ const payload = (await response.json()) as unknown;
298
+ if (!isRecord(payload)) {
299
+ return null;
300
+ }
301
+ return {
302
+ contextWindow: extractLlamaCppContextWindow(payload),
303
+ input: extractLlamaCppInputCapabilities(payload),
304
+ };
305
+ } catch {
306
+ return null;
307
+ }
308
+ }
309
+
310
+ export async function discoverLlamaCppModels(
311
+ providerConfig: DiscoveryProviderConfig,
312
+ ctx: DiscoveryContext,
313
+ ): Promise<Model<Api>[]> {
314
+ const baseUrl = normalizeLlamaCppBaseUrl(providerConfig.baseUrl);
315
+ const modelsUrl = `${baseUrl}/models`;
316
+
317
+ const headers: Record<string, string> = { ...(providerConfig.headers ?? {}) };
318
+ const apiKey = await ctx.getBearerApiKey(providerConfig.provider);
319
+ if (apiKey) {
320
+ headers.Authorization = `Bearer ${apiKey}`;
321
+ }
322
+
323
+ const [response, serverMetadata] = await Promise.all([
324
+ ctx.fetch(modelsUrl, {
325
+ headers,
326
+ signal: AbortSignal.timeout(250),
327
+ }),
328
+ discoverLlamaCppServerMetadata(ctx, baseUrl, headers),
329
+ ]);
330
+ if (!response.ok) {
331
+ throw new Error(`HTTP ${response.status} from ${modelsUrl}`);
332
+ }
333
+ const payload = (await response.json()) as { data?: Array<{ id: string }> };
334
+ const models = payload.data ?? [];
335
+ const discovered: Model<Api>[] = [];
336
+ for (const item of models) {
337
+ const id = item.id;
338
+ if (!id) continue;
339
+ discovered.push(
340
+ buildModel({
341
+ id,
342
+ name: id,
343
+ api: providerConfig.api,
344
+ provider: providerConfig.provider,
345
+ baseUrl,
346
+ reasoning: false,
347
+ input: serverMetadata?.input ?? ["text"],
348
+ cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
349
+ contextWindow: serverMetadata?.contextWindow ?? 128000,
350
+ maxTokens: Math.min(
351
+ serverMetadata?.contextWindow ?? Number.POSITIVE_INFINITY,
352
+ DISCOVERY_DEFAULT_MAX_TOKENS,
353
+ ),
354
+ headers,
355
+ compat: {
356
+ supportsStore: false,
357
+ supportsDeveloperRole: false,
358
+ supportsReasoningEffort: false,
359
+ },
360
+ } as ModelSpec<Api>),
361
+ );
362
+ }
363
+ return discovered;
364
+ }
365
+
366
+ export async function discoverOpenAIModelsList(
367
+ providerConfig: DiscoveryProviderConfig,
368
+ ctx: DiscoveryContext,
369
+ ): Promise<Model<Api>[]> {
370
+ const baseUrl = normalizeOpenAIModelsListBaseUrl(providerConfig.baseUrl);
371
+ const modelsUrl = `${baseUrl}/models`;
372
+
373
+ const headers: Record<string, string> = { ...(providerConfig.headers ?? {}) };
374
+ const apiKey = await ctx.getBearerApiKey(providerConfig.provider);
375
+ if (apiKey) {
376
+ headers.Authorization = `Bearer ${apiKey}`;
377
+ }
378
+
379
+ const response = await ctx.fetch(modelsUrl, {
380
+ headers,
381
+ signal: AbortSignal.timeout(10_000),
382
+ });
383
+ if (!response.ok) {
384
+ throw new Error(`HTTP ${response.status} from ${modelsUrl}`);
385
+ }
386
+ const payload = (await response.json()) as { data?: Array<{ id: string }> };
387
+ const models = payload.data ?? [];
388
+ const discovered: Model<Api>[] = [];
389
+ for (const item of models) {
390
+ const id = item.id;
391
+ if (!id) continue;
392
+ discovered.push(
393
+ buildModel({
394
+ id,
395
+ name: id,
396
+ api: providerConfig.api,
397
+ provider: providerConfig.provider,
398
+ baseUrl,
399
+ reasoning: false,
400
+ input: ["text"],
401
+ cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
402
+ contextWindow: 128000,
403
+ maxTokens: discoveryDefaultMaxTokens(providerConfig.api),
404
+ headers,
405
+ compat: {
406
+ supportsStore: false,
407
+ supportsDeveloperRole: false,
408
+ supportsReasoningEffort: false,
409
+ },
410
+ } as ModelSpec<Api>),
411
+ );
412
+ }
413
+ return discovered;
414
+ }
415
+
416
+ /**
417
+ * Discover models from an Anthropic+OpenAI-compatible reseller proxy that
418
+ * exposes both `/v1/messages` and `/v1/chat/completions`, advertising each
419
+ * model's wire capabilities through `supported_endpoint_types` on
420
+ * `GET /v1/models` (new-api / one-api-style proxies).
421
+ *
422
+ * Routing per model:
423
+ * supported_endpoint_types: ["anthropic", ...] -> api: "anthropic-messages"
424
+ * supported_endpoint_types: ["openai"] -> api: "openai-completions"
425
+ * missing / neither -> provider-level api fallback
426
+ *
427
+ * Anthropic models share the same baseUrl; the Anthropic SDK strips a
428
+ * trailing `/v1` itself before appending `/v1/messages`, so the discovery
429
+ * URL (which ends in `/v1`) round-trips correctly.
430
+ */
431
+ export async function discoverProxyModels(
432
+ providerConfig: DiscoveryProviderConfig,
433
+ ctx: DiscoveryContext,
434
+ ): Promise<Model<Api>[]> {
435
+ const baseUrl = normalizeOpenAIModelsListBaseUrl(providerConfig.baseUrl);
436
+ const modelsUrl = `${baseUrl}/models`;
437
+
438
+ const headers: Record<string, string> = { ...(providerConfig.headers ?? {}) };
439
+ const apiKey = await ctx.getBearerApiKey(providerConfig.provider);
440
+ if (apiKey) {
441
+ headers.Authorization = `Bearer ${apiKey}`;
442
+ }
443
+
444
+ const response = await ctx.fetch(modelsUrl, {
445
+ headers,
446
+ signal: AbortSignal.timeout(10_000),
447
+ });
448
+ if (!response.ok) {
449
+ throw new Error(`HTTP ${response.status} from ${modelsUrl}`);
450
+ }
451
+ const payload = (await response.json()) as {
452
+ data?: Array<{ id?: string; name?: string; supported_endpoint_types?: string[] }>;
453
+ };
454
+ const items = payload.data ?? [];
455
+ const discovered: Model<Api>[] = [];
456
+ for (const item of items) {
457
+ const id = item.id;
458
+ if (!id) continue;
459
+ const endpoints = item.supported_endpoint_types ?? [];
460
+ const api: Api | undefined = endpoints.includes("anthropic")
461
+ ? "anthropic-messages"
462
+ : endpoints.includes("openai")
463
+ ? "openai-completions"
464
+ : providerConfig.api;
465
+ if (!api) continue;
466
+ const isAnthropic = api === "anthropic-messages";
467
+ const reference = resolveModelReference(id, getBundledModelReferenceIndex());
468
+ const discoveryName = typeof item.name === "string" ? item.name.trim() : "";
469
+ const displayName =
470
+ reference?.name ??
471
+ (discoveryName && discoveryName !== id ? discoveryName : undefined) ??
472
+ stripBracketedModelIdAffixes(id) ??
473
+ id;
474
+ discovered.push(
475
+ buildModel({
476
+ id,
477
+ name: displayName,
478
+ api,
479
+ provider: providerConfig.provider,
480
+ baseUrl,
481
+ reasoning: reference?.reasoning ?? false,
482
+ thinking: reference?.thinking,
483
+ input: reference?.input ?? ["text"],
484
+ // Proxy pricing is provider-specific and usually does not match
485
+ // upstream bundled catalogs, so keep costs local-unknown even when
486
+ // we successfully recover the upstream model identity.
487
+ cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
488
+ contextWindow: reference?.contextWindow ?? 128000,
489
+ maxTokens: reference?.maxTokens ?? discoveryDefaultMaxTokens(api),
490
+ headers,
491
+ // OpenAI-compat fields are no-ops on anthropic models; the
492
+ // Anthropic SDK ignores them. Provider-level disableStrictTools
493
+ // flows in via #applyProviderCompat for the third-party-Anthropic
494
+ // path. Cross-wire bundled compat is intentionally not copied:
495
+ // request-shaping fields are provider-wire specific.
496
+ compat: isAnthropic
497
+ ? undefined
498
+ : {
499
+ supportsStore: false,
500
+ supportsDeveloperRole: false,
501
+ supportsReasoningEffort: false,
502
+ },
503
+ } as ModelSpec<Api>),
504
+ );
505
+ }
506
+ return discovered;
507
+ }
508
+
509
+ function normalizeLlamaCppBaseUrl(baseUrl?: string): string {
510
+ const defaultBaseUrl = "http://127.0.0.1:8080";
511
+ const raw = baseUrl || defaultBaseUrl;
512
+ try {
513
+ const parsed = new URL(raw);
514
+ const trimmedPath = parsed.pathname.replace(/\/+$/g, "");
515
+ return `${parsed.protocol}//${parsed.host}${trimmedPath}`;
516
+ } catch {
517
+ return raw;
518
+ }
519
+ }
520
+
521
+ function toLlamaCppNativeBaseUrl(baseUrl: string): string {
522
+ try {
523
+ const parsed = new URL(baseUrl);
524
+ const trimmedPath = parsed.pathname.replace(/\/+$/g, "");
525
+ parsed.pathname = trimmedPath.endsWith("/v1") ? trimmedPath.slice(0, -3) || "/" : trimmedPath || "/";
526
+ const normalized = `${parsed.protocol}//${parsed.host}${parsed.pathname}`;
527
+ return normalized.endsWith("/") ? normalized.slice(0, -1) : normalized;
528
+ } catch {
529
+ return baseUrl.endsWith("/v1") ? baseUrl.slice(0, -3) : baseUrl;
530
+ }
531
+ }
532
+
533
+ function normalizeOpenAIModelsListBaseUrl(baseUrl?: string): string {
534
+ const defaultBaseUrl = "http://127.0.0.1:1234/v1";
535
+ const raw = baseUrl || defaultBaseUrl;
536
+ try {
537
+ const parsed = new URL(raw);
538
+ const trimmedPath = parsed.pathname.replace(/\/+$/g, "");
539
+ parsed.pathname = trimmedPath.endsWith("/v1") ? trimmedPath || "/v1" : `${trimmedPath}/v1`;
540
+ return `${parsed.protocol}//${parsed.host}${parsed.pathname}`;
541
+ } catch {
542
+ return raw;
543
+ }
544
+ }
545
+
546
+ function normalizeOllamaBaseUrl(baseUrl?: string): string {
547
+ const raw = baseUrl || DEFAULT_OLLAMA_BASE_URL;
548
+ try {
549
+ const parsed = new URL(raw);
550
+ return `${parsed.protocol}//${parsed.host}`;
551
+ } catch {
552
+ return DEFAULT_OLLAMA_BASE_URL;
553
+ }
554
+ }