cozyclay 2.1.1 → 3.0.1

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 (585) hide show
  1. package/CHANGELOG.md +88 -0
  2. package/README.md +12 -30
  3. package/bin/agent/agent-routes.mjs +43 -116
  4. package/bin/agent/agent-runner.mjs +14 -15
  5. package/bin/agent/cliproxy-provider.mjs +135 -0
  6. package/bin/agent/credential-store.mjs +30 -7
  7. package/bin/agent/pi-tools.mjs +4 -1
  8. package/bin/agent/provider-keys.mjs +32 -0
  9. package/bin/agent/provider-login.mjs +110 -0
  10. package/bin/agent/provider-routes.mjs +99 -0
  11. package/bin/agent/providers.mjs +19 -97
  12. package/bin/agent/script-tool.mjs +17 -4
  13. package/bin/agent/session-store.mjs +2 -2
  14. package/bin/agent/studio-context-budget.mjs +93 -0
  15. package/bin/agent/studio-motion-reference.mjs +23 -0
  16. package/bin/agent/studio-prompt.mjs +1 -1
  17. package/bin/agent/studio-rebase-text.mjs +32 -0
  18. package/bin/agent/studio-rebase.mjs +26 -39
  19. package/bin/agent/studio-tools.mjs +9 -28
  20. package/bin/agent/tool-arguments.mjs +49 -0
  21. package/bin/agent/turn-frames.mjs +66 -0
  22. package/bin/codex-auth.mjs +9 -2
  23. package/bin/cozyclay.mjs +10 -116
  24. package/bin/live/cli.mjs +3 -3
  25. package/bin/mcp-runtime.mjs +2 -12
  26. package/bin/package-signature.mjs +0 -4
  27. package/dist/app/index.html +7 -6
  28. package/dist/assets/EXRLoader-CvVIvGVE.js +1 -0
  29. package/dist/assets/analytics-BWCqdgWA.js +1 -0
  30. package/dist/assets/app-DfTCxaKk.js +5087 -0
  31. package/dist/assets/app-f-fkV5uP.css +1 -0
  32. package/dist/assets/compile-worker-BxKXh07s.js +5 -0
  33. package/dist/assets/fflate.module-KbGj5-H6.js +5 -0
  34. package/dist/assets/{first-shot-handoff-msLI71g1.js → first-shot-handoff-DNO8kv_N.js} +1 -1
  35. package/dist/assets/{landing-ChUvB_zL.js → landing-yfZaJkWc.js} +1 -1
  36. package/dist/assets/scene-asset-sync-U3kS2rvK.js +20 -0
  37. package/dist/assets/scene-asset-sync-_S8NQl5B.css +1 -0
  38. package/dist/assets/workflow-D78eiKTa.js +179 -0
  39. package/dist/cozyclay-package.json +1 -1
  40. package/dist/index.html +3 -3
  41. package/dist/sw.js +3 -6
  42. package/dist/workflow/index.html +5 -4
  43. package/mcp/LIVE-PROTOCOL.md +7 -47
  44. package/mcp/README.md +16 -26
  45. package/mcp/live-hub.mjs +6 -10
  46. package/mcp/package.json +1 -3
  47. package/mcp/tool-handlers.mjs +4 -168
  48. package/package.json +3 -22
  49. package/src/App.jsx +151 -2625
  50. package/src/agent-progress.js +80 -0
  51. package/src/agent-turn-mark.js +30 -0
  52. package/src/analytics.js +1 -19
  53. package/src/app-context.js +12 -2
  54. package/src/app-stage.jsx +2 -34
  55. package/src/asset-pane.jsx +6 -5
  56. package/src/camera-block.js +21 -0
  57. package/src/camera-direction.js +120 -0
  58. package/src/character-kind.js +3 -9
  59. package/src/command-bus.js +10 -8
  60. package/src/commands/cast.js +5 -34
  61. package/src/commands/elements/character.js +1 -1
  62. package/src/commands/elements/motion.js +1 -6
  63. package/src/commands/elements/object.js +6 -2
  64. package/src/commands/elements/shot.js +4 -1
  65. package/src/commands/elements.js +38 -14
  66. package/src/commands/index.js +3 -1
  67. package/src/commands/kits.js +37 -0
  68. package/src/commands/motion-timing.js +57 -0
  69. package/src/commands/motion.js +9 -152
  70. package/src/commands/motion3-compile.js +65 -0
  71. package/src/commands/motion3.js +214 -0
  72. package/src/commands/objects.js +6 -4
  73. package/src/commands/shot.js +55 -13
  74. package/src/commands/stage.js +14 -1
  75. package/src/commands/view.js +4 -1
  76. package/src/document-mirrors.js +42 -0
  77. package/src/document-store.js +4 -0
  78. package/src/domains/cast.js +19 -194
  79. package/src/domains/motion/motion-range-pin-rules.js +1 -1
  80. package/src/domains/motion/take-source.js +53 -0
  81. package/src/domains/motion.js +60 -1838
  82. package/src/domains/scenes.js +12 -55
  83. package/src/domains/shots.js +2 -2
  84. package/src/domains/stage.js +4 -2
  85. package/src/draft-ghost.js +47 -0
  86. package/src/draft-ghost.jsx +129 -0
  87. package/src/dualview.jsx +4 -3
  88. package/src/effects/EffectsLayer.jsx +112 -0
  89. package/src/effects/effect-meshes.js +232 -0
  90. package/src/effects/effect-record.js +103 -0
  91. package/src/effects/emitter.js +59 -0
  92. package/src/effects/simulate.js +261 -0
  93. package/src/effects/state.js +59 -0
  94. package/src/execution-telemetry.js +2 -3
  95. package/src/first-success-guide.jsx +5 -5
  96. package/src/ghost-set.js +257 -0
  97. package/src/held-object-edit.jsx +113 -0
  98. package/src/held-object-pose.js +175 -0
  99. package/src/hierarchy-model.js +1 -1
  100. package/src/hierarchy-panel.jsx +1 -1
  101. package/src/imported-mesh.jsx +103 -0
  102. package/src/kits/held.js +84 -0
  103. package/src/kits/index.js +70 -0
  104. package/src/kits/smoking.js +57 -0
  105. package/src/light-blockers.js +93 -0
  106. package/src/motion/beat-board-model.js +84 -0
  107. package/src/motion/beat-board.css +124 -0
  108. package/src/motion/beat-board.jsx +60 -0
  109. package/src/motion/center-of-mass.js +66 -0
  110. package/src/motion/fix-collisions.js +850 -0
  111. package/src/motion/physics-review.js +118 -0
  112. package/src/{ardy → motion}/physics-support.js +1 -76
  113. package/src/motion/rig-snapshot.js +22 -0
  114. package/src/motion/take-segments.jsx +140 -0
  115. package/src/{ardy → motion}/timeline-coordinates.js +0 -21
  116. package/src/{ardy → motion}/timeline.css +1 -2
  117. package/src/{ardy → motion}/timeline.jsx +42 -413
  118. package/src/motion/waypoints.js +76 -0
  119. package/src/motion3/beats.js +83 -0
  120. package/src/motion3/body.js +158 -0
  121. package/src/motion3/bones.js +47 -0
  122. package/src/motion3/carried.js +47 -0
  123. package/src/motion3/compile-job.js +79 -0
  124. package/src/motion3/compile-worker.js +10 -0
  125. package/src/motion3/compiler/anchors.js +113 -0
  126. package/src/motion3/compiler/arm-orient.js +128 -0
  127. package/src/motion3/compiler/channels.js +125 -0
  128. package/src/motion3/compiler/compile.js +205 -0
  129. package/src/motion3/compiler/gait-rhythm.js +97 -0
  130. package/src/motion3/compiler/grip.js +171 -0
  131. package/src/motion3/compiler/head-steady.js +48 -0
  132. package/src/motion3/compiler/hold.js +134 -0
  133. package/src/motion3/compiler/idle.js +113 -0
  134. package/src/motion3/compiler/limbs.js +197 -0
  135. package/src/motion3/compiler/math.js +72 -0
  136. package/src/motion3/compiler/pose.js +177 -0
  137. package/src/motion3/compiler/quat.js +49 -0
  138. package/src/motion3/compiler/skeleton.js +74 -0
  139. package/src/motion3/compiler/validate-common.js +32 -0
  140. package/src/motion3/compiler/validate-goals.js +118 -0
  141. package/src/motion3/compiler/validate-objects.js +144 -0
  142. package/src/motion3/compiler/validate-parts.js +118 -0
  143. package/src/motion3/compiler/validate.js +122 -0
  144. package/src/motion3/compiler/vocab.js +74 -0
  145. package/src/motion3/compiler/walk-pelvis.js +275 -0
  146. package/src/motion3/compiler/walk.js +207 -0
  147. package/src/motion3/compiler/wrist-clamp.js +32 -0
  148. package/src/motion3/delta.js +47 -0
  149. package/src/motion3/describe.js +177 -0
  150. package/src/motion3/docs.js +27 -0
  151. package/src/motion3/edit-carry.js +21 -0
  152. package/src/motion3/fix/ease.js +16 -0
  153. package/src/motion3/fix/ground.js +37 -0
  154. package/src/motion3/fix/index.js +52 -0
  155. package/src/motion3/fix/push.js +71 -0
  156. package/src/motion3/fix/reach.js +46 -0
  157. package/src/motion3/fix/retime.js +79 -0
  158. package/src/motion3/fix/self.js +211 -0
  159. package/src/motion3/fix/slide.js +70 -0
  160. package/src/motion3/fix/turn.js +51 -0
  161. package/src/motion3/follow.js +39 -0
  162. package/src/motion3/free-motion.js +204 -0
  163. package/src/motion3/grip-choice.js +130 -0
  164. package/src/motion3/hold-plan.js +157 -0
  165. package/src/motion3/json-patch.js +77 -0
  166. package/src/motion3/key-aim.js +105 -0
  167. package/src/motion3/key-edit.js +82 -0
  168. package/src/motion3/lenient.js +127 -0
  169. package/src/motion3/object-keys.js +236 -0
  170. package/src/motion3/object-pose.js +182 -0
  171. package/src/motion3/object-tracks.js +12 -0
  172. package/src/motion3/people.js +82 -0
  173. package/src/motion3/pipeline.js +102 -0
  174. package/src/motion3/playback.js +51 -0
  175. package/src/motion3/probes.js +91 -0
  176. package/src/motion3/qa/body-checks.js +34 -0
  177. package/src/motion3/qa/check-names.js +14 -0
  178. package/src/motion3/qa/checks.js +125 -0
  179. package/src/motion3/qa/contact-checks.js +51 -0
  180. package/src/motion3/qa/explain.js +80 -0
  181. package/src/motion3/qa/fmt.js +5 -0
  182. package/src/motion3/qa/foot-checks.js +72 -0
  183. package/src/motion3/qa/geometry.js +77 -0
  184. package/src/motion3/qa/goals.js +191 -0
  185. package/src/motion3/qa/ground.js +60 -0
  186. package/src/motion3/qa/hints-contact.js +195 -0
  187. package/src/motion3/qa/hints-goal.js +160 -0
  188. package/src/motion3/qa/hints-knee.js +24 -0
  189. package/src/motion3/qa/hints-motion.js +209 -0
  190. package/src/motion3/qa/hints-object.js +91 -0
  191. package/src/motion3/qa/index.js +44 -0
  192. package/src/motion3/qa/key-checks.js +82 -0
  193. package/src/motion3/qa/near.js +96 -0
  194. package/src/motion3/qa/object-checks.js +135 -0
  195. package/src/motion3/qa/patch.js +161 -0
  196. package/src/motion3/qa/people-checks.js +37 -0
  197. package/src/motion3/qa/severity.js +38 -0
  198. package/src/motion3/qa/spec-checks.js +65 -0
  199. package/src/motion3/qa/style.js +228 -0
  200. package/src/motion3/qa/wrist-checks.js +69 -0
  201. package/src/motion3/qa/wrist-obs.js +60 -0
  202. package/src/motion3/refs.js +129 -0
  203. package/src/motion3/repair-tune.js +121 -0
  204. package/src/motion3/repair.js +83 -0
  205. package/src/motion3/report.js +67 -0
  206. package/src/motion3/rigs/index.js +14 -0
  207. package/src/motion3/rigs/x-bot-tpose-body.json +1 -0
  208. package/src/motion3/rigs/x-bot-tpose.json +1 -0
  209. package/src/motion3/rigs/y-bot-tpose-body.json +1 -0
  210. package/src/motion3/rigs/y-bot-tpose.json +1 -0
  211. package/src/motion3/scene.js +60 -0
  212. package/src/motion3/schema/brief.js +89 -0
  213. package/src/motion3/schema/fields.js +44 -0
  214. package/src/motion3/schema/spec-examples.js +46 -0
  215. package/src/motion3/schema/topics.js +113 -0
  216. package/src/motion3/schema/types.js +149 -0
  217. package/src/motion3/search.js +117 -0
  218. package/src/motion3/second-hand.js +112 -0
  219. package/src/motion3/settle.js +136 -0
  220. package/src/motion3/skills/basic.js +83 -0
  221. package/src/motion3/skills/book.js +76 -0
  222. package/src/motion3/skills/car.js +145 -0
  223. package/src/motion3/skills/flip.js +200 -0
  224. package/src/motion3/skills/frames-auto.js +29 -0
  225. package/src/motion3/skills/impact.js +109 -0
  226. package/src/motion3/skills/index.js +85 -0
  227. package/src/motion3/skills/info.js +80 -0
  228. package/src/motion3/skills/smoke-kit.js +109 -0
  229. package/src/motion3/skills/smoke.js +160 -0
  230. package/src/motion3/spec-store.js +59 -0
  231. package/src/motion3/take-edit.js +155 -0
  232. package/src/motion3/take.js +63 -0
  233. package/src/motion3/timeline.js +127 -0
  234. package/src/motion3/views.js +121 -0
  235. package/src/motion3/walk-plan.js +119 -0
  236. package/src/object-catalog.jsx +7 -1
  237. package/src/object-colors.js +98 -0
  238. package/src/object-gizmo-parts.js +115 -0
  239. package/src/object-gizmo.jsx +37 -122
  240. package/src/object-glow.js +139 -0
  241. package/src/object-hidden.js +33 -0
  242. package/src/object-look.js +196 -0
  243. package/src/object-size.js +10 -0
  244. package/src/object-travel.js +71 -6
  245. package/src/panels/CameraPanel.jsx +7 -7
  246. package/src/panels/HdrEnvironmentControls.jsx +59 -0
  247. package/src/panels/LightPanel.jsx +25 -4
  248. package/src/panels/ObjectLookPanel.jsx +88 -0
  249. package/src/panels/PosePanel.jsx +2 -17
  250. package/src/panels/RigControlPanel.jsx +5 -53
  251. package/src/panels/SubjectBox.jsx +4 -1
  252. package/src/panels/details.css +14 -0
  253. package/src/posestudio.jsx +4 -27
  254. package/src/project-browser.jsx +3 -4
  255. package/src/project-resources.js +18 -31
  256. package/src/project.js +13 -104
  257. package/src/props.jsx +25 -280
  258. package/src/qa-label.js +25 -0
  259. package/src/rail-dot-drag.js +23 -0
  260. package/src/render-passes.js +10 -0
  261. package/src/resource-status.jsx +1 -0
  262. package/src/result-modal.jsx +4 -19
  263. package/src/room.jsx +20 -12
  264. package/src/scene-assets.js +8 -6
  265. package/src/scene-light-place.js +72 -0
  266. package/src/scene-lights.jsx +148 -0
  267. package/src/scene-objects.js +31 -139
  268. package/src/scenes.js +15 -105
  269. package/src/semantic-edit.js +2 -5
  270. package/src/set-pieces.jsx +183 -0
  271. package/src/settings-menu.jsx +3 -11
  272. package/src/shell/BottomDock.jsx +9 -51
  273. package/src/shell/DetailsSlot.jsx +16 -200
  274. package/src/shell/GhostsMenuGroup.jsx +74 -0
  275. package/src/shell/LibrarySlot.jsx +1 -3
  276. package/src/shell/MenuBar.jsx +3 -3
  277. package/src/shell/PreferencesDialog.jsx +15 -264
  278. package/src/shell/PreferencesSlot.jsx +2 -8
  279. package/src/shell/TopBar.jsx +5 -84
  280. package/src/shell/ViewportToolbar.jsx +10 -70
  281. package/src/shell/agent-glass.css +19 -6
  282. package/src/shell/details-selection.js +42 -0
  283. package/src/shell/ghosts-menu.css +22 -0
  284. package/src/shell/glass-regions.css +29 -3
  285. package/src/shell/preferences.css +6 -1
  286. package/src/shot-authoring.js +4 -7
  287. package/src/soft-shadows.js +37 -0
  288. package/src/stage-attach-frames.js +12 -12
  289. package/src/stage-canvas-rigs.jsx +3 -3
  290. package/src/stage-capture.jsx +42 -8
  291. package/src/stage-characters.jsx +22 -216
  292. package/src/stage-editor-gizmos.jsx +6 -6
  293. package/src/stage-environment.js +93 -0
  294. package/src/stage-hdr-builtins.js +107 -0
  295. package/src/stage-hdr-image.js +116 -0
  296. package/src/stage-hdr-library.js +70 -0
  297. package/src/stage-hdr-map.js +94 -0
  298. package/src/stage-hdr.js +130 -0
  299. package/src/stage-lighting.js +149 -0
  300. package/src/stage-occlusion.js +210 -0
  301. package/src/stage-sky.jsx +159 -0
  302. package/src/stage-startup-defaults.js +5 -9
  303. package/src/studio-actions.js +16 -14
  304. package/src/studio-agent-commands.js +19 -16
  305. package/src/studio-agent-motion.js +9 -9
  306. package/src/studio-agent-protocol.js +47 -77
  307. package/src/studio-app-binding.js +27 -47
  308. package/src/studio-asset-list.js +17 -0
  309. package/src/studio-contact-sheet.js +37 -5
  310. package/src/studio-elements.js +27 -7
  311. package/src/studio-entity-bounds.js +18 -0
  312. package/src/studio-error-codes.js +15 -0
  313. package/src/studio-export-framing.js +152 -0
  314. package/src/studio-lighting-check.js +103 -0
  315. package/src/studio-motion-visual.js +150 -0
  316. package/src/studio-schema-errors.js +80 -0
  317. package/src/studio-support-gap.js +74 -0
  318. package/src/styles.css +1 -19
  319. package/src/timeline-defaults.js +5 -0
  320. package/src/timeline-extent.js +8 -20
  321. package/src/tutorial-walk.js +17 -0
  322. package/src/ui-scrub.js +1 -1
  323. package/src/use-shot-camera-tracks.js +12 -0
  324. package/src/view-ghosts.js +97 -0
  325. package/src/view-ghosts.jsx +49 -0
  326. package/src/workflow/AgentPanel.jsx +47 -166
  327. package/src/workflow/AgentProgressCard.jsx +109 -0
  328. package/src/workflow/AgentReplyText.jsx +37 -0
  329. package/src/workflow/AgentTurnSteps.jsx +84 -0
  330. package/src/workflow/LoginPanel.jsx +140 -0
  331. package/src/workflow/WorkflowBuilder.jsx +7 -23
  332. package/src/workflow/agent-client.js +54 -51
  333. package/src/workflow/agent-panel.css +71 -1
  334. package/src/workflow/agent-progress-card.css +167 -0
  335. package/src/workflow/agent-progress-view.js +47 -0
  336. package/src/workflow/agent-turn-steps.css +113 -0
  337. package/src/workflow/agent-turn.js +191 -0
  338. package/src/workflow/login-client.js +34 -0
  339. package/src/workflow/motion-input.js +18 -31
  340. package/src/workflow/node-schema.js +1 -1
  341. package/src/workflow/reply-markdown.js +69 -0
  342. package/src/workflow/scene-asset-sync.js +8 -8
  343. package/tools/agent-session-stats.mjs +61 -0
  344. package/tools/dev-full.mjs +5 -56
  345. package/tools/eslint-baseline.json +12 -13
  346. package/tools/knip-baseline.json +0 -179
  347. package/tools/loc-baseline.json +31 -81
  348. package/tools/motion3/extract-body.mjs +103 -0
  349. package/tools/motion3/extract-rig.mjs +85 -0
  350. package/tools/process-supervisor.mjs +0 -88
  351. package/tools/run-tests.mjs +9 -125
  352. package/bin/agent/motion-runtime.mjs +0 -76
  353. package/dist/ardy/cskel27-rest.json +0 -557
  354. package/dist/assets/analytics-C9pMpW7s.js +0 -1
  355. package/dist/assets/app-B_2XB9rw.css +0 -1
  356. package/dist/assets/app-DAuvKPP8.js +0 -4920
  357. package/dist/assets/config-Bx7pYsD9.js +0 -1
  358. package/dist/assets/config-DeoFTYzb.css +0 -1
  359. package/dist/assets/demo-CvKuBgy5.js +0 -1
  360. package/dist/assets/scene-asset-sync-DKkpjKBV.js +0 -19
  361. package/dist/assets/scene-asset-sync-DjJ_cJ2R.css +0 -1
  362. package/dist/assets/ticket-BPk2Ni95.js +0 -1
  363. package/dist/assets/workflow-jJRBR94Z.js +0 -179
  364. package/dist/demo/walk-then-stop.npz +0 -0
  365. package/mcp/ardy-prompts.mjs +0 -218
  366. package/src/ardy/auto-fix-panel.css +0 -108
  367. package/src/ardy/auto-physics.js +0 -1194
  368. package/src/ardy/client.js +0 -153
  369. package/src/ardy/convert.js +0 -313
  370. package/src/ardy/cskel27-neutral.js +0 -39
  371. package/src/ardy/cskel27.js +0 -77
  372. package/src/ardy/export.js +0 -165
  373. package/src/ardy/fix-collisions.js +0 -2257
  374. package/src/ardy/foot-lock.js +0 -230
  375. package/src/ardy/motion-calibration.js +0 -118
  376. package/src/ardy/motion-edit.js +0 -213
  377. package/src/ardy/motion-url.js +0 -55
  378. package/src/ardy/mp4-duration.js +0 -102
  379. package/src/ardy/npz.js +0 -595
  380. package/src/ardy/physics-panel.css +0 -21
  381. package/src/ardy/physics-panel.jsx +0 -89
  382. package/src/ardy/physics-refine.js +0 -111
  383. package/src/ardy/physics-review.js +0 -567
  384. package/src/ardy/physics-temporal.js +0 -54
  385. package/src/ardy/platform-fit-panel.css +0 -64
  386. package/src/ardy/platform-fit-panel.jsx +0 -41
  387. package/src/ardy/platform-fit.js +0 -326
  388. package/src/ardy/playback.js +0 -711
  389. package/src/ardy/pose-pin.js +0 -97
  390. package/src/ardy/prompt-clips.js +0 -30
  391. package/src/ardy/retime.js +0 -211
  392. package/src/ardy/root-drop.js +0 -369
  393. package/src/ardy/timeline-resize.js +0 -13
  394. package/src/ardy/to-cskel27.js +0 -225
  395. package/src/ardy/trim.js +0 -33
  396. package/src/ardy/waypoints.js +0 -369
  397. package/src/ardy-bridge-contract.js +0 -281
  398. package/src/domains/motion/motion-video-capture.js +0 -664
  399. package/src/i2v-motion-client.js +0 -77
  400. package/src/i2v-motion-studio.jsx +0 -180
  401. package/src/line-edit.js +0 -1348
  402. package/src/motion/generation.js +0 -105
  403. package/src/motion-readiness-ui.jsx +0 -67
  404. package/src/motion-readiness.js +0 -40
  405. package/src/motion-resources.js +0 -177
  406. package/src/motion-store.js +0 -65
  407. package/src/motion-trail.js +0 -601
  408. package/src/multimodel-ingest.js +0 -394
  409. package/src/panels/PromptBlocksPanel.jsx +0 -444
  410. package/src/panels/VideoCapturePanel.jsx +0 -181
  411. package/src/pose-extract/browser.js +0 -42
  412. package/src/pose-extract/detector.js +0 -100
  413. package/src/pose-extract/fit.js +0 -227
  414. package/src/pose-extract/image-frame.js +0 -51
  415. package/src/pose-extract/index.js +0 -58
  416. package/src/pose-extract/keyframes.js +0 -74
  417. package/src/pose-extract/landmarks.js +0 -162
  418. package/src/pose-extract/mirror.js +0 -153
  419. package/src/pose-extract/take.js +0 -131
  420. package/src/pose-extract/video-frames.js +0 -91
  421. package/src/take-recipe.js +0 -165
  422. package/src/trail-key-conflicts.js +0 -39
  423. package/src/trail-pick.js +0 -59
  424. package/src/use-i2v-capture-modal.js +0 -15
  425. package/tools/ardy/BRIDGE.md +0 -37
  426. package/tools/ardy/GVHMR-NPZ.md +0 -68
  427. package/tools/ardy/README.md +0 -124
  428. package/tools/ardy/__pycache__/cclay_gvhmr_worker.cpython-313.pyc +0 -0
  429. package/tools/ardy/artifacts.mjs +0 -34
  430. package/tools/ardy/bridge.mjs +0 -1732
  431. package/tools/ardy/bvh-cskel27.mjs +0 -1265
  432. package/tools/ardy/cclay_constrained_generate.py +0 -561
  433. package/tools/ardy/cclay_gvhmr_worker.py +0 -264
  434. package/tools/ardy/cclay_motion_edit.py +0 -510
  435. package/tools/ardy/cclay_sequence_generate.py +0 -705
  436. package/tools/ardy/cclay_worker.py +0 -304
  437. package/tools/ardy/dump-npz.py +0 -205
  438. package/tools/ardy/extract-rest.mjs +0 -299
  439. package/tools/ardy/extract.mjs +0 -507
  440. package/tools/ardy/footage.mjs +0 -467
  441. package/tools/ardy/gvhmr-floor.mjs +0 -102
  442. package/tools/ardy/gvhmr-to-cskel27.mjs +0 -21
  443. package/tools/ardy/gvhmr_fastpath.py +0 -168
  444. package/tools/ardy/gvhmr_trajectory.py +0 -240
  445. package/tools/ardy/merge-text-encoder.py +0 -160
  446. package/tools/ardy/mocap-metrics.mjs +0 -200
  447. package/tools/ardy/mocap-quality-gate.mjs +0 -110
  448. package/tools/ardy/motion-stabilize.mjs +0 -348
  449. package/tools/ardy/npz.mjs +0 -414
  450. package/tools/ardy/pose-to-npz.mjs +0 -106
  451. package/tools/ardy/prompt-limits.mjs +0 -5
  452. package/tools/ardy/run-edit-on-box.sh +0 -73
  453. package/tools/ardy/run-local.mjs +0 -233
  454. package/tools/ardy/run-on-box.sh +0 -593
  455. package/tools/ardy/run-sequence-on-box.sh +0 -177
  456. package/tools/ardy/runners/gvhmr-worker.mjs +0 -206
  457. package/tools/ardy/runners/index.mjs +0 -18
  458. package/tools/ardy/runners/local.mjs +0 -455
  459. package/tools/ardy/runners/proc.mjs +0 -125
  460. package/tools/ardy/runners/remote.mjs +0 -252
  461. package/tools/ardy/setup-local.mjs +0 -189
  462. package/tools/ardy/setup-text-encoder-on-box.sh +0 -93
  463. package/tools/ardy/setup-text-encoder.py +0 -306
  464. package/tools/ardy/smpl-cskel27.mjs +0 -178
  465. package/tools/ardy/visual-qa.mjs +0 -196
  466. package/tools/ardy/vq-car.mjs +0 -52
  467. package/tools/bench/EXP3.md +0 -61
  468. package/tools/bench/cclay_bench_extract_incam.py +0 -125
  469. package/tools/bench/cclay_bench_extract_obs.py +0 -204
  470. package/tools/bench/cclay_bench_runner.py +0 -42
  471. package/tools/bench/cube-contact.mjs +0 -162
  472. package/tools/bench/exp3.mjs +0 -152
  473. package/tools/bench/extract-bench-lib.mjs +0 -216
  474. package/tools/bench/extract-bench.mjs +0 -302
  475. package/tools/bench/fal-generate.mjs +0 -88
  476. package/tools/bench/fit/README.md +0 -189
  477. package/tools/bench/fit/camera.mjs +0 -59
  478. package/tools/bench/fit/contact.mjs +0 -419
  479. package/tools/bench/fit/footlock.mjs +0 -234
  480. package/tools/bench/fit/motion.mjs +0 -85
  481. package/tools/bench/fit/pin.mjs +0 -27
  482. package/tools/bench/fit/remote.mjs +0 -105
  483. package/tools/bench/fit-bench.mjs +0 -126
  484. package/tools/bench/fit-sanity.mjs +0 -45
  485. package/tools/bench/metrics.mjs +0 -313
  486. package/tools/bench/obs/depth.mjs +0 -258
  487. package/tools/bench/obs/extrinsics.mjs +0 -175
  488. package/tools/bench/obs/fit_mannequin_betas.py +0 -340
  489. package/tools/bench/obs/ground.mjs +0 -294
  490. package/tools/bench/obs/heading.mjs +0 -151
  491. package/tools/bench/obs/ladder.mjs +0 -555
  492. package/tools/bench/obs/mannequin-betas.json +0 -124
  493. package/tools/bench/obs/remote.mjs +0 -83
  494. package/tools/bench/obs/rest_joints.py +0 -52
  495. package/tools/bench/obs/ybot-targets.mjs +0 -49
  496. package/tools/bench/obs-bench.mjs +0 -520
  497. package/tools/bench/score.mjs +0 -343
  498. package/tools/bench/summarize.mjs +0 -86
  499. package/tools/demo-worker/README.md +0 -137
  500. package/tools/demo-worker/api-client.mjs +0 -233
  501. package/tools/demo-worker/generate.mjs +0 -301
  502. package/tools/demo-worker/index.mjs +0 -466
  503. package/tools/demo-worker/sign-core.mjs +0 -112
  504. package/tools/demo-worker/sign.mjs +0 -161
  505. package/tools/gt-render/browser.mjs +0 -182
  506. package/tools/gt-render/camera-math.mjs +0 -276
  507. package/tools/gt-render/page.mjs +0 -267
  508. package/tools/gt-render/render.mjs +0 -514
  509. package/tools/gt-render/scene-box.mjs +0 -31
  510. package/tools/gt-render/take-transform.mjs +0 -87
  511. package/tools/kimodo/compare-seam.mjs +0 -108
  512. package/tools/kimodo/constraints.mjs +0 -244
  513. package/tools/kimodo/edit.mjs +0 -259
  514. package/tools/kimodo/effector-constraints.mjs +0 -200
  515. package/tools/kimodo/generate.mjs +0 -646
  516. package/tools/kimodo/local-output.mjs +0 -265
  517. package/tools/kimodo/measure-pose.mjs +0 -65
  518. package/tools/kimodo/measure-preserve.mjs +0 -899
  519. package/tools/kimodo/measure-waypoints.mjs +0 -55
  520. package/tools/kimodo/mlx-generate.py +0 -111
  521. package/tools/kimodo/pose-constraints.mjs +0 -248
  522. package/tools/kimodo/preserve-mask.mjs +0 -543
  523. package/tools/kimodo/read-npz.mjs +0 -189
  524. package/tools/kimodo/run-edit-on-box.mjs +0 -155
  525. package/tools/kimodo/run-sequence-on-box.mjs +0 -178
  526. package/tools/kimodo/runner.mjs +0 -284
  527. package/tools/kimodo/seam-batch.mjs +0 -77
  528. package/tools/kimodo/setup-local.mjs +0 -30
  529. package/tools/kimodo/setup-on-box.sh +0 -339
  530. package/tools/kimodo/soma77-to-cskel27.mjs +0 -305
  531. package/tools/morphgs/assets/gen_truth.py +0 -39
  532. package/tools/morphgs/assets/mesh_ori_rig.txt +0 -27
  533. package/tools/morphgs/assets/playback-check.mjs +0 -30
  534. package/tools/morphgs/demo-gate.sh +0 -26
  535. package/tools/morphgs/fbx2morphgs.mjs +0 -68
  536. package/tools/morphgs/morphgs-to-cskel27.mjs +0 -105
  537. package/tools/morphgs/patches/preprocess_src-none-mode.patch +0 -76
  538. package/tools/morphgs/setup-on-cluster.sh +0 -126
  539. package/tools/projflow/driver.py +0 -1069
  540. package/tools/projflow/generate.mjs +0 -805
  541. package/tools/projflow/hml22-to-cskel27.mjs +0 -735
  542. package/tools/projflow/line-edit-job.mjs +0 -547
  543. package/tools/projflow/replay.mjs +0 -452
  544. package/tools/projflow/runner.mjs +0 -269
  545. package/tools/projflow/service.mjs +0 -742
  546. package/tools/track/DESIGN.md +0 -513
  547. package/tools/track/backfill-provenance.mjs +0 -116
  548. package/tools/track/budget.mjs +0 -8
  549. package/tools/track/check-rig.mjs +0 -209
  550. package/tools/track/diagnostics.schema.json +0 -60
  551. package/tools/track/export-rig.mjs +0 -256
  552. package/tools/track/fallback.mjs +0 -7
  553. package/tools/track/fk-parity-fixture.mjs +0 -161
  554. package/tools/track/gate.mjs +0 -349
  555. package/tools/track/masks.mjs +0 -318
  556. package/tools/track/metrics.mjs +0 -201
  557. package/tools/track/publish-obs.mjs +0 -108
  558. package/tools/track/py/check_env.py +0 -61
  559. package/tools/track/py/eval_lr.py +0 -229
  560. package/tools/track/py/lr_viterbi.py +0 -294
  561. package/tools/track/py/masks.py +0 -405
  562. package/tools/track/py/objective.py +0 -382
  563. package/tools/track/py/rig.py +0 -508
  564. package/tools/track/py/scene.py +0 -326
  565. package/tools/track/py/test_joint_indices.py +0 -123
  566. package/tools/track/py/test_lr_viterbi.py +0 -195
  567. package/tools/track/py/test_masks.py +0 -229
  568. package/tools/track/py/test_rig.py +0 -288
  569. package/tools/track/py/test_scene.py +0 -354
  570. package/tools/track/py/test_track.py +0 -328
  571. package/tools/track/py/track.py +0 -411
  572. package/tools/track/remote.mjs +0 -140
  573. package/tools/track/rig-dump.mjs +0 -227
  574. package/tools/track/run-box-tests.mjs +0 -14
  575. package/tools/track/run-box.mjs +0 -168
  576. package/tools/track/setup-box.sh +0 -59
  577. package/tools/track/study-2d.mjs +0 -728
  578. /package/src/{ardy → motion}/collision-blockers.js +0 -0
  579. /package/src/{ardy → motion}/ground.js +0 -0
  580. /package/src/{ardy → motion}/ik-drag.js +0 -0
  581. /package/src/{ardy → motion}/ik-key-json.js +0 -0
  582. /package/src/{ardy → motion}/ik.js +0 -0
  583. /package/src/{ardy → motion}/physics-surface.js +0 -0
  584. /package/src/{ardy → motion}/playback-clock.js +0 -0
  585. /package/src/{ardy → motion}/range-pin.js +0 -0
@@ -0,0 +1,135 @@
1
+ // CLIProxyAPI, the one provider CozyClay adds to pi's built-ins (moved out of providers.mjs for #pi-login).
2
+ // Its login is pi's own api-key login shape: the flow asks for the server address and then the key, and
3
+ // returns one api-key credential that carries the address as provider env (CLIPROXY_BASE_URL), so an app
4
+ // opened from the Dock needs no shell variable. The address is read when the registry is built: the
5
+ // environment wins, then the stored credential's env, then the default local port.
6
+
7
+ const CLIPROXY_DEFAULT_BASE = "http://127.0.0.1:8317";
8
+ const BASE_ENV = "CLIPROXY_BASE_URL";
9
+ const KEY_ENV = "CLIPROXY_API_KEY";
10
+
11
+ /** A server address as typed, cleaned: http(s), no trailing slash, no trailing /v1 (the models add it). */
12
+ function cleanBase(value) {
13
+ const url = new URL(String(value ?? "").trim());
14
+ if (!["http:", "https:"].includes(url.protocol)) throw new Error("The server address must start with http:// or https://.");
15
+ return url.href.replace(/\/+$/, "").replace(/\/v1$/, "");
16
+ }
17
+
18
+ /** The address the registry builds CLIProxyAPI's models on: environment, then the stored credential, then the default. */
19
+ export function cliproxyBase({ env = process.env, credential } = {}) {
20
+ const fromEnv = typeof env?.[BASE_ENV] === "string" ? env[BASE_ENV].trim() : "";
21
+ return (fromEnv || credential?.env?.[BASE_ENV] || CLIPROXY_DEFAULT_BASE).replace(/\/$/, "");
22
+ }
23
+
24
+ /** pi ApiKeyAuth: login asks for the address, then the key; resolve prefers the stored key, then CLIPROXY_API_KEY. */
25
+ function cliproxyAuth() {
26
+ return {
27
+ name: "CLIProxyAPI server and key",
28
+ login: async (interaction) => {
29
+ interaction.signal.throwIfAborted();
30
+ const address = await interaction.prompt({ type: "text", message: "CLIProxyAPI server address", placeholder: CLIPROXY_DEFAULT_BASE });
31
+ const base = cleanBase(address.trim() || CLIPROXY_DEFAULT_BASE);
32
+ interaction.signal.throwIfAborted();
33
+ const key = await interaction.prompt({ type: "secret", message: "CLIProxyAPI key" });
34
+ interaction.signal.throwIfAborted();
35
+ return { type: "api_key", key, env: { [BASE_ENV]: base } };
36
+ },
37
+ resolve: async ({ ctx, credential, signal }) => {
38
+ signal.throwIfAborted();
39
+ if (credential?.key) return { auth: { apiKey: credential.key }, env: credential.env, source: "stored credential" };
40
+ const key = await ctx.env(KEY_ENV);
41
+ return key ? { auth: { apiKey: key }, source: KEY_ENV } : undefined;
42
+ },
43
+ };
44
+ }
45
+
46
+ export async function cliproxyProvider({ baseUrl, env = process.env } = {}) {
47
+ const base = (baseUrl ?? cliproxyBase({ env })).replace(/\/$/, "");
48
+ const [{ createProvider }, { openaiProvider }, { anthropicProvider }, openaiResponsesApi, anthropicMessagesApi] = await Promise.all([
49
+ import("@earendil-works/pi-ai"),
50
+ import("@earendil-works/pi-ai/providers/openai"),
51
+ import("@earendil-works/pi-ai/providers/anthropic"),
52
+ import("@earendil-works/pi-ai/api/openai-responses"),
53
+ import("@earendil-works/pi-ai/api/anthropic-messages"),
54
+ ]);
55
+ const ids = new Set();
56
+ const models = [
57
+ ...openaiProvider().getModels().map((model) => ({ ...model, provider: "cliproxy", baseUrl: `${base}/v1` })),
58
+ ...anthropicProvider().getModels().map((model) => ({
59
+ ...model,
60
+ provider: "cliproxy",
61
+ baseUrl: base,
62
+ // CLIProxyAPI's /v1/messages rejects messages[].output_config and thinking.block_binding, so keep
63
+ // adaptive thinking (forceAdaptiveThinking) but drop the mid-conversation output_config shape.
64
+ compat: { ...model.compat, supportsMidConvoEffort: false },
65
+ })),
66
+ ].filter((model) => !ids.has(model.id) && ids.add(model.id));
67
+ return createProvider({
68
+ id: "cliproxy",
69
+ name: "CLIProxyAPI",
70
+ baseUrl: base,
71
+ auth: { apiKey: cliproxyAuth() },
72
+ models,
73
+ api: { "openai-responses": openaiResponsesApi, "anthropic-messages": anthropicMessagesApi },
74
+ });
75
+ }
76
+
77
+ /** The proxy's own /v1/models (id -> owner), fetched once per registry; a failed fetch is asked again next time. */
78
+ export async function registryLiveCliproxy(models, apiKey, state) {
79
+ if (!state.cliproxyFetch) {
80
+ state.cliproxyFetch = Promise.resolve().then(async () => {
81
+ const provider = models?.getProvider?.("cliproxy");
82
+ const model = provider?.getModels?.().find((entry) => entry.api === "openai-responses");
83
+ if (!model?.baseUrl || !apiKey) throw new Error("CLIProxyAPI is not configured");
84
+ const response = await fetch(`${model.baseUrl}/models`, {
85
+ headers: { Authorization: `Bearer ${apiKey}` },
86
+ signal: AbortSignal.timeout(5000),
87
+ });
88
+ if (!response.ok) throw new Error(`CLIProxyAPI models request failed: ${response.status}`);
89
+ const body = await response.json();
90
+ // id -> owned_by: the owner is how CLIProxyAPI routes a model (openai, anthropic, or an
91
+ // openai-compatibility upstream name), so it picks the wire format for models pi does not know.
92
+ return new Map((Array.isArray(body?.data) ? body.data : [])
93
+ .map((entry) => typeof entry === "string" ? [entry, undefined] : [entry?.id, typeof entry?.owned_by === "string" ? entry.owned_by : undefined])
94
+ .filter(([id]) => id));
95
+ }).catch(() => { state.cliproxyFetch = null; return null; });
96
+ }
97
+ return state.cliproxyFetch;
98
+ }
99
+
100
+ const CLIPROXY_NON_CHAT = /^gpt-image-|-auto-review$/;
101
+
102
+ /** A live CLIProxyAPI model pi's catalogue does not know yet (#400 follow-up), built on the newest
103
+ * catalogue model of the same family and wire format: `owned_by: openai` rides openai-responses,
104
+ * `owned_by: anthropic` rides anthropic-messages. Other owners (openai-compatibility upstreams such as
105
+ * opencode-go) are left out: they need upstream-specific headers the proxy does not pass through. */
106
+ export function cliproxyLiveModel(id, owner, knownModels) {
107
+ if (CLIPROXY_NON_CHAT.test(id)) return null;
108
+ const api = owner === "openai" ? "openai-responses" : owner === "anthropic" ? "anthropic-messages" : null;
109
+ if (!api) return null;
110
+ const candidates = knownModels.filter((model) => model.api === api);
111
+ const family = owner === "anthropic" ? id.match(/^claude-[a-z]+/)?.[0] : id.match(/^gpt-\d+/)?.[0];
112
+ const sameFamily = family ? candidates.filter((model) => model.id.startsWith(family)) : [];
113
+ const template = [...(sameFamily.length ? sameFamily : candidates)].sort((a, b) => b.id.localeCompare(a.id, undefined, { numeric: true }))[0];
114
+ if (!template) return null;
115
+ // The 5-5 claude generation rejects thinking {type:"disabled"}; a null "off" makes pi-ai send no thinking block (#723).
116
+ const generation = owner === "anthropic" ? id.match(/-(\d+)-(\d+)$/) : null;
117
+ if (generation && Number(generation[1]) >= 5) return { ...template, id, name: id, thinkingLevelMap: { ...template.thinkingLevelMap, off: null } };
118
+ return { ...template, id, name: id };
119
+ }
120
+
121
+ export function extendCliproxyProvider(provider, live, state) {
122
+ if (state.extendedProviders.has(provider)) return false;
123
+ const getModels = provider.getModels.bind(provider);
124
+ const knownModels = getModels();
125
+ const known = new Set(knownModels.map((model) => model.id));
126
+ const additions = [...live]
127
+ .filter(([id]) => !known.has(id))
128
+ .map(([id, owner]) => cliproxyLiveModel(id, owner, knownModels))
129
+ .filter(Boolean)
130
+ .sort((a, b) => b.id.localeCompare(a.id, undefined, { numeric: true }));
131
+ if (!additions.length) return false;
132
+ provider.getModels = () => [...getModels(), ...additions];
133
+ state.extendedProviders.add(provider);
134
+ return true;
135
+ }
@@ -1,4 +1,5 @@
1
1
  import { PROVIDERS } from "./providers.mjs";
2
+ import { readCredentials, removeCredential, setCredential } from "./provider-keys.mjs";
2
3
 
3
4
  const codexId = "openai-codex";
4
5
  const apiProvider = (id) => PROVIDERS.find((provider) => provider.id === id);
@@ -44,7 +45,12 @@ function rawOAuth(credential) {
44
45
  };
45
46
  }
46
47
 
47
- export function createCredentialStore({ auth, keys, env = process.env }) {
48
+ // pi's CredentialStore for every provider pi can log in to (#pi-login): ChatGPT stays in codex-auth's file (its
49
+ // id_token carries the plan and account the image routes need), a bare api key in providers.json, and what pi's
50
+ // login flows return beyond that (OAuth tokens, api keys with provider env) in auth.json. A credential that
51
+ // lands in one file is removed from the other, so one provider never has two.
52
+
53
+ export function createCredentialStore({ auth, keys, env = process.env, credentialFile = { read: readCredentials, set: setCredential, remove: removeCredential } }) {
48
54
  const configured = (id) => apiProvider(id);
49
55
  return {
50
56
  async read(providerId, { signal } = {}) {
@@ -54,8 +60,11 @@ export function createCredentialStore({ auth, keys, env = process.env }) {
54
60
  return mapOAuth(stored);
55
61
  }
56
62
  const provider = configured(providerId);
57
- if (!provider) return undefined;
58
- const key = envValue(provider, env) ?? keys.readKeys()[providerId];
63
+ const fromEnv = provider && envValue(provider, env);
64
+ if (fromEnv) return { type: "api_key", key: fromEnv };
65
+ const credential = credentialFile.read()[providerId];
66
+ if (credential) return credential;
67
+ const key = keys.readKeys()[providerId];
59
68
  return key ? { type: "api_key", key } : undefined;
60
69
  },
61
70
  async list({ signal } = {}) {
@@ -68,8 +77,13 @@ export function createCredentialStore({ auth, keys, env = process.env }) {
68
77
  if (stored) result.push({ providerId: provider.id, type: "oauth", source: "chatgpt" });
69
78
  continue;
70
79
  }
71
- const source = envValue(provider, env) ? "env" : saved[provider.id] ? "file" : null;
72
- if (source) result.push({ providerId: provider.id, type: "api_key", source });
80
+ const source = envValue(provider, env) ? "env" : saved[provider.id] || credentialFile.read()[provider.id] ? "file" : null;
81
+ if (source) result.push({ providerId: provider.id, type: credentialFile.read()[provider.id]?.type ?? "api_key", source });
82
+ }
83
+ // providers outside the curated list that a pi login stored
84
+ const listed = new Set(result.map((entry) => entry.providerId));
85
+ for (const [providerId, credential] of Object.entries({ ...Object.fromEntries(Object.keys(saved).map((id) => [id, { type: "api_key" }])), ...credentialFile.read() })) {
86
+ if (!listed.has(providerId)) result.push({ providerId, type: credential.type, source: "file" });
73
87
  }
74
88
  return result;
75
89
  },
@@ -79,14 +93,23 @@ export function createCredentialStore({ auth, keys, env = process.env }) {
79
93
  const next = await fn(current);
80
94
  if (next === undefined) return current;
81
95
  if (providerId === codexId) await auth.writeStored(rawOAuth(next));
82
- else keys.setKey(providerId, next.key);
96
+ else if (next.type === "api_key" && !next.env) {
97
+ keys.setKey(providerId, next.key);
98
+ credentialFile.remove(providerId);
99
+ } else {
100
+ credentialFile.set(providerId, next);
101
+ if (keys.readKeys()[providerId]) keys.removeKey(providerId);
102
+ }
83
103
  return next;
84
104
  }, signal);
85
105
  },
86
106
  async delete(providerId, { signal } = {}) {
87
107
  await serialized(providerId, async () => {
88
108
  if (providerId === codexId) await auth.logout();
89
- else if (configured(providerId)) keys.removeKey(providerId);
109
+ else {
110
+ if (keys.readKeys()[providerId]) keys.removeKey(providerId);
111
+ credentialFile.remove(providerId);
112
+ }
90
113
  }, signal);
91
114
  },
92
115
  };
@@ -1,6 +1,7 @@
1
1
  import { Value } from "typebox/value";
2
2
  import { STUDIO_TOOL_LABELS } from "../../src/studio-agent-protocol.js";
3
3
  import { toTypeBox } from "./schema-to-typebox.mjs";
4
+ import { toolArgumentPreparer } from "./tool-arguments.mjs";
4
5
 
5
6
 
6
7
  function dataUrlImage(dataUrl) {
@@ -28,12 +29,14 @@ function formatToolError(error) {
28
29
  /** Convert CozyClay's handler catalogue into pi AgentTools. */
29
30
  export function toAgentTools(tools, { emit, signal, labels = STUDIO_TOOL_LABELS } = {}) {
30
31
  return (Array.isArray(tools) ? tools : []).map((tool) => {
31
- const parameters = toTypeBox(tool.parameters || { type: "object", properties: {}, additionalProperties: false });
32
+ const schema = tool.parameters || { type: "object", properties: {}, additionalProperties: false }, parameters = toTypeBox(schema);
32
33
  return {
33
34
  name: tool.name,
34
35
  label: labels?.[tool.name] ?? tool.label ?? tool.name.replaceAll("_", " "),
35
36
  description: tool.description || tool.name,
36
37
  parameters,
38
+ // Before pi's own check: array texts parsed, unknown fields named (tool-arguments.mjs).
39
+ prepareArguments: toolArgumentPreparer(tool.name, schema),
37
40
  // pi's low-level `Agent` calls `execute(id, params, signal, onUpdate)`;
38
41
  // pi's `AgentHarness` calls `execute(id, params, onUpdate, toolContext,
39
42
  // invocation, context)` where `context.abortSignal` is the REAL abort
@@ -54,3 +54,35 @@ export function removeKey(id) {
54
54
  return next;
55
55
  }
56
56
 
57
+ // Credentials pi's login flows return that are more than a bare key (#pi-login): OAuth tokens (Claude Pro/Max,
58
+ // GitHub Copilot, xAI, Kimi, OpenRouter) and api-key credentials that carry provider env (CLIProxyAPI's server
59
+ // address). They are kept as pi's canonical Credential, one per provider id, in auth.json beside providers.json;
60
+ // a bare api key stays in providers.json as before. credential-store.mjs decides which file a write goes to.
61
+ function authPath() {
62
+ return join(configDir(), "auth.json");
63
+ }
64
+
65
+ /** { [providerId]: Credential } from auth.json; a missing or unreadable file is {}. */
66
+ export function readCredentials() {
67
+ try {
68
+ const value = JSON.parse(readFileSync(authPath(), "utf8"));
69
+ if (!value || typeof value !== "object" || Array.isArray(value)) return {};
70
+ return Object.fromEntries(Object.entries(value).filter(([id, entry]) => id && entry && typeof entry === "object" && ["api_key", "oauth"].includes(entry.type)));
71
+ } catch {
72
+ return {};
73
+ }
74
+ }
75
+
76
+ export function setCredential(id, credential) {
77
+ const next = { ...readCredentials(), [id]: credential };
78
+ writeSecureJson(authPath(), next);
79
+ return next;
80
+ }
81
+
82
+ export function removeCredential(id) {
83
+ const current = readCredentials();
84
+ if (!(id in current)) return current;
85
+ delete current[id];
86
+ writeSecureJson(authPath(), current);
87
+ return current;
88
+ }
@@ -0,0 +1,110 @@
1
+ // pi's provider login (the coding agent's /login) over HTTP for the agent panel (#pi-login). The sidecar
2
+ // runs `models.login(providerId, type, interaction)` itself; the interaction is a queue the panel reads:
3
+ // { type: "prompt", promptId, prompt: { type: text|secret|select|manual_code, message, placeholder?, options? } }
4
+ // { type: "event", event: pi AuthEvent (info, auth_url, device_code, progress) }
5
+ // { type: "prompt_closed", promptId } pi withdrew a prompt (e.g. a callback beat the pasted code)
6
+ // { type: "done", ok, message? }
7
+ // and answers with the prompt's value. Nothing the user types is ever put in the queue. pi persists the
8
+ // credential through the registry's CredentialStore (credential-store.mjs); onSignedIn rebuilds the registry.
9
+ import { randomUUID } from "node:crypto";
10
+
11
+ const WAIT_MS = 25_000;
12
+ const KEEP_MS = 60_000;
13
+
14
+ /** The login methods pi offers for each provider, as the panel lists them (subscriptions first). */
15
+ export async function loginOptions(models, { chatgpt } = {}) {
16
+ const options = [];
17
+ for (const provider of models.getProviders()) {
18
+ const auth = provider.auth ?? {};
19
+ // ChatGPT keeps CozyClay's own sign-in: its id_token carries the plan and account the image routes read.
20
+ const methods = provider.id === "openai-codex"
21
+ ? [{ type: "chatgpt", label: "Sign in with ChatGPT (Plus/Pro)", subscription: true }]
22
+ : [
23
+ ...(auth.oauth?.login ? [{ type: "oauth", label: auth.oauth.loginLabel || auth.oauth.name, subscription: !!auth.oauth.isSubscription }] : []),
24
+ ...(auth.apiKey?.login ? [{ type: "api_key", label: auth.apiKey.name }] : []),
25
+ ];
26
+ if (!methods.length) continue;
27
+ const check = provider.id === "openai-codex"
28
+ ? (chatgpt?.() ? { type: "oauth", source: "ChatGPT" } : undefined)
29
+ : await models.checkAuth(provider.id).catch(() => undefined);
30
+ options.push({ id: provider.id, name: provider.name ?? provider.id, methods, connected: !!check, via: check?.type ?? null, source: check?.source ?? null });
31
+ }
32
+ const rank = (entry) => (entry.connected ? 0 : entry.methods.some((method) => method.subscription) ? 1 : 2);
33
+ return options.sort((a, b) => rank(a) - rank(b) || a.name.localeCompare(b.name));
34
+ }
35
+
36
+ export function createLoginFlows({ getModels, onSignedIn }) {
37
+ const flows = new Map();
38
+ const wake = (flow) => { for (const waiter of [...flow.waiters]) waiter(); };
39
+ const push = (flow, entry) => { flow.events.push(entry); wake(flow); };
40
+ const finish = (flow, entry) => {
41
+ flow.done = true;
42
+ for (const pending of flow.pending.values()) pending.reject(new Error("The login ended."));
43
+ flow.pending.clear();
44
+ push(flow, entry);
45
+ setTimeout(() => flows.delete(flow.id), KEEP_MS).unref?.();
46
+ };
47
+
48
+ function start(providerId, type) {
49
+ const flow = { id: randomUUID(), providerId, type, events: [], waiters: new Set(), pending: new Map(), controller: new AbortController(), done: false };
50
+ const interaction = {
51
+ signal: flow.controller.signal,
52
+ prompt(prompt) {
53
+ return new Promise((resolve, reject) => {
54
+ const promptId = randomUUID();
55
+ flow.pending.set(promptId, { resolve, reject });
56
+ const { signal, ...shown } = prompt;
57
+ push(flow, { type: "prompt", promptId, prompt: shown });
58
+ signal?.addEventListener("abort", () => {
59
+ if (!flow.pending.delete(promptId)) return;
60
+ push(flow, { type: "prompt_closed", promptId });
61
+ reject(signal.reason ?? new Error("The prompt was withdrawn."));
62
+ }, { once: true });
63
+ });
64
+ },
65
+ notify(event) { push(flow, { type: "event", event }); },
66
+ };
67
+ flows.set(flow.id, flow);
68
+ void (async () => {
69
+ try {
70
+ await (await getModels()).login(providerId, type, interaction);
71
+ await onSignedIn?.(providerId);
72
+ finish(flow, { type: "done", ok: true });
73
+ } catch (error) {
74
+ finish(flow, { type: "done", ok: false, message: flow.controller.signal.aborted ? "The login was cancelled." : String(error?.message || error || "The login failed.") });
75
+ }
76
+ })();
77
+ return flow.id;
78
+ }
79
+
80
+ /** Entries after `after`, waiting up to WAIT_MS for the first new one; null for an unknown flow. */
81
+ async function read(id, after = 0) {
82
+ const flow = flows.get(id);
83
+ if (!flow) return null;
84
+ if (flow.events.length <= after && !flow.done) {
85
+ await new Promise((resolve) => {
86
+ const done = () => { clearTimeout(timer); flow.waiters.delete(done); resolve(); };
87
+ const timer = setTimeout(done, WAIT_MS);
88
+ flow.waiters.add(done);
89
+ });
90
+ }
91
+ return { events: flow.events.slice(after), next: flow.events.length, done: flow.done };
92
+ }
93
+
94
+ function answer(id, promptId, value) {
95
+ const pending = flows.get(id)?.pending.get(promptId);
96
+ if (!pending) return false;
97
+ flows.get(id).pending.delete(promptId);
98
+ pending.resolve(String(value ?? ""));
99
+ return true;
100
+ }
101
+
102
+ function cancel(id) {
103
+ const flow = flows.get(id);
104
+ if (!flow || flow.done) return false;
105
+ flow.controller.abort();
106
+ return true;
107
+ }
108
+
109
+ return { start, read, answer, cancel };
110
+ }
@@ -0,0 +1,99 @@
1
+ // Provider routes (moved out of agent-routes.mjs):
2
+ // GET /agent/providers the curated providers' sign-in state (MCP and older panels)
3
+ // PUT /agent/providers/:id { key }: store a bare api key for a curated provider
4
+ // DELETE /agent/providers/:id forget a curated provider's key
5
+ // and pi's own login (#pi-login), which the panel uses for every provider pi ships:
6
+ // GET /agent/logins each provider's login methods and whether it is connected
7
+ // POST /agent/logins { providerId, type: "oauth" | "api_key" } -> { flowId }
8
+ // GET /agent/logins/:flowId?after=n the flow's prompts and events after n (long poll, provider-login.mjs)
9
+ // POST /agent/logins/:flowId/answer { promptId, value }
10
+ // DELETE /agent/logins/:flowId cancel the flow
11
+ // POST /agent/logout/:providerId pi's logout for that provider
12
+ // Every credential change calls onChange, so the model registry is rebuilt with what was just stored.
13
+ import { createLoginFlows, loginOptions } from "./provider-login.mjs";
14
+
15
+ const KEY_PATH = /^\/agent\/providers\/([^/]+)$/;
16
+ const FLOW_PATH = /^\/agent\/logins\/([^/]+)(\/answer)?$/;
17
+ const LOGOUT_PATH = /^\/agent\/logout\/([^/]+)$/;
18
+
19
+ /** Returns the route handler for one sidecar; `getModels` is its registry, `onChange` rebuilds it. */
20
+ export function createProviderRoutes({ auth, getModels, onChange, json, readBody }) {
21
+ const flows = createLoginFlows({ getModels, onSignedIn: () => onChange?.() });
22
+ return async function handleProviderRoute(req, res, path) {
23
+ if (path === "/agent/providers" && req.method === "GET") {
24
+ const { providerStatus } = await import("./providers.mjs");
25
+ try { json(res, 200, { providers: providerStatus({ auth }) }); }
26
+ catch { json(res, 500, { error: "provider credentials unavailable" }); }
27
+ return true;
28
+ }
29
+ const keyMatch = KEY_PATH.exec(path);
30
+ if (keyMatch && ["PUT", "DELETE"].includes(req.method)) return keyRoute(req, res, keyMatch[1]);
31
+ if (path === "/agent/logins" && req.method === "GET") {
32
+ try { json(res, 200, { providers: await loginOptions(await getModels(), { chatgpt: () => !!auth.status?.().signedIn }) }); }
33
+ catch (error) { json(res, 502, { error: String(error?.message || error) }); }
34
+ return true;
35
+ }
36
+ if (path === "/agent/logins" && req.method === "POST") {
37
+ let value;
38
+ try { value = await readBody(req); } catch { json(res, 400, { error: "invalid request" }); return true; }
39
+ const models = await getModels();
40
+ const provider = models.getProvider(String(value?.providerId ?? ""));
41
+ const method = value?.type === "oauth" ? provider?.auth?.oauth : value?.type === "api_key" ? provider?.auth?.apiKey : null;
42
+ if (!provider || !method?.login || provider.id === "openai-codex") { json(res, 400, { error: "That provider has no such login." }); return true; }
43
+ json(res, 200, { flowId: flows.start(provider.id, value.type) });
44
+ return true;
45
+ }
46
+ const flowMatch = FLOW_PATH.exec(path);
47
+ if (flowMatch && !flowMatch[2] && req.method === "GET") {
48
+ const after = Number(new URL(req.url, "http://127.0.0.1").searchParams.get("after")) || 0;
49
+ const page = await flows.read(flowMatch[1], after);
50
+ if (!page) json(res, 404, { error: "login not found" }); else json(res, 200, page);
51
+ return true;
52
+ }
53
+ if (flowMatch && flowMatch[2] && req.method === "POST") {
54
+ let value;
55
+ try { value = await readBody(req); } catch { json(res, 400, { error: "invalid request" }); return true; }
56
+ if (!flows.answer(flowMatch[1], String(value?.promptId ?? ""), value?.value)) { json(res, 409, { error: "That prompt is no longer open." }); return true; }
57
+ json(res, 200, { ok: true });
58
+ return true;
59
+ }
60
+ if (flowMatch && !flowMatch[2] && req.method === "DELETE") {
61
+ json(res, 200, { ok: flows.cancel(flowMatch[1]) });
62
+ return true;
63
+ }
64
+ const logoutMatch = LOGOUT_PATH.exec(path);
65
+ if (logoutMatch && req.method === "POST") {
66
+ if (logoutMatch[1] === "openai-codex") { json(res, 400, { error: "Use Sign out for ChatGPT." }); return true; }
67
+ try { await (await getModels()).logout(logoutMatch[1]); }
68
+ catch (error) { json(res, 500, { error: String(error?.message || error) }); return true; }
69
+ await onChange?.();
70
+ json(res, 200, { ok: true });
71
+ return true;
72
+ }
73
+ return false;
74
+ };
75
+
76
+ async function keyRoute(req, res, id) {
77
+ const { PROVIDERS } = await import("./providers.mjs");
78
+ const keys = await import("./provider-keys.mjs");
79
+ const { createCredentialStore } = await import("./credential-store.mjs");
80
+ if (!PROVIDERS.some((provider) => provider.id === id)) { json(res, 404, { error: "provider not found" }); return true; }
81
+ const credentials = createCredentialStore({ auth, keys });
82
+ let key;
83
+ if (req.method === "PUT") {
84
+ if (id === "openai-codex") { json(res, 400, { error: "Use ChatGPT sign-in for OpenAI Codex." }); return true; }
85
+ try {
86
+ const value = await readBody(req);
87
+ if (typeof value?.key !== "string" || !value.key.trim()) throw new Error("Invalid key.");
88
+ key = value.key.trim();
89
+ } catch { json(res, 400, { error: "a non-empty API key is required" }); return true; }
90
+ }
91
+ try {
92
+ if (req.method === "PUT") await credentials.modify(id, async () => ({ type: "api_key", key }));
93
+ else await credentials.delete(id);
94
+ } catch { json(res, 500, { error: "provider credentials could not be saved" }); return true; }
95
+ await onChange?.();
96
+ json(res, 200, { ok: true });
97
+ return true;
98
+ }
99
+ }
@@ -2,6 +2,9 @@ import { AsyncLocalStorage } from "node:async_hooks";
2
2
  import * as defaultAuth from "../codex-auth.mjs";
3
3
  import * as defaultKeys from "./provider-keys.mjs";
4
4
  import { createCredentialStore } from "./credential-store.mjs";
5
+ import { readCredentials } from "./provider-keys.mjs";
6
+ import { cliproxyBase, cliproxyProvider, extendCliproxyProvider, registryLiveCliproxy } from "./cliproxy-provider.mjs";
7
+ export { cliproxyLiveModel } from "./cliproxy-provider.mjs";
5
8
 
6
9
  // #379 (F2 pass 5 finding 4): pi never forwards a per-request `fetch` to the
7
10
  // codex API module — `pi-agent-core`'s `createRequestOptions` (the options
@@ -69,37 +72,6 @@ const factories = {
69
72
  openrouter: "openrouterProvider",
70
73
  };
71
74
 
72
- async function cliproxyProvider({ baseUrl, env = process.env } = {}) {
73
- const base = (baseUrl ?? env.CLIPROXY_BASE_URL ?? "http://127.0.0.1:8317").replace(/\/$/, "");
74
- const [{ createProvider, envApiKeyAuth }, { openaiProvider }, { anthropicProvider }, openaiResponsesApi, anthropicMessagesApi] = await Promise.all([
75
- import("@earendil-works/pi-ai"),
76
- import("@earendil-works/pi-ai/providers/openai"),
77
- import("@earendil-works/pi-ai/providers/anthropic"),
78
- import("@earendil-works/pi-ai/api/openai-responses"),
79
- import("@earendil-works/pi-ai/api/anthropic-messages"),
80
- ]);
81
- const ids = new Set();
82
- const models = [
83
- ...openaiProvider().getModels().map((model) => ({ ...model, provider: "cliproxy", baseUrl: `${base}/v1` })),
84
- ...anthropicProvider().getModels().map((model) => ({
85
- ...model,
86
- provider: "cliproxy",
87
- baseUrl: base,
88
- // CLIProxyAPI's /v1/messages rejects messages[].output_config and thinking.block_binding, so keep
89
- // adaptive thinking (forceAdaptiveThinking) but drop the mid-conversation output_config shape.
90
- compat: { ...model.compat, supportsMidConvoEffort: false },
91
- })),
92
- ].filter((model) => !ids.has(model.id) && ids.add(model.id));
93
- return createProvider({
94
- id: "cliproxy",
95
- name: "CLIProxyAPI",
96
- baseUrl: base,
97
- auth: { apiKey: envApiKeyAuth("CLIProxyAPI key", ["CLIPROXY_API_KEY"]) },
98
- models,
99
- api: { "openai-responses": openaiResponsesApi, "anthropic-messages": anthropicMessagesApi },
100
- });
101
- }
102
-
103
75
  const providerConfig = (id) => PROVIDERS.find((provider) => provider.id === id);
104
76
 
105
77
  export async function loadProvider(id, options = {}) {
@@ -159,7 +131,7 @@ async function registryLiveCatalogue(models, codex) {
159
131
  state.fetch = Promise.resolve().then(() => codex.listModels()).then((result) => {
160
132
  state.models = liveModelsCodex(result);
161
133
  return state.models;
162
- }).catch(() => []);
134
+ }).catch(() => { state.fetch = null; return []; });
163
135
  }
164
136
  return state.fetch;
165
137
  }
@@ -176,75 +148,17 @@ async function registerLiveCodexModels(models, codex) {
176
148
  return live;
177
149
  }
178
150
 
179
- async function registryLiveCliproxy(models, apiKey) {
180
- const state = catalogueState(models);
181
- if (!state.cliproxyFetch) {
182
- state.cliproxyFetch = Promise.resolve().then(async () => {
183
- const provider = models?.getProvider?.("cliproxy");
184
- const model = provider?.getModels?.().find((entry) => entry.api === "openai-responses");
185
- if (!model?.baseUrl || !apiKey) throw new Error("CLIProxyAPI is not configured");
186
- const response = await fetch(`${model.baseUrl}/models`, {
187
- headers: { Authorization: `Bearer ${apiKey}` },
188
- signal: AbortSignal.timeout(5000),
189
- });
190
- if (!response.ok) throw new Error(`CLIProxyAPI models request failed: ${response.status}`);
191
- const body = await response.json();
192
- // id -> owned_by: the owner is how CLIProxyAPI routes a model (openai, anthropic, or an
193
- // openai-compatibility upstream name), so it picks the wire format for models pi does not know.
194
- return new Map((Array.isArray(body?.data) ? body.data : [])
195
- .map((entry) => typeof entry === "string" ? [entry, undefined] : [entry?.id, typeof entry?.owned_by === "string" ? entry.owned_by : undefined])
196
- .filter(([id]) => id));
197
- }).catch(() => null);
198
- }
199
- return state.cliproxyFetch;
200
- }
201
-
202
- const CLIPROXY_NON_CHAT = /^gpt-image-|-auto-review$/;
203
-
204
- /** A live CLIProxyAPI model pi's catalogue does not know yet (#400 follow-up), built on the newest
205
- * catalogue model of the same family and wire format: `owned_by: openai` rides openai-responses,
206
- * `owned_by: anthropic` rides anthropic-messages. Other owners (openai-compatibility upstreams such as
207
- * opencode-go) are left out: they need upstream-specific headers the proxy does not pass through. */
208
- export function cliproxyLiveModel(id, owner, knownModels) {
209
- if (CLIPROXY_NON_CHAT.test(id)) return null;
210
- const api = owner === "openai" ? "openai-responses" : owner === "anthropic" ? "anthropic-messages" : null;
211
- if (!api) return null;
212
- const candidates = knownModels.filter((model) => model.api === api);
213
- const family = owner === "anthropic" ? id.match(/^claude-[a-z]+/)?.[0] : id.match(/^gpt-\d+/)?.[0];
214
- const sameFamily = family ? candidates.filter((model) => model.id.startsWith(family)) : [];
215
- const template = [...(sameFamily.length ? sameFamily : candidates)].sort((a, b) => b.id.localeCompare(a.id, undefined, { numeric: true }))[0];
216
- if (!template) return null;
217
- // The 5-5 claude generation rejects thinking {type:"disabled"}; a null "off" makes pi-ai send no thinking block (#723).
218
- const generation = owner === "anthropic" ? id.match(/-(\d+)-(\d+)$/) : null;
219
- if (generation && Number(generation[1]) >= 5) return { ...template, id, name: id, thinkingLevelMap: { ...template.thinkingLevelMap, off: null } };
220
- return { ...template, id, name: id };
221
- }
222
-
223
- function extendCliproxyProvider(provider, live, state) {
224
- if (state.extendedProviders.has(provider)) return false;
225
- const getModels = provider.getModels.bind(provider);
226
- const knownModels = getModels();
227
- const known = new Set(knownModels.map((model) => model.id));
228
- const additions = [...live]
229
- .filter(([id]) => !known.has(id))
230
- .map(([id, owner]) => cliproxyLiveModel(id, owner, knownModels))
231
- .filter(Boolean)
232
- .sort((a, b) => b.id.localeCompare(a.id, undefined, { numeric: true }));
233
- if (!additions.length) return false;
234
- provider.getModels = () => [...getModels(), ...additions];
235
- state.extendedProviders.add(provider);
236
- return true;
237
- }
238
-
239
151
  export async function createModels({ credentials, auth = defaultAuth, keys = defaultKeys, env = process.env, codexBaseUrl, cliproxyBaseUrl } = {}) {
240
152
  const { createModels: createPiModels } = await import("@earendil-works/pi-ai");
241
153
  const store = credentials ?? createCredentialStore({ auth, keys, env });
242
154
  const models = createPiModels({ credentials: store });
243
155
  for (const provider of PROVIDERS) {
244
- const baseUrl = provider.id === "openai-codex" ? codexBaseUrl : provider.id === "cliproxy" ? cliproxyBaseUrl : undefined;
245
- const loaded = await loadProvider(provider.id, { baseUrl, env });
246
- models.setProvider(loaded);
156
+ const baseUrl = provider.id === "openai-codex" ? codexBaseUrl : provider.id === "cliproxy" ? cliproxyBaseUrl ?? cliproxyBase({ env, credential: await store.read("cliproxy").catch(() => undefined) }) : undefined;
157
+ models.setProvider(await loadProvider(provider.id, { baseUrl, env }));
247
158
  }
159
+ // every other provider pi ships, so each of pi's logins (subscriptions and api keys) has a provider to land on (#pi-login)
160
+ const { builtinProviders } = await import("@earendil-works/pi-ai/providers/all");
161
+ for (const provider of builtinProviders()) if (!models.getProvider(provider.id)) models.setProvider(provider);
248
162
  return models;
249
163
  }
250
164
 
@@ -254,8 +168,9 @@ export async function resolveModel(requested, options = {}) {
254
168
  const slash = value.indexOf("/");
255
169
  const provider = slash === -1 ? "openai-codex" : value.slice(0, slash);
256
170
  const modelId = slash === -1 ? value : value.slice(slash + 1);
257
- if (!providerConfig(provider) || !modelId) throw unknownModel(requested);
171
+ if (!modelId) throw unknownModel(requested);
258
172
  const models = options.models ?? await createModels(options);
173
+ if (!providerConfig(provider) && !models.getProvider?.(provider)) throw unknownModel(requested);
259
174
  const model = models.getModel(provider, modelId);
260
175
  if (!model) throw unknownModel(requested);
261
176
  return { provider, modelId, model };
@@ -483,7 +398,7 @@ export async function listAgentModels({ models, codex, auth = defaultAuth, keys
483
398
  list = [...list].sort(astraFirst);
484
399
  } else if (provider.id === "cliproxy") {
485
400
  if (signedIn) {
486
- const live = await registryLiveCliproxy(resolvedModels, authResult?.auth?.apiKey);
401
+ const live = await registryLiveCliproxy(resolvedModels, authResult?.auth?.apiKey, catalogueState(resolvedModels));
487
402
  if (live) {
488
403
  // The proxy's own /v1/models is the lineup: register what pi's catalogue has not caught up
489
404
  // with on the shared registry, so the turn route resolves the same ids the dropdown shows.
@@ -496,6 +411,13 @@ export async function listAgentModels({ models, codex, auth = defaultAuth, keys
496
411
  }
497
412
  providers.push({ id: provider.id, label: provider.label, signedIn, authSource, models: list });
498
413
  }
414
+ // a provider outside the curated list joins the picker once a pi login stored its credential here (#pi-login); ambient
415
+ // credentials (an AWS profile, cloud ADC files) alone do not list it, so the picker shows what the author connected
416
+ const stored = new Set([...Object.keys(readCredentials()), ...Object.keys(saved)]);
417
+ for (const provider of resolvedModels.getProviders?.() ?? []) {
418
+ const check = providerConfig(provider.id) || !stored.has(provider.id) ? undefined : await resolvedModels.checkAuth?.(provider.id).catch(() => undefined);
419
+ if (check) providers.push({ id: provider.id, label: provider.name ?? provider.id, signedIn: true, authSource: check.type === "oauth" || /stored/i.test(check.source ?? "") ? "file" : "env", models: catalogModels(resolvedModels, provider.id, getSupportedThinkingLevels) });
420
+ }
499
421
  // The flat union is what the panel's dropdown reads today (`models[].id`);
500
422
  // with six providers in play the id has to be the provider/id key so two
501
423
  // providers' same-named model never collide there, while each provider's