cozyclay 2.1.0 → 3.0.0
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.
- package/CHANGELOG.md +65 -0
- package/README.md +12 -30
- package/bin/agent/agent-routes.mjs +40 -113
- package/bin/agent/agent-runner.mjs +9 -10
- package/bin/agent/cliproxy-provider.mjs +135 -0
- package/bin/agent/credential-store.mjs +30 -7
- package/bin/agent/pi-tools.mjs +4 -1
- package/bin/agent/provider-keys.mjs +32 -0
- package/bin/agent/provider-login.mjs +110 -0
- package/bin/agent/provider-routes.mjs +99 -0
- package/bin/agent/providers.mjs +19 -97
- package/bin/agent/script-tool.mjs +17 -4
- package/bin/agent/studio-motion-reference.mjs +23 -0
- package/bin/agent/studio-prompt.mjs +1 -1
- package/bin/agent/studio-rebase-text.mjs +32 -0
- package/bin/agent/studio-rebase.mjs +26 -39
- package/bin/agent/studio-tools.mjs +9 -28
- package/bin/agent/tool-arguments.mjs +49 -0
- package/bin/agent/turn-frames.mjs +66 -0
- package/bin/codex-auth.mjs +9 -2
- package/bin/cozyclay.mjs +10 -116
- package/bin/live/cli.mjs +22 -6
- package/bin/mcp-runtime.mjs +2 -12
- package/bin/package-signature.mjs +0 -4
- package/dist/app/index.html +7 -6
- package/dist/assets/EXRLoader-CvVIvGVE.js +1 -0
- package/dist/assets/analytics-C9sUnndd.js +1 -0
- package/dist/assets/app-CCh--1Hc.js +5087 -0
- package/dist/assets/app-f-fkV5uP.css +1 -0
- package/dist/assets/compile-worker-CkxiD6lH.js +5 -0
- package/dist/assets/fflate.module-KbGj5-H6.js +5 -0
- package/dist/assets/{first-shot-handoff-BlT33CGf.js → first-shot-handoff-CC4xe4HM.js} +1 -1
- package/dist/assets/{landing-BSesrCpL.js → landing-DjHD8u7k.js} +1 -1
- package/dist/assets/scene-asset-sync-Hx_FzdOH.js +20 -0
- package/dist/assets/scene-asset-sync-_S8NQl5B.css +1 -0
- package/dist/assets/workflow-C4yKH1p4.js +179 -0
- package/dist/cozyclay-package.json +1 -1
- package/dist/index.html +3 -3
- package/dist/llms.txt +14 -0
- package/dist/sw.js +3 -6
- package/dist/workflow/index.html +5 -4
- package/mcp/LIVE-PROTOCOL.md +7 -47
- package/mcp/README.md +16 -26
- package/mcp/live-hub.mjs +6 -10
- package/mcp/package.json +1 -3
- package/mcp/tool-handlers.mjs +4 -168
- package/package.json +3 -22
- package/src/App.jsx +174 -2639
- package/src/agent-progress.js +79 -0
- package/src/agent-turn-mark.js +30 -0
- package/src/analytics.js +1 -19
- package/src/app-context.js +5 -0
- package/src/app-stage.jsx +2 -34
- package/src/asset-pane.jsx +6 -5
- package/src/camera-block.js +21 -0
- package/src/camera-direction.js +120 -0
- package/src/character-kind.js +3 -9
- package/src/command-bus.js +8 -6
- package/src/commands/cast.js +5 -34
- package/src/commands/elements/character.js +1 -1
- package/src/commands/elements/motion.js +1 -6
- package/src/commands/elements/object.js +6 -2
- package/src/commands/elements/shot.js +4 -1
- package/src/commands/elements.js +38 -14
- package/src/commands/index.js +3 -1
- package/src/commands/kits.js +37 -0
- package/src/commands/motion-timing.js +57 -0
- package/src/commands/motion.js +9 -152
- package/src/commands/motion3-compile.js +65 -0
- package/src/commands/motion3.js +207 -0
- package/src/commands/objects.js +6 -4
- package/src/commands/shot.js +49 -11
- package/src/commands/stage.js +14 -1
- package/src/commands/view.js +4 -1
- package/src/document-mirrors.js +42 -0
- package/src/document-store.js +4 -0
- package/src/domains/cast.js +19 -194
- package/src/domains/motion/motion-range-pin-rules.js +1 -1
- package/src/domains/motion/take-source.js +51 -0
- package/src/domains/motion.js +60 -1838
- package/src/domains/scenes.js +12 -55
- package/src/domains/shots.js +2 -2
- package/src/domains/stage.js +4 -2
- package/src/draft-ghost.js +46 -0
- package/src/draft-ghost.jsx +129 -0
- package/src/dualview.jsx +4 -3
- package/src/effects/EffectsLayer.jsx +112 -0
- package/src/effects/effect-meshes.js +232 -0
- package/src/effects/effect-record.js +103 -0
- package/src/effects/emitter.js +59 -0
- package/src/effects/simulate.js +261 -0
- package/src/effects/state.js +59 -0
- package/src/execution-telemetry.js +2 -3
- package/src/first-success-guide.jsx +5 -5
- package/src/ghost-set.js +257 -0
- package/src/held-object-edit.jsx +113 -0
- package/src/held-object-pose.js +175 -0
- package/src/hierarchy-model.js +1 -1
- package/src/hierarchy-panel.jsx +1 -1
- package/src/imported-mesh.jsx +103 -0
- package/src/kits/held.js +84 -0
- package/src/kits/index.js +70 -0
- package/src/kits/smoking.js +57 -0
- package/src/light-blockers.js +93 -0
- package/src/motion/beat-board-model.js +84 -0
- package/src/motion/beat-board.css +124 -0
- package/src/motion/beat-board.jsx +59 -0
- package/src/motion/center-of-mass.js +66 -0
- package/src/motion/fix-collisions.js +850 -0
- package/src/motion/physics-review.js +118 -0
- package/src/{ardy → motion}/physics-support.js +1 -76
- package/src/motion/rig-snapshot.js +22 -0
- package/src/motion/take-segments.jsx +140 -0
- package/src/{ardy → motion}/timeline-coordinates.js +0 -21
- package/src/{ardy → motion}/timeline.css +1 -2
- package/src/{ardy → motion}/timeline.jsx +48 -419
- package/src/motion/waypoints.js +76 -0
- package/src/motion3/beats.js +83 -0
- package/src/motion3/body.js +158 -0
- package/src/motion3/bones.js +47 -0
- package/src/motion3/carried.js +47 -0
- package/src/motion3/compile-job.js +75 -0
- package/src/motion3/compile-worker.js +10 -0
- package/src/motion3/compiler/anchors.js +105 -0
- package/src/motion3/compiler/arm-orient.js +128 -0
- package/src/motion3/compiler/channels.js +125 -0
- package/src/motion3/compiler/compile.js +205 -0
- package/src/motion3/compiler/gait-rhythm.js +97 -0
- package/src/motion3/compiler/grip.js +171 -0
- package/src/motion3/compiler/head-steady.js +48 -0
- package/src/motion3/compiler/hold.js +134 -0
- package/src/motion3/compiler/idle.js +113 -0
- package/src/motion3/compiler/limbs.js +170 -0
- package/src/motion3/compiler/math.js +72 -0
- package/src/motion3/compiler/pose.js +162 -0
- package/src/motion3/compiler/quat.js +49 -0
- package/src/motion3/compiler/skeleton.js +74 -0
- package/src/motion3/compiler/validate-common.js +32 -0
- package/src/motion3/compiler/validate-goals.js +118 -0
- package/src/motion3/compiler/validate-objects.js +143 -0
- package/src/motion3/compiler/validate-parts.js +116 -0
- package/src/motion3/compiler/validate.js +122 -0
- package/src/motion3/compiler/vocab.js +73 -0
- package/src/motion3/compiler/walk-pelvis.js +275 -0
- package/src/motion3/compiler/walk.js +207 -0
- package/src/motion3/compiler/wrist-clamp.js +32 -0
- package/src/motion3/delta.js +47 -0
- package/src/motion3/describe.js +174 -0
- package/src/motion3/docs.js +27 -0
- package/src/motion3/edit-carry.js +21 -0
- package/src/motion3/fix/ease.js +16 -0
- package/src/motion3/fix/ground.js +37 -0
- package/src/motion3/fix/index.js +52 -0
- package/src/motion3/fix/push.js +71 -0
- package/src/motion3/fix/reach.js +46 -0
- package/src/motion3/fix/retime.js +79 -0
- package/src/motion3/fix/self.js +211 -0
- package/src/motion3/fix/slide.js +70 -0
- package/src/motion3/fix/turn.js +51 -0
- package/src/motion3/follow.js +39 -0
- package/src/motion3/free-motion.js +173 -0
- package/src/motion3/grip-choice.js +130 -0
- package/src/motion3/hold-plan.js +157 -0
- package/src/motion3/json-patch.js +77 -0
- package/src/motion3/key-aim.js +105 -0
- package/src/motion3/key-edit.js +82 -0
- package/src/motion3/lenient.js +127 -0
- package/src/motion3/object-keys.js +236 -0
- package/src/motion3/object-pose.js +182 -0
- package/src/motion3/object-tracks.js +12 -0
- package/src/motion3/pipeline.js +102 -0
- package/src/motion3/playback.js +51 -0
- package/src/motion3/probes.js +91 -0
- package/src/motion3/qa/body-checks.js +34 -0
- package/src/motion3/qa/check-names.js +14 -0
- package/src/motion3/qa/checks.js +121 -0
- package/src/motion3/qa/contact-checks.js +51 -0
- package/src/motion3/qa/explain.js +80 -0
- package/src/motion3/qa/fmt.js +5 -0
- package/src/motion3/qa/foot-checks.js +72 -0
- package/src/motion3/qa/geometry.js +77 -0
- package/src/motion3/qa/goals.js +191 -0
- package/src/motion3/qa/ground.js +60 -0
- package/src/motion3/qa/hints-contact.js +179 -0
- package/src/motion3/qa/hints-goal.js +160 -0
- package/src/motion3/qa/hints-knee.js +24 -0
- package/src/motion3/qa/hints-motion.js +209 -0
- package/src/motion3/qa/hints-object.js +91 -0
- package/src/motion3/qa/index.js +42 -0
- package/src/motion3/qa/key-checks.js +82 -0
- package/src/motion3/qa/near.js +96 -0
- package/src/motion3/qa/object-checks.js +135 -0
- package/src/motion3/qa/patch.js +161 -0
- package/src/motion3/qa/severity.js +38 -0
- package/src/motion3/qa/spec-checks.js +65 -0
- package/src/motion3/qa/style.js +218 -0
- package/src/motion3/qa/wrist-checks.js +69 -0
- package/src/motion3/qa/wrist-obs.js +60 -0
- package/src/motion3/refs.js +120 -0
- package/src/motion3/repair-tune.js +121 -0
- package/src/motion3/repair.js +83 -0
- package/src/motion3/report.js +67 -0
- package/src/motion3/rigs/index.js +14 -0
- package/src/motion3/rigs/x-bot-tpose-body.json +1 -0
- package/src/motion3/rigs/x-bot-tpose.json +1 -0
- package/src/motion3/rigs/y-bot-tpose-body.json +1 -0
- package/src/motion3/rigs/y-bot-tpose.json +1 -0
- package/src/motion3/scene.js +60 -0
- package/src/motion3/schema/brief.js +89 -0
- package/src/motion3/schema/fields.js +44 -0
- package/src/motion3/schema/spec-examples.js +46 -0
- package/src/motion3/schema/topics.js +111 -0
- package/src/motion3/schema/types.js +147 -0
- package/src/motion3/search.js +117 -0
- package/src/motion3/second-hand.js +112 -0
- package/src/motion3/skills/basic.js +82 -0
- package/src/motion3/skills/book.js +76 -0
- package/src/motion3/skills/car.js +145 -0
- package/src/motion3/skills/frames-auto.js +29 -0
- package/src/motion3/skills/index.js +83 -0
- package/src/motion3/skills/info.js +65 -0
- package/src/motion3/skills/smoke-kit.js +109 -0
- package/src/motion3/skills/smoke.js +160 -0
- package/src/motion3/spec-store.js +59 -0
- package/src/motion3/take-edit.js +155 -0
- package/src/motion3/take.js +63 -0
- package/src/motion3/timeline.js +127 -0
- package/src/motion3/views.js +121 -0
- package/src/motion3/walk-plan.js +119 -0
- package/src/object-catalog.jsx +7 -1
- package/src/object-colors.js +98 -0
- package/src/object-gizmo-parts.js +115 -0
- package/src/object-gizmo.jsx +37 -122
- package/src/object-glow.js +139 -0
- package/src/object-hidden.js +33 -0
- package/src/object-look.js +196 -0
- package/src/object-size.js +10 -0
- package/src/object-travel.js +71 -6
- package/src/panels/CameraPanel.jsx +7 -7
- package/src/panels/HdrEnvironmentControls.jsx +59 -0
- package/src/panels/LightPanel.jsx +25 -4
- package/src/panels/ObjectLookPanel.jsx +88 -0
- package/src/panels/PoseImagePanel.jsx +1 -1
- package/src/panels/PosePanel.jsx +2 -17
- package/src/panels/RigControlPanel.jsx +5 -53
- package/src/panels/SubjectBox.jsx +4 -1
- package/src/panels/details.css +14 -0
- package/src/posestudio.jsx +4 -27
- package/src/project-browser.jsx +3 -4
- package/src/project-resources.js +18 -31
- package/src/project.js +13 -104
- package/src/props.jsx +25 -280
- package/src/rail-dot-drag.js +23 -0
- package/src/render-passes.js +10 -0
- package/src/resource-status.jsx +1 -0
- package/src/result-modal.jsx +4 -19
- package/src/room.jsx +20 -12
- package/src/scene-assets.js +8 -6
- package/src/scene-light-place.js +72 -0
- package/src/scene-lights.jsx +148 -0
- package/src/scene-objects.js +31 -139
- package/src/scenes.js +15 -105
- package/src/semantic-edit.js +2 -5
- package/src/set-pieces.jsx +183 -0
- package/src/settings-menu.jsx +3 -11
- package/src/shell/BottomDock.jsx +11 -53
- package/src/shell/CompositionGuideButton.jsx +20 -0
- package/src/shell/DetailsSlot.jsx +16 -200
- package/src/shell/GhostsMenuGroup.jsx +74 -0
- package/src/shell/LibrarySlot.jsx +1 -3
- package/src/shell/MenuBar.jsx +3 -3
- package/src/shell/PreferencesDialog.jsx +15 -264
- package/src/shell/PreferencesSlot.jsx +2 -8
- package/src/shell/ShotPreviewHeader.jsx +17 -0
- package/src/shell/TopBar.jsx +5 -83
- package/src/shell/ViewportToolbar.jsx +23 -93
- package/src/shell/agent-glass.css +19 -6
- package/src/shell/details-selection.js +42 -0
- package/src/shell/ghosts-menu.css +22 -0
- package/src/shell/glass-regions.css +29 -3
- package/src/shell/preferences.css +6 -1
- package/src/shell/viewport.css +12 -0
- package/src/shot-authoring.js +4 -7
- package/src/soft-shadows.js +37 -0
- package/src/stage-attach-frames.js +12 -12
- package/src/stage-canvas-rigs.jsx +3 -3
- package/src/stage-capture.jsx +42 -8
- package/src/stage-characters.jsx +22 -216
- package/src/stage-editor-gizmos.jsx +6 -6
- package/src/stage-environment.js +93 -0
- package/src/stage-hdr-builtins.js +107 -0
- package/src/stage-hdr-image.js +116 -0
- package/src/stage-hdr-library.js +70 -0
- package/src/stage-hdr-map.js +94 -0
- package/src/stage-hdr.js +130 -0
- package/src/stage-lighting.js +149 -0
- package/src/stage-occlusion.js +210 -0
- package/src/stage-sky.jsx +159 -0
- package/src/stage-startup-defaults.js +5 -9
- package/src/studio-actions.js +12 -14
- package/src/studio-agent-commands.js +46 -16
- package/src/studio-agent-motion.js +9 -9
- package/src/studio-agent-protocol.js +46 -73
- package/src/studio-app-binding.js +29 -45
- package/src/studio-asset-list.js +17 -0
- package/src/studio-contact-sheet.js +37 -5
- package/src/studio-elements.js +27 -7
- package/src/studio-entity-bounds.js +18 -0
- package/src/studio-error-codes.js +8 -0
- package/src/studio-export-framing.js +152 -0
- package/src/studio-lighting-check.js +103 -0
- package/src/studio-motion-visual.js +150 -0
- package/src/studio-schema-errors.js +80 -0
- package/src/studio-support-gap.js +74 -0
- package/src/styles.css +1 -19
- package/src/timeline-defaults.js +5 -0
- package/src/timeline-extent.js +8 -20
- package/src/tutorial-walk.js +17 -0
- package/src/ui-scrub.js +1 -1
- package/src/use-shot-camera-tracks.js +12 -0
- package/src/view-ghosts.js +97 -0
- package/src/view-ghosts.jsx +49 -0
- package/src/workflow/AgentPanel.jsx +47 -166
- package/src/workflow/AgentProgressCard.jsx +107 -0
- package/src/workflow/AgentReplyText.jsx +37 -0
- package/src/workflow/AgentTurnSteps.jsx +84 -0
- package/src/workflow/LoginPanel.jsx +140 -0
- package/src/workflow/WorkflowBuilder.jsx +7 -23
- package/src/workflow/agent-client.js +54 -51
- package/src/workflow/agent-panel.css +71 -1
- package/src/workflow/agent-progress-card.css +167 -0
- package/src/workflow/agent-progress-view.js +47 -0
- package/src/workflow/agent-turn-steps.css +113 -0
- package/src/workflow/agent-turn.js +191 -0
- package/src/workflow/login-client.js +34 -0
- package/src/workflow/motion-input.js +18 -31
- package/src/workflow/node-schema.js +1 -1
- package/src/workflow/reply-markdown.js +69 -0
- package/src/workflow/scene-asset-sync.js +8 -8
- package/tools/agent-session-stats.mjs +61 -0
- package/tools/dev-full.mjs +5 -56
- package/tools/eslint-baseline.json +12 -13
- package/tools/headless-studio.mjs +85 -0
- package/tools/knip-baseline.json +0 -179
- package/tools/loc-baseline.json +33 -83
- package/tools/motion3/extract-body.mjs +103 -0
- package/tools/motion3/extract-rig.mjs +85 -0
- package/tools/process-supervisor.mjs +0 -88
- package/tools/run-tests.mjs +9 -124
- package/bin/agent/motion-runtime.mjs +0 -76
- package/dist/ardy/cskel27-rest.json +0 -557
- package/dist/assets/analytics-B41FmPYI.js +0 -1
- package/dist/assets/app-Do-Og5Pu.css +0 -1
- package/dist/assets/app-DwP-tl2x.js +0 -4920
- package/dist/assets/config-Bx7pYsD9.js +0 -1
- package/dist/assets/config-DeoFTYzb.css +0 -1
- package/dist/assets/demo-Dt0qZfS7.js +0 -1
- package/dist/assets/scene-asset-sync-BCWDJB1d.js +0 -19
- package/dist/assets/scene-asset-sync-DjJ_cJ2R.css +0 -1
- package/dist/assets/ticket-jqMhqL88.js +0 -1
- package/dist/assets/workflow-Br-K7Srs.js +0 -179
- package/dist/demo/walk-then-stop.npz +0 -0
- package/mcp/ardy-prompts.mjs +0 -218
- package/src/ardy/auto-fix-panel.css +0 -108
- package/src/ardy/auto-physics.js +0 -1194
- package/src/ardy/client.js +0 -153
- package/src/ardy/convert.js +0 -313
- package/src/ardy/cskel27-neutral.js +0 -39
- package/src/ardy/cskel27.js +0 -77
- package/src/ardy/export.js +0 -165
- package/src/ardy/fix-collisions.js +0 -2257
- package/src/ardy/foot-lock.js +0 -230
- package/src/ardy/motion-calibration.js +0 -118
- package/src/ardy/motion-edit.js +0 -213
- package/src/ardy/motion-url.js +0 -55
- package/src/ardy/mp4-duration.js +0 -102
- package/src/ardy/npz.js +0 -595
- package/src/ardy/physics-panel.css +0 -21
- package/src/ardy/physics-panel.jsx +0 -89
- package/src/ardy/physics-refine.js +0 -111
- package/src/ardy/physics-review.js +0 -567
- package/src/ardy/physics-temporal.js +0 -54
- package/src/ardy/platform-fit-panel.css +0 -64
- package/src/ardy/platform-fit-panel.jsx +0 -41
- package/src/ardy/platform-fit.js +0 -326
- package/src/ardy/playback.js +0 -711
- package/src/ardy/pose-pin.js +0 -97
- package/src/ardy/prompt-clips.js +0 -30
- package/src/ardy/retime.js +0 -211
- package/src/ardy/root-drop.js +0 -369
- package/src/ardy/timeline-resize.js +0 -13
- package/src/ardy/to-cskel27.js +0 -225
- package/src/ardy/trim.js +0 -33
- package/src/ardy/waypoints.js +0 -369
- package/src/ardy-bridge-contract.js +0 -281
- package/src/domains/motion/motion-video-capture.js +0 -664
- package/src/i2v-motion-client.js +0 -77
- package/src/i2v-motion-studio.jsx +0 -180
- package/src/line-edit.js +0 -1348
- package/src/motion/generation.js +0 -105
- package/src/motion-readiness-ui.jsx +0 -67
- package/src/motion-readiness.js +0 -40
- package/src/motion-resources.js +0 -177
- package/src/motion-store.js +0 -65
- package/src/motion-trail.js +0 -601
- package/src/multimodel-ingest.js +0 -394
- package/src/panels/PromptBlocksPanel.jsx +0 -444
- package/src/panels/VideoCapturePanel.jsx +0 -181
- package/src/pose-extract/browser.js +0 -42
- package/src/pose-extract/detector.js +0 -100
- package/src/pose-extract/fit.js +0 -227
- package/src/pose-extract/image-frame.js +0 -51
- package/src/pose-extract/index.js +0 -58
- package/src/pose-extract/keyframes.js +0 -74
- package/src/pose-extract/landmarks.js +0 -162
- package/src/pose-extract/mirror.js +0 -153
- package/src/pose-extract/take.js +0 -131
- package/src/pose-extract/video-frames.js +0 -91
- package/src/take-recipe.js +0 -165
- package/src/trail-key-conflicts.js +0 -39
- package/src/trail-pick.js +0 -59
- package/src/use-i2v-capture-modal.js +0 -15
- package/tools/ardy/BRIDGE.md +0 -37
- package/tools/ardy/GVHMR-NPZ.md +0 -68
- package/tools/ardy/README.md +0 -124
- package/tools/ardy/__pycache__/cclay_gvhmr_worker.cpython-313.pyc +0 -0
- package/tools/ardy/artifacts.mjs +0 -34
- package/tools/ardy/bridge.mjs +0 -1732
- package/tools/ardy/bvh-cskel27.mjs +0 -1265
- package/tools/ardy/cclay_constrained_generate.py +0 -561
- package/tools/ardy/cclay_gvhmr_worker.py +0 -264
- package/tools/ardy/cclay_motion_edit.py +0 -510
- package/tools/ardy/cclay_sequence_generate.py +0 -705
- package/tools/ardy/cclay_worker.py +0 -304
- package/tools/ardy/dump-npz.py +0 -205
- package/tools/ardy/extract-rest.mjs +0 -299
- package/tools/ardy/extract.mjs +0 -507
- package/tools/ardy/footage.mjs +0 -467
- package/tools/ardy/gvhmr-floor.mjs +0 -102
- package/tools/ardy/gvhmr-to-cskel27.mjs +0 -21
- package/tools/ardy/gvhmr_fastpath.py +0 -168
- package/tools/ardy/gvhmr_trajectory.py +0 -240
- package/tools/ardy/merge-text-encoder.py +0 -160
- package/tools/ardy/mocap-metrics.mjs +0 -200
- package/tools/ardy/mocap-quality-gate.mjs +0 -110
- package/tools/ardy/motion-stabilize.mjs +0 -348
- package/tools/ardy/npz.mjs +0 -414
- package/tools/ardy/pose-to-npz.mjs +0 -106
- package/tools/ardy/prompt-limits.mjs +0 -5
- package/tools/ardy/run-edit-on-box.sh +0 -73
- package/tools/ardy/run-local.mjs +0 -233
- package/tools/ardy/run-on-box.sh +0 -593
- package/tools/ardy/run-sequence-on-box.sh +0 -177
- package/tools/ardy/runners/gvhmr-worker.mjs +0 -206
- package/tools/ardy/runners/index.mjs +0 -18
- package/tools/ardy/runners/local.mjs +0 -455
- package/tools/ardy/runners/proc.mjs +0 -125
- package/tools/ardy/runners/remote.mjs +0 -252
- package/tools/ardy/setup-local.mjs +0 -189
- package/tools/ardy/setup-text-encoder-on-box.sh +0 -93
- package/tools/ardy/setup-text-encoder.py +0 -306
- package/tools/ardy/smpl-cskel27.mjs +0 -178
- package/tools/ardy/visual-qa.mjs +0 -196
- package/tools/ardy/vq-car.mjs +0 -52
- package/tools/bench/EXP3.md +0 -61
- package/tools/bench/cclay_bench_extract_incam.py +0 -125
- package/tools/bench/cclay_bench_extract_obs.py +0 -204
- package/tools/bench/cclay_bench_runner.py +0 -42
- package/tools/bench/cube-contact.mjs +0 -162
- package/tools/bench/exp3.mjs +0 -152
- package/tools/bench/extract-bench-lib.mjs +0 -216
- package/tools/bench/extract-bench.mjs +0 -302
- package/tools/bench/fal-generate.mjs +0 -88
- package/tools/bench/fit/README.md +0 -189
- package/tools/bench/fit/camera.mjs +0 -59
- package/tools/bench/fit/contact.mjs +0 -419
- package/tools/bench/fit/footlock.mjs +0 -234
- package/tools/bench/fit/motion.mjs +0 -85
- package/tools/bench/fit/pin.mjs +0 -27
- package/tools/bench/fit/remote.mjs +0 -105
- package/tools/bench/fit-bench.mjs +0 -126
- package/tools/bench/fit-sanity.mjs +0 -45
- package/tools/bench/metrics.mjs +0 -313
- package/tools/bench/obs/depth.mjs +0 -258
- package/tools/bench/obs/extrinsics.mjs +0 -175
- package/tools/bench/obs/fit_mannequin_betas.py +0 -340
- package/tools/bench/obs/ground.mjs +0 -294
- package/tools/bench/obs/heading.mjs +0 -151
- package/tools/bench/obs/ladder.mjs +0 -555
- package/tools/bench/obs/mannequin-betas.json +0 -124
- package/tools/bench/obs/remote.mjs +0 -83
- package/tools/bench/obs/rest_joints.py +0 -52
- package/tools/bench/obs/ybot-targets.mjs +0 -49
- package/tools/bench/obs-bench.mjs +0 -520
- package/tools/bench/score.mjs +0 -343
- package/tools/bench/summarize.mjs +0 -86
- package/tools/demo-worker/README.md +0 -137
- package/tools/demo-worker/api-client.mjs +0 -233
- package/tools/demo-worker/generate.mjs +0 -301
- package/tools/demo-worker/index.mjs +0 -466
- package/tools/demo-worker/sign-core.mjs +0 -112
- package/tools/demo-worker/sign.mjs +0 -161
- package/tools/gt-render/browser.mjs +0 -159
- package/tools/gt-render/camera-math.mjs +0 -276
- package/tools/gt-render/page.mjs +0 -267
- package/tools/gt-render/render.mjs +0 -514
- package/tools/gt-render/scene-box.mjs +0 -31
- package/tools/gt-render/take-transform.mjs +0 -87
- package/tools/kimodo/compare-seam.mjs +0 -108
- package/tools/kimodo/constraints.mjs +0 -244
- package/tools/kimodo/edit.mjs +0 -259
- package/tools/kimodo/effector-constraints.mjs +0 -200
- package/tools/kimodo/generate.mjs +0 -646
- package/tools/kimodo/local-output.mjs +0 -265
- package/tools/kimodo/measure-pose.mjs +0 -65
- package/tools/kimodo/measure-preserve.mjs +0 -899
- package/tools/kimodo/measure-waypoints.mjs +0 -55
- package/tools/kimodo/mlx-generate.py +0 -111
- package/tools/kimodo/pose-constraints.mjs +0 -248
- package/tools/kimodo/preserve-mask.mjs +0 -543
- package/tools/kimodo/read-npz.mjs +0 -189
- package/tools/kimodo/run-edit-on-box.mjs +0 -155
- package/tools/kimodo/run-sequence-on-box.mjs +0 -178
- package/tools/kimodo/runner.mjs +0 -284
- package/tools/kimodo/seam-batch.mjs +0 -77
- package/tools/kimodo/setup-local.mjs +0 -30
- package/tools/kimodo/setup-on-box.sh +0 -339
- package/tools/kimodo/soma77-to-cskel27.mjs +0 -305
- package/tools/morphgs/assets/gen_truth.py +0 -39
- package/tools/morphgs/assets/mesh_ori_rig.txt +0 -27
- package/tools/morphgs/assets/playback-check.mjs +0 -30
- package/tools/morphgs/demo-gate.sh +0 -26
- package/tools/morphgs/fbx2morphgs.mjs +0 -68
- package/tools/morphgs/morphgs-to-cskel27.mjs +0 -105
- package/tools/morphgs/patches/preprocess_src-none-mode.patch +0 -76
- package/tools/morphgs/setup-on-cluster.sh +0 -126
- package/tools/projflow/driver.py +0 -1069
- package/tools/projflow/generate.mjs +0 -805
- package/tools/projflow/hml22-to-cskel27.mjs +0 -735
- package/tools/projflow/line-edit-job.mjs +0 -547
- package/tools/projflow/replay.mjs +0 -452
- package/tools/projflow/runner.mjs +0 -269
- package/tools/projflow/service.mjs +0 -742
- package/tools/track/DESIGN.md +0 -513
- package/tools/track/backfill-provenance.mjs +0 -116
- package/tools/track/budget.mjs +0 -8
- package/tools/track/check-rig.mjs +0 -209
- package/tools/track/diagnostics.schema.json +0 -60
- package/tools/track/export-rig.mjs +0 -256
- package/tools/track/fallback.mjs +0 -7
- package/tools/track/fk-parity-fixture.mjs +0 -161
- package/tools/track/gate.mjs +0 -349
- package/tools/track/masks.mjs +0 -318
- package/tools/track/metrics.mjs +0 -201
- package/tools/track/publish-obs.mjs +0 -108
- package/tools/track/py/check_env.py +0 -61
- package/tools/track/py/eval_lr.py +0 -229
- package/tools/track/py/lr_viterbi.py +0 -294
- package/tools/track/py/masks.py +0 -405
- package/tools/track/py/objective.py +0 -382
- package/tools/track/py/rig.py +0 -508
- package/tools/track/py/scene.py +0 -326
- package/tools/track/py/test_joint_indices.py +0 -123
- package/tools/track/py/test_lr_viterbi.py +0 -195
- package/tools/track/py/test_masks.py +0 -229
- package/tools/track/py/test_rig.py +0 -288
- package/tools/track/py/test_scene.py +0 -354
- package/tools/track/py/test_track.py +0 -328
- package/tools/track/py/track.py +0 -411
- package/tools/track/remote.mjs +0 -140
- package/tools/track/rig-dump.mjs +0 -227
- package/tools/track/run-box-tests.mjs +0 -14
- package/tools/track/run-box.mjs +0 -168
- package/tools/track/setup-box.sh +0 -59
- package/tools/track/study-2d.mjs +0 -728
- /package/src/{ardy → motion}/collision-blockers.js +0 -0
- /package/src/{ardy → motion}/ground.js +0 -0
- /package/src/{ardy → motion}/ik-drag.js +0 -0
- /package/src/{ardy → motion}/ik-key-json.js +0 -0
- /package/src/{ardy → motion}/ik.js +0 -0
- /package/src/{ardy → motion}/physics-surface.js +0 -0
- /package/src/{ardy → motion}/playback-clock.js +0 -0
- /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
|
-
|
|
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
|
-
|
|
58
|
-
|
|
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
|
|
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
|
|
109
|
+
else {
|
|
110
|
+
if (keys.readKeys()[providerId]) keys.removeKey(providerId);
|
|
111
|
+
credentialFile.remove(providerId);
|
|
112
|
+
}
|
|
90
113
|
}, signal);
|
|
91
114
|
},
|
|
92
115
|
};
|
package/bin/agent/pi-tools.mjs
CHANGED
|
@@ -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
|
|
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
|
+
}
|
package/bin/agent/providers.mjs
CHANGED
|
@@ -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
|
-
|
|
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 (!
|
|
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
|