@baique/pi-web-sky 0.1.35 → 0.1.37

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 (483) hide show
  1. package/.next/BUILD_ID +1 -1
  2. package/.next/app-path-routes-manifest.json +11 -11
  3. package/.next/build-manifest.json +4 -4
  4. package/.next/next-server.js.nft.json +1 -1
  5. package/.next/prerender-manifest.json +3 -3
  6. package/.next/react-loadable-manifest.json +2 -2
  7. package/.next/required-server-files.js +6 -6
  8. package/.next/required-server-files.json +6 -6
  9. package/.next/server/app/_global-error/page.js +2 -2
  10. package/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  11. package/.next/server/app/_global-error.html +1 -1
  12. package/.next/server/app/_global-error.rsc +1 -1
  13. package/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  14. package/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  15. package/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  16. package/.next/server/app/_not-found/page.js +1 -1
  17. package/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  18. package/.next/server/app/_not-found.html +1 -1
  19. package/.next/server/app/_not-found.rsc +3 -3
  20. package/.next/server/app/_not-found.segments/_full.segment.rsc +3 -3
  21. package/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +3 -3
  22. package/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  23. package/.next/server/app/api/agent/[id]/bash-output/route.js +2 -2
  24. package/.next/server/app/api/agent/[id]/bash-output/route_client-reference-manifest.js +1 -1
  25. package/.next/server/app/api/agent/[id]/events/route.js +3 -3
  26. package/.next/server/app/api/agent/[id]/events/route_client-reference-manifest.js +1 -1
  27. package/.next/server/app/api/agent/[id]/route.js +2 -2
  28. package/.next/server/app/api/agent/[id]/route_client-reference-manifest.js +1 -1
  29. package/.next/server/app/api/agent/new/route.js +1 -1
  30. package/.next/server/app/api/agent/new/route_client-reference-manifest.js +1 -1
  31. package/.next/server/app/api/agent/running/route.js +1 -1
  32. package/.next/server/app/api/agent/running/route_client-reference-manifest.js +1 -1
  33. package/.next/server/app/api/app-update/route.js +1 -1
  34. package/.next/server/app/api/app-update/route_client-reference-manifest.js +1 -1
  35. package/.next/server/app/api/auth/all-providers/route.js +1 -1
  36. package/.next/server/app/api/auth/all-providers/route_client-reference-manifest.js +1 -1
  37. package/.next/server/app/api/auth/api-key/[provider]/route.js +1 -1
  38. package/.next/server/app/api/auth/api-key/[provider]/route_client-reference-manifest.js +1 -1
  39. package/.next/server/app/api/auth/login/[provider]/route.js +3 -3
  40. package/.next/server/app/api/auth/login/[provider]/route_client-reference-manifest.js +1 -1
  41. package/.next/server/app/api/auth/logout/[provider]/route.js +1 -1
  42. package/.next/server/app/api/auth/logout/[provider]/route_client-reference-manifest.js +1 -1
  43. package/.next/server/app/api/auth/providers/route.js +1 -1
  44. package/.next/server/app/api/auth/providers/route_client-reference-manifest.js +1 -1
  45. package/.next/server/app/api/boards/[id]/canvas/route.js +1 -1
  46. package/.next/server/app/api/boards/[id]/canvas/route_client-reference-manifest.js +1 -1
  47. package/.next/server/app/api/boards/[id]/edges/[eid]/route.js +1 -1
  48. package/.next/server/app/api/boards/[id]/edges/[eid]/route_client-reference-manifest.js +1 -1
  49. package/.next/server/app/api/boards/[id]/edges/route.js +1 -1
  50. package/.next/server/app/api/boards/[id]/edges/route_client-reference-manifest.js +1 -1
  51. package/.next/server/app/api/boards/[id]/nodes/[nid]/route.js +1 -1
  52. package/.next/server/app/api/boards/[id]/nodes/[nid]/route_client-reference-manifest.js +1 -1
  53. package/.next/server/app/api/boards/[id]/nodes/route.js +1 -1
  54. package/.next/server/app/api/boards/[id]/nodes/route_client-reference-manifest.js +1 -1
  55. package/.next/server/app/api/boards/[id]/reconcile-task-edges/route.js +1 -1
  56. package/.next/server/app/api/boards/[id]/reconcile-task-edges/route_client-reference-manifest.js +1 -1
  57. package/.next/server/app/api/boards/[id]/route.js +1 -1
  58. package/.next/server/app/api/boards/[id]/route_client-reference-manifest.js +1 -1
  59. package/.next/server/app/api/boards/purge-orphans/route.js +1 -1
  60. package/.next/server/app/api/boards/purge-orphans/route_client-reference-manifest.js +1 -1
  61. package/.next/server/app/api/boards/reorder/route.js +1 -1
  62. package/.next/server/app/api/boards/reorder/route_client-reference-manifest.js +1 -1
  63. package/.next/server/app/api/boards/route.js +1 -1
  64. package/.next/server/app/api/boards/route_client-reference-manifest.js +1 -1
  65. package/.next/server/app/api/cwd/browse/route.js +1 -1
  66. package/.next/server/app/api/cwd/browse/route_client-reference-manifest.js +1 -1
  67. package/.next/server/app/api/cwd/validate/route.js +2 -2
  68. package/.next/server/app/api/cwd/validate/route_client-reference-manifest.js +1 -1
  69. package/.next/server/app/api/default-cwd/route.js +2 -2
  70. package/.next/server/app/api/default-cwd/route_client-reference-manifest.js +1 -1
  71. package/.next/server/app/api/file-index/route.js +2 -2
  72. package/.next/server/app/api/file-index/route_client-reference-manifest.js +1 -1
  73. package/.next/server/app/api/files/[...path]/route.js +2 -2
  74. package/.next/server/app/api/files/[...path]/route_client-reference-manifest.js +1 -1
  75. package/.next/server/app/api/git/diff/route.js +1 -1
  76. package/.next/server/app/api/git/diff/route_client-reference-manifest.js +1 -1
  77. package/.next/server/app/api/git/status/route.js +1 -1
  78. package/.next/server/app/api/git/status/route_client-reference-manifest.js +1 -1
  79. package/.next/server/app/api/home/route.js +1 -1
  80. package/.next/server/app/api/home/route_client-reference-manifest.js +1 -1
  81. package/.next/server/app/api/mcp/route.js +2 -2
  82. package/.next/server/app/api/mcp/route_client-reference-manifest.js +1 -1
  83. package/.next/server/app/api/models/route.js +2 -2
  84. package/.next/server/app/api/models/route_client-reference-manifest.js +1 -1
  85. package/.next/server/app/api/models-config/catalog/route.js +1 -1
  86. package/.next/server/app/api/models-config/catalog/route_client-reference-manifest.js +1 -1
  87. package/.next/server/app/api/models-config/discover/route.js +1 -1
  88. package/.next/server/app/api/models-config/discover/route_client-reference-manifest.js +1 -1
  89. package/.next/server/app/api/models-config/route.js +1 -1
  90. package/.next/server/app/api/models-config/route_client-reference-manifest.js +1 -1
  91. package/.next/server/app/api/models-config/test/route.js +1 -1
  92. package/.next/server/app/api/models-config/test/route_client-reference-manifest.js +1 -1
  93. package/.next/server/app/api/plugins/route.js +2 -2
  94. package/.next/server/app/api/plugins/route_client-reference-manifest.js +1 -1
  95. package/.next/server/app/api/project-trust/route.js +2 -2
  96. package/.next/server/app/api/project-trust/route_client-reference-manifest.js +1 -1
  97. package/.next/server/app/api/quota/route.js +1 -1
  98. package/.next/server/app/api/quota/route_client-reference-manifest.js +1 -1
  99. package/.next/server/app/api/search/route.js +2 -2
  100. package/.next/server/app/api/search/route_client-reference-manifest.js +1 -1
  101. package/.next/server/app/api/sessions/[id]/auto-name/route.js +3 -3
  102. package/.next/server/app/api/sessions/[id]/auto-name/route_client-reference-manifest.js +1 -1
  103. package/.next/server/app/api/sessions/[id]/context/route.js +2 -2
  104. package/.next/server/app/api/sessions/[id]/context/route_client-reference-manifest.js +1 -1
  105. package/.next/server/app/api/sessions/[id]/entries/[entryId]/thinking/route.js +2 -2
  106. package/.next/server/app/api/sessions/[id]/entries/[entryId]/thinking/route_client-reference-manifest.js +1 -1
  107. package/.next/server/app/api/sessions/[id]/export/route.js +2 -2
  108. package/.next/server/app/api/sessions/[id]/export/route_client-reference-manifest.js +1 -1
  109. package/.next/server/app/api/sessions/[id]/route.js +1 -1
  110. package/.next/server/app/api/sessions/[id]/route_client-reference-manifest.js +1 -1
  111. package/.next/server/app/api/sessions/[id]/state/route.js +2 -2
  112. package/.next/server/app/api/sessions/[id]/state/route_client-reference-manifest.js +1 -1
  113. package/.next/server/app/api/sessions/[id]/todos/route.js +2 -2
  114. package/.next/server/app/api/sessions/[id]/todos/route_client-reference-manifest.js +1 -1
  115. package/.next/server/app/api/sessions/route.js +2 -2
  116. package/.next/server/app/api/sessions/route_client-reference-manifest.js +1 -1
  117. package/.next/server/app/api/skills/check/route.js +1 -1
  118. package/.next/server/app/api/skills/check/route.js.nft.json +1 -1
  119. package/.next/server/app/api/skills/check/route_client-reference-manifest.js +1 -1
  120. package/.next/server/app/api/skills/install/route.js +2 -2
  121. package/.next/server/app/api/skills/install/route_client-reference-manifest.js +1 -1
  122. package/.next/server/app/api/skills/route.js +2 -31
  123. package/.next/server/app/api/skills/route.js.nft.json +1 -1
  124. package/.next/server/app/api/skills/route_client-reference-manifest.js +1 -1
  125. package/.next/server/app/api/skills/search/route.js +1 -1
  126. package/.next/server/app/api/skills/search/route_client-reference-manifest.js +1 -1
  127. package/.next/server/app/api/skills/update/route.js +1 -1
  128. package/.next/server/app/api/skills/update/route.js.nft.json +1 -1
  129. package/.next/server/app/api/skills/update/route_client-reference-manifest.js +1 -1
  130. package/.next/server/app/api/task-card-questions/[id]/answer/route.js +1 -1
  131. package/.next/server/app/api/task-card-questions/[id]/answer/route_client-reference-manifest.js +1 -1
  132. package/.next/server/app/api/task-card-questions/route.js +1 -1
  133. package/.next/server/app/api/task-card-questions/route_client-reference-manifest.js +1 -1
  134. package/.next/server/app/api/task-cards/[id]/route.js +1 -1
  135. package/.next/server/app/api/task-cards/[id]/route_client-reference-manifest.js +1 -1
  136. package/.next/server/app/api/task-cards/route.js +1 -1
  137. package/.next/server/app/api/task-cards/route_client-reference-manifest.js +1 -1
  138. package/.next/server/app/api/task-scheduler/status/route.js +1 -1
  139. package/.next/server/app/api/task-scheduler/status/route_client-reference-manifest.js +1 -1
  140. package/.next/server/app/api/tasks/[id]/board/route.js +1 -1
  141. package/.next/server/app/api/tasks/[id]/board/route_client-reference-manifest.js +1 -1
  142. package/.next/server/app/api/tasks/[id]/route.js +1 -1
  143. package/.next/server/app/api/tasks/[id]/route_client-reference-manifest.js +1 -1
  144. package/.next/server/app/api/tasks/reorder/route.js +1 -1
  145. package/.next/server/app/api/tasks/reorder/route_client-reference-manifest.js +1 -1
  146. package/.next/server/app/api/tasks/route.js +1 -1
  147. package/.next/server/app/api/tasks/route_client-reference-manifest.js +1 -1
  148. package/.next/server/app/api/terminal/[id]/events/route.js +3 -3
  149. package/.next/server/app/api/terminal/[id]/events/route_client-reference-manifest.js +1 -1
  150. package/.next/server/app/api/terminal/[id]/route.js +2 -2
  151. package/.next/server/app/api/terminal/[id]/route_client-reference-manifest.js +1 -1
  152. package/.next/server/app/api/terminal/route.js +2 -2
  153. package/.next/server/app/api/terminal/route_client-reference-manifest.js +1 -1
  154. package/.next/server/app/api/worktrees/route.js +2 -2
  155. package/.next/server/app/api/worktrees/route_client-reference-manifest.js +1 -1
  156. package/.next/server/app/favicon.ico/route.js +1 -1
  157. package/.next/server/app/index.html +1 -1
  158. package/.next/server/app/index.rsc +5 -5
  159. package/.next/server/app/index.segments/__PAGE__.segment.rsc +5 -5
  160. package/.next/server/app/index.segments/_full.segment.rsc +5 -5
  161. package/.next/server/app/index.segments/_tree.segment.rsc +2 -2
  162. package/.next/server/app/manifest.webmanifest/route.js +2 -2
  163. package/.next/server/app/manifest.webmanifest/route_client-reference-manifest.js +1 -1
  164. package/.next/server/app/page.js +3 -3
  165. package/.next/server/app/page_client-reference-manifest.js +1 -1
  166. package/.next/server/app-paths-manifest.json +11 -11
  167. package/.next/server/chunks/2404.js +2 -2
  168. package/.next/server/chunks/2430.js +4 -4
  169. package/.next/server/chunks/3500.js +30 -0
  170. package/.next/server/chunks/4226.js +2 -2
  171. package/.next/server/chunks/542.js +11 -6
  172. package/.next/server/chunks/5794.js +2 -2
  173. package/.next/server/chunks/6429.js +1 -1
  174. package/.next/server/chunks/898.js +2 -2
  175. package/.next/server/instrumentation.js +1 -1
  176. package/.next/server/middleware-build-manifest.js +1 -1
  177. package/.next/server/middleware-react-loadable-manifest.js +1 -1
  178. package/.next/server/middleware.js +3 -3
  179. package/.next/server/next-font-manifest.js +1 -1
  180. package/.next/server/next-font-manifest.json +1 -1
  181. package/.next/server/pages/404.html +1 -1
  182. package/.next/server/pages/500.html +1 -1
  183. package/.next/server/server-reference-manifest.json +1 -1
  184. package/.next/static/chunks/5315.dba7515ae6e139ae.js +4 -0
  185. package/.next/static/chunks/{7999.f4ab221861378788.js → 7999.879d5a20e8641752.js} +1 -1
  186. package/.next/static/chunks/app/_not-found/{page-af4fe23994cb3d66.js → page-8cea8d8de35f4da4.js} +1 -1
  187. package/.next/static/chunks/app/{layout-1d9a7a44fbc92d2e.js → layout-c4ca28ccc08bc975.js} +1 -1
  188. package/.next/static/chunks/app/{page-6d6779a81a0a7639.js → page-c9ead38525a85686.js} +3 -3
  189. package/.next/static/chunks/{main-app-b3e8a8d15d83e1f0.js → main-app-dd2385a1a5a588df.js} +1 -1
  190. package/.next/static/chunks/next/dist/client/components/builtin/global-error-be943dcb89867ab9.js +1 -0
  191. package/.next/static/chunks/{webpack-fa4f40085b1ec1ac.js → webpack-4bbf04891e910713.js} +1 -1
  192. package/.next/static/css/422c7dc0faa04d89.css +1 -0
  193. package/.next/trace +8 -9
  194. package/.next/trace-build +1 -1
  195. package/.next/types/app/api/agent/[id]/bash-output/route.ts +1 -1
  196. package/.next/types/app/api/agent/[id]/events/route.ts +1 -1
  197. package/.next/types/app/api/agent/[id]/route.ts +1 -1
  198. package/.next/types/app/api/agent/new/route.ts +1 -1
  199. package/.next/types/app/api/agent/running/route.ts +1 -1
  200. package/.next/types/app/api/app-update/route.ts +1 -1
  201. package/.next/types/app/api/auth/all-providers/route.ts +1 -1
  202. package/.next/types/app/api/auth/api-key/[provider]/route.ts +1 -1
  203. package/.next/types/app/api/auth/login/[provider]/route.ts +1 -1
  204. package/.next/types/app/api/auth/logout/[provider]/route.ts +1 -1
  205. package/.next/types/app/api/auth/providers/route.ts +1 -1
  206. package/.next/types/app/api/boards/[id]/canvas/route.ts +1 -1
  207. package/.next/types/app/api/boards/[id]/edges/[eid]/route.ts +1 -1
  208. package/.next/types/app/api/boards/[id]/edges/route.ts +1 -1
  209. package/.next/types/app/api/boards/[id]/nodes/[nid]/route.ts +1 -1
  210. package/.next/types/app/api/boards/[id]/nodes/route.ts +1 -1
  211. package/.next/types/app/api/boards/[id]/reconcile-task-edges/route.ts +1 -1
  212. package/.next/types/app/api/boards/[id]/route.ts +1 -1
  213. package/.next/types/app/api/boards/purge-orphans/route.ts +1 -1
  214. package/.next/types/app/api/boards/reorder/route.ts +1 -1
  215. package/.next/types/app/api/boards/route.ts +1 -1
  216. package/.next/types/app/api/cwd/browse/route.ts +1 -1
  217. package/.next/types/app/api/cwd/validate/route.ts +1 -1
  218. package/.next/types/app/api/default-cwd/route.ts +1 -1
  219. package/.next/types/app/api/file-index/route.ts +1 -1
  220. package/.next/types/app/api/files/[...path]/route.ts +1 -1
  221. package/.next/types/app/api/git/diff/route.ts +1 -1
  222. package/.next/types/app/api/git/status/route.ts +1 -1
  223. package/.next/types/app/api/home/route.ts +1 -1
  224. package/.next/types/app/api/mcp/route.ts +1 -1
  225. package/.next/types/app/api/models/route.ts +1 -1
  226. package/.next/types/app/api/models-config/catalog/route.ts +1 -1
  227. package/.next/types/app/api/models-config/discover/route.ts +1 -1
  228. package/.next/types/app/api/models-config/route.ts +1 -1
  229. package/.next/types/app/api/models-config/test/route.ts +1 -1
  230. package/.next/types/app/api/plugins/route.ts +1 -1
  231. package/.next/types/app/api/project-trust/route.ts +1 -1
  232. package/.next/types/app/api/quota/route.ts +1 -1
  233. package/.next/types/app/api/search/route.ts +1 -1
  234. package/.next/types/app/api/sessions/[id]/auto-name/route.ts +1 -1
  235. package/.next/types/app/api/sessions/[id]/context/route.ts +1 -1
  236. package/.next/types/app/api/sessions/[id]/entries/[entryId]/thinking/route.ts +1 -1
  237. package/.next/types/app/api/sessions/[id]/export/route.ts +1 -1
  238. package/.next/types/app/api/sessions/[id]/route.ts +1 -1
  239. package/.next/types/app/api/sessions/[id]/state/route.ts +1 -1
  240. package/.next/types/app/api/sessions/[id]/todos/route.ts +1 -1
  241. package/.next/types/app/api/sessions/route.ts +1 -1
  242. package/.next/types/app/api/skills/check/route.ts +1 -1
  243. package/.next/types/app/api/skills/install/route.ts +1 -1
  244. package/.next/types/app/api/skills/route.ts +1 -1
  245. package/.next/types/app/api/skills/search/route.ts +1 -1
  246. package/.next/types/app/api/skills/update/route.ts +1 -1
  247. package/.next/types/app/api/task-card-questions/[id]/answer/route.ts +1 -1
  248. package/.next/types/app/api/task-card-questions/route.ts +1 -1
  249. package/.next/types/app/api/task-cards/[id]/route.ts +1 -1
  250. package/.next/types/app/api/task-cards/route.ts +1 -1
  251. package/.next/types/app/api/task-scheduler/status/route.ts +1 -1
  252. package/.next/types/app/api/tasks/[id]/board/route.ts +1 -1
  253. package/.next/types/app/api/tasks/[id]/route.ts +1 -1
  254. package/.next/types/app/api/tasks/reorder/route.ts +1 -1
  255. package/.next/types/app/api/tasks/route.ts +1 -1
  256. package/.next/types/app/api/terminal/[id]/events/route.ts +1 -1
  257. package/.next/types/app/api/terminal/[id]/route.ts +1 -1
  258. package/.next/types/app/api/terminal/route.ts +1 -1
  259. package/.next/types/app/api/worktrees/route.ts +1 -1
  260. package/.next/types/app/layout.ts +1 -1
  261. package/.next/types/app/page.ts +1 -1
  262. package/bin/pi-web-sky.js +15 -20
  263. package/lib/agent-client.test.mjs +64 -0
  264. package/lib/agent-client.ts +53 -0
  265. package/lib/agent-event-connection.test.mjs +193 -0
  266. package/lib/agent-event-connection.ts +202 -0
  267. package/lib/agent-event-stream.test.mjs +155 -0
  268. package/lib/agent-event-stream.ts +132 -0
  269. package/lib/agent-event-wire.test.mjs +278 -0
  270. package/lib/agent-event-wire.ts +107 -0
  271. package/lib/agent-phase.ts +24 -0
  272. package/lib/allowed-roots.ts +31 -0
  273. package/lib/ansi.test.mjs +52 -0
  274. package/lib/ansi.ts +211 -0
  275. package/lib/api-types.ts +139 -0
  276. package/lib/app-update.test.mjs +27 -0
  277. package/lib/app-update.ts +28 -0
  278. package/lib/atomic-file.test.mjs +37 -0
  279. package/lib/atomic-file.ts +34 -0
  280. package/lib/audit-session.ts +235 -0
  281. package/lib/bash-output.test.mjs +63 -0
  282. package/lib/bash-output.ts +53 -0
  283. package/lib/bg-image.ts +129 -0
  284. package/lib/board-events.ts +83 -0
  285. package/lib/board-purge.test.mjs +66 -0
  286. package/lib/board-purge.ts +65 -0
  287. package/lib/board-search.test.mjs +110 -0
  288. package/lib/board-search.ts +63 -0
  289. package/lib/board-store.test.mjs +542 -0
  290. package/lib/board-store.ts +794 -0
  291. package/lib/board-types.ts +96 -0
  292. package/lib/board-utils.ts +17 -0
  293. package/lib/bounded-form-data.test.mjs +54 -0
  294. package/lib/bounded-form-data.ts +49 -0
  295. package/lib/browser-notifications.test.mjs +167 -0
  296. package/lib/browser-notifications.ts +104 -0
  297. package/lib/chat-lazy-load.test.mjs +140 -0
  298. package/lib/chat-lazy-load.ts +64 -0
  299. package/lib/clipboard-paths.test.mjs +140 -0
  300. package/lib/clipboard-paths.ts +192 -0
  301. package/lib/clipboard.ts +18 -0
  302. package/lib/compaction-summary.test.mjs +44 -0
  303. package/lib/compaction-summary.ts +34 -0
  304. package/lib/custom-ui-terminal.test.mjs +32 -0
  305. package/lib/custom-ui-terminal.ts +27 -0
  306. package/lib/deepseek-pricing.test.mjs +120 -0
  307. package/lib/deepseek-pricing.ts +71 -0
  308. package/lib/directory-browser.test.mjs +60 -0
  309. package/lib/directory-browser.ts +83 -0
  310. package/lib/draft-stash.test.mjs +91 -0
  311. package/lib/draft-stash.ts +102 -0
  312. package/lib/draft-store.ts +105 -0
  313. package/lib/dropdown-direction.ts +27 -0
  314. package/lib/extension-command.test.mjs +63 -0
  315. package/lib/extension-command.ts +81 -0
  316. package/lib/file-access.test.mjs +31 -0
  317. package/lib/file-access.ts +59 -0
  318. package/lib/file-dirent.test.mjs +56 -0
  319. package/lib/file-dirent.ts +17 -0
  320. package/lib/file-explorer-state.test.mjs +63 -0
  321. package/lib/file-explorer-state.ts +36 -0
  322. package/lib/file-fuzzy.test.mjs +29 -0
  323. package/lib/file-fuzzy.ts +189 -0
  324. package/lib/file-links.test.mjs +92 -0
  325. package/lib/file-links.ts +148 -0
  326. package/lib/file-paths.ts +43 -0
  327. package/lib/file-types.test.mjs +34 -0
  328. package/lib/file-types.ts +73 -0
  329. package/lib/file-upload.test.mjs +70 -0
  330. package/lib/file-upload.ts +59 -0
  331. package/lib/file-viewer-state.test.mjs +20 -0
  332. package/lib/file-viewer-state.ts +15 -0
  333. package/lib/frontmatter.test.mjs +55 -0
  334. package/lib/frontmatter.ts +87 -0
  335. package/lib/git-changes.test.mjs +52 -0
  336. package/lib/git-changes.ts +232 -0
  337. package/lib/git-status.ts +47 -0
  338. package/lib/git-types.ts +29 -0
  339. package/lib/http-dispatcher.test.mjs +83 -0
  340. package/lib/http-dispatcher.ts +86 -0
  341. package/lib/i18n/format.test.mjs +19 -0
  342. package/lib/i18n/format.ts +60 -0
  343. package/lib/i18n/messages/en.ts +662 -0
  344. package/lib/i18n/messages/zh-CN.ts +661 -0
  345. package/lib/i18n/registry.test.mjs +32 -0
  346. package/lib/i18n/registry.ts +43 -0
  347. package/lib/i18n/types.ts +15 -0
  348. package/lib/image-attachments.test.mjs +27 -0
  349. package/lib/image-attachments.ts +56 -0
  350. package/lib/initial-navigation.test.mjs +65 -0
  351. package/lib/initial-navigation.ts +15 -0
  352. package/lib/markdown.test.mjs +104 -0
  353. package/lib/markdown.ts +347 -0
  354. package/lib/message-display.test.mjs +130 -0
  355. package/lib/message-display.ts +47 -0
  356. package/lib/model-catalog.test.mjs +327 -0
  357. package/lib/model-catalog.ts +414 -0
  358. package/lib/model-discovery-auth.ts +58 -0
  359. package/lib/model-discovery.test.mjs +50 -0
  360. package/lib/model-discovery.ts +75 -0
  361. package/lib/model-id-preference.ts +46 -0
  362. package/lib/model-scope.test.mjs +193 -0
  363. package/lib/model-scope.ts +198 -0
  364. package/lib/models-cache.test.mjs +122 -0
  365. package/lib/models-cache.ts +90 -0
  366. package/lib/models-config-store.test.mjs +200 -0
  367. package/lib/models-config-store.ts +84 -0
  368. package/lib/next-config-esm.test.mjs +14 -0
  369. package/lib/next-config.test.mjs +13 -0
  370. package/lib/node-version.test.mjs +33 -0
  371. package/lib/normalize.test.mjs +54 -0
  372. package/lib/normalize.ts +63 -0
  373. package/lib/npx.ts +62 -0
  374. package/lib/panel-layout.test.mjs +56 -0
  375. package/lib/panel-layout.ts +52 -0
  376. package/lib/patch.test.mjs +110 -0
  377. package/lib/patch.ts +134 -0
  378. package/lib/path-security.ts +42 -0
  379. package/lib/paths.test.mjs +58 -0
  380. package/lib/paths.ts +71 -0
  381. package/lib/pi-types.ts +183 -0
  382. package/lib/pi-web-options.test.mjs +63 -0
  383. package/lib/process-lifecycle.test.mjs +83 -0
  384. package/lib/project-command-env.test.mjs +298 -0
  385. package/lib/project-command-env.ts +129 -0
  386. package/lib/project-groups.test.mjs +60 -0
  387. package/lib/project-groups.ts +53 -0
  388. package/lib/project-identity.test.mjs +37 -0
  389. package/lib/project-identity.ts +26 -0
  390. package/lib/project-tree.test.mjs +115 -0
  391. package/lib/project-tree.ts +176 -0
  392. package/lib/project-trust.test.mjs +117 -0
  393. package/lib/project-trust.ts +48 -0
  394. package/lib/prompt-recovery.test.mjs +33 -0
  395. package/lib/prompt-recovery.ts +47 -0
  396. package/lib/provider-api-key-route.test.mjs +12 -0
  397. package/lib/provider-credential-store.test.mjs +126 -0
  398. package/lib/provider-credential-store.ts +114 -0
  399. package/lib/provider-listing-runtime.ts +37 -0
  400. package/lib/provider-listing.test.mjs +114 -0
  401. package/lib/provider-listing.ts +118 -0
  402. package/lib/provider-services.ts +64 -0
  403. package/lib/request-security.test.mjs +256 -0
  404. package/lib/request-security.ts +136 -0
  405. package/lib/rpc-manager-shutdown.test.mjs +372 -0
  406. package/lib/rpc-manager-widgets.test.mjs +342 -0
  407. package/lib/rpc-manager.test.mjs +139 -0
  408. package/lib/rpc-manager.ts +1703 -0
  409. package/lib/rpc-session-info.test.mjs +93 -0
  410. package/lib/session-delete.test.mjs +53 -0
  411. package/lib/session-delete.ts +86 -0
  412. package/lib/session-file-references-core.ts +82 -0
  413. package/lib/session-file-references.test.mjs +91 -0
  414. package/lib/session-file-references.ts +29 -0
  415. package/lib/session-path.test.mjs +24 -0
  416. package/lib/session-path.ts +11 -0
  417. package/lib/session-reader.pagination.test.mjs +115 -0
  418. package/lib/session-reader.test.mjs +446 -0
  419. package/lib/session-reader.ts +548 -0
  420. package/lib/session-resolve.test.mjs +65 -0
  421. package/lib/session-row-context-menu.test.mjs +46 -0
  422. package/lib/session-row-context-menu.ts +29 -0
  423. package/lib/session-scanner.test.mjs +151 -0
  424. package/lib/session-scanner.ts +238 -0
  425. package/lib/session-search.test.mjs +173 -0
  426. package/lib/session-search.ts +284 -0
  427. package/lib/session-stats.ts +26 -0
  428. package/lib/session-timing.test.mjs +107 -0
  429. package/lib/session-timing.ts +45 -0
  430. package/lib/session-title.test.mjs +284 -0
  431. package/lib/session-title.ts +255 -0
  432. package/lib/skill-frontmatter.test.mjs +75 -0
  433. package/lib/skill-frontmatter.ts +40 -0
  434. package/lib/skill-lock.test.mjs +182 -0
  435. package/lib/skill-lock.ts +146 -0
  436. package/lib/skill-updates.test.mjs +182 -0
  437. package/lib/skill-updates.ts +263 -0
  438. package/lib/skills-service.ts +16 -0
  439. package/lib/slash-display.test.mjs +122 -0
  440. package/lib/slash-display.ts +30 -0
  441. package/lib/sqlite-db.test.mjs +140 -0
  442. package/lib/sqlite-db.ts +205 -0
  443. package/lib/startup-preferences.test.mjs +130 -0
  444. package/lib/startup-preferences.ts +65 -0
  445. package/lib/streaming-message.test.mjs +212 -0
  446. package/lib/streaming-message.ts +151 -0
  447. package/lib/subagent-widget.test.mjs +87 -0
  448. package/lib/subagent-widget.ts +109 -0
  449. package/lib/sync-room-server.mjs +137 -0
  450. package/lib/task-card-store.test.mjs +309 -0
  451. package/lib/task-card-store.ts +559 -0
  452. package/lib/task-scheduler.ts +498 -0
  453. package/lib/task-store.test.mjs +216 -0
  454. package/lib/task-store.ts +271 -0
  455. package/lib/terminal-input.test.mjs +46 -0
  456. package/lib/terminal-input.ts +63 -0
  457. package/lib/terminal-manager.test.mjs +71 -0
  458. package/lib/terminal-manager.ts +245 -0
  459. package/lib/tool-execution-progress.test.mjs +32 -0
  460. package/lib/tool-execution-progress.ts +29 -0
  461. package/lib/tool-names.ts +25 -0
  462. package/lib/tool-preset-preference.test.mjs +72 -0
  463. package/lib/tool-preset-preference.ts +41 -0
  464. package/lib/tool-presets.test.mjs +56 -0
  465. package/lib/tool-presets.ts +42 -0
  466. package/lib/turn-written-files.test.mjs +137 -0
  467. package/lib/turn-written-files.ts +60 -0
  468. package/lib/types.ts +349 -0
  469. package/lib/wallpaper-settings.ts +191 -0
  470. package/lib/web-auth.test.mjs +50 -0
  471. package/lib/web-auth.ts +45 -0
  472. package/lib/workspace-memory.test.mjs +129 -0
  473. package/lib/workspace-memory.ts +98 -0
  474. package/lib/worktree.test.mjs +50 -0
  475. package/lib/worktree.ts +250 -0
  476. package/package.json +3 -1
  477. package/server.mjs +34 -0
  478. package/.next/server/chunks/7513.js +0 -30
  479. package/.next/static/chunks/5315.03de3c6abd8b283e.js +0 -4
  480. package/.next/static/chunks/next/dist/client/components/builtin/global-error-00a4badaccb388e6.js +0 -1
  481. package/.next/static/css/cf25164cb7c04d95.css +0 -1
  482. /package/.next/static/{Ud6Y2ONkGuPVFbo1xZQvk → 7sDSz-wW-RWYnxh1l6-fD}/_buildManifest.js +0 -0
  483. /package/.next/static/{Ud6Y2ONkGuPVFbo1xZQvk → 7sDSz-wW-RWYnxh1l6-fD}/_ssgManifest.js +0 -0
@@ -0,0 +1,193 @@
1
+ import assert from "node:assert/strict";
2
+ import test from "node:test";
3
+
4
+ async function loadSubject() {
5
+ try {
6
+ const { createJiti } = await import("jiti");
7
+ return createJiti(import.meta.url).import("./model-scope.ts");
8
+ } catch {
9
+ return import("./model-scope.ts");
10
+ }
11
+ }
12
+
13
+ const { resolveVisibleModels, selectInitialModelScope } = await loadSubject();
14
+
15
+ const MODELS = [
16
+ { id: "claude-opus-5", provider: "anthropic", name: "Claude Opus 5" },
17
+ { id: "claude-sonnet-5", provider: "anthropic", name: "Claude Sonnet 5" },
18
+ { id: "claude-sonnet-4-6", provider: "anthropic", name: "Claude Sonnet 4.6" },
19
+ { id: "claude-opus-4-8", provider: "acme-gateway", name: "Claude Opus 4.8 (Acme)" },
20
+ { id: "claude-sonnet-5", provider: "acme-gateway", name: "Claude Sonnet 5 (Acme)" },
21
+ { id: "gpt-5.6-sol", provider: "acme-gateway-openai", name: "GPT-5.6 (Acme)" },
22
+ ];
23
+
24
+ const runtime = { getAvailable: async () => MODELS };
25
+
26
+ const refs = (result) => result.visible.map((m) => `${m.provider}/${m.id}`);
27
+
28
+ test("returns every available model when no patterns are configured", async () => {
29
+ for (const patterns of [undefined, [], ["", " "]]) {
30
+ const result = await resolveVisibleModels(runtime, patterns);
31
+ assert.deepEqual(refs(result), MODELS.map((m) => `${m.provider}/${m.id}`));
32
+ assert.deepEqual(result.scopedModels, []);
33
+ assert.deepEqual(result.warnings, []);
34
+ }
35
+ });
36
+
37
+ test("expands provider globs alongside exact references (#307)", async () => {
38
+ const result = await resolveVisibleModels(runtime, [
39
+ "anthropic/claude-sonnet-5",
40
+ "anthropic/claude-opus-5",
41
+ "acme-gateway/*",
42
+ "acme-gateway-openai/*",
43
+ ]);
44
+
45
+ assert.deepEqual(refs(result).sort(), [
46
+ "acme-gateway-openai/gpt-5.6-sol",
47
+ "acme-gateway/claude-opus-4-8",
48
+ "acme-gateway/claude-sonnet-5",
49
+ "anthropic/claude-opus-5",
50
+ "anthropic/claude-sonnet-5",
51
+ ]);
52
+ assert.deepEqual(result.warnings, []);
53
+ });
54
+
55
+ test("matches bare model id globs across providers without duplicates", async () => {
56
+ const result = await resolveVisibleModels(runtime, ["*sonnet*"]);
57
+
58
+ assert.deepEqual(refs(result).sort(), [
59
+ "acme-gateway/claude-sonnet-5",
60
+ "anthropic/claude-sonnet-4-6",
61
+ "anthropic/claude-sonnet-5",
62
+ ]);
63
+ });
64
+
65
+ test("rejects an ambiguous bare exact model id before fuzzy matching", async () => {
66
+ await assert.rejects(
67
+ resolveVisibleModels(runtime, ["claude-sonnet-5"]),
68
+ /Ambiguous enabledModels entry.*acme-gateway\/claude-sonnet-5.*anthropic\/claude-sonnet-5.*provider\/modelId/,
69
+ );
70
+ });
71
+
72
+ test("accepts provider-qualified and unique bare exact model ids", async () => {
73
+ const qualified = await resolveVisibleModels(runtime, ["acme-gateway/claude-sonnet-5"]);
74
+ assert.deepEqual(refs(qualified), ["acme-gateway/claude-sonnet-5"]);
75
+
76
+ const unique = await resolveVisibleModels(runtime, ["gpt-5.6-sol"]);
77
+ assert.deepEqual(refs(unique), ["acme-gateway-openai/gpt-5.6-sol"]);
78
+ });
79
+
80
+ test("rejects an ambiguous bare exact model id with a thinking suffix", async () => {
81
+ await assert.rejects(
82
+ resolveVisibleModels(runtime, ["claude-sonnet-5:high"]),
83
+ /Ambiguous enabledModels entry/,
84
+ );
85
+ });
86
+
87
+ test("keeps thinking-level suffixes out of the matched reference and reports them as pins", async () => {
88
+ const pinned = await resolveVisibleModels(runtime, ["anthropic/*:high"]);
89
+ assert.deepEqual(refs(pinned).sort(), [
90
+ "anthropic/claude-opus-5",
91
+ "anthropic/claude-sonnet-4-6",
92
+ "anthropic/claude-sonnet-5",
93
+ ]);
94
+ assert.deepEqual(pinned.thinkingLevelPins, {
95
+ "anthropic/claude-opus-5": "high",
96
+ "anthropic/claude-sonnet-5": "high",
97
+ "anthropic/claude-sonnet-4-6": "high",
98
+ });
99
+
100
+ const single = await resolveVisibleModels(runtime, ["acme-gateway/claude-opus-4-8:low"]);
101
+ assert.deepEqual(refs(single), ["acme-gateway/claude-opus-4-8"]);
102
+ assert.deepEqual(single.thinkingLevelPins, { "acme-gateway/claude-opus-4-8": "low" });
103
+ });
104
+
105
+ test("leaves models without a pinned thinking level unpinned", async () => {
106
+ const result = await resolveVisibleModels(runtime, ["anthropic/claude-opus-5:high", "acme-gateway/*"]);
107
+
108
+ assert.deepEqual(result.thinkingLevelPins, { "anthropic/claude-opus-5": "high" });
109
+ });
110
+
111
+ test("reports patterns that match nothing but keeps the models that matched", async () => {
112
+ const result = await resolveVisibleModels(runtime, ["anthropic/claude-opus-5", "ghost-gateway/*"]);
113
+
114
+ assert.deepEqual(refs(result), ["anthropic/claude-opus-5"]);
115
+ assert.equal(result.warnings.length, 1);
116
+ assert.match(result.warnings[0], /ghost-gateway\/\*/);
117
+ });
118
+
119
+ test("falls back to all available models when nothing matches at all", async () => {
120
+ const result = await resolveVisibleModels(runtime, ["ghost-gateway/*"]);
121
+
122
+ assert.deepEqual(refs(result), MODELS.map((m) => `${m.provider}/${m.id}`));
123
+ assert.equal(result.warnings.length, 1);
124
+ assert.deepEqual(result.scopedModels, []);
125
+ assert.deepEqual(result.thinkingLevelPins, {});
126
+ });
127
+
128
+ test("selects the saved scoped default and applies its thinking pin", async () => {
129
+ const scope = await resolveVisibleModels(runtime, [
130
+ "anthropic/claude-opus-5:high",
131
+ "acme-gateway/*:low",
132
+ ]);
133
+ const initial = selectInitialModelScope(scope, {
134
+ defaultModel: { provider: "acme-gateway", modelId: "claude-sonnet-5" },
135
+ });
136
+
137
+ assert.equal(`${initial.model.provider}/${initial.model.id}`, "acme-gateway/claude-sonnet-5");
138
+ assert.equal(initial.thinkingLevel, "low");
139
+ assert.equal(initial.scopedModels.length, 3);
140
+ });
141
+
142
+ test("uses the saved default without creating a synthetic scope when unconfigured", async () => {
143
+ const scope = await resolveVisibleModels(runtime, undefined);
144
+ const initial = selectInitialModelScope(scope, {
145
+ defaultModel: { provider: "acme-gateway-openai", modelId: "gpt-5.6-sol" },
146
+ });
147
+
148
+ assert.equal(`${initial.model.provider}/${initial.model.id}`, "acme-gateway-openai/gpt-5.6-sol");
149
+ assert.equal(initial.thinkingLevel, undefined);
150
+ assert.deepEqual(initial.scopedModels, []);
151
+ });
152
+
153
+ test("uses resolver order when the saved default is outside the enabled scope", async () => {
154
+ const scope = await resolveVisibleModels(runtime, [
155
+ "anthropic/claude-opus-5:high",
156
+ "anthropic/claude-sonnet-5:low",
157
+ ]);
158
+ const initial = selectInitialModelScope(scope, {
159
+ defaultModel: { provider: "acme-gateway-openai", modelId: "gpt-5.6-sol" },
160
+ });
161
+
162
+ assert.equal(`${initial.model.provider}/${initial.model.id}`, "anthropic/claude-opus-5");
163
+ assert.equal(initial.thinkingLevel, "high");
164
+ });
165
+
166
+ test("applies a requested scoped model pin unless thinking was explicitly overridden", async () => {
167
+ const scope = await resolveVisibleModels(runtime, ["anthropic/*:high"]);
168
+ const requestedModel = { provider: "anthropic", modelId: "claude-sonnet-5" };
169
+
170
+ assert.equal(
171
+ selectInitialModelScope(scope, { requestedModel }).thinkingLevel,
172
+ "high",
173
+ );
174
+ assert.equal(
175
+ selectInitialModelScope(scope, { requestedModel, thinkingLevel: "low" }).thinkingLevel,
176
+ "low",
177
+ );
178
+ assert.equal(
179
+ selectInitialModelScope(scope, { requestedModel, thinkingLevel: "off" }).thinkingLevel,
180
+ "off",
181
+ );
182
+ });
183
+
184
+ test("rejects an explicit model outside the enabled scope", async () => {
185
+ const scope = await resolveVisibleModels(runtime, ["anthropic/*"]);
186
+
187
+ assert.throws(
188
+ () => selectInitialModelScope(scope, {
189
+ requestedModel: { provider: "acme-gateway", modelId: "claude-sonnet-5" },
190
+ }),
191
+ /not available in the enabled scope/,
192
+ );
193
+ });
@@ -0,0 +1,198 @@
1
+ import type { ThinkingLevel } from "@earendil-works/pi-agent-core";
2
+ import {
3
+ resolveModelScopeWithDiagnostics,
4
+ type ModelRuntime,
5
+ type ScopedModel,
6
+ } from "@earendil-works/pi-coding-agent";
7
+ import type { Api, Model } from "@earendil-works/pi-ai";
8
+
9
+ const THINKING_LEVEL_SUFFIXES = new Set<ThinkingLevel>([
10
+ "off",
11
+ "minimal",
12
+ "low",
13
+ "medium",
14
+ "high",
15
+ "xhigh",
16
+ "max",
17
+ ]);
18
+
19
+ /**
20
+ * Model scoping shared by the UI selector and AgentSession startup.
21
+ *
22
+ * The `enabledModels` setting uses the same syntax as pi's `--models` flag:
23
+ * globs matched with minimatch against `provider/modelId` or a bare `modelId`,
24
+ * fuzzy matching for non-glob patterns, plus an optional `:thinkingLevel` suffix
25
+ * (`anthropic/*:high`). Exact string comparison silently drops every model
26
+ * behind a pattern like `my-gateway/*` (#307), so delegate to pi's own resolver
27
+ * instead of reimplementing the matching rules here.
28
+ */
29
+
30
+ export interface ModelScopeResult {
31
+ /** Models the UI should offer, in resolver order (all available when unscoped). */
32
+ visible: readonly Model<Api>[];
33
+ /** SDK-native scope retained for AgentSession model cycling and extensions. */
34
+ scopedModels: readonly ScopedModel[];
35
+ /** `provider/modelId` → thinking level pinned with a `:level` pattern suffix. */
36
+ thinkingLevelPins: Record<string, string>;
37
+ /** Resolver diagnostics, e.g. a pattern that matched no model. */
38
+ warnings: string[];
39
+ }
40
+
41
+ export interface InitialModelScopeOptions {
42
+ requestedModel?: { provider: string; modelId: string };
43
+ defaultModel?: { provider: string; modelId: string };
44
+ thinkingLevel?: ThinkingLevel;
45
+ }
46
+
47
+ export interface InitialModelScopeResult {
48
+ model?: Model<Api>;
49
+ thinkingLevel?: ThinkingLevel;
50
+ scopedModels: ScopedModel[];
51
+ }
52
+
53
+ function matchesModel(
54
+ model: { provider: string; id: string },
55
+ ref: { provider: string; modelId: string },
56
+ ): boolean {
57
+ return model.provider === ref.provider && model.id === ref.modelId;
58
+ }
59
+
60
+ function hasGlob(pattern: string): boolean {
61
+ return pattern.includes("*") || pattern.includes("?") || pattern.includes("[");
62
+ }
63
+
64
+ function exactReferenceMatches(pattern: string, models: readonly Model<Api>[]): Model<Api>[] {
65
+ const normalized = pattern.toLowerCase();
66
+ const canonical = models.filter(
67
+ (model) => `${model.provider}/${model.id}`.toLowerCase() === normalized,
68
+ );
69
+ if (canonical.length > 0) return canonical;
70
+ return models.filter((model) => model.id.toLowerCase() === normalized);
71
+ }
72
+
73
+ function assertNoAmbiguousExactPatterns(
74
+ patterns: readonly string[],
75
+ models: readonly Model<Api>[],
76
+ ): void {
77
+ for (const pattern of patterns) {
78
+ if (hasGlob(pattern)) continue;
79
+
80
+ let matches = exactReferenceMatches(pattern, models);
81
+ if (matches.length === 0) {
82
+ const colonIndex = pattern.lastIndexOf(":");
83
+ const suffix = colonIndex >= 0 ? pattern.slice(colonIndex + 1) : "";
84
+ if (THINKING_LEVEL_SUFFIXES.has(suffix as ThinkingLevel)) {
85
+ matches = exactReferenceMatches(pattern.slice(0, colonIndex), models);
86
+ }
87
+ }
88
+
89
+ if (matches.length > 1) {
90
+ const references = matches
91
+ .map((model) => `${model.provider}/${model.id}`)
92
+ .sort()
93
+ .join(", ");
94
+ throw new Error(
95
+ `Ambiguous enabledModels entry "${pattern}" matches multiple models: ${references}. Use provider/modelId.`,
96
+ );
97
+ }
98
+ }
99
+ }
100
+
101
+ /**
102
+ * Resolve the visible model list for `patterns`.
103
+ *
104
+ * Falls back to every available model when no patterns are configured or when
105
+ * the patterns resolve to nothing, so a stale or typo'd setting can never leave
106
+ * the UI without any selectable model.
107
+ */
108
+ export async function resolveVisibleModels(
109
+ modelRuntime: ModelRuntime,
110
+ patterns: string[] | undefined,
111
+ ): Promise<ModelScopeResult> {
112
+ const cleaned = (patterns ?? []).map((pattern) => pattern.trim()).filter(Boolean);
113
+ if (cleaned.length === 0) {
114
+ return {
115
+ visible: await modelRuntime.getAvailable(),
116
+ scopedModels: [],
117
+ thinkingLevelPins: {},
118
+ warnings: [],
119
+ };
120
+ }
121
+
122
+ const available = await modelRuntime.getAvailable();
123
+ assertNoAmbiguousExactPatterns(cleaned, available);
124
+ const snapshotRuntime = {
125
+ getAvailable: async () => available,
126
+ } as ModelRuntime;
127
+ const { scopedModels, diagnostics } = await resolveModelScopeWithDiagnostics(cleaned, snapshotRuntime);
128
+ const warnings = diagnostics.map((diagnostic) => diagnostic.message);
129
+ if (scopedModels.length === 0) {
130
+ return {
131
+ visible: available,
132
+ scopedModels: [],
133
+ thinkingLevelPins: {},
134
+ warnings,
135
+ };
136
+ }
137
+
138
+ // `anthropic/*:high` pins a thinking level on every model the glob matched.
139
+ // pi applies the pin of the model a new session starts with; report them all
140
+ // so the client can look up whichever model it pre-selects.
141
+ const thinkingLevelPins: Record<string, string> = {};
142
+ for (const scoped of scopedModels) {
143
+ if (scoped.thinkingLevel) {
144
+ thinkingLevelPins[`${scoped.model.provider}/${scoped.model.id}`] = scoped.thinkingLevel;
145
+ }
146
+ }
147
+ return {
148
+ visible: scopedModels.map((scoped) => scoped.model),
149
+ scopedModels,
150
+ thinkingLevelPins,
151
+ warnings,
152
+ };
153
+ }
154
+
155
+ /**
156
+ * Select the model and thinking level used to create a new AgentSession.
157
+ *
158
+ * This mirrors pi's startup rule: prefer an explicit selection, otherwise use
159
+ * the saved default when it is in scope, then the first resolver-ordered model.
160
+ * A scoped-model thinking pin is applied unless the caller supplied an explicit
161
+ * thinking level.
162
+ */
163
+ export function selectInitialModelScope(
164
+ scope: ModelScopeResult,
165
+ options: InitialModelScopeOptions = {},
166
+ ): InitialModelScopeResult {
167
+ const requestedRef = options.requestedModel;
168
+ const defaultRef = options.defaultModel;
169
+ const requested = requestedRef
170
+ ? scope.visible.find((model) => matchesModel(model, requestedRef))
171
+ : undefined;
172
+ if (requestedRef && !requested) {
173
+ throw new Error(
174
+ `Model is not available in the enabled scope: ${requestedRef.provider}/${requestedRef.modelId}`,
175
+ );
176
+ }
177
+
178
+ const requestedScoped = requested
179
+ ? scope.scopedModels.find((scoped) => scoped.model === requested
180
+ || matchesModel(scoped.model, { provider: requested.provider, modelId: requested.id }))
181
+ : undefined;
182
+ const defaultScoped = !requested && defaultRef
183
+ ? scope.scopedModels.find((scoped) => matchesModel(scoped.model, defaultRef))
184
+ : undefined;
185
+ const fallbackScoped = !requested ? (defaultScoped ?? scope.scopedModels[0]) : undefined;
186
+ const defaultVisible = !requested && !fallbackScoped && defaultRef
187
+ ? scope.visible.find((model) => matchesModel(model, defaultRef))
188
+ : undefined;
189
+ const selectedModel = requested ?? fallbackScoped?.model ?? defaultVisible;
190
+ const scopedSelection = requestedScoped ?? fallbackScoped;
191
+ const thinkingLevel = options.thinkingLevel ?? scopedSelection?.thinkingLevel;
192
+
193
+ return {
194
+ ...(selectedModel ? { model: selectedModel } : {}),
195
+ ...(thinkingLevel ? { thinkingLevel } : {}),
196
+ scopedModels: [...scope.scopedModels],
197
+ };
198
+ }
@@ -0,0 +1,122 @@
1
+ import assert from "node:assert/strict";
2
+ import test from "node:test";
3
+
4
+ import {
5
+ invalidateModelsCache,
6
+ loadModelsWithCache,
7
+ withModelRuntimeError,
8
+ withSafeModelLoadFailure,
9
+ } from "./models-cache.ts";
10
+
11
+ function modelsData(id) {
12
+ return {
13
+ models: { [`provider:${id}`]: id },
14
+ modelList: [{ id, name: id, provider: "provider" }],
15
+ defaultModel: null,
16
+ thinkingLevels: {},
17
+ thinkingLevelMaps: {},
18
+ };
19
+ }
20
+
21
+ test("caches model data independently for each cwd", async () => {
22
+ invalidateModelsCache();
23
+ let firstLoads = 0;
24
+ let secondLoads = 0;
25
+
26
+ const first = await loadModelsWithCache("/first", async () => {
27
+ firstLoads += 1;
28
+ return modelsData("first");
29
+ });
30
+ await loadModelsWithCache("/second", async () => {
31
+ secondLoads += 1;
32
+ return modelsData("second");
33
+ });
34
+ const firstAgain = await loadModelsWithCache("/first", async () => {
35
+ firstLoads += 1;
36
+ return modelsData("replacement");
37
+ });
38
+
39
+ assert.deepEqual(firstAgain, first);
40
+ assert.equal(firstLoads, 1);
41
+ assert.equal(secondLoads, 1);
42
+ });
43
+
44
+ test("shares one loader between concurrent requests for the same cwd", async () => {
45
+ invalidateModelsCache();
46
+ let loads = 0;
47
+ let finishLoad;
48
+ const loader = () => {
49
+ loads += 1;
50
+ return new Promise((resolve) => { finishLoad = resolve; });
51
+ };
52
+
53
+ const first = loadModelsWithCache("/shared", loader);
54
+ const second = loadModelsWithCache("/shared", loader);
55
+ await Promise.resolve();
56
+
57
+ assert.equal(loads, 1);
58
+ finishLoad(modelsData("shared"));
59
+ assert.deepEqual(await second, await first);
60
+ });
61
+
62
+ test("does not cache a stale load that finishes after invalidation", async () => {
63
+ invalidateModelsCache();
64
+ let finishOldLoad;
65
+ const oldLoad = loadModelsWithCache("/stale", () => new Promise((resolve) => { finishOldLoad = resolve; }));
66
+ await Promise.resolve();
67
+
68
+ invalidateModelsCache();
69
+ let freshLoads = 0;
70
+ const fresh = await loadModelsWithCache("/stale", async () => {
71
+ freshLoads += 1;
72
+ return modelsData("fresh");
73
+ });
74
+ finishOldLoad(modelsData("stale"));
75
+ await oldLoad;
76
+
77
+ const cached = await loadModelsWithCache("/stale", async () => {
78
+ freshLoads += 1;
79
+ return modelsData("unexpected");
80
+ });
81
+ assert.deepEqual(cached, fresh);
82
+ assert.equal(freshLoads, 1);
83
+ });
84
+
85
+ test("retries after a model load fails", async () => {
86
+ invalidateModelsCache();
87
+ await assert.rejects(
88
+ loadModelsWithCache("/failed", async () => { throw new Error("load failed"); }),
89
+ /load failed/,
90
+ );
91
+
92
+ let retries = 0;
93
+ const fresh = await loadModelsWithCache("/failed", async () => {
94
+ retries += 1;
95
+ return modelsData("fresh");
96
+ });
97
+ assert.deepEqual(fresh, modelsData("fresh"));
98
+ assert.equal(retries, 1);
99
+ });
100
+
101
+ test("adds runtime errors without discarding available models", () => {
102
+ const data = modelsData("builtin");
103
+ const result = withModelRuntimeError(data, "Invalid models.json schema");
104
+
105
+ assert.deepEqual(result, {
106
+ ...data,
107
+ modelError: "Invalid models.json schema",
108
+ });
109
+ });
110
+
111
+ test("uses a safe error for unexpected model load failures", () => {
112
+ const data = {
113
+ ...modelsData("builtin"),
114
+ modelError: "Failed to load /Users/example/.pi/agent/models.json with token secret",
115
+ };
116
+ const result = withSafeModelLoadFailure(data);
117
+
118
+ assert.deepEqual(result, {
119
+ ...data,
120
+ modelError: "Model list is temporarily unavailable. Check your configuration and try again.",
121
+ });
122
+ });
@@ -0,0 +1,90 @@
1
+ export interface ModelsData {
2
+ models: Record<string, string>;
3
+ modelList: { id: string; name: string; provider: string }[];
4
+ defaultModel: { provider: string; modelId: string } | null;
5
+ thinkingLevels: Record<string, string[]>;
6
+ thinkingLevelMaps: Record<string, Record<string, string | null>>;
7
+ /** `provider/modelId` → thinking level pinned by an `enabledModels` `:level` suffix. */
8
+ thinkingLevelPins: Record<string, string>;
9
+ modelError?: string;
10
+ /** Warnings from resolving the `enabledModels` scope (e.g. a pattern matched nothing). */
11
+ modelScopeWarnings?: string[];
12
+ }
13
+
14
+ interface ModelsCacheState {
15
+ entries: Map<string, { data: ModelsData; expiresAt: number }>;
16
+ inFlight: Map<string, Promise<ModelsData>>;
17
+ generation: number;
18
+ }
19
+
20
+ declare global {
21
+ var __piModelsCacheState: ModelsCacheState | undefined;
22
+ }
23
+
24
+ const MODELS_CACHE_TTL_MS = 60_000;
25
+ const MAX_MODELS_CACHE_ENTRIES = 32;
26
+ // Never interpolate the caught error here; SDK errors can contain paths and provider details.
27
+ const SAFE_MODEL_LOAD_FAILURE_MESSAGE = "Model list is temporarily unavailable. Check your configuration and try again.";
28
+
29
+ function getModelsCacheState(): ModelsCacheState {
30
+ if (!globalThis.__piModelsCacheState) {
31
+ globalThis.__piModelsCacheState = {
32
+ entries: new Map(),
33
+ inFlight: new Map(),
34
+ generation: 0,
35
+ };
36
+ }
37
+ return globalThis.__piModelsCacheState;
38
+ }
39
+
40
+ export function invalidateModelsCache(): void {
41
+ const state = getModelsCacheState();
42
+ state.generation += 1;
43
+ state.entries.clear();
44
+ state.inFlight.clear();
45
+ }
46
+
47
+ export function withModelRuntimeError(data: ModelsData, modelError: string | undefined): ModelsData {
48
+ return modelError ? { ...data, modelError } : data;
49
+ }
50
+
51
+ export function withSafeModelLoadFailure(data: ModelsData): ModelsData {
52
+ return { ...data, modelError: SAFE_MODEL_LOAD_FAILURE_MESSAGE };
53
+ }
54
+
55
+ export function loadModelsWithCache(cwd: string, loader: () => Promise<ModelsData>): Promise<ModelsData> {
56
+ const state = getModelsCacheState();
57
+ const cached = state.entries.get(cwd);
58
+ if (cached) {
59
+ if (cached.expiresAt > Date.now()) return Promise.resolve(cached.data);
60
+ state.entries.delete(cwd);
61
+ }
62
+
63
+ const existingLoad = state.inFlight.get(cwd);
64
+ if (existingLoad) return existingLoad;
65
+
66
+ const generation = state.generation;
67
+ const loadPromise: Promise<ModelsData> = Promise.resolve()
68
+ .then(loader)
69
+ .then((data) => {
70
+ if (state.generation === generation && state.inFlight.get(cwd) === loadPromise) {
71
+ const now = Date.now();
72
+ for (const [key, entry] of state.entries) {
73
+ if (entry.expiresAt <= now) state.entries.delete(key);
74
+ }
75
+ while (state.entries.size >= MAX_MODELS_CACHE_ENTRIES) {
76
+ const oldestKey = state.entries.keys().next().value;
77
+ if (oldestKey === undefined) break;
78
+ state.entries.delete(oldestKey);
79
+ }
80
+ state.entries.set(cwd, { data, expiresAt: now + MODELS_CACHE_TTL_MS });
81
+ }
82
+ return data;
83
+ })
84
+ .finally(() => {
85
+ if (state.inFlight.get(cwd) === loadPromise) state.inFlight.delete(cwd);
86
+ });
87
+
88
+ state.inFlight.set(cwd, loadPromise);
89
+ return loadPromise;
90
+ }