@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,414 @@
1
+ export interface ModelCatalogCost {
2
+ input?: number;
3
+ output?: number;
4
+ cacheRead?: number;
5
+ cacheWrite?: number;
6
+ }
7
+
8
+ export type CompleteModelCatalogCost = Required<ModelCatalogCost>;
9
+
10
+ export interface ModelCatalogEntry {
11
+ key: string;
12
+ providerId: string;
13
+ providerName: string;
14
+ providerBaseUrl?: string;
15
+ id: string;
16
+ name: string;
17
+ reasoning?: boolean;
18
+ input?: string[];
19
+ contextWindow?: number;
20
+ maxTokens?: number;
21
+ cost: ModelCatalogCost;
22
+ }
23
+
24
+ export interface ModelCatalogPreset {
25
+ name?: string;
26
+ reasoning?: boolean;
27
+ input?: string[];
28
+ contextWindow?: number;
29
+ maxTokens?: number;
30
+ cost?: CompleteModelCatalogCost;
31
+ }
32
+
33
+ export type ModelCatalogMatchMethod = "provider" | "base-url" | "consensus" | "none";
34
+
35
+ export type ModelCatalogPriceRecommendation =
36
+ | {
37
+ status: "reliable";
38
+ method: Exclude<ModelCatalogMatchMethod, "none">;
39
+ cost: CompleteModelCatalogCost;
40
+ providerId?: string;
41
+ providerName?: string;
42
+ support: number;
43
+ total: number;
44
+ }
45
+ | {
46
+ status: "unreliable";
47
+ reason: "no-exact-match" | "no-valid-price" | "insufficient-support" | "conflict";
48
+ support: number;
49
+ total: number;
50
+ };
51
+
52
+ export interface ModelCatalogRecommendation {
53
+ exactMatches: number;
54
+ metadataMethod: ModelCatalogMatchMethod;
55
+ matchedProviderId?: string;
56
+ matchedProviderName?: string;
57
+ preset: ModelCatalogPreset;
58
+ price: ModelCatalogPriceRecommendation;
59
+ }
60
+
61
+ const CONSENSUS_MIN_SHARE = 0.6;
62
+ const CONSENSUS_MIN_SUPPORT = 5;
63
+ const KNOWN_PROVIDER_HOSTS: Record<string, readonly string[]> = {
64
+ anthropic: ["api.anthropic.com"],
65
+ google: ["generativelanguage.googleapis.com"],
66
+ openai: ["api.openai.com"],
67
+ openrouter: ["openrouter.ai"],
68
+ };
69
+ const SUPPORTED_INPUT_MODALITIES = new Set(["text", "image"]);
70
+
71
+ function isRecord(value: unknown): value is Record<string, unknown> {
72
+ return typeof value === "object" && value !== null && !Array.isArray(value);
73
+ }
74
+
75
+ function cleanString(value: unknown): string | undefined {
76
+ return typeof value === "string" && value.trim() ? value.trim() : undefined;
77
+ }
78
+
79
+ function optionalNonNegativeNumber(value: unknown): number | undefined {
80
+ return typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : undefined;
81
+ }
82
+
83
+ function optionalPositiveNumber(value: unknown): number | undefined {
84
+ return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : undefined;
85
+ }
86
+
87
+ function readCost(value: unknown): ModelCatalogCost {
88
+ if (!isRecord(value)) return {};
89
+ return {
90
+ input: optionalNonNegativeNumber(value.input),
91
+ output: optionalNonNegativeNumber(value.output),
92
+ cacheRead: optionalNonNegativeNumber(value.cache_read),
93
+ cacheWrite: optionalNonNegativeNumber(value.cache_write),
94
+ };
95
+ }
96
+
97
+ function readInputModalities(value: unknown): string[] | undefined {
98
+ if (!isRecord(value) || !Array.isArray(value.input)) return undefined;
99
+ const input = Array.from(new Set(value.input
100
+ .filter((entry): entry is string => typeof entry === "string")
101
+ .map((entry) => entry.trim().toLocaleLowerCase())
102
+ .filter((entry) => SUPPORTED_INPUT_MODALITIES.has(entry))));
103
+ return input.length ? input : undefined;
104
+ }
105
+
106
+ function normalizeProvider(value: string): string {
107
+ return value.trim().toLocaleLowerCase().replace(/[^a-z0-9]/g, "");
108
+ }
109
+
110
+ function normalizeModelId(value: string): string {
111
+ return value.trim().toLocaleLowerCase().replace(/^models\//, "");
112
+ }
113
+
114
+ function hostname(value: string | undefined): string | undefined {
115
+ if (!value) return undefined;
116
+ try {
117
+ return new URL(value).hostname.toLocaleLowerCase().replace(/\.$/, "");
118
+ } catch {
119
+ return undefined;
120
+ }
121
+ }
122
+
123
+ function hostMatches(actual: string, expected: string): boolean {
124
+ return actual === expected || actual.endsWith(`.${expected}`);
125
+ }
126
+
127
+ function providerMatches(entry: ModelCatalogEntry, providerHint: string): boolean {
128
+ const normalizedHint = normalizeProvider(providerHint);
129
+ if (!normalizedHint) return false;
130
+ return normalizeProvider(entry.providerId) === normalizedHint
131
+ || normalizeProvider(entry.providerName) === normalizedHint;
132
+ }
133
+
134
+ function baseUrlMatches(entry: ModelCatalogEntry, baseUrl: string): boolean {
135
+ const actualHost = hostname(baseUrl);
136
+ if (!actualHost) return false;
137
+ const knownHosts = KNOWN_PROVIDER_HOSTS[normalizeProvider(entry.providerId)] ?? [];
138
+ const providerHost = hostname(entry.providerBaseUrl);
139
+ return [...knownHosts, ...(providerHost ? [providerHost] : [])]
140
+ .some((candidate) => hostMatches(actualHost, candidate));
141
+ }
142
+
143
+ function exactModelMatches(entry: ModelCatalogEntry, query: string): boolean {
144
+ const normalizedQuery = normalizeModelId(query);
145
+ if (!normalizedQuery) return false;
146
+ const normalizedId = normalizeModelId(entry.id);
147
+ const normalizedFullId = `${entry.providerId.toLocaleLowerCase()}/${normalizedId}`;
148
+ return normalizedId === normalizedQuery || normalizedFullId === normalizedQuery;
149
+ }
150
+
151
+ function validPrice(entry: ModelCatalogEntry): entry is ModelCatalogEntry & {
152
+ cost: ModelCatalogCost & { input: number; output: number };
153
+ } {
154
+ return entry.cost.input !== undefined && entry.cost.output !== undefined;
155
+ }
156
+
157
+ function modeValue<T>(
158
+ values: readonly T[],
159
+ total: number,
160
+ keyFor: (value: T) => string,
161
+ ): T | undefined {
162
+ if (values.length === 0 || total <= 0) return undefined;
163
+ const groups = new Map<string, { value: T; count: number }>();
164
+ for (const value of values) {
165
+ const key = keyFor(value);
166
+ const current = groups.get(key);
167
+ if (current) current.count += 1;
168
+ else groups.set(key, { value, count: 1 });
169
+ }
170
+ const ranked = [...groups.values()].sort((a, b) => b.count - a.count);
171
+ const winner = ranked[0];
172
+ if (!winner || winner.count / total < CONSENSUS_MIN_SHARE) return undefined;
173
+ if (ranked[1]?.count === winner.count) return undefined;
174
+ return winner.value;
175
+ }
176
+
177
+ function modeNumber(values: readonly number[]): number | undefined {
178
+ if (values.length === 0) return undefined;
179
+ const groups = new Map<number, number>();
180
+ for (const value of values) groups.set(value, (groups.get(value) ?? 0) + 1);
181
+ const ranked = [...groups.entries()].sort((a, b) => b[1] - a[1]);
182
+ if (!ranked[0] || ranked[1]?.[1] === ranked[0][1]) return undefined;
183
+ return ranked[0][0];
184
+ }
185
+
186
+ function metadataFromEntry(entry: ModelCatalogEntry): ModelCatalogPreset {
187
+ return {
188
+ name: entry.name,
189
+ reasoning: entry.reasoning,
190
+ input: entry.input,
191
+ contextWindow: entry.contextWindow,
192
+ maxTokens: entry.maxTokens,
193
+ };
194
+ }
195
+
196
+ function consensusMetadata(entries: readonly ModelCatalogEntry[]): ModelCatalogPreset {
197
+ const total = entries.length;
198
+ return {
199
+ name: modeValue(entries.map((entry) => entry.name), total, (value) => value.toLocaleLowerCase()),
200
+ reasoning: modeValue(
201
+ entries.flatMap((entry) => entry.reasoning === undefined ? [] : [entry.reasoning]),
202
+ total,
203
+ String,
204
+ ),
205
+ input: modeValue(
206
+ entries.flatMap((entry) => entry.input ? [entry.input] : []),
207
+ total,
208
+ (value) => [...value].sort().join(","),
209
+ ),
210
+ contextWindow: modeValue(
211
+ entries.flatMap((entry) => entry.contextWindow === undefined ? [] : [entry.contextWindow]),
212
+ total,
213
+ String,
214
+ ),
215
+ maxTokens: modeValue(
216
+ entries.flatMap((entry) => entry.maxTokens === undefined ? [] : [entry.maxTokens]),
217
+ total,
218
+ String,
219
+ ),
220
+ };
221
+ }
222
+
223
+ function priceFromEntry(
224
+ entry: ModelCatalogEntry & { cost: ModelCatalogCost & { input: number; output: number } },
225
+ method: "provider" | "base-url",
226
+ ): ModelCatalogPriceRecommendation {
227
+ return {
228
+ status: "reliable",
229
+ method,
230
+ cost: {
231
+ input: entry.cost.input,
232
+ output: entry.cost.output,
233
+ cacheRead: entry.cost.cacheRead ?? 0,
234
+ cacheWrite: entry.cost.cacheWrite ?? 0,
235
+ },
236
+ providerId: entry.providerId,
237
+ providerName: entry.providerName,
238
+ support: 1,
239
+ total: 1,
240
+ };
241
+ }
242
+
243
+ function consensusPrice(entries: readonly ModelCatalogEntry[]): ModelCatalogPriceRecommendation {
244
+ const priced = entries.filter(validPrice);
245
+ if (priced.length === 0) {
246
+ return { status: "unreliable", reason: "no-valid-price", support: 0, total: 0 };
247
+ }
248
+ if (priced.length === 1) {
249
+ return { status: "unreliable", reason: "insufficient-support", support: 1, total: 1 };
250
+ }
251
+
252
+ const groups = new Map<string, typeof priced>();
253
+ for (const entry of priced) {
254
+ const key = JSON.stringify([entry.cost.input, entry.cost.output]);
255
+ const group = groups.get(key);
256
+ if (group) group.push(entry);
257
+ else groups.set(key, [entry]);
258
+ }
259
+ const ranked = [...groups.values()].sort((a, b) => b.length - a.length);
260
+ const winner = ranked[0];
261
+ if (!winner) {
262
+ return { status: "unreliable", reason: "no-valid-price", support: 0, total: priced.length };
263
+ }
264
+ const hasConsensus = winner.length / priced.length >= CONSENSUS_MIN_SHARE
265
+ || winner.length >= CONSENSUS_MIN_SUPPORT;
266
+ if (ranked[1]?.length === winner.length || !hasConsensus) {
267
+ return {
268
+ status: "unreliable",
269
+ reason: "conflict",
270
+ support: winner.length,
271
+ total: priced.length,
272
+ };
273
+ }
274
+
275
+ const cacheRead = modeNumber(winner.map((entry) => entry.cost.cacheRead ?? 0)) ?? 0;
276
+ const cacheWrite = modeNumber(winner.map((entry) => entry.cost.cacheWrite ?? 0)) ?? 0;
277
+ return {
278
+ status: "reliable",
279
+ method: "consensus",
280
+ cost: {
281
+ input: winner[0].cost.input,
282
+ output: winner[0].cost.output,
283
+ cacheRead,
284
+ cacheWrite,
285
+ },
286
+ support: winner.length,
287
+ total: priced.length,
288
+ };
289
+ }
290
+
291
+ export function flattenModelsDevCatalog(value: unknown): ModelCatalogEntry[] {
292
+ if (!isRecord(value)) return [];
293
+
294
+ const entries: ModelCatalogEntry[] = [];
295
+ for (const [providerId, rawProvider] of Object.entries(value)) {
296
+ if (!isRecord(rawProvider) || !isRecord(rawProvider.models)) continue;
297
+ const providerName = cleanString(rawProvider.name) ?? providerId;
298
+ const providerBaseUrl = cleanString(rawProvider.api);
299
+
300
+ for (const [fallbackId, rawModel] of Object.entries(rawProvider.models)) {
301
+ if (!isRecord(rawModel)) continue;
302
+ const id = cleanString(rawModel.id) ?? fallbackId;
303
+ if (!id) continue;
304
+ const name = cleanString(rawModel.name) ?? id;
305
+ const entry: ModelCatalogEntry = {
306
+ key: `${providerId}/${id}`,
307
+ providerId,
308
+ providerName,
309
+ id,
310
+ name,
311
+ cost: readCost(rawModel.cost),
312
+ };
313
+ if (providerBaseUrl) entry.providerBaseUrl = providerBaseUrl;
314
+ if (typeof rawModel.reasoning === "boolean") entry.reasoning = rawModel.reasoning;
315
+ const input = readInputModalities(rawModel.modalities);
316
+ if (input) entry.input = input;
317
+ if (isRecord(rawModel.limit)) {
318
+ const contextWindow = optionalPositiveNumber(rawModel.limit.context);
319
+ const maxTokens = optionalPositiveNumber(rawModel.limit.output);
320
+ if (contextWindow !== undefined) entry.contextWindow = contextWindow;
321
+ if (maxTokens !== undefined) entry.maxTokens = maxTokens;
322
+ }
323
+ entries.push(entry);
324
+ }
325
+ }
326
+
327
+ return entries;
328
+ }
329
+
330
+ export function recommendModelCatalogPreset(
331
+ entries: readonly ModelCatalogEntry[],
332
+ query: string,
333
+ providerHint = "",
334
+ baseUrl = "",
335
+ ): ModelCatalogRecommendation {
336
+ const exactEntries = entries.filter((entry) => exactModelMatches(entry, query));
337
+ if (exactEntries.length === 0) {
338
+ return {
339
+ exactMatches: 0,
340
+ metadataMethod: "none",
341
+ preset: {},
342
+ price: { status: "unreliable", reason: "no-exact-match", support: 0, total: 0 },
343
+ };
344
+ }
345
+
346
+ const providerEntries = exactEntries.filter((entry) => providerMatches(entry, providerHint));
347
+ const baseUrlEntries = exactEntries.filter((entry) => baseUrlMatches(entry, baseUrl));
348
+ const metadataEntry = providerEntries[0] ?? baseUrlEntries[0];
349
+ const metadataMethod: ModelCatalogMatchMethod = providerEntries.length
350
+ ? "provider"
351
+ : baseUrlEntries.length
352
+ ? "base-url"
353
+ : "consensus";
354
+ const preset = metadataEntry ? metadataFromEntry(metadataEntry) : consensusMetadata(exactEntries);
355
+
356
+ const providerPrice = providerEntries.find(validPrice);
357
+ const baseUrlPrice = baseUrlEntries.find(validPrice);
358
+ const price = providerPrice
359
+ ? priceFromEntry(providerPrice, "provider")
360
+ : baseUrlPrice
361
+ ? priceFromEntry(baseUrlPrice, "base-url")
362
+ : consensusPrice(exactEntries);
363
+ if (price.status === "reliable") preset.cost = price.cost;
364
+
365
+ return {
366
+ exactMatches: exactEntries.length,
367
+ metadataMethod,
368
+ matchedProviderId: metadataEntry?.providerId,
369
+ matchedProviderName: metadataEntry?.providerName,
370
+ preset,
371
+ price,
372
+ };
373
+ }
374
+
375
+ function matchRank(entry: ModelCatalogEntry, query: string, providerHint: string): number {
376
+ const id = entry.id.toLocaleLowerCase();
377
+ const name = entry.name.toLocaleLowerCase();
378
+ const providerId = entry.providerId.toLocaleLowerCase();
379
+ const providerName = entry.providerName.toLocaleLowerCase();
380
+ const fullId = `${providerId}/${id}`;
381
+
382
+ let rank = 20;
383
+ if (!query) rank = 10;
384
+ else if (id === query || fullId === query) rank = 0;
385
+ else if (name === query) rank = 1;
386
+ else if (id.startsWith(query) || name.startsWith(query)) rank = 2;
387
+ else if (fullId.startsWith(query) || providerId === query || providerName === query) rank = 3;
388
+ else if (id.includes(query) || name.includes(query)) rank = 4;
389
+ else if (fullId.includes(query) || providerName.includes(query)) rank = 5;
390
+
391
+ if (rank < 20 && providerHint && (providerId === providerHint || providerName === providerHint)) rank -= 0.5;
392
+ return rank;
393
+ }
394
+
395
+ export function searchModelCatalog(
396
+ entries: readonly ModelCatalogEntry[],
397
+ query: string,
398
+ providerHint = "",
399
+ limit = 50,
400
+ ): ModelCatalogEntry[] {
401
+ const normalizedQuery = query.trim().toLocaleLowerCase();
402
+ const normalizedProvider = providerHint.trim().toLocaleLowerCase();
403
+ const cappedLimit = Math.max(1, Math.min(100, Math.floor(limit) || 50));
404
+
405
+ return entries
406
+ .map((entry) => ({ entry, rank: matchRank(entry, normalizedQuery, normalizedProvider) }))
407
+ .filter(({ rank }) => !normalizedQuery || rank < 20)
408
+ .sort((a, b) => a.rank - b.rank
409
+ || a.entry.providerName.localeCompare(b.entry.providerName, undefined, { sensitivity: "base" })
410
+ || a.entry.name.localeCompare(b.entry.name, undefined, { numeric: true, sensitivity: "base" })
411
+ || a.entry.id.localeCompare(b.entry.id, undefined, { numeric: true, sensitivity: "base" }))
412
+ .slice(0, cappedLimit)
413
+ .map(({ entry }) => entry);
414
+ }
@@ -0,0 +1,58 @@
1
+ import { mkdtempSync, rmSync, writeFileSync } from "fs";
2
+ import { tmpdir } from "os";
3
+ import { join } from "path";
4
+ import { ModelRuntime } from "@earendil-works/pi-coding-agent";
5
+
6
+ export interface ModelDiscoveryAuth {
7
+ apiKey?: string;
8
+ headers: Record<string, string>;
9
+ }
10
+
11
+ function isRecord(value: unknown): value is Record<string, unknown> {
12
+ return typeof value === "object" && value !== null && !Array.isArray(value);
13
+ }
14
+
15
+ function stringRecord(value: unknown): Record<string, string> {
16
+ if (!isRecord(value)) return {};
17
+ return Object.fromEntries(Object.entries(value).filter((entry): entry is [string, string] => typeof entry[1] === "string"));
18
+ }
19
+
20
+ export async function resolveModelDiscoveryAuth(
21
+ providerName: string,
22
+ provider: Record<string, unknown>,
23
+ ): Promise<ModelDiscoveryAuth> {
24
+ let tempDir: string | undefined;
25
+ try {
26
+ tempDir = mkdtempSync(join(tmpdir(), "pi-web-model-discovery-"));
27
+ const modelsPath = join(tempDir, "models.json");
28
+ const discoveryModelId = "__pi_web_model_discovery__";
29
+ writeFileSync(modelsPath, JSON.stringify({
30
+ providers: {
31
+ [providerName]: {
32
+ ...provider,
33
+ models: [{ id: discoveryModelId }],
34
+ },
35
+ },
36
+ }, null, 2), "utf8");
37
+
38
+ const modelRuntime = await ModelRuntime.create({ modelsPath });
39
+ const loadError = modelRuntime.getError();
40
+ if (loadError) throw new Error(loadError);
41
+ const model = modelRuntime.getModel(providerName, discoveryModelId);
42
+ if (!model) throw new Error(`Unable to load provider "${providerName}"`);
43
+
44
+ const resolved = await modelRuntime.getAuth(model);
45
+ if (resolved) {
46
+ return {
47
+ apiKey: resolved.auth.apiKey,
48
+ headers: stringRecord(resolved.auth.headers),
49
+ };
50
+ }
51
+
52
+ return {
53
+ headers: stringRecord(modelRuntime.getCompatibilityRequestConfig(model).headers),
54
+ };
55
+ } finally {
56
+ if (tempDir) rmSync(tempDir, { recursive: true, force: true });
57
+ }
58
+ }
@@ -0,0 +1,50 @@
1
+ import assert from "node:assert/strict";
2
+ import test from "node:test";
3
+
4
+ async function loadSubject(path) {
5
+ try {
6
+ const { createJiti } = await import("jiti");
7
+ return createJiti(import.meta.url).import(path);
8
+ } catch {
9
+ return import(path);
10
+ }
11
+ }
12
+
13
+ const { buildModelsListUrl, parseDiscoveredModels } = await loadSubject("./model-discovery.ts");
14
+ const { resolveModelDiscoveryAuth } = await loadSubject("./model-discovery-auth.ts");
15
+
16
+ test("builds protocol-appropriate model list URLs", () => {
17
+ assert.equal(buildModelsListUrl("https://api.example.com/v1/", "openai-completions").toString(), "https://api.example.com/v1/models");
18
+ assert.equal(buildModelsListUrl("https://api.anthropic.com", "anthropic-messages").toString(), "https://api.anthropic.com/v1/models?limit=1000");
19
+ assert.equal(buildModelsListUrl("https://generativelanguage.googleapis.com", "google-generative-ai").toString(), "https://generativelanguage.googleapis.com/v1beta/models?pageSize=1000");
20
+ assert.equal(buildModelsListUrl("https://api.example.com/custom/models", "openai-responses").toString(), "https://api.example.com/custom/models");
21
+ });
22
+
23
+ test("parses OpenAI, Anthropic, Google, and string model lists", () => {
24
+ assert.deepEqual(parseDiscoveredModels({ data: [{ id: "gpt-5" }, { id: "claude", display_name: "Claude" }] }), [
25
+ { id: "claude", name: "Claude" },
26
+ { id: "gpt-5" },
27
+ ]);
28
+ assert.deepEqual(parseDiscoveredModels({ models: [{ name: "models/gemini-2.5-pro", displayName: "Gemini 2.5 Pro" }] }), [
29
+ { id: "gemini-2.5-pro", name: "Gemini 2.5 Pro" },
30
+ ]);
31
+ assert.deepEqual(parseDiscoveredModels(["zeta", "alpha", "alpha"]), [
32
+ { id: "alpha" },
33
+ { id: "zeta" },
34
+ ]);
35
+ });
36
+
37
+ test("resolves environment-backed headers without an API key", async () => {
38
+ process.env.PI_WEB_DISCOVERY_TEST_TOKEN = "resolved-token";
39
+ try {
40
+ const auth = await resolveModelDiscoveryAuth("pi-web-header-only-test", {
41
+ baseUrl: "https://example.invalid/v1",
42
+ api: "openai-completions",
43
+ headers: { "X-Discovery-Token": "$PI_WEB_DISCOVERY_TEST_TOKEN" },
44
+ });
45
+ assert.equal(auth.apiKey, undefined);
46
+ assert.deepEqual(auth.headers, { "X-Discovery-Token": "resolved-token" });
47
+ } finally {
48
+ delete process.env.PI_WEB_DISCOVERY_TEST_TOKEN;
49
+ }
50
+ });
@@ -0,0 +1,75 @@
1
+ export interface DiscoveredModel {
2
+ id: string;
3
+ name?: string;
4
+ }
5
+
6
+ function isRecord(value: unknown): value is Record<string, unknown> {
7
+ return typeof value === "object" && value !== null && !Array.isArray(value);
8
+ }
9
+
10
+ function cleanString(value: unknown): string | undefined {
11
+ return typeof value === "string" && value.trim() ? value.trim() : undefined;
12
+ }
13
+
14
+ function modelFromValue(value: unknown): DiscoveredModel | null {
15
+ if (typeof value === "string") {
16
+ const id = value.trim();
17
+ return id ? { id } : null;
18
+ }
19
+ if (!isRecord(value)) return null;
20
+
21
+ const rawId = cleanString(value.id) ?? cleanString(value.model) ?? cleanString(value.name);
22
+ if (!rawId) return null;
23
+ const id = rawId.startsWith("models/") ? rawId.slice("models/".length) : rawId;
24
+ if (!id) return null;
25
+ const name = cleanString(value.display_name)
26
+ ?? cleanString(value.displayName)
27
+ ?? (cleanString(value.id) || cleanString(value.model) ? cleanString(value.name) : undefined);
28
+ return name && name !== id ? { id, name } : { id };
29
+ }
30
+
31
+ function listFromResponse(value: unknown): unknown[] {
32
+ if (Array.isArray(value)) return value;
33
+ if (!isRecord(value)) return [];
34
+ for (const key of ["data", "models", "results", "items"]) {
35
+ const candidate = value[key];
36
+ if (Array.isArray(candidate)) return candidate;
37
+ if (isRecord(candidate)) return Object.values(candidate);
38
+ }
39
+ return [];
40
+ }
41
+
42
+ export function parseDiscoveredModels(value: unknown): DiscoveredModel[] {
43
+ const seen = new Set<string>();
44
+ const models: DiscoveredModel[] = [];
45
+ for (const item of listFromResponse(value)) {
46
+ const model = modelFromValue(item);
47
+ if (!model || seen.has(model.id)) continue;
48
+ seen.add(model.id);
49
+ models.push(model);
50
+ }
51
+ return models.sort((a, b) => (a.name ?? a.id).localeCompare(b.name ?? b.id, undefined, {
52
+ numeric: true,
53
+ sensitivity: "base",
54
+ }));
55
+ }
56
+
57
+ export function buildModelsListUrl(baseUrl: string, api: string): URL {
58
+ const url = new URL(baseUrl.trim());
59
+ const trimmedPath = url.pathname.replace(/\/+$/, "");
60
+
61
+ if (!/\/models$/i.test(trimmedPath)) {
62
+ let path = trimmedPath;
63
+ if (api === "anthropic-messages" && !/\/v\d+(?:beta)?$/i.test(path)) path += "/v1";
64
+ if (api === "google-generative-ai" && !/\/v\d+(?:beta)?$/i.test(path)) path += "/v1beta";
65
+ url.pathname = `${path}/models`.replace(/\/+/g, "/");
66
+ }
67
+
68
+ if (api === "anthropic-messages" && !url.searchParams.has("limit")) {
69
+ url.searchParams.set("limit", "1000");
70
+ }
71
+ if (api === "google-generative-ai" && !url.searchParams.has("pageSize")) {
72
+ url.searchParams.set("pageSize", "1000");
73
+ }
74
+ return url;
75
+ }
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Browser-persisted preference for the model selector: show model IDs
3
+ * instead of display names. Some providers expose multiple models that
4
+ * share the same display name, so the ID is the only way to tell them
5
+ * apart. Mirrors the tool-preset preference pattern (localStorage,
6
+ * best-effort).
7
+ */
8
+
9
+ const STORAGE_KEY = "pi-model-id-display";
10
+
11
+ interface StorageLike {
12
+ getItem(key: string): string | null;
13
+ setItem(key: string, value: string): void;
14
+ }
15
+
16
+ function getBrowserStorage(): StorageLike | null {
17
+ if (typeof window === "undefined") return null;
18
+ try {
19
+ return window.localStorage;
20
+ } catch {
21
+ return null;
22
+ }
23
+ }
24
+
25
+ export function getPreferredModelIdDisplay(
26
+ storage: StorageLike | null = getBrowserStorage(),
27
+ ): boolean {
28
+ if (!storage) return false;
29
+ try {
30
+ return storage.getItem(STORAGE_KEY) === "1";
31
+ } catch {
32
+ return false;
33
+ }
34
+ }
35
+
36
+ export function setPreferredModelIdDisplay(
37
+ enabled: boolean,
38
+ storage: StorageLike | null = getBrowserStorage(),
39
+ ): void {
40
+ if (!storage) return;
41
+ try {
42
+ storage.setItem(STORAGE_KEY, enabled ? "1" : "0");
43
+ } catch {
44
+ // Browser storage is best-effort.
45
+ }
46
+ }