@pi-unipi/unipi 3.0.0-alpha.2 → 3.0.0-alpha.21

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 (359) hide show
  1. package/CHANGELOG.md +292 -0
  2. package/README.md +8 -11
  3. package/docs/prefix-cache-architecture.md +14 -0
  4. package/package.json +22 -24
  5. package/packages/ask-user/README.md +55 -107
  6. package/packages/ask-user/ask-ui.ts +306 -732
  7. package/packages/ask-user/clipboard.ts +50 -0
  8. package/packages/ask-user/config.ts +74 -78
  9. package/packages/ask-user/package.json +2 -2
  10. package/packages/ask-user/questions.ts +151 -0
  11. package/packages/ask-user/skills/ask-user/SKILL.md +59 -133
  12. package/packages/ask-user/tools.ts +198 -432
  13. package/packages/autocomplete/package.json +1 -1
  14. package/packages/autocomplete/src/constants.ts +38 -43
  15. package/packages/autocomplete/src/sorting.ts +8 -0
  16. package/packages/background-tasks/package.json +2 -2
  17. package/packages/background-tasks/src/config.ts +0 -73
  18. package/packages/background-tasks/src/index.ts +20 -16
  19. package/packages/background-tasks/src/registry.ts +1 -171
  20. package/packages/background-tasks/src/types.ts +0 -32
  21. package/packages/btw/README.md +13 -56
  22. package/packages/btw/extensions/btw.ts +403 -1796
  23. package/packages/btw/package.json +5 -2
  24. package/packages/btw/skills/btw/SKILL.md +7 -143
  25. package/packages/compactor/README.md +56 -287
  26. package/packages/compactor/package.json +5 -9
  27. package/packages/compactor/skills/compactor/SKILL.md +13 -20
  28. package/packages/compactor/src/card.ts +90 -0
  29. package/packages/compactor/src/commands/index.ts +73 -130
  30. package/packages/compactor/src/compaction/compact-args.ts +5 -3
  31. package/packages/compactor/src/compaction/filter-noise.ts +1 -1
  32. package/packages/compactor/src/compaction/hooks.ts +487 -311
  33. package/packages/compactor/src/compaction/source.ts +178 -0
  34. package/packages/compactor/src/compaction/summarize.ts +586 -52
  35. package/packages/compactor/src/config/manager.ts +130 -127
  36. package/packages/compactor/src/config/schema.ts +32 -48
  37. package/packages/compactor/src/index.ts +40 -431
  38. package/packages/compactor/src/info-screen.ts +44 -190
  39. package/packages/compactor/src/session/recall-blocks.ts +1 -1
  40. package/packages/compactor/src/stats.ts +76 -0
  41. package/packages/compactor/src/tools/context-budget.ts +17 -34
  42. package/packages/compactor/src/tools/ctx-doctor.ts +45 -36
  43. package/packages/compactor/src/tools/register.ts +29 -269
  44. package/packages/compactor/src/types.ts +47 -186
  45. package/packages/core/README.md +25 -0
  46. package/packages/core/child-agent.ts +1 -0
  47. package/packages/core/command-echo.ts +63 -0
  48. package/packages/core/compaction-context.ts +41 -0
  49. package/packages/core/constants.ts +8 -38
  50. package/packages/core/events.ts +0 -53
  51. package/packages/core/fusion-status.ts +4 -0
  52. package/packages/core/global.d.ts +10 -0
  53. package/packages/core/harness-messages.ts +392 -0
  54. package/packages/core/index.ts +14 -0
  55. package/packages/core/kanboard-status.ts +49 -0
  56. package/packages/core/long-horizon-owner-status.ts +46 -0
  57. package/packages/core/model-cache.ts +81 -7
  58. package/packages/core/package.json +6 -3
  59. package/packages/core/src/attach/detect.ts +163 -0
  60. package/packages/core/src/child-agent/child-args.ts +31 -0
  61. package/packages/core/src/child-agent/delivery.ts +41 -0
  62. package/packages/core/src/child-agent/index.ts +5 -0
  63. package/packages/core/src/child-agent/readers.ts +203 -0
  64. package/packages/core/src/child-agent/runtime.ts +584 -0
  65. package/packages/core/src/evidence.ts +78 -0
  66. package/packages/core/src/hints/browser.ts +278 -0
  67. package/packages/core/src/hints/crab-data.ts +19 -0
  68. package/packages/core/src/hints/index.ts +865 -0
  69. package/packages/core/src/hints/lines.ts +607 -0
  70. package/packages/core/src/hints/store.ts +120 -0
  71. package/packages/core/src/jev/chatter.ts +24 -0
  72. package/packages/core/src/jev/settings.ts +177 -14
  73. package/packages/core/src/package-colors.ts +2 -0
  74. package/packages/core/src/settings/catalog.ts +39 -15
  75. package/packages/core/src/settings/engine.ts +48 -2
  76. package/packages/core/src/settings/hub.ts +83 -20
  77. package/packages/core/src/settings/open.ts +49 -0
  78. package/packages/core/src/settings/schema.ts +27 -7
  79. package/packages/core/src/tui/hub-kit.ts +18 -5
  80. package/packages/core/src/tui/kit.ts +198 -0
  81. package/packages/core/src/tui/progress.ts +73 -0
  82. package/packages/core/src/turn/arbiter.ts +290 -0
  83. package/packages/footer/README.md +4 -6
  84. package/packages/footer/package.json +3 -3
  85. package/packages/footer/src/glance-editor.ts +19 -11
  86. package/packages/footer/src/help.ts +1 -1
  87. package/packages/footer/src/index.ts +36 -3
  88. package/packages/footer/src/presets.ts +3 -4
  89. package/packages/footer/src/rendering/icons.ts +0 -20
  90. package/packages/footer/src/segments/compactor.ts +52 -142
  91. package/packages/footer/src/segments/status-ext.ts +0 -1
  92. package/packages/fusion/README.md +14 -4
  93. package/packages/fusion/package.json +4 -3
  94. package/packages/fusion/src/child-args.ts +1 -0
  95. package/packages/fusion/src/index.ts +161 -8
  96. package/packages/fusion/src/pi-settings.ts +57 -0
  97. package/packages/fusion/src/picker.ts +186 -51
  98. package/packages/fusion/src/preset.ts +56 -2
  99. package/packages/fusion/src/prompts.ts +1 -1
  100. package/packages/fusion/src/sidekick-runtime.ts +1 -420
  101. package/packages/fusion/src/tools.ts +107 -128
  102. package/packages/fusion/src/transcript.ts +27 -85
  103. package/packages/info-screen/package.json +2 -2
  104. package/packages/input-shortcuts/README.md +18 -30
  105. package/packages/input-shortcuts/package.json +2 -2
  106. package/packages/input-shortcuts/src/burst.ts +88 -0
  107. package/packages/input-shortcuts/src/chord-overlay.ts +20 -70
  108. package/packages/input-shortcuts/src/cleared-input.ts +97 -0
  109. package/packages/input-shortcuts/src/index.ts +99 -193
  110. package/packages/input-shortcuts/src/last-response.ts +40 -0
  111. package/packages/input-shortcuts/src/registers.ts +8 -23
  112. package/packages/input-shortcuts/src/types.ts +4 -16
  113. package/packages/input-shortcuts/src/undo-redo.ts +59 -47
  114. package/packages/kanboard/README.md +51 -47
  115. package/packages/kanboard/index.ts +293 -55
  116. package/packages/kanboard/package.json +8 -8
  117. package/packages/kanboard/skills/kanboard/SKILL.md +61 -27
  118. package/packages/kanboard/src/badges.ts +93 -0
  119. package/packages/kanboard/src/bin.ts +4 -2
  120. package/packages/kanboard/src/commands.ts +218 -84
  121. package/packages/kanboard/src/debug.ts +20 -0
  122. package/packages/kanboard/src/guard.ts +267 -109
  123. package/packages/kanboard/src/monitor.ts +330 -0
  124. package/packages/kanboard/src/notice-buffer.ts +74 -0
  125. package/packages/kanboard/src/progress.ts +27 -0
  126. package/packages/kanboard/src/reminders.ts +234 -0
  127. package/packages/kanboard/src/settings.ts +69 -15
  128. package/packages/kanboard/src/shapes.ts +5 -0
  129. package/packages/long-horizon/index.ts +190 -13
  130. package/packages/long-horizon/package.json +3 -3
  131. package/packages/long-horizon/src/commands.ts +103 -7
  132. package/packages/long-horizon/src/compaction-brief.ts +35 -0
  133. package/packages/long-horizon/src/engine/continuation.ts +84 -15
  134. package/packages/long-horizon/src/engine/goal-state.ts +64 -13
  135. package/packages/long-horizon/src/engine/nudge-stash.ts +71 -0
  136. package/packages/long-horizon/src/engine/ralph.ts +3 -2
  137. package/packages/long-horizon/src/engine/runaway.ts +68 -11
  138. package/packages/long-horizon/src/engine/verifier.ts +19 -0
  139. package/packages/long-horizon/src/gate.ts +37 -12
  140. package/packages/long-horizon/src/judge/resolve.ts +1 -0
  141. package/packages/long-horizon/src/owner.ts +35 -2
  142. package/packages/long-horizon/src/progress.ts +87 -0
  143. package/packages/long-horizon/src/prompts/goal.ts +1 -1
  144. package/packages/long-horizon/src/runtime.ts +172 -18
  145. package/packages/long-horizon/src/settings.ts +54 -39
  146. package/packages/long-horizon/src/tools/goal.ts +6 -0
  147. package/packages/long-horizon/src/tools/graph.ts +1 -1
  148. package/packages/long-horizon/src/tools/swarm.ts +3 -3
  149. package/packages/mcp/package.json +2 -2
  150. package/packages/memory/commands.ts +7 -2
  151. package/packages/memory/index.ts +210 -32
  152. package/packages/memory/package.json +3 -3
  153. package/packages/memory/save-session.ts +264 -0
  154. package/packages/memory/settings.ts +17 -0
  155. package/packages/memory/tools.ts +253 -203
  156. package/packages/notify/package.json +2 -2
  157. package/packages/skill-registry/README.md +53 -16
  158. package/packages/skill-registry/index.ts +230 -0
  159. package/packages/skill-registry/package.json +19 -8
  160. package/packages/skill-registry/src/editor.ts +206 -0
  161. package/packages/skill-registry/src/judge.ts +289 -0
  162. package/packages/skill-registry/src/registry.ts +87 -0
  163. package/packages/skill-registry/src/settings.ts +259 -0
  164. package/packages/skill-registry/src/vault.ts +80 -0
  165. package/packages/subagents/README.md +39 -119
  166. package/packages/subagents/package.json +6 -13
  167. package/packages/subagents/skills/subagents/SKILL.md +18 -42
  168. package/packages/subagents/src/agents.ts +213 -0
  169. package/packages/subagents/src/cards.ts +182 -0
  170. package/packages/subagents/src/index.ts +609 -1084
  171. package/packages/subagents/src/manager.ts +429 -0
  172. package/packages/subagents/src/profiles.ts +130 -0
  173. package/packages/subagents/src/transcript.ts +154 -0
  174. package/packages/subagents/src/ui.ts +389 -0
  175. package/packages/unipi/bundled.js +39756 -45569
  176. package/packages/unipi/index.ts +55 -10
  177. package/packages/updater/package.json +2 -2
  178. package/packages/utility/README.md +76 -69
  179. package/packages/utility/package.json +2 -2
  180. package/packages/utility/skills/image/SKILL.md +37 -0
  181. package/packages/utility/skills/utility/SKILL.md +17 -60
  182. package/packages/utility/src/answer/extract.ts +128 -0
  183. package/packages/utility/src/answer/index.ts +213 -0
  184. package/packages/utility/src/answer/reply.ts +133 -0
  185. package/packages/utility/src/answer/web.ts +167 -0
  186. package/packages/utility/src/attach/index.ts +205 -0
  187. package/packages/utility/src/commands.ts +38 -142
  188. package/packages/utility/src/diagnostics/engine.ts +50 -5
  189. package/packages/utility/src/image/index.ts +33 -0
  190. package/packages/utility/src/image/models.ts +221 -0
  191. package/packages/{image/src → utility/src/image}/recognize.ts +2 -2
  192. package/packages/utility/src/image/settings.ts +178 -0
  193. package/packages/{image/src/image-source.ts → utility/src/image/source.ts} +1 -1
  194. package/packages/utility/src/image/tools.ts +168 -0
  195. package/packages/utility/src/image/transports.ts +186 -0
  196. package/packages/utility/src/index.ts +68 -389
  197. package/packages/utility/src/lifecycle/cleanup.ts +116 -311
  198. package/packages/utility/src/rename/gate.ts +102 -0
  199. package/packages/utility/src/rename/index.ts +191 -0
  200. package/packages/utility/src/rename/session.ts +170 -0
  201. package/packages/utility/src/render/delegated.ts +285 -0
  202. package/packages/utility/src/render/harness.ts +683 -0
  203. package/packages/utility/src/render/parse.ts +156 -0
  204. package/packages/utility/src/render/pi-settings.ts +38 -0
  205. package/packages/utility/src/render/reply-bg.ts +307 -0
  206. package/packages/utility/src/render/simple.ts +724 -0
  207. package/packages/utility/src/render/spacing.ts +235 -0
  208. package/packages/utility/src/render/styled.ts +251 -0
  209. package/packages/utility/src/render/tools.ts +353 -0
  210. package/packages/utility/src/settings.ts +166 -233
  211. package/packages/utility/src/types.ts +1 -222
  212. package/packages/watchdog/index.ts +17 -6
  213. package/packages/watchdog/package.json +3 -3
  214. package/packages/watchdog/src/config.ts +3 -2
  215. package/packages/web-api/package.json +2 -2
  216. package/packages/workflow/README.md +3 -0
  217. package/packages/workflow/index.ts +0 -2
  218. package/packages/workflow/package.json +2 -2
  219. package/packages/workflow/src/permission/decide.ts +6 -4
  220. package/packages/workflow/src/permission/index.ts +17 -7
  221. package/packages/workflow/src/permission/settings.ts +39 -4
  222. package/packages/workflow/src/plan/index.ts +19 -4
  223. package/packages/background-tasks/extensions/delegate-child.ts +0 -1
  224. package/packages/background-tasks/src/context-parent-snapshot.ts +0 -142
  225. package/packages/background-tasks/src/context-token-budget.ts +0 -903
  226. package/packages/background-tasks/src/context-visible-conversation-v2.ts +0 -551
  227. package/packages/background-tasks/src/delegate/artifacts.ts +0 -487
  228. package/packages/background-tasks/src/delegate/budget.ts +0 -415
  229. package/packages/background-tasks/src/delegate/hook-contract-evidence.json +0 -18
  230. package/packages/background-tasks/src/delegate/hook-contract.ts +0 -154
  231. package/packages/background-tasks/src/delegate/launch.ts +0 -484
  232. package/packages/background-tasks/src/delegate/result-package.ts +0 -459
  233. package/packages/background-tasks/src/delegate/runner.ts +0 -430
  234. package/packages/background-tasks/src/delegate/seed.ts +0 -423
  235. package/packages/background-tasks/src/delegate/types.ts +0 -323
  236. package/packages/background-tasks/src/delegate-child-extension.ts +0 -978
  237. package/packages/background-tasks/src/delegate-extension.ts +0 -654
  238. package/packages/background-tasks/src/fixtures/delegate-context-incident.json +0 -17
  239. package/packages/compactor/skills/compactor-detail/SKILL.md +0 -116
  240. package/packages/compactor/skills/compactor-doctor/SKILL.md +0 -71
  241. package/packages/compactor/skills/compactor-stats/SKILL.md +0 -51
  242. package/packages/compactor/src/compaction/build-sections.ts +0 -76
  243. package/packages/compactor/src/compaction/extract/goals.ts +0 -36
  244. package/packages/compactor/src/compaction/extract/preferences.ts +0 -40
  245. package/packages/compactor/src/compaction/format.ts +0 -53
  246. package/packages/compactor/src/compaction/merge.ts +0 -143
  247. package/packages/compactor/src/config/presets.ts +0 -160
  248. package/packages/compactor/src/executor/executor.ts +0 -233
  249. package/packages/compactor/src/executor/runtime.ts +0 -115
  250. package/packages/compactor/src/security/evaluator.ts +0 -89
  251. package/packages/compactor/src/security/policy.ts +0 -96
  252. package/packages/compactor/src/security/scanner.ts +0 -65
  253. package/packages/compactor/src/session/auto-inject.ts +0 -60
  254. package/packages/compactor/src/session/db.ts +0 -325
  255. package/packages/compactor/src/session/extract.ts +0 -144
  256. package/packages/compactor/src/session/resume-inject.ts +0 -64
  257. package/packages/compactor/src/session/snapshot.ts +0 -327
  258. package/packages/compactor/src/tools/ctx-batch-execute.ts +0 -41
  259. package/packages/compactor/src/tools/ctx-execute-file.ts +0 -23
  260. package/packages/compactor/src/tools/ctx-execute.ts +0 -23
  261. package/packages/compactor/src/tools/ctx-stats.ts +0 -96
  262. package/packages/image/README.md +0 -99
  263. package/packages/image/package.json +0 -61
  264. package/packages/image/skills/image/SKILL.md +0 -76
  265. package/packages/image/src/generate.ts +0 -256
  266. package/packages/image/src/index.ts +0 -135
  267. package/packages/image/src/models.ts +0 -519
  268. package/packages/image/src/openai-images-api.ts +0 -282
  269. package/packages/image/src/register-providers.ts +0 -209
  270. package/packages/image/src/settings.ts +0 -173
  271. package/packages/image/src/tools.ts +0 -326
  272. package/packages/input-shortcuts/src/clipboard.ts +0 -119
  273. package/packages/kanboard/src/runner.ts +0 -817
  274. package/packages/skill-registry/skills/auto/SKILL.md +0 -465
  275. package/packages/skill-registry/skills/brainstorm/SKILL.md +0 -219
  276. package/packages/skill-registry/skills/chore-create/SKILL.md +0 -315
  277. package/packages/skill-registry/skills/chore-execute/SKILL.md +0 -314
  278. package/packages/skill-registry/skills/consolidate/SKILL.md +0 -142
  279. package/packages/skill-registry/skills/consultant/SKILL.md +0 -97
  280. package/packages/skill-registry/skills/debug/SKILL.md +0 -228
  281. package/packages/skill-registry/skills/document/SKILL.md +0 -120
  282. package/packages/skill-registry/skills/fix/SKILL.md +0 -212
  283. package/packages/skill-registry/skills/gather-context/SKILL.md +0 -145
  284. package/packages/skill-registry/skills/plan/SKILL.md +0 -231
  285. package/packages/skill-registry/skills/quick-fix/SKILL.md +0 -135
  286. package/packages/skill-registry/skills/quick-work/SKILL.md +0 -110
  287. package/packages/skill-registry/skills/research/SKILL.md +0 -251
  288. package/packages/skill-registry/skills/review-work/SKILL.md +0 -187
  289. package/packages/skill-registry/skills/scan-issues/SKILL.md +0 -195
  290. package/packages/skill-registry/skills/work/SKILL.md +0 -218
  291. package/packages/skill-registry/skills/worktree-create/SKILL.md +0 -69
  292. package/packages/skill-registry/skills/worktree-list/SKILL.md +0 -78
  293. package/packages/skill-registry/skills/worktree-merge/SKILL.md +0 -89
  294. package/packages/subagents/agents/delegate.md +0 -14
  295. package/packages/subagents/agents/oracle.md +0 -78
  296. package/packages/subagents/agents/researcher.md +0 -52
  297. package/packages/subagents/agents/reviewer.md +0 -80
  298. package/packages/subagents/agents/scout.md +0 -50
  299. package/packages/subagents/agents/worker.md +0 -59
  300. package/packages/subagents/prompts/council.md +0 -48
  301. package/packages/subagents/prompts/gather-context-and-clarify.md +0 -13
  302. package/packages/subagents/prompts/parallel-cleanup.md +0 -59
  303. package/packages/subagents/prompts/parallel-research.md +0 -50
  304. package/packages/subagents/prompts/parallel-review.md +0 -54
  305. package/packages/subagents/prompts/review-loop.md +0 -43
  306. package/packages/subagents/skills/council-mode/SKILL.md +0 -230
  307. package/packages/subagents/skills/subagents/references/constraints-and-recipes.md +0 -259
  308. package/packages/subagents/skills/subagents/references/execution-controls.md +0 -454
  309. package/packages/subagents/skills/subagents/references/management-authoring-rpc.md +0 -161
  310. package/packages/subagents/skills/subagents/references/multi-lane-orchestration.md +0 -39
  311. package/packages/subagents/skills/subagents/references/prompting-and-roles.md +0 -267
  312. package/packages/subagents/src/acceptance.ts +0 -331
  313. package/packages/subagents/src/agent-manager.ts +0 -405
  314. package/packages/subagents/src/agent-memory.ts +0 -300
  315. package/packages/subagents/src/agent-overrides.ts +0 -183
  316. package/packages/subagents/src/agent-runner.ts +0 -271
  317. package/packages/subagents/src/async-runner.ts +0 -422
  318. package/packages/subagents/src/authority-policy.ts +0 -47
  319. package/packages/subagents/src/budgets.ts +0 -285
  320. package/packages/subagents/src/child-safety.ts +0 -140
  321. package/packages/subagents/src/config.ts +0 -249
  322. package/packages/subagents/src/conversation-viewer.ts +0 -281
  323. package/packages/subagents/src/core-compat.ts +0 -94
  324. package/packages/subagents/src/custom-agents.ts +0 -343
  325. package/packages/subagents/src/file-system-retry.ts +0 -50
  326. package/packages/subagents/src/fleet-data.ts +0 -105
  327. package/packages/subagents/src/fleet-view.ts +0 -322
  328. package/packages/subagents/src/foreground-detach.ts +0 -59
  329. package/packages/subagents/src/fork-context.ts +0 -285
  330. package/packages/subagents/src/global.d.ts +0 -7
  331. package/packages/subagents/src/guide.ts +0 -129
  332. package/packages/subagents/src/mission-state.ts +0 -133
  333. package/packages/subagents/src/mission-store.ts +0 -436
  334. package/packages/subagents/src/output-limits.ts +0 -142
  335. package/packages/subagents/src/parity-types.ts +0 -315
  336. package/packages/subagents/src/pi-args.ts +0 -167
  337. package/packages/subagents/src/result-files.ts +0 -232
  338. package/packages/subagents/src/result-watcher.ts +0 -221
  339. package/packages/subagents/src/retained-children.ts +0 -176
  340. package/packages/subagents/src/run-fanout-budget.ts +0 -356
  341. package/packages/subagents/src/scheduled-runs.ts +0 -349
  342. package/packages/subagents/src/schemas.ts +0 -263
  343. package/packages/subagents/src/slash-commands.ts +0 -80
  344. package/packages/subagents/src/supervisor-channel.ts +0 -249
  345. package/packages/subagents/src/tool-handler.ts +0 -1144
  346. package/packages/subagents/src/types.ts +0 -238
  347. package/packages/subagents/src/widget.ts +0 -457
  348. package/packages/subagents/src/workflow-script.ts +0 -737
  349. package/packages/subagents/src/workflow-worker.ts +0 -384
  350. package/packages/subagents/src/worktree.ts +0 -614
  351. package/packages/utility/src/analytics/collector.ts +0 -293
  352. package/packages/utility/src/info-screen.ts +0 -80
  353. package/packages/utility/src/lifecycle/process.ts +0 -162
  354. package/packages/utility/src/prefix-cache.ts +0 -263
  355. package/packages/utility/src/skill-discovery.ts +0 -542
  356. package/packages/utility/src/tools/env.ts +0 -113
  357. package/packages/utility/src/tui/name-badge-state.ts +0 -315
  358. package/packages/utility/src/tui/name-badge.ts +0 -109
  359. /package/packages/{subagents/src → core/src/child-agent}/pi-spawn.ts +0 -0
@@ -1,519 +0,0 @@
1
- /**
2
- * @pi-unipi/image — Model discovery and resolution
3
- *
4
- * Two different model families are involved:
5
- *
6
- * - **Image generation** uses pi-ai's `ImagesModel` catalog, which
7
- * pi-coding-agent does not expose on `ExtensionContext` — it is reached
8
- * through the pi-ai subpath exports.
9
- * - **Image recognition** uses ordinary chat models filtered to those whose
10
- * `input` modality includes `"image"` (`ctx.modelRegistry`).
11
- */
12
-
13
- /** A generation model, kept structural to avoid deep pi-ai type coupling. */
14
- export interface ImageGenModel {
15
- id: string;
16
- name?: string;
17
- provider: string;
18
- api: string;
19
- baseUrl?: string;
20
- input?: string[];
21
- output?: string[];
22
- [key: string]: unknown;
23
- }
24
-
25
- /** A vision-capable chat model. */
26
- export interface VisionModel {
27
- id: string;
28
- name?: string;
29
- provider: string;
30
- input?: string[];
31
- }
32
-
33
- /** Minimal chat-model registry surface (pi's ModelRegistry). */
34
- export interface ChatModelRegistry {
35
- find(provider: string, modelId: string): unknown;
36
- getAll(): unknown[];
37
- getAvailable?(): unknown[];
38
- getApiKeyForProvider?(provider: string): Promise<string | undefined>;
39
- }
40
-
41
- /** Split "provider/model-id" — the model id may itself contain slashes. */
42
- export function splitModelRef(ref: string): { provider: string; id: string } | null {
43
- const trimmed = ref.trim();
44
- const slash = trimmed.indexOf("/");
45
- if (slash <= 0 || slash === trimmed.length - 1) return null;
46
- return { provider: trimmed.slice(0, slash), id: trimmed.slice(slash + 1) };
47
- }
48
-
49
- /** Format a model as "provider/model-id". */
50
- export function formatModelRef(model: { provider: string; id: string }): string {
51
- return `${model.provider}/${model.id}`;
52
- }
53
-
54
- /**
55
- * pi-ai's runtime image-model collection: model catalog, auth resolution and
56
- * generation in one object. Only the parts used here are typed.
57
- */
58
- export interface ImagesModelsLike {
59
- getModels(provider?: string): readonly ImageGenModel[];
60
- getModel(provider: string, id: string): ImageGenModel | undefined;
61
- getAuth(model: ImageGenModel): Promise<{ apiKey?: string } | undefined>;
62
- generateImages(
63
- model: ImageGenModel,
64
- context: { input: Array<{ type: string; text?: string }> },
65
- options?: { apiKey?: string; signal?: AbortSignal },
66
- ): Promise<unknown>;
67
- }
68
-
69
- let cachedImagesModels: ImagesModelsLike | null = null;
70
- let imagesModelsAttempted = false;
71
-
72
- /**
73
- * Load pi-ai's built-in images collection.
74
- *
75
- * ⚠️ Do NOT reach for `getImageModels`/`getImageProviders`/`generateImages`:
76
- *
77
- * - They are NOT re-exported from the pi-ai package root. Importing
78
- * `@earendil-works/pi-ai` and calling `getImageModels("openrouter")`
79
- * returns an EMPTY ARRAY rather than throwing, so the mistake looks like
80
- * "no models are installed" and costs a long debugging session.
81
- * - The file that defines them, `dist/image-models.js`, is not a permitted
82
- * subpath in pi-ai's `exports` map, so importing it directly throws
83
- * ERR_PACKAGE_PATH_NOT_EXPORTED.
84
- *
85
- * The supported entry point is `@earendil-works/pi-ai/providers/all` →
86
- * `builtinImagesModels()`, which returns the catalog AND resolves auth.
87
- * A failure is not fatal: it degrades to null and callers report that no
88
- * models are available.
89
- */
90
- export async function getImagesModels(): Promise<ImagesModelsLike | null> {
91
- if (cachedImagesModels || imagesModelsAttempted) return cachedImagesModels;
92
- imagesModelsAttempted = true;
93
-
94
- try {
95
- const mod = (await import("@earendil-works/pi-ai/providers/all")) as unknown as {
96
- builtinImagesModels?: () => ImagesModelsLike;
97
- };
98
- if (typeof mod.builtinImagesModels === "function") {
99
- cachedImagesModels = mod.builtinImagesModels();
100
- }
101
- } catch {
102
- cachedImagesModels = null;
103
- }
104
-
105
- return cachedImagesModels;
106
- }
107
-
108
- /**
109
- * Providers pi-ai's images collection can actually generate with.
110
- *
111
- * This is NOT the same set as pi's chat model registry: a chat provider
112
- * registered by another extension may list image models that image generation
113
- * cannot drive. Empty means "unknown", which callers treat as permissive.
114
- */
115
- export async function getGeneratingProviders(): Promise<string[]> {
116
- const images = await getImagesModels();
117
- if (!images) return [];
118
- try {
119
- return [...new Set(images.getModels().map((m) => m.provider))];
120
- } catch {
121
- return [];
122
- }
123
- }
124
-
125
- /** List available image-generation models. Empty when unavailable. */
126
- export async function listImageGenModels(): Promise<ImageGenModel[]> {
127
- const images = await getImagesModels();
128
- if (!images) return [];
129
- try {
130
- return [...images.getModels()];
131
- } catch {
132
- return [];
133
- }
134
- }
135
-
136
- /**
137
- * Heuristic: does a chat-registry model look like an image generator?
138
- *
139
- * Third-party providers registered by other extensions (pi-omniroute-bridge,
140
- * for example) surface text-to-image endpoints as ordinary chat models. They
141
- * declare no `output` modality at all, so a strict `output.includes("image")`
142
- * check finds nothing and the user sees only pi-ai's built-in OpenRouter
143
- * catalog. We therefore accept an explicit image output when present, and
144
- * otherwise fall back to well-known generator naming.
145
- */
146
- const GENERATOR_NAME_HINTS = [
147
- "text-to-image",
148
- "flux",
149
- "dall-e",
150
- "dalle",
151
- "imagen",
152
- "recraft",
153
- "seedream",
154
- "riverflow",
155
- "grok-imagine",
156
- "stable-diffusion",
157
- "sdxl",
158
- "midjourney",
159
- "ideogram",
160
- "nano-banana",
161
- ];
162
-
163
- export function looksLikeImageGenerator(model: {
164
- id: string;
165
- name?: string;
166
- output?: string[];
167
- }): boolean {
168
- // An explicit declaration always wins.
169
- if (Array.isArray(model.output)) {
170
- if (model.output.includes("image")) return true;
171
- // Declared, but text-only — trust it and do not guess from the name.
172
- if (model.output.length > 0) return false;
173
- }
174
-
175
- const haystack = `${model.id} ${model.name ?? ""}`.toLowerCase();
176
- // Match "image" as a delimited segment ("gpt-5-image", "gemini-3-pro-image"),
177
- // not the bare word anywhere — which would wrongly catch vision models such
178
- // as "claude-image-understanding". \b handles the whitespace between id and
179
- // name; a character class alone missed ids ending the id portion.
180
- if (/(^|[/\-_\s])image\b/.test(haystack) && !haystack.includes("understand")) {
181
- return true;
182
- }
183
- return GENERATOR_NAME_HINTS.some((hint) => haystack.includes(hint));
184
- }
185
-
186
- /**
187
- * Image-generation models discovered from the chat registry — i.e. providers
188
- * registered by other extensions, which pi-ai's built-in catalog knows nothing
189
- * about.
190
- */
191
- export function listRegistryImageGenModels(
192
- registry: ChatModelRegistry,
193
- ): ImageGenModel[] {
194
- let models: unknown[];
195
- try {
196
- models = registry.getAvailable?.() ?? registry.getAll();
197
- } catch {
198
- return [];
199
- }
200
-
201
- const out: ImageGenModel[] = [];
202
- for (const model of models) {
203
- if (model === null || typeof model !== "object") continue;
204
- const candidate = model as Partial<ImageGenModel>;
205
- if (typeof candidate.id !== "string" || typeof candidate.provider !== "string") {
206
- continue;
207
- }
208
- if (!looksLikeImageGenerator(candidate as ImageGenModel)) continue;
209
- out.push({
210
- id: candidate.id,
211
- provider: candidate.provider,
212
- name: candidate.name,
213
- api: candidate.api ?? "",
214
- // Carry the endpoint through. The generic images adapter POSTs to
215
- // `{baseUrl}/images/generations`, and this is the only place the
216
- // registry's baseUrl is available — dropping it here surfaces later as
217
- // "No baseUrl for image model ..." once generation is attempted.
218
- ...(candidate.baseUrl ? { baseUrl: candidate.baseUrl } : {}),
219
- ...(candidate.output ? { output: candidate.output } : {}),
220
- });
221
- }
222
- return out;
223
- }
224
-
225
- /**
226
- * Find a provider's API endpoint in pi's registry.
227
- *
228
- * Needed because a model can reach generation without one: a user-typed
229
- * "provider/model-id" is accepted at face value by `asExplicitModelRef`, and
230
- * carries no baseUrl of its own.
231
- */
232
- export function findProviderBaseUrl(
233
- registry: ChatModelRegistry | undefined,
234
- provider: string,
235
- ): string | undefined {
236
- if (!registry) return undefined;
237
- try {
238
- const models = (registry.getAvailable?.() ?? registry.getAll()) as Array<{
239
- provider?: string;
240
- baseUrl?: string;
241
- }>;
242
- return models.find((m) => m?.provider === provider && m.baseUrl)?.baseUrl;
243
- } catch {
244
- return undefined;
245
- }
246
- }
247
-
248
- /**
249
- * Every selectable generation model: pi-ai's built-in catalog plus anything
250
- * contributed by registered providers, de-duplicated by "provider/id".
251
- */
252
- export async function listAllImageGenModels(
253
- registry?: ChatModelRegistry | null,
254
- ): Promise<ImageGenModel[]> {
255
- const builtin = await listImageGenModels();
256
- const fromRegistry = registry ? listRegistryImageGenModels(registry) : [];
257
-
258
- const seen = new Set(builtin.map((m) => formatModelRef(m).toLowerCase()));
259
- const merged = [...builtin];
260
- for (const model of fromRegistry) {
261
- const key = formatModelRef(model).toLowerCase();
262
- if (seen.has(key)) continue;
263
- seen.add(key);
264
- merged.push(model);
265
- }
266
- return merged;
267
- }
268
-
269
- /** Inject a stub images collection. Test-only. */
270
- export function __setImagesModelsForTests(models: ImagesModelsLike | null): void {
271
- cachedImagesModels = models;
272
- imagesModelsAttempted = models !== null;
273
- }
274
-
275
- /** Reset the model cache. Test-only. */
276
- export function __resetModelCacheForTests(): void {
277
- cachedImagesModels = null;
278
- imagesModelsAttempted = false;
279
- }
280
-
281
- /**
282
- * Resolve a generation-model reference against the catalog.
283
- *
284
- * Exact "provider/id" first, then a scored fuzzy match so "flux" or
285
- * "gemini-3-pro" work. Returns an error string (not a throw) so the tool can
286
- * surface it as a normal tool error listing the alternatives.
287
- */
288
- export function resolveImageGenModel(
289
- input: string,
290
- models: ImageGenModel[],
291
- ): ImageGenModel | string {
292
- const raw = input.trim();
293
- const query = raw.toLowerCase();
294
- if (!query) return "No image model specified.";
295
- if (models.length === 0) {
296
- // A fully-qualified reference still works: detection is heuristic, so the
297
- // user must be able to name a model we failed to discover.
298
- const explicit = asExplicitModelRef(raw);
299
- if (explicit) return explicit;
300
- return (
301
- "No image generation models are available.\n" +
302
- "→ Image generation requires an OpenRouter account: https://openrouter.ai/keys\n" +
303
- "→ Or set an exact model with /unipi:settings (press c to enter one manually)."
304
- );
305
- }
306
-
307
- // 1. Exact "provider/id"
308
- const exact = models.find((m) => formatModelRef(m).toLowerCase() === query);
309
- if (exact) return exact;
310
-
311
- // 2. Exact id, ignoring the provider
312
- const byId = models.find((m) => m.id.toLowerCase() === query);
313
- if (byId) return byId;
314
-
315
- // 3. Fuzzy
316
- let best: ImageGenModel | undefined;
317
- let bestScore = 0;
318
-
319
- for (const model of models) {
320
- const id = model.id.toLowerCase();
321
- const full = formatModelRef(model).toLowerCase();
322
- const name = (model.name ?? model.id).toLowerCase();
323
-
324
- let score = 0;
325
- if (id.includes(query) || full.includes(query)) {
326
- score = 60 + (query.length / id.length) * 30;
327
- } else if (name.includes(query)) {
328
- score = 40 + (query.length / name.length) * 20;
329
- }
330
-
331
- if (score > bestScore) {
332
- bestScore = score;
333
- best = model;
334
- }
335
- }
336
-
337
- if (best && bestScore > 0) return best;
338
-
339
- // Nothing matched, but an explicit "provider/model-id" is taken at face
340
- // value — the catalog is not authoritative for third-party providers.
341
- const explicit = asExplicitModelRef(raw);
342
- if (explicit) return explicit;
343
-
344
- const sample = models.slice(0, 10).map((m) => ` ${formatModelRef(m)}`).join("\n");
345
- return (
346
- `Unknown image model "${input}".\n` +
347
- `Available models (${models.length} total):\n${sample}` +
348
- (models.length > 10 ? "\n …run /unipi:settings to browse all" : "")
349
- );
350
- }
351
-
352
- /**
353
- * Treat a well-formed "provider/model-id" as a usable model even when it is
354
- * absent from the catalog.
355
- *
356
- * Generator detection is heuristic and third-party providers publish no image
357
- * metadata, so refusing an unknown-but-well-formed reference would make some
358
- * models permanently unreachable. Requiring the provider segment keeps this
359
- * from swallowing plain typos, which still get the "Unknown image model" list.
360
- */
361
- function asExplicitModelRef(raw: string): ImageGenModel | null {
362
- const parts = splitModelRef(raw);
363
- if (!parts) return null;
364
- if (/\s/.test(raw)) return null;
365
- return { id: parts.id, provider: parts.provider, api: "" };
366
- }
367
-
368
- /**
369
- * List vision-capable chat models — those accepting image input.
370
- *
371
- * `Model.input` is `("text" | "image")[]` in pi-ai. Models that do not declare
372
- * the field are excluded rather than assumed capable, so a bad guess never
373
- * produces a confusing API error.
374
- */
375
- export function listVisionModels(registry: ChatModelRegistry): VisionModel[] {
376
- let models: unknown[];
377
- try {
378
- models = registry.getAvailable?.() ?? registry.getAll();
379
- } catch {
380
- return [];
381
- }
382
-
383
- return models.filter(isVisionModel);
384
- }
385
-
386
- export function isVisionModel(model: unknown): model is VisionModel {
387
- if (model === null || typeof model !== "object") return false;
388
- const candidate = model as Partial<VisionModel>;
389
- if (typeof candidate.id !== "string" || typeof candidate.provider !== "string") {
390
- return false;
391
- }
392
- return Array.isArray(candidate.input) && candidate.input.includes("image");
393
- }
394
-
395
- /**
396
- * Active tool names after hiding `recognizeTool` for a vision-capable model.
397
- *
398
- * A model that accepts image input can read images natively (pi's own read
399
- * tool hands it the pixels), so a separate image_recognize round-trip through
400
- * another model only duplicates the ability and burns system-prompt context.
401
- * Text-only models get the tool back. Models that do not declare their input
402
- * modalities are treated as non-vision, matching `isVisionModel`.
403
- */
404
- export function applyRecognizeGating(
405
- active: string[],
406
- model: unknown,
407
- recognizeTool: string,
408
- ): string[] {
409
- const vision = isVisionModel(model);
410
- const present = active.includes(recognizeTool);
411
- // Already correct: hidden for a vision model, or provided for a text-only one.
412
- if (vision !== present) return active;
413
- return vision
414
- ? active.filter((name) => name !== recognizeTool)
415
- : [...active, recognizeTool];
416
- }
417
-
418
- /**
419
- * Resolve a vision-model reference, restricted to image-capable models.
420
- *
421
- * Rejecting a text-only model here gives a much clearer message than letting
422
- * the provider fail on an unexpected image part.
423
- */
424
- export function resolveVisionModel(
425
- input: string,
426
- registry: ChatModelRegistry,
427
- ): VisionModel | string {
428
- const vision = listVisionModels(registry);
429
- const raw = input.trim();
430
-
431
- if (vision.length === 0) {
432
- // Accept an explicit reference so a provider we cannot introspect is still
433
- // usable (mirrors resolveImageGenModel).
434
- const explicit = asExplicitVisionRef(raw);
435
- if (explicit) return explicit;
436
- return (
437
- "No vision-capable models are configured.\n" +
438
- "→ image_recognize needs a model that accepts image input " +
439
- "(e.g. anthropic/claude-sonnet, openai/gpt-5, google/gemini-3-pro).\n" +
440
- "→ Configure one with /model or /unipi:settings."
441
- );
442
- }
443
-
444
- const query = raw.toLowerCase();
445
- if (!query) return "No model specified.";
446
-
447
- const exact = vision.find((m) => formatModelRef(m).toLowerCase() === query);
448
- if (exact) return exact;
449
-
450
- const byId = vision.find((m) => m.id.toLowerCase() === query);
451
- if (byId) return byId;
452
-
453
- let best: VisionModel | undefined;
454
- let bestScore = 0;
455
-
456
- for (const model of vision) {
457
- const id = model.id.toLowerCase();
458
- const full = formatModelRef(model).toLowerCase();
459
- const name = (model.name ?? model.id).toLowerCase();
460
-
461
- let score = 0;
462
- if (id.includes(query) || full.includes(query)) {
463
- score = 60 + (query.length / id.length) * 30;
464
- } else if (name.includes(query)) {
465
- score = 40 + (query.length / name.length) * 20;
466
- }
467
-
468
- if (score > bestScore) {
469
- bestScore = score;
470
- best = model;
471
- }
472
- }
473
-
474
- if (best && bestScore > 0) return best;
475
-
476
- // A known model that simply cannot see gets a targeted message.
477
- let all: unknown[] = [];
478
- try {
479
- all = registry.getAvailable?.() ?? registry.getAll();
480
- } catch {
481
- all = [];
482
- }
483
- const knownButBlind = all.some((m) => {
484
- const candidate = m as Partial<VisionModel>;
485
- if (typeof candidate.id !== "string" || typeof candidate.provider !== "string") {
486
- return false;
487
- }
488
- return (
489
- formatModelRef(candidate as VisionModel).toLowerCase() === query ||
490
- candidate.id.toLowerCase() === query
491
- );
492
- });
493
-
494
- if (knownButBlind) {
495
- return `Model "${input}" does not accept image input. Vision-capable models: ${vision
496
- .slice(0, 5)
497
- .map(formatModelRef)
498
- .join(", ")}`;
499
- }
500
-
501
- // Unknown to the registry, but well-formed — accept it. Checked after
502
- // `knownButBlind` so a registered text-only model still gets the precise
503
- // "does not accept image input" error rather than being waved through.
504
- const explicit = asExplicitVisionRef(raw);
505
- if (explicit) return explicit;
506
-
507
- return (
508
- `Unknown model "${input}".\n` +
509
- `Vision-capable models: ${vision.map(formatModelRef).join(", ")}`
510
- );
511
- }
512
-
513
- /** Accept a well-formed "provider/model-id" the registry does not know. */
514
- function asExplicitVisionRef(raw: string): VisionModel | null {
515
- const parts = splitModelRef(raw);
516
- if (!parts) return null;
517
- if (/\s/.test(raw)) return null;
518
- return { id: parts.id, provider: parts.provider, input: ["text", "image"] };
519
- }