@herbertgao/pi-extensions 2026.9.20 → 2026.10.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/README.md +13 -8
- package/THIRD_PARTY_NOTICES.md +29 -3
- package/node_modules/@herbertgao/pi-bark/README.md +39 -3
- package/node_modules/@herbertgao/pi-bark/package.json +1 -1
- package/node_modules/@herbertgao/pi-bark/src/index.ts +275 -15
- package/node_modules/@herbertgao/pi-cc-extensions/README.en.md +50 -50
- package/node_modules/@herbertgao/pi-cc-extensions/README.md +7 -7
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/config/config.ts +25 -2
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/config/panel.ts +24 -1
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/compact-thinking.ts +7 -4
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/shell/footer.ts +47 -5
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/shell/mcp-chip.ts +136 -0
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/compact-mode.ts +56 -11
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/default-mode.ts +36 -4
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/mouse/interaction.ts +11 -2
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/codemode.ts +303 -0
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/diff-header.ts +1 -1
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/diff-highlight.ts +36 -0
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/diff-presentation.ts +2 -2
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/diff-write-render.ts +2 -2
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/index.ts +88 -34
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/line-width-safety.ts +6 -5
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/write-execution.ts +1 -1
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/grouping.ts +31 -6
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/message-display.ts +3 -3
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/names.ts +3 -0
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/result.ts +173 -36
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/utils/ansi-text.ts +1 -0
- package/node_modules/@herbertgao/pi-cc-extensions/package.json +6 -4
- package/node_modules/@herbertgao/pi-subagents/CHANGELOG.md +6 -0
- package/node_modules/@herbertgao/pi-subagents/package.json +1 -1
- package/node_modules/@herbertgao/pi-subagents/src/mention-clone.ts +3 -2
- package/node_modules/@herbertgao/sol-pi/CONTRIBUTING.md +22 -0
- package/node_modules/@herbertgao/sol-pi/README.md +36 -17
- package/node_modules/@herbertgao/sol-pi/THIRD_PARTY_NOTICES.md +10 -6
- package/node_modules/@herbertgao/sol-pi/agents-install.md +15 -12
- package/node_modules/@herbertgao/sol-pi/docs/compatibility.md +24 -8
- package/node_modules/@herbertgao/sol-pi/docs/configuration.md +2 -2
- package/node_modules/@herbertgao/sol-pi/docs/star-history.md +18 -0
- package/node_modules/@herbertgao/sol-pi/package.json +4 -2
- package/node_modules/@herbertgao/sol-pi/scripts/check-sol-pi-config.mjs +1 -1
- package/node_modules/@herbertgao/sol-pi/src/sol-pi/config.ts +1 -1
- package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/action-fusion/file-queue.ts +23 -4
- package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/action-fusion/index.ts +32 -1
- package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/action-fusion/then-run.ts +3 -2
- package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/observation-pack/observation.ts +1 -1
- package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/online-context-compact/economics.ts +27 -7
- package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/online-context-compact/extension.ts +137 -41
- package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/online-context-compact/index.ts +1 -0
- package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/online-context-compact/plan.ts +2 -1
- package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/online-context-compact/projection.ts +58 -0
- package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/online-context-compact/state.ts +28 -6
- package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/online-context-compact/tools.ts +2 -1
- package/node_modules/@herbertgao/sol-pi/src/sol-pi/runtime-paths.ts +15 -2
- package/node_modules/@juicesharp/rpiv-ask-user-question/package.json +2 -2
- package/node_modules/@pi-plugins/fast-mode/README.md +7 -1
- package/node_modules/@pi-plugins/fast-mode/dist/index.d.mts.map +1 -1
- package/node_modules/@pi-plugins/fast-mode/dist/index.mjs +7 -1
- package/node_modules/@pi-plugins/fast-mode/dist/index.mjs.map +1 -1
- package/node_modules/@pi-plugins/fast-mode/package.json +5 -5
- package/node_modules/pi-antigravity/README.md +3 -0
- package/node_modules/pi-antigravity/package.json +8 -8
- package/node_modules/pi-antigravity/src/auth/oauth.ts +6 -3
- package/node_modules/pi-antigravity/src/index.ts +114 -110
- package/node_modules/pi-antigravity/src/stream/stream.ts +1 -1
- package/node_modules/pi-antigravity/src/utils/util.ts +18 -1
- package/node_modules/pi-multi-account/CHANGELOG.md +20 -0
- package/node_modules/pi-multi-account/README.md +46 -8
- package/node_modules/pi-multi-account/config-file-transaction.ts +42 -0
- package/node_modules/pi-multi-account/index.ts +255 -269
- package/node_modules/pi-multi-account/model-pins.ts +36 -0
- package/node_modules/pi-multi-account/package.json +9 -7
- package/node_modules/pi-multi-account/slot-proxy-auth.ts +12 -2
- package/node_modules/pi-multi-account/slot-proxy.ts +27 -5
- package/node_modules/pi-web-access/CHANGELOG.md +17 -0
- package/node_modules/pi-web-access/README.md +6 -3
- package/node_modules/pi-web-access/brave-rate-limit.ts +170 -0
- package/node_modules/pi-web-access/brave.ts +28 -11
- package/node_modules/pi-web-access/dist/index.js +202 -33
- package/node_modules/pi-web-access/gemini-search.ts +2 -5
- package/node_modules/pi-web-access/index.ts +13 -7
- package/node_modules/pi-web-access/openai-search.ts +24 -10
- package/node_modules/pi-web-access/package.json +1 -1
- package/node_modules/pi-web-access/summary-review.ts +8 -2
- package/node_modules/{@herbertgao/resume-from → resume-from}/README.md +16 -12
- package/node_modules/{@herbertgao/resume-from → resume-from}/dist/adapters/claude-code/adapter.d.ts +0 -5
- package/node_modules/{@herbertgao/resume-from → resume-from}/dist/adapters/claude-code/adapter.js +2 -3
- package/node_modules/{@herbertgao/resume-from → resume-from}/dist/adapters/claude-code/contract.d.ts +2 -2
- package/node_modules/{@herbertgao/resume-from → resume-from}/dist/adapters/claude-code/reader.d.ts +3 -0
- package/node_modules/{@herbertgao/resume-from → resume-from}/dist/adapters/claude-code/reader.js +27 -15
- package/node_modules/{@herbertgao/resume-from/dist/adapters/pi → resume-from/dist/adapters/claude-code}/redaction.js +16 -3
- package/node_modules/{@herbertgao/resume-from → resume-from}/dist/adapters/claude-code/writer.d.ts +2 -4
- package/node_modules/{@herbertgao/resume-from → resume-from}/dist/adapters/claude-code/writer.js +1 -1
- package/node_modules/{@herbertgao/resume-from → resume-from}/dist/adapters/codex/contract.d.ts +2 -4
- package/node_modules/{@herbertgao/resume-from → resume-from}/dist/adapters/codex/index.js +1 -2
- package/node_modules/{@herbertgao/resume-from → resume-from}/dist/adapters/codex/read.d.ts +16 -0
- package/node_modules/resume-from/dist/adapters/codex/read.js +723 -0
- package/node_modules/{@herbertgao/resume-from/dist/adapters/claude-code → resume-from/dist/adapters/codex}/redaction.js +16 -3
- package/node_modules/{@herbertgao/resume-from → resume-from}/dist/adapters/codex/rollout.d.ts +44 -4
- package/node_modules/{@herbertgao/resume-from → resume-from}/dist/adapters/codex/rollout.js +88 -4
- package/node_modules/{@herbertgao/resume-from → resume-from}/dist/adapters/codex/write.d.ts +2 -2
- package/node_modules/{@herbertgao/resume-from → resume-from}/dist/adapters/codex/write.js +2 -2
- package/node_modules/{@herbertgao/resume-from → resume-from}/dist/adapters/contract.d.ts +5 -1
- package/node_modules/{@herbertgao/resume-from → resume-from}/dist/adapters/pi/adapter.js +10 -6
- package/node_modules/{@herbertgao/resume-from → resume-from}/dist/adapters/pi/contract.d.ts +2 -2
- package/node_modules/{@herbertgao/resume-from/dist/adapters/codex → resume-from/dist/adapters/pi}/redaction.js +16 -3
- package/node_modules/{@herbertgao/resume-from → resume-from}/dist/adapters/pi/serialize.d.ts +2 -4
- package/node_modules/{@herbertgao/resume-from → resume-from}/dist/adapters/pi/serialize.js +2 -2
- package/node_modules/{@herbertgao/resume-from → resume-from}/dist/host/cli/render.d.ts +1 -1
- package/node_modules/{@herbertgao/resume-from → resume-from}/dist/host/cli/render.js +4 -4
- package/node_modules/{@herbertgao/resume-from → resume-from}/dist/host/cli/runner.js +2 -2
- package/node_modules/{@herbertgao/resume-from → resume-from}/dist/host/contract.d.ts +2 -2
- package/node_modules/{@herbertgao/resume-from → resume-from}/dist/host/pi-extension/command.js +31 -8
- package/node_modules/{@herbertgao/resume-from → resume-from}/dist/host/wiring.d.ts +1 -1
- package/node_modules/{@herbertgao/resume-from → resume-from}/dist/host/wiring.js +1 -2
- package/node_modules/{@herbertgao/resume-from → resume-from}/dist/import/confirmation.d.ts +4 -1
- package/node_modules/{@herbertgao/resume-from → resume-from}/dist/import/confirmation.js +2 -2
- package/node_modules/{@herbertgao/resume-from → resume-from}/dist/import/contract.d.ts +8 -6
- package/node_modules/{@herbertgao/resume-from → resume-from}/dist/import/discovery/contract.d.ts +10 -5
- package/node_modules/{@herbertgao/resume-from → resume-from}/dist/import/discovery/finder.d.ts +2 -1
- package/node_modules/resume-from/dist/import/discovery/finder.js +269 -0
- package/node_modules/{@herbertgao/resume-from → resume-from}/dist/import/landing/contract.d.ts +3 -3
- package/node_modules/{@herbertgao/resume-from → resume-from}/dist/import/landing/lander.js +3 -3
- package/node_modules/resume-from/dist/import/pipeline.d.ts +28 -0
- package/node_modules/{@herbertgao/resume-from → resume-from}/dist/import/pipeline.js +31 -11
- package/node_modules/{@herbertgao/resume-from → resume-from}/dist/import/preview/builder.d.ts +2 -2
- package/node_modules/{@herbertgao/resume-from → resume-from}/dist/import/preview/builder.js +9 -5
- package/node_modules/resume-from/dist/import/preview/warnings.d.ts +14 -0
- package/node_modules/{@herbertgao/resume-from → resume-from}/dist/import/preview/warnings.js +26 -20
- package/node_modules/{@herbertgao/resume-from → resume-from}/dist/import/wiring.js +12 -2
- package/node_modules/{@herbertgao/resume-from → resume-from}/dist/platform/repo/contract.d.ts +9 -3
- package/node_modules/{@herbertgao/resume-from → resume-from}/dist/platform/repo/git.d.ts +4 -4
- package/node_modules/{@herbertgao/resume-from → resume-from}/dist/platform/repo/git.js +7 -4
- package/node_modules/resume-from/dist/platform/repo/reader.d.ts +3 -0
- package/node_modules/{@herbertgao/resume-from → resume-from}/dist/platform/repo/reader.js +60 -20
- package/node_modules/{@herbertgao/resume-from → resume-from}/dist/session/contract.d.ts +8 -1
- package/node_modules/{@herbertgao/resume-from → resume-from}/package.json +19 -32
- package/node_modules/{@herbertgao/resume-from → resume-from}/shims/pi/extensions/resume-from.js +2 -2
- package/package.json +17 -17
- package/node_modules/@herbertgao/resume-from/dist/adapters/codex/read.js +0 -270
- package/node_modules/@herbertgao/resume-from/dist/import/discovery/finder.js +0 -129
- package/node_modules/@herbertgao/resume-from/dist/import/pipeline.d.ts +0 -17
- package/node_modules/@herbertgao/resume-from/dist/import/preview/warnings.d.ts +0 -11
- package/node_modules/@herbertgao/resume-from/dist/platform/repo/reader.d.ts +0 -8
- /package/node_modules/{@herbertgao/resume-from → resume-from}/LICENSE +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/adapters/claude-code/contract.js +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/adapters/claude-code/entries.d.ts +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/adapters/claude-code/entries.js +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/adapters/claude-code/index.d.ts +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/adapters/claude-code/index.js +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/adapters/claude-code/layout.d.ts +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/adapters/claude-code/layout.js +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/adapters/claude-code/readback.d.ts +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/adapters/claude-code/readback.js +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/adapters/claude-code/redaction.d.ts +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/adapters/claude-code/validation.d.ts +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/adapters/claude-code/validation.js +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/adapters/codex/contract.js +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/adapters/codex/index.d.ts +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/adapters/codex/readback.d.ts +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/adapters/codex/readback.js +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/adapters/codex/redaction.d.ts +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/adapters/codex/validation.d.ts +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/adapters/codex/validation.js +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/adapters/contract.js +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/adapters/pi/adapter.d.ts +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/adapters/pi/contract.js +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/adapters/pi/format.d.ts +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/adapters/pi/format.js +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/adapters/pi/index.d.ts +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/adapters/pi/index.js +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/adapters/pi/parse.d.ts +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/adapters/pi/parse.js +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/adapters/pi/redaction.d.ts +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/adapters/pi/validate.d.ts +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/adapters/pi/validate.js +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/bin.d.ts +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/bin.js +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/contract.d.ts +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/contract.js +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/host/agents.d.ts +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/host/agents.js +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/host/cli/args.d.ts +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/host/cli/args.js +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/host/cli/contract.d.ts +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/host/cli/contract.js +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/host/cli/index.d.ts +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/host/cli/index.js +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/host/cli/presentation.d.ts +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/host/cli/presentation.js +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/host/cli/runner.d.ts +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/host/contract.js +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/host/entry.d.ts +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/host/entry.js +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/host/index.d.ts +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/host/index.js +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/host/pi-extension/command.d.ts +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/host/pi-extension/contract.d.ts +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/host/pi-extension/contract.js +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/host/pi-extension/index.d.ts +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/host/pi-extension/index.js +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/host/pi-extension/picker.d.ts +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/host/pi-extension/picker.js +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/host/pi-extension/presentation.d.ts +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/host/pi-extension/presentation.js +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/host/pi-extension/register.d.ts +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/host/pi-extension/register.js +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/host/pi-extension/ui.d.ts +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/host/pi-extension/ui.js +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/host/profile.d.ts +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/host/profile.js +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/host/registry.d.ts +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/host/registry.js +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/import/contract.js +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/import/discovery/contract.js +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/import/discovery/errors.d.ts +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/import/discovery/errors.js +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/import/discovery/homes.d.ts +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/import/discovery/homes.js +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/import/discovery/index.d.ts +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/import/discovery/index.js +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/import/discovery/ordering.d.ts +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/import/discovery/ordering.js +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/import/errors.d.ts +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/import/errors.js +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/import/index.d.ts +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/import/index.js +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/import/landing/contract.js +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/import/landing/errors.d.ts +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/import/landing/errors.js +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/import/landing/handover.d.ts +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/import/landing/handover.js +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/import/landing/index.d.ts +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/import/landing/index.js +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/import/landing/lander.d.ts +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/import/landing/marker.d.ts +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/import/landing/marker.js +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/import/preview/contract.d.ts +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/import/preview/contract.js +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/import/preview/format.d.ts +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/import/preview/format.js +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/import/preview/index.d.ts +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/import/preview/index.js +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/import/transfer/contract.d.ts +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/import/transfer/contract.js +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/import/transfer/index.d.ts +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/import/transfer/index.js +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/import/transfer/rules.d.ts +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/import/transfer/rules.js +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/import/wiring.d.ts +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/index.d.ts +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/index.js +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/platform/config/contract.d.ts +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/platform/config/contract.js +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/platform/config/defaults.d.ts +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/platform/config/defaults.js +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/platform/config/errors.d.ts +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/platform/config/errors.js +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/platform/config/index.d.ts +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/platform/config/index.js +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/platform/config/loader.d.ts +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/platform/config/loader.js +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/platform/config/paths.d.ts +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/platform/config/paths.js +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/platform/config/validate.d.ts +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/platform/config/validate.js +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/platform/repo/contract.js +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/platform/repo/index.d.ts +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/platform/repo/index.js +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/platform/store/contract.d.ts +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/platform/store/contract.js +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/platform/store/file-committer.d.ts +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/platform/store/file-committer.js +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/platform/store/index.d.ts +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/platform/store/index.js +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/platform/tokens/contract.d.ts +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/platform/tokens/contract.js +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/platform/tokens/estimator.d.ts +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/platform/tokens/estimator.js +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/platform/tokens/index.d.ts +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/platform/tokens/index.js +0 -0
- /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/session/contract.js +0 -0
|
@@ -28,6 +28,8 @@ import { AsyncLocalStorage } from "node:async_hooks";
|
|
|
28
28
|
import { createPayloadStream } from "./provider-payload-stream.ts";
|
|
29
29
|
import { mutateProxyAuth } from "./auth-file-transaction.ts";
|
|
30
30
|
import { mergeStateDeltas, mutateStateFile } from "./state-file-transaction.ts";
|
|
31
|
+
import { normalizeModelPins, persistModelPin, validModelPinScope } from "./model-pins.ts";
|
|
32
|
+
import { mutateConfigFile } from "./config-file-transaction.ts";
|
|
31
33
|
import { createHash } from "node:crypto";
|
|
32
34
|
import {
|
|
33
35
|
accessSync,
|
|
@@ -46,7 +48,8 @@ import { createServer, type IncomingMessage, type Server, type ServerResponse }
|
|
|
46
48
|
import { homedir } from "node:os";
|
|
47
49
|
import { Readable } from "node:stream";
|
|
48
50
|
import { dirname, join } from "node:path";
|
|
49
|
-
import {
|
|
51
|
+
import { getModel as getHostModel } from "@earendil-works/pi-ai/compat";
|
|
52
|
+
import { builtinProviders } from "@earendil-works/pi-ai/providers/all";
|
|
50
53
|
import { fileURLToPath } from "node:url";
|
|
51
54
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
52
55
|
import {
|
|
@@ -120,33 +123,15 @@ import {
|
|
|
120
123
|
} from "./context-guard.ts";
|
|
121
124
|
|
|
122
125
|
// ---------------------------------------------------------------------------
|
|
123
|
-
// pi-ai OAuth bridge
|
|
126
|
+
// Host-bound pi-ai OAuth bridge
|
|
124
127
|
// ---------------------------------------------------------------------------
|
|
125
|
-
//
|
|
126
|
-
//
|
|
127
|
-
//
|
|
128
|
-
//
|
|
129
|
-
// * pi-coding-agent's extension loader aliases `@earendil-works/pi-ai/oauth` to an
|
|
130
|
-
// empty stub in its own node_modules, so a plain static import yields undefined —
|
|
131
|
-
// the `undefined is not an object (evaluating
|
|
132
|
-
// '_oauth.openaiCodexOAuthProvider.usesCallbackServer')` crash at startup.
|
|
133
|
-
// * pi-ai 0.80 REMOVED the runtime OAuth surface: `dist/oauth.js` is now literally
|
|
134
|
-
// `export {}` (types only), `getModel` moved to `dist/compat.js`, and the OAuth
|
|
135
|
-
// implementations live behind provider factories
|
|
136
|
-
// (`anthropicProvider().auth.oauth`) with a new `login(interaction)` /
|
|
137
|
-
// `refresh(credential)` shape.
|
|
138
|
-
//
|
|
139
|
-
// So we resolve pi-ai on disk ourselves and normalize BOTH eras behind one internal
|
|
140
|
-
// surface. Everything here is best-effort: a pi-ai we cannot adapt degrades to
|
|
141
|
-
// "subscription logins unavailable" and the extension still loads, so API-key
|
|
142
|
-
// accounts keep rotating instead of the whole extension dying at load time.
|
|
143
|
-
//
|
|
144
|
-
// The 0.80+ adaptation follows the approach contributed by @lfoscari in PR #4.
|
|
145
|
-
|
|
128
|
+
// Pi's extension loader binds these public imports to the running host SDK.
|
|
129
|
+
// Never probe a private SDK copy: its provider catalog/auth may differ from Pi's.
|
|
130
|
+
// Supported Pi versions use provider factories; legacy callbacks are adapted below.
|
|
146
131
|
/** Normalized OAuth surface — identical for every supported pi-ai version. */
|
|
147
132
|
type PiAiOauthBridge = {
|
|
148
133
|
/** pi-ai release line this was adapted from, for the debug log. */
|
|
149
|
-
era: "
|
|
134
|
+
era: "provider-factories";
|
|
150
135
|
anthropic: {
|
|
151
136
|
login: (callbacks: any) => Promise<any>;
|
|
152
137
|
refresh: (credentials: any, signal: AbortSignal) => Promise<any>;
|
|
@@ -174,57 +159,6 @@ type PiAiOauthBridge = {
|
|
|
174
159
|
|
|
175
160
|
let piAiOauthBridge: PiAiOauthBridge | undefined;
|
|
176
161
|
let piAiOauthLoadError: string | undefined;
|
|
177
|
-
let piAiPackageRoot: string | undefined | null; // null = searched, not found
|
|
178
|
-
|
|
179
|
-
/**
|
|
180
|
-
* Locate the `@earendil-works/pi-ai` package directory, nearest-first.
|
|
181
|
-
*
|
|
182
|
-
* A git checkout keeps pi-ai in the extension's OWN node_modules, but `npm i` /
|
|
183
|
-
* `pi install` HOIST it next to the package instead
|
|
184
|
-
* (`~/.pi/agent/npm/node_modules/pi-multi-account` +
|
|
185
|
-
* `~/.pi/agent/npm/node_modules/@earendil-works/pi-ai`), and pnpm hides it behind a
|
|
186
|
-
* symlinked store. Probing only the nested path made the extension fail to load on
|
|
187
|
-
* every hoisted install, so walk ancestors the way Node's own resolver does and fall
|
|
188
|
-
* back to require.resolve().
|
|
189
|
-
*/
|
|
190
|
-
export function piAiRootCandidates(
|
|
191
|
-
fromFile: string,
|
|
192
|
-
resolver?: (specifier: string) => string,
|
|
193
|
-
): string[] {
|
|
194
|
-
const candidates: string[] = [];
|
|
195
|
-
let dir = dirname(fromFile);
|
|
196
|
-
for (let depth = 0; depth < 16; depth++) {
|
|
197
|
-
candidates.push(join(dir, "node_modules", "@earendil-works", "pi-ai"));
|
|
198
|
-
const parent = dirname(dir);
|
|
199
|
-
if (parent === dir) break;
|
|
200
|
-
dir = parent;
|
|
201
|
-
}
|
|
202
|
-
if (resolver) {
|
|
203
|
-
try {
|
|
204
|
-
// Resolves through "exports"/symlinks even when no ancestor path matched.
|
|
205
|
-
candidates.push(dirname(resolver("@earendil-works/pi-ai/package.json")));
|
|
206
|
-
} catch {
|
|
207
|
-
// Not resolvable from here — the ancestor paths above may still hit.
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
return [...new Set(candidates)];
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
function findPiAiRoot(): string | undefined {
|
|
214
|
-
if (piAiPackageRoot !== undefined) return piAiPackageRoot ?? undefined;
|
|
215
|
-
const here = fileURLToPath(import.meta.url);
|
|
216
|
-
const localRequire = createRequire(import.meta.url);
|
|
217
|
-
for (const candidate of piAiRootCandidates(here, (specifier) =>
|
|
218
|
-
localRequire.resolve(specifier),
|
|
219
|
-
)) {
|
|
220
|
-
if (existsSync(join(candidate, "package.json"))) {
|
|
221
|
-
piAiPackageRoot = candidate;
|
|
222
|
-
return candidate;
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
piAiPackageRoot = null;
|
|
226
|
-
return undefined;
|
|
227
|
-
}
|
|
228
162
|
|
|
229
163
|
/**
|
|
230
164
|
* Bridge Pi's legacy extension OAuth callbacks (`onAuth`/`onDeviceCode`/`onPrompt`/
|
|
@@ -272,48 +206,15 @@ function toAuthInteraction(callbacks: any) {
|
|
|
272
206
|
};
|
|
273
207
|
}
|
|
274
208
|
|
|
275
|
-
/** pi-ai <= 0.79: runtime OAuth helpers exported straight from `dist/oauth.js`. */
|
|
276
|
-
function adaptLegacyOauthEntry(mod: any): PiAiOauthBridge | undefined {
|
|
277
|
-
const codex = mod?.openaiCodexOAuthProvider;
|
|
278
|
-
if (
|
|
279
|
-
typeof mod?.loginAnthropic !== "function" ||
|
|
280
|
-
typeof mod?.refreshAnthropicToken !== "function" ||
|
|
281
|
-
typeof codex?.login !== "function" ||
|
|
282
|
-
typeof codex?.refreshToken !== "function"
|
|
283
|
-
) {
|
|
284
|
-
return undefined;
|
|
285
|
-
}
|
|
286
|
-
return {
|
|
287
|
-
era: "legacy-oauth-entry",
|
|
288
|
-
anthropic: {
|
|
289
|
-
login: (callbacks) => mod.loginAnthropic(callbacks),
|
|
290
|
-
// This era exchanges the bare refresh token, not the whole credential.
|
|
291
|
-
refresh: (credentials, signal) =>
|
|
292
|
-
mod.refreshAnthropicToken(credentials.refresh, signal),
|
|
293
|
-
},
|
|
294
|
-
codex: {
|
|
295
|
-
usesCallbackServer: codex.usesCallbackServer ?? true,
|
|
296
|
-
login: (callbacks) => codex.login(callbacks),
|
|
297
|
-
refresh: (credentials, signal) => codex.refreshToken(credentials, signal),
|
|
298
|
-
getApiKey: (credentials) =>
|
|
299
|
-
typeof codex.getApiKey === "function"
|
|
300
|
-
? codex.getApiKey(credentials)
|
|
301
|
-
: credentials.access,
|
|
302
|
-
},
|
|
303
|
-
};
|
|
304
|
-
}
|
|
305
209
|
|
|
306
210
|
/** pi-ai >= 0.80: OAuth lives behind provider factories, with an AuthInteraction API. */
|
|
307
|
-
function adaptProviderFactories(
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
const
|
|
314
|
-
const codexOauth = codexMod?.openaiCodexProvider?.()?.auth?.oauth;
|
|
315
|
-
const kimiOauth = kimiMod?.kimiCodingProvider?.()?.auth?.oauth;
|
|
316
|
-
const xaiOauth = xaiMod?.xaiProvider?.()?.auth?.oauth;
|
|
211
|
+
function adaptProviderFactories(): PiAiOauthBridge | undefined {
|
|
212
|
+
const providers = builtinProviders();
|
|
213
|
+
const oauth = (id: string): any => providers.find((provider) => provider.id === id)?.auth?.oauth;
|
|
214
|
+
const anthropicOauth = oauth("anthropic");
|
|
215
|
+
const codexOauth = oauth("openai-codex");
|
|
216
|
+
const kimiOauth = oauth("kimi-coding");
|
|
217
|
+
const xaiOauth = oauth("xai");
|
|
317
218
|
if (
|
|
318
219
|
typeof anthropicOauth?.login !== "function" ||
|
|
319
220
|
typeof anthropicOauth?.refresh !== "function" ||
|
|
@@ -355,58 +256,18 @@ function adaptProviderFactories(
|
|
|
355
256
|
};
|
|
356
257
|
}
|
|
357
258
|
|
|
358
|
-
/**
|
|
359
|
-
* Best-effort load + normalization of pi-ai's OAuth surface. NEVER throws.
|
|
360
|
-
*
|
|
361
|
-
* Uses a synchronous require() (Node >= 22 supports require() of ESM without
|
|
362
|
-
* top-level await, and pi-ai qualifies) because providers are registered
|
|
363
|
-
* synchronously — `usesCallbackServer` is read while Pi merely LISTS providers.
|
|
364
|
-
*/
|
|
259
|
+
/** Adapt the running host's public factories without loading another SDK instance. */
|
|
365
260
|
function tryLoadPiAiOauth(): PiAiOauthBridge | undefined {
|
|
366
261
|
if (piAiOauthBridge) return piAiOauthBridge;
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
piAiOauthLoadError =
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
const file = join(root, "dist", relative);
|
|
375
|
-
if (!existsSync(file)) return undefined;
|
|
376
|
-
try {
|
|
377
|
-
return localRequire(file);
|
|
378
|
-
} catch (error) {
|
|
379
|
-
tried.push(
|
|
380
|
-
`${relative}: ${error instanceof Error ? error.message : String(error)}`,
|
|
381
|
-
);
|
|
382
|
-
return undefined;
|
|
383
|
-
}
|
|
384
|
-
};
|
|
385
|
-
const tried: string[] = [];
|
|
386
|
-
|
|
387
|
-
const legacy = adaptLegacyOauthEntry(load("oauth.js"));
|
|
388
|
-
if (legacy) {
|
|
389
|
-
piAiOauthBridge = legacy;
|
|
390
|
-
piAiOauthLoadError = undefined;
|
|
391
|
-
return legacy;
|
|
392
|
-
}
|
|
393
|
-
tried.push("dist/oauth.js exports no runtime OAuth helpers (pi-ai >= 0.80)");
|
|
394
|
-
|
|
395
|
-
const modern = adaptProviderFactories(
|
|
396
|
-
load(join("providers", "anthropic.js")),
|
|
397
|
-
load(join("providers", "openai-codex.js")),
|
|
398
|
-
load(join("providers", "kimi-coding.js")),
|
|
399
|
-
load(join("providers", "xai.js")),
|
|
400
|
-
);
|
|
401
|
-
if (modern) {
|
|
402
|
-
piAiOauthBridge = modern;
|
|
403
|
-
piAiOauthLoadError = undefined;
|
|
404
|
-
return modern;
|
|
262
|
+
try {
|
|
263
|
+
piAiOauthBridge = adaptProviderFactories();
|
|
264
|
+
piAiOauthLoadError = piAiOauthBridge
|
|
265
|
+
? undefined
|
|
266
|
+
: "the running Pi host exposes no usable pi-ai OAuth provider factories";
|
|
267
|
+
} catch (error) {
|
|
268
|
+
piAiOauthLoadError = error instanceof Error ? error.message : String(error);
|
|
405
269
|
}
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
piAiOauthLoadError = `the @earendil-works/pi-ai at ${root} exposes no OAuth surface this extension can use. ${tried.join("; ")}`;
|
|
409
|
-
return undefined;
|
|
270
|
+
return piAiOauthBridge;
|
|
410
271
|
}
|
|
411
272
|
|
|
412
273
|
/** Load reason, or undefined when OAuth is available. */
|
|
@@ -425,36 +286,10 @@ function requirePiAiOauth(): PiAiOauthBridge {
|
|
|
425
286
|
return bridge;
|
|
426
287
|
}
|
|
427
288
|
|
|
428
|
-
/**
|
|
429
|
-
* pi-ai's static model catalog lookup. It lived on the package root until 0.79 and
|
|
430
|
-
* moved to `dist/compat.js` in 0.80, so resolve it lazily from whichever is present
|
|
431
|
-
* — and treat "absent" as "no canonical metadata", never as a load failure.
|
|
432
|
-
*/
|
|
433
|
-
let piAiGetModelFn: ((provider: string, id: string) => any) | null | undefined;
|
|
434
|
-
|
|
289
|
+
/** Canonical chat metadata comes from the same SDK catalog as the running host. */
|
|
435
290
|
function piAiGetModel(provider: string, id: string): any {
|
|
436
|
-
if (piAiGetModelFn === undefined) {
|
|
437
|
-
piAiGetModelFn = null;
|
|
438
|
-
const root = findPiAiRoot();
|
|
439
|
-
if (root) {
|
|
440
|
-
const localRequire = createRequire(import.meta.url);
|
|
441
|
-
for (const relative of ["compat.js", "index.js"]) {
|
|
442
|
-
const file = join(root, "dist", relative);
|
|
443
|
-
if (!existsSync(file)) continue;
|
|
444
|
-
try {
|
|
445
|
-
const candidate = localRequire(file)?.getModel;
|
|
446
|
-
if (typeof candidate === "function") {
|
|
447
|
-
piAiGetModelFn = candidate;
|
|
448
|
-
break;
|
|
449
|
-
}
|
|
450
|
-
} catch {
|
|
451
|
-
// Try the next entry point.
|
|
452
|
-
}
|
|
453
|
-
}
|
|
454
|
-
}
|
|
455
|
-
}
|
|
456
291
|
try {
|
|
457
|
-
return
|
|
292
|
+
return getHostModel(provider as any, id as any);
|
|
458
293
|
} catch {
|
|
459
294
|
return undefined;
|
|
460
295
|
}
|
|
@@ -653,6 +488,9 @@ type ProviderFailoverConfig = {
|
|
|
653
488
|
// { "openai-codex": ["gpt-5.6", "gpt-5.5"], "anthropic": ["claude-opus-4-9"] }.
|
|
654
489
|
// Keys: anthropic | openai-codex | cursor | qwen | ollama.
|
|
655
490
|
preferredModels?: Record<string, string[]>;
|
|
491
|
+
// Explicit per-account-group model preference. It never selects a foreground model,
|
|
492
|
+
// bypasses a cooldown, or authorizes an automatic quality-tier change.
|
|
493
|
+
pinnedModels?: Record<string, string>;
|
|
656
494
|
// Forward-progress watchdog tunables (see constants above). 0/absent ⇒ built-in default.
|
|
657
495
|
resumeIdleTimeoutMs?: number;
|
|
658
496
|
stuckWatchdogMs?: number;
|
|
@@ -702,6 +540,7 @@ type RuntimeConfig = Required<
|
|
|
702
540
|
| "preferLatestModel"
|
|
703
541
|
| "reasoningLevel"
|
|
704
542
|
| "preferredModels"
|
|
543
|
+
| "pinnedModels"
|
|
705
544
|
| "resumeIdleTimeoutMs"
|
|
706
545
|
| "stuckWatchdogMs"
|
|
707
546
|
| "compactionWatchdogMs"
|
|
@@ -830,6 +669,7 @@ function configuredModelIds(provider: string): string[] {
|
|
|
830
669
|
const models = parsed?.providers?.[provider]?.models;
|
|
831
670
|
if (Array.isArray(models)) {
|
|
832
671
|
return models
|
|
672
|
+
.filter((model: any) => !model?.type || model.type === "chat")
|
|
833
673
|
.map((model: any) => (typeof model === "string" ? model : model?.id))
|
|
834
674
|
.filter((id: unknown): id is string => typeof id === "string" && id.length > 0);
|
|
835
675
|
}
|
|
@@ -840,6 +680,21 @@ function configuredModelIds(provider: string): string[] {
|
|
|
840
680
|
}
|
|
841
681
|
}
|
|
842
682
|
|
|
683
|
+
// Pi 0.99's provider composer replaces a supplied models array in full. Preserve
|
|
684
|
+
// non-chat definitions verbatim; they must never become account chat aliases.
|
|
685
|
+
function configuredOperationModels(provider: string): any[] {
|
|
686
|
+
try {
|
|
687
|
+
const parsed = JSON.parse(readFileSync(MODELS_CONFIG_PATH, "utf8"));
|
|
688
|
+
const models = parsed?.providers?.[provider]?.models;
|
|
689
|
+
return Array.isArray(models)
|
|
690
|
+
? models.filter((model: any) => model && typeof model === "object" &&
|
|
691
|
+
(model.type === "image" || model.type === "classifier"))
|
|
692
|
+
: [];
|
|
693
|
+
} catch {
|
|
694
|
+
return [];
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
|
|
843
698
|
function readHostDefaultModel(): { provider: string; id: string } | undefined {
|
|
844
699
|
try {
|
|
845
700
|
const parsed = JSON.parse(readFileSync(SETTINGS_PATH, "utf8"));
|
|
@@ -1195,7 +1050,7 @@ const ANTI_PINGPONG_MS = 60 * 1000; // don't switch straight back to the account
|
|
|
1195
1050
|
// Bumped on every release. Printed at startup and in `/multi-account status` so you can verify
|
|
1196
1051
|
// which version Pi actually loaded (a running Pi keeps the version it started with — /login and
|
|
1197
1052
|
// /reload do NOT reload extension code; only a full restart does).
|
|
1198
|
-
const VERSION = "1.
|
|
1053
|
+
const VERSION = "1.24.0";
|
|
1199
1054
|
function sourceFingerprint(): string {
|
|
1200
1055
|
try {
|
|
1201
1056
|
const root = dirname(fileURLToPath(import.meta.url));
|
|
@@ -1816,6 +1671,7 @@ const DEFAULT_CONFIG: ProviderFailoverConfig = {
|
|
|
1816
1671
|
preferLatestModel: true,
|
|
1817
1672
|
reasoningLevel: "auto",
|
|
1818
1673
|
preferredModels: {},
|
|
1674
|
+
pinnedModels: {},
|
|
1819
1675
|
resumeIdleTimeoutMs: RESUME_IDLE_TIMEOUT_MS,
|
|
1820
1676
|
stuckWatchdogMs: STUCK_WATCHDOG_MS,
|
|
1821
1677
|
compactionWatchdogMs: COMPACTION_WATCHDOG_MS,
|
|
@@ -1828,11 +1684,12 @@ const DEFAULT_CONFIG: ProviderFailoverConfig = {
|
|
|
1828
1684
|
function ensureDefaultConfig() {
|
|
1829
1685
|
if (existsSync(CONFIG_PATH)) return;
|
|
1830
1686
|
mkdirSync(dirname(CONFIG_PATH), { recursive: true, mode: 0o700 });
|
|
1831
|
-
|
|
1832
|
-
CONFIG_PATH,
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1687
|
+
try {
|
|
1688
|
+
writeFileSync(CONFIG_PATH, `${JSON.stringify(DEFAULT_CONFIG, null, "\t")}\n`,
|
|
1689
|
+
{ encoding: "utf8", mode: 0o600, flag: "wx" });
|
|
1690
|
+
} catch (error: any) {
|
|
1691
|
+
if (error?.code !== "EEXIST") throw error; // Another root initialized it first.
|
|
1692
|
+
}
|
|
1836
1693
|
}
|
|
1837
1694
|
|
|
1838
1695
|
function positiveOr(value: unknown, fallback: number) {
|
|
@@ -2036,6 +1893,7 @@ function normalizeConfig(raw: ProviderFailoverConfig): RuntimeConfig {
|
|
|
2036
1893
|
.filter(([, v]) => v.length > 0),
|
|
2037
1894
|
)
|
|
2038
1895
|
: {},
|
|
1896
|
+
pinnedModels: normalizeModelPins(raw.pinnedModels),
|
|
2039
1897
|
resumeIdleTimeoutMs: positiveOr(
|
|
2040
1898
|
raw.resumeIdleTimeoutMs,
|
|
2041
1899
|
RESUME_IDLE_TIMEOUT_MS,
|
|
@@ -2287,6 +2145,20 @@ export async function persistRefreshedCredentials(
|
|
|
2287
2145
|
write?: (data: Record<string, any>) => void;
|
|
2288
2146
|
},
|
|
2289
2147
|
): Promise<boolean> {
|
|
2148
|
+
// Even legacy/set-only hosts must never unhide a numbered slot while saving a refresh.
|
|
2149
|
+
if (!io && isChildFacingPlaceholderForSlot(readAuthFileRaw()[provider], provider)) {
|
|
2150
|
+
try {
|
|
2151
|
+
let persisted = false;
|
|
2152
|
+
mutateProxyAuth(AUTH_PATH, PROXY_OAUTH_PATH, (auth, sidecar) => {
|
|
2153
|
+
if (!isChildFacingPlaceholderForSlot(auth[provider], provider) || sidecar[provider]?.type !== "oauth") {
|
|
2154
|
+
return { auth, sidecar, changed: false };
|
|
2155
|
+
}
|
|
2156
|
+
persisted = true;
|
|
2157
|
+
return { auth, sidecar: { ...sidecar, [provider]: credential }, changed: true };
|
|
2158
|
+
}, "shadow");
|
|
2159
|
+
return persisted;
|
|
2160
|
+
} catch { return false; }
|
|
2161
|
+
}
|
|
2290
2162
|
if (typeof authStorage?.modify === "function") {
|
|
2291
2163
|
try {
|
|
2292
2164
|
await authStorage.modify(provider, () => credential);
|
|
@@ -2371,6 +2243,17 @@ function jwtExpMs(token: string): number | undefined {
|
|
|
2371
2243
|
: undefined;
|
|
2372
2244
|
}
|
|
2373
2245
|
|
|
2246
|
+
/**
|
|
2247
|
+
* When an OAuth access token expires. Anthropic's `sk-ant-oat…` tokens are opaque, so the JWT
|
|
2248
|
+
* `exp` is absent and the stored `expires` (seconds or milliseconds) is the only signal.
|
|
2249
|
+
*/
|
|
2250
|
+
function oauthExpiryMs(entry: AuthEntry): number | undefined {
|
|
2251
|
+
const fromJwt = typeof entry.access === "string" ? jwtExpMs(entry.access) : undefined;
|
|
2252
|
+
if (fromJwt !== undefined) return fromJwt;
|
|
2253
|
+
if (typeof entry.expires !== "number" || !Number.isFinite(entry.expires)) return undefined;
|
|
2254
|
+
return entry.expires < 10_000_000_000 ? entry.expires * 1000 : entry.expires;
|
|
2255
|
+
}
|
|
2256
|
+
|
|
2374
2257
|
function getCursorSubFromAccessToken(token: string): string | undefined {
|
|
2375
2258
|
const sub = decodeJwtPayload(token)?.sub;
|
|
2376
2259
|
return typeof sub === "string" && sub.length > 0 ? sub : undefined;
|
|
@@ -2501,13 +2384,7 @@ function isEntryUsable(entry: AuthEntry | undefined): boolean {
|
|
|
2501
2384
|
if (typeof entry.access !== "string" || entry.access.length === 0)
|
|
2502
2385
|
return false;
|
|
2503
2386
|
// Expired access token with no refresh token → unrecoverable.
|
|
2504
|
-
const
|
|
2505
|
-
typeof entry.expires === "number" && Number.isFinite(entry.expires)
|
|
2506
|
-
? entry.expires < 10_000_000_000
|
|
2507
|
-
? entry.expires * 1000
|
|
2508
|
-
: entry.expires
|
|
2509
|
-
: undefined;
|
|
2510
|
-
const expMs = jwtExpMs(entry.access) ?? storedExpiry;
|
|
2387
|
+
const expMs = oauthExpiryMs(entry);
|
|
2511
2388
|
if (
|
|
2512
2389
|
expMs !== undefined &&
|
|
2513
2390
|
expMs <= Date.now() &&
|
|
@@ -3072,7 +2949,7 @@ function registerCodexCatalog(
|
|
|
3072
2949
|
baseUrl,
|
|
3073
2950
|
api: "openai-codex-responses" as any,
|
|
3074
2951
|
oauth: codexOAuthOverride(id, name),
|
|
3075
|
-
models: models as any,
|
|
2952
|
+
models: [...models, ...configuredOperationModels(id)] as any,
|
|
3076
2953
|
});
|
|
3077
2954
|
}
|
|
3078
2955
|
|
|
@@ -3548,7 +3425,7 @@ const MINIMAL_ANTHROPIC_OAUTH_PROMPT = [
|
|
|
3548
3425
|
].join("\n");
|
|
3549
3426
|
const CLAUDE_CODE_IDENTITY_PREFIX =
|
|
3550
3427
|
"You are Claude Code, Anthropic's official CLI";
|
|
3551
|
-
const CLAUDE_CODE_VERSION = "2.1.
|
|
3428
|
+
const CLAUDE_CODE_VERSION = "2.1.288";
|
|
3552
3429
|
const BILLING_HEADER_SALT = "59cf53e54c78";
|
|
3553
3430
|
const BILLING_HEADER_POSITIONS = [4, 7, 20] as const;
|
|
3554
3431
|
const CLAUDE_CODE_ENTRYPOINT = "sdk-cli";
|
|
@@ -3925,10 +3802,8 @@ interface HostCompletionRouterAPI {
|
|
|
3925
3802
|
export default function piMultiAccount(pi: ExtensionAPI) {
|
|
3926
3803
|
// Warm up the OAuth helpers before any provider registration: providers are
|
|
3927
3804
|
// registered synchronously below and their `usesCallbackServer`/`getApiKey`
|
|
3928
|
-
//
|
|
3929
|
-
//
|
|
3930
|
-
// still loads and every non-OAuth account keeps working; only subscription
|
|
3931
|
-
// logins are unavailable, and the user is told once at session start.
|
|
3805
|
+
// use the cached host adapter. An unusable OAuth factory remains non-fatal:
|
|
3806
|
+
// API-key accounts keep working and session start reports unavailable OAuth.
|
|
3932
3807
|
const oauthUnavailable = piAiOauthUnavailableReason();
|
|
3933
3808
|
let subagentChild = isSubagentChildProcess();
|
|
3934
3809
|
let hostOwnsSessionModel = false;
|
|
@@ -4881,11 +4756,19 @@ export default function piMultiAccount(pi: ExtensionAPI) {
|
|
|
4881
4756
|
let refreshed: AuthEntry | undefined;
|
|
4882
4757
|
let persistedUnderLock = false;
|
|
4883
4758
|
if (family === "anthropic") {
|
|
4884
|
-
|
|
4885
|
-
|
|
4886
|
-
|
|
4887
|
-
|
|
4888
|
-
|
|
4759
|
+
const refreshAnthropic = (current: AuthEntry) => refreshAnthropicCredentials(current, undefined, provider);
|
|
4760
|
+
if (typeof authStorage?.modify === "function") {
|
|
4761
|
+
refreshed = await refreshAndPersistWithStorageLock({
|
|
4762
|
+
provider, credentials: entry, authStorage,
|
|
4763
|
+
readLatest: () => readAuthFile()[provider],
|
|
4764
|
+
refresh: refreshAnthropic,
|
|
4765
|
+
isShadowed: stored => isChildFacingPlaceholderForSlot(stored, provider),
|
|
4766
|
+
persistShadowed: credential => writeProxyOAuthSidecar({ ...readProxyOAuthSidecar(), [provider]: credential }),
|
|
4767
|
+
});
|
|
4768
|
+
persistedUnderLock = true;
|
|
4769
|
+
} else {
|
|
4770
|
+
refreshed = await refreshAnthropic(entry);
|
|
4771
|
+
}
|
|
4889
4772
|
} else if (family === "openai-codex") {
|
|
4890
4773
|
const refreshCodex = async (current: AuthEntry) => {
|
|
4891
4774
|
const next = mergeRefreshedCredentials(
|
|
@@ -5281,6 +5164,7 @@ export default function piMultiAccount(pi: ExtensionAPI) {
|
|
|
5281
5164
|
return; // keep the previous (or empty) discovered set; configured ids still serve
|
|
5282
5165
|
}
|
|
5283
5166
|
const models = ollamaRotationModelIds().map((m) => ollamaModelDef(m, OLLAMA_BASE));
|
|
5167
|
+
models.push(...configuredOperationModels(OLLAMA_BASE));
|
|
5284
5168
|
pi.registerProvider(OLLAMA_BASE, {
|
|
5285
5169
|
name: "Ollama",
|
|
5286
5170
|
baseUrl: OLLAMA_CLOUD_BASE_URL,
|
|
@@ -6682,16 +6566,37 @@ export default function piMultiAccount(pi: ExtensionAPI) {
|
|
|
6682
6566
|
return [last, ...defaults.filter((id) => id !== last)];
|
|
6683
6567
|
}
|
|
6684
6568
|
|
|
6685
|
-
|
|
6686
|
-
|
|
6687
|
-
|
|
6569
|
+
function pinnedModelForProvider(provider: string): string | undefined {
|
|
6570
|
+
const scope = accountGroup(provider, config.qwenProvider);
|
|
6571
|
+
return Object.hasOwn(config.pinnedModels, scope) ? config.pinnedModels[scope] : undefined;
|
|
6572
|
+
}
|
|
6573
|
+
|
|
6574
|
+
function pinQualityCompatible(model: any, source: any): boolean {
|
|
6575
|
+
const pin = pinnedModelForProvider(model.provider);
|
|
6576
|
+
const sourceBand = modelQualityBand(source?.id, source?.provider);
|
|
6577
|
+
return !pin || !sameModelIdentity(model.id, pin) || !sourceBand || modelQualityBand(model.id, model.provider) === sourceBand;
|
|
6578
|
+
}
|
|
6579
|
+
|
|
6580
|
+
function preferredModelsForProvider(provider: string): string[] {
|
|
6581
|
+
const order = familyPreferredModels(classifyProvider(provider, config.qwenProvider));
|
|
6582
|
+
const pin = pinnedModelForProvider(provider);
|
|
6583
|
+
return pin ? [pin, ...order.filter(id => !sameModelIdentity(id, pin))] : order;
|
|
6584
|
+
}
|
|
6585
|
+
|
|
6586
|
+
// Rank includes an explicit pin, but a pin is not evidence of a quality upgrade.
|
|
6587
|
+
// Unknown models sort last; identity/liveness/quality constraints still apply first.
|
|
6688
6588
|
function modelRecencyRank(model: any): number {
|
|
6689
|
-
const
|
|
6690
|
-
const order = familyPreferredModels(family);
|
|
6589
|
+
const order = preferredModelsForProvider(String(model?.provider ?? ""));
|
|
6691
6590
|
const idx = order.findIndex((id) => sameModelIdentity(id, model?.id));
|
|
6692
6591
|
return idx < 0 ? Number.MAX_SAFE_INTEGER : idx;
|
|
6693
6592
|
}
|
|
6694
6593
|
|
|
6594
|
+
// Virtual and non-chat operations belong to Pi, not account chat rotation.
|
|
6595
|
+
function isAccountChatModel(model: any): boolean {
|
|
6596
|
+
return !!model && model.api !== "pi-virtual" &&
|
|
6597
|
+
(!model.type || model.type === "chat");
|
|
6598
|
+
}
|
|
6599
|
+
|
|
6695
6600
|
function resolveTargets(
|
|
6696
6601
|
ctx: any,
|
|
6697
6602
|
target: string,
|
|
@@ -6702,7 +6607,7 @@ export default function piMultiAccount(pi: ExtensionAPI) {
|
|
|
6702
6607
|
if (!parsed) return [];
|
|
6703
6608
|
if (parsed.modelId) {
|
|
6704
6609
|
const model = findModelIncludingHidden(ctx, parsed.provider, parsed.modelId);
|
|
6705
|
-
return model ? [model] : [];
|
|
6610
|
+
return isAccountChatModel(model) ? [model] : [];
|
|
6706
6611
|
}
|
|
6707
6612
|
|
|
6708
6613
|
const family = classifyProvider(parsed.provider, config.qwenProvider);
|
|
@@ -6710,15 +6615,15 @@ export default function piMultiAccount(pi: ExtensionAPI) {
|
|
|
6710
6615
|
? classifyProvider(currentModel.provider, config.qwenProvider)
|
|
6711
6616
|
: undefined;
|
|
6712
6617
|
const sameFamily = !!family && !!currentFamily && family === currentFamily;
|
|
6713
|
-
// A
|
|
6714
|
-
//
|
|
6715
|
-
const preferred =
|
|
6618
|
+
// A pin leads this account's model order without synthesizing a model or losing the
|
|
6619
|
+
// underlying preferredModels/catalog order when that pin is missing or ineligible.
|
|
6620
|
+
const preferred = preferredModelsForProvider(parsed.provider);
|
|
6716
6621
|
const keepCurrent = sameFamily && currentModel?.id ? [currentModel.id] : [];
|
|
6717
6622
|
// `preferredOnly` means "take this family's flagship, not an arbitrary model". A provider
|
|
6718
6623
|
// outside the managed families has no flagship list at all, so honouring it literally
|
|
6719
6624
|
// yields no candidate and the switch fails — which is what made an unmanaged account
|
|
6720
6625
|
// unreachable by name. Fall back to what Pi knows for it.
|
|
6721
|
-
const usePreferredOnly = preferredOnly && preferred.length > 0;
|
|
6626
|
+
const usePreferredOnly = preferredOnly && preferred.length > 0 && !pinnedModelForProvider(parsed.provider);
|
|
6722
6627
|
const registryModels = usePreferredOnly
|
|
6723
6628
|
? []
|
|
6724
6629
|
: [
|
|
@@ -6731,7 +6636,7 @@ export default function piMultiAccount(pi: ExtensionAPI) {
|
|
|
6731
6636
|
// once downgraded (e.g. gpt-5.4 after a momentary limit on gpt-5.5) is upgraded back to
|
|
6732
6637
|
// the latest the moment it is available again — instead of carrying the old model forever.
|
|
6733
6638
|
// Legacy mode keeps the current model first (never changes the model unless it has to).
|
|
6734
|
-
const modelIds = config.preferLatestModel
|
|
6639
|
+
const modelIds = config.preferLatestModel || pinnedModelForProvider(parsed.provider)
|
|
6735
6640
|
? [...preferred, ...keepCurrent, ...registryModels]
|
|
6736
6641
|
: [...keepCurrent, ...preferred, ...registryModels];
|
|
6737
6642
|
const result: any[] = [];
|
|
@@ -6739,7 +6644,7 @@ export default function piMultiAccount(pi: ExtensionAPI) {
|
|
|
6739
6644
|
for (const modelId of modelIds) {
|
|
6740
6645
|
if (seen.has(modelId)) continue;
|
|
6741
6646
|
const model = findModelIncludingHidden(ctx, parsed.provider, modelId);
|
|
6742
|
-
if (!model) {
|
|
6647
|
+
if (!isAccountChatModel(model)) {
|
|
6743
6648
|
seen.add(modelId);
|
|
6744
6649
|
continue;
|
|
6745
6650
|
}
|
|
@@ -6817,6 +6722,8 @@ export default function piMultiAccount(pi: ExtensionAPI) {
|
|
|
6817
6722
|
const sourceBand = modelQualityBand(currentModel?.id, currentModel?.provider);
|
|
6818
6723
|
const sourceExceptional = apexIdentity(currentModel?.id);
|
|
6819
6724
|
models = models.filter((model: any) => {
|
|
6725
|
+
// A preference is not tier consent, including routed automatic compaction.
|
|
6726
|
+
if (!pinQualityCompatible(model, currentModel)) return false;
|
|
6820
6727
|
const candidateBand = modelQualityBand(model.id, model.provider);
|
|
6821
6728
|
const candidateExceptional = apexIdentity(model.id);
|
|
6822
6729
|
// Cursor's Fable-named aliases are deliberately uncalibrated. A user may select one
|
|
@@ -6865,8 +6772,10 @@ export default function piMultiAccount(pi: ExtensionAPI) {
|
|
|
6865
6772
|
// preferLatestModel still upgrades a known older sibling (gpt-5.4 → gpt-5.5)
|
|
6866
6773
|
// on a healthy account. An unranked catalog leftover (claude-4-sonnet beating
|
|
6867
6774
|
// grok only because it sorts first) must never steal the user's model.
|
|
6775
|
+
const pinRepresentative = sameModelIdentity(pinnedModelForProvider(flagshipPick.provider), flagshipPick.id);
|
|
6868
6776
|
const upgrade =
|
|
6869
6777
|
config.preferLatestModel &&
|
|
6778
|
+
!pinRepresentative &&
|
|
6870
6779
|
idRank !== Number.MAX_SAFE_INTEGER &&
|
|
6871
6780
|
flagRank < idRank;
|
|
6872
6781
|
pick = upgrade ? flagshipPick : identityPick;
|
|
@@ -7116,6 +7025,7 @@ export default function piMultiAccount(pi: ExtensionAPI) {
|
|
|
7116
7025
|
waitForQuotaRecovery?: boolean;
|
|
7117
7026
|
} = {},
|
|
7118
7027
|
) {
|
|
7028
|
+
if (!isAccountChatModel(failedModel) || (ctx.model && !isAccountChatModel(ctx.model))) return false;
|
|
7119
7029
|
if (!options.manual && (isFailoverExempt(failedModel?.provider) || isFailoverExempt(ctx.model?.provider))) return false;
|
|
7120
7030
|
const switchEpoch = chainEpoch;
|
|
7121
7031
|
if (!automaticFailoverEnabled() || !failedModel?.provider || !failedModel?.id)
|
|
@@ -7376,6 +7286,7 @@ export default function piMultiAccount(pi: ExtensionAPI) {
|
|
|
7376
7286
|
}
|
|
7377
7287
|
|
|
7378
7288
|
async function ensureReadyModel(ctx: any, reason: string) {
|
|
7289
|
+
if (ctx.model && !isAccountChatModel(ctx.model)) return true;
|
|
7379
7290
|
// The launch candidate is authoritative in a pi-subagents child. Let the
|
|
7380
7291
|
// request produce its real error so the parent runner can advance its own
|
|
7381
7292
|
// fallbackModels chain instead of starting a competing router here.
|
|
@@ -9093,10 +9004,85 @@ export default function piMultiAccount(pi: ExtensionAPI) {
|
|
|
9093
9004
|
return true;
|
|
9094
9005
|
}
|
|
9095
9006
|
|
|
9007
|
+
function modelPinLines(ctx: any): string[] {
|
|
9008
|
+
const entries = Object.entries(config.pinnedModels).sort(([a], [b]) => a.localeCompare(b));
|
|
9009
|
+
if (!entries.length) return [" none — /multi-account pin <provider/model>"];
|
|
9010
|
+
const models = [...ctx.modelRegistry.getAll(), ...[...hiddenProviderModels.values()].flat()];
|
|
9011
|
+
const now = Date.now();
|
|
9012
|
+
return entries.map(([scope, id]) => {
|
|
9013
|
+
const matching = models.filter((model: any) => accountGroup(model.provider, config.qwenProvider) === scope &&
|
|
9014
|
+
sameModelIdentity(model.id, id) && isAccountChatModel(model));
|
|
9015
|
+
const usable = matching.filter((model: any) => !isInvalidated(model.provider) && providerHasUsableAuth(ctx, model.provider));
|
|
9016
|
+
const sourceBand = modelQualityBand(ctx.model?.id, ctx.model?.provider);
|
|
9017
|
+
const compatible = usable.filter((model: any) => {
|
|
9018
|
+
const band = modelQualityBand(model.id, model.provider);
|
|
9019
|
+
if (sourceBand === "apex") return band === "apex" && explicitlyAllowsPaidApexFallback(model, ctx.model);
|
|
9020
|
+
return band !== "apex" && (!sourceBand || band === sourceBand) && (!apexIdentity(model.id) || sameModelIdentity(model.id, ctx.model?.id));
|
|
9021
|
+
});
|
|
9022
|
+
const free = compatible.filter((model: any) => providerRecoveryAt(model.provider, now) <= now &&
|
|
9023
|
+
(exhaustedUntilByModel.get(`${model.provider}/${model.id}`) ?? 0) <= now);
|
|
9024
|
+
const status = !matching.length ? "missing from catalog" : !usable.length ? "no usable account" :
|
|
9025
|
+
!compatible.length ? "incompatible with current quality band" : !free.length ? "cooling" : "preference available";
|
|
9026
|
+
return ` ${scope}: ${id} — ${status}`;
|
|
9027
|
+
});
|
|
9028
|
+
}
|
|
9029
|
+
|
|
9096
9030
|
async function handleCommand(args: string, ctx: any) {
|
|
9097
|
-
const
|
|
9031
|
+
const parts = args.trim().split(/\s+/);
|
|
9032
|
+
const [commandRaw, arg1] = parts;
|
|
9098
9033
|
const command = (commandRaw || "status").toLowerCase();
|
|
9099
9034
|
|
|
9035
|
+
if (command === "pins") {
|
|
9036
|
+
if (parts.length > 2 || (arg1 && arg1 !== "list")) {
|
|
9037
|
+
ctx.ui.notify("pi-multi-account: usage: /multi-account pins [list]", "warning");
|
|
9038
|
+
return;
|
|
9039
|
+
}
|
|
9040
|
+
ctx.ui.notify(["pi-multi-account model pins (routing preferences, not foreground selections):", ...modelPinLines(ctx),
|
|
9041
|
+
"Exact siblings, authentication, health and quality checks still apply. Other sessions adopt edits on reload/restart."].join("\n"), "info");
|
|
9042
|
+
return;
|
|
9043
|
+
}
|
|
9044
|
+
|
|
9045
|
+
if (command === "pin" || command === "unpin") {
|
|
9046
|
+
const parsed = command === "pin" ? parseTarget(arg1) : undefined;
|
|
9047
|
+
const provider = command === "pin" ? parsed?.provider : arg1;
|
|
9048
|
+
if (parts.length !== 2 || !provider || !validModelPinScope(provider) ||
|
|
9049
|
+
(command === "pin" && (!parsed?.modelId || /\s/.test(parsed.modelId)))) {
|
|
9050
|
+
ctx.ui.notify("pi-multi-account: usage: /multi-account pin <provider/model> | unpin <provider-or-family> | pins [list]", "warning");
|
|
9051
|
+
return;
|
|
9052
|
+
}
|
|
9053
|
+
if (subagentChild || sessionClosed || !ctx.isIdle()) {
|
|
9054
|
+
ctx.ui.notify("pi-multi-account: change model pins from an idle live parent session; no preference was saved", "warning");
|
|
9055
|
+
return;
|
|
9056
|
+
}
|
|
9057
|
+
const epoch = chainEpoch;
|
|
9058
|
+
const selected = ctx.model ? ref(ctx.model.provider, ctx.model.id) : undefined;
|
|
9059
|
+
const session = ctx.sessionManager?.getSessionId?.();
|
|
9060
|
+
const canCommit = () => !subagentChild && !sessionClosed && ctx.isIdle() && epoch === chainEpoch &&
|
|
9061
|
+
(ctx.model ? ref(ctx.model.provider, ctx.model.id) : undefined) === selected && ctx.sessionManager?.getSessionId?.() === session;
|
|
9062
|
+
const scope = accountGroup(provider, config.qwenProvider);
|
|
9063
|
+
const modelId = parsed?.modelId;
|
|
9064
|
+
const exact = modelId ? findModelIncludingHidden(ctx, provider, modelId) : undefined;
|
|
9065
|
+
const known = modelId ? [exact, ...ctx.modelRegistry.getAll(), ...[...hiddenProviderModels.values()].flat()].filter((model: any) =>
|
|
9066
|
+
model && accountGroup(model.provider, config.qwenProvider) === scope && sameModelIdentity(model.id, modelId)) : [];
|
|
9067
|
+
if ((exact && !isAccountChatModel(exact)) || (known.length > 0 && !known.some(isAccountChatModel))) {
|
|
9068
|
+
ctx.ui.notify("pi-multi-account: only account chat models can be pinned; virtual/image/classifier models remain host-owned", "warning");
|
|
9069
|
+
return;
|
|
9070
|
+
}
|
|
9071
|
+
try {
|
|
9072
|
+
const pins = persistModelPin(CONFIG_PATH, scope, modelId, canCommit);
|
|
9073
|
+
config = { ...config, pinnedModels: pins };
|
|
9074
|
+
claimUserControl("model pin preference changed");
|
|
9075
|
+
logEvent("model_pin_set", { scope, model: modelId ?? null });
|
|
9076
|
+
ctx.ui.notify(command === "unpin"
|
|
9077
|
+
? `pi-multi-account: removed model pin for ${scope}; the underlying preferredModels/catalog order is unchanged`
|
|
9078
|
+
: `pi-multi-account: pinned ${scope}/${modelId} across sibling accounts.${known.length ? "" : " Saved but currently absent; inactive until the catalog supplies it."} Current model was not changed; select a cheaper tier explicitly. Other sessions adopt this preference on reload/restart.`,
|
|
9079
|
+
known.length || command === "unpin" ? "info" : "warning");
|
|
9080
|
+
} catch {
|
|
9081
|
+
ctx.ui.notify("pi-multi-account: model pin was not saved; check config syntax, permissions/lock and session ownership. Runtime preference is unchanged", "error");
|
|
9082
|
+
}
|
|
9083
|
+
return;
|
|
9084
|
+
}
|
|
9085
|
+
|
|
9100
9086
|
if (command === "log" || command === "logs" || command === "debug") {
|
|
9101
9087
|
// Show the diagnostic black box so a misbehaviour can be reported precisely.
|
|
9102
9088
|
if (arg1 === "off") {
|
|
@@ -9389,14 +9375,7 @@ export default function piMultiAccount(pi: ExtensionAPI) {
|
|
|
9389
9375
|
configuredFallbacks = [];
|
|
9390
9376
|
config = { ...config, fallbacks: [] };
|
|
9391
9377
|
try {
|
|
9392
|
-
|
|
9393
|
-
? JSON.parse(readFileSync(CONFIG_PATH, "utf8"))
|
|
9394
|
-
: {};
|
|
9395
|
-
raw.fallbacks = [];
|
|
9396
|
-
writeFileSync(CONFIG_PATH, `${JSON.stringify(raw, null, "\t")}\n`, {
|
|
9397
|
-
encoding: "utf8",
|
|
9398
|
-
mode: 0o600,
|
|
9399
|
-
});
|
|
9378
|
+
mutateConfigFile(CONFIG_PATH, () => ({ fallbacks: [] }));
|
|
9400
9379
|
} catch {
|
|
9401
9380
|
// non-fatal: in-memory state is still cleared
|
|
9402
9381
|
}
|
|
@@ -9610,7 +9589,8 @@ export default function piMultiAccount(pi: ExtensionAPI) {
|
|
|
9610
9589
|
? `${parsed.provider}/${parsed.modelId}`
|
|
9611
9590
|
: parsed.provider;
|
|
9612
9591
|
const candidates = resolveTargets(ctx, resolvedTarget, ctx.model, true).filter(
|
|
9613
|
-
(model: any) => providerHasUsableAuth(ctx, model.provider)
|
|
9592
|
+
(model: any) => providerHasUsableAuth(ctx, model.provider) &&
|
|
9593
|
+
(parsed.modelId !== undefined || pinQualityCompatible(model, ctx.model)),
|
|
9614
9594
|
);
|
|
9615
9595
|
if (candidates.length === 0) {
|
|
9616
9596
|
const hasAuth = providerHasUsableAuth(ctx, parsed.provider);
|
|
@@ -9755,14 +9735,7 @@ export default function piMultiAccount(pi: ExtensionAPI) {
|
|
|
9755
9735
|
onlyActiveModels = next2;
|
|
9756
9736
|
config = { ...config, onlyActive: next2 };
|
|
9757
9737
|
try {
|
|
9758
|
-
|
|
9759
|
-
? JSON.parse(readFileSync(CONFIG_PATH, "utf8"))
|
|
9760
|
-
: {};
|
|
9761
|
-
raw.onlyActive = next2;
|
|
9762
|
-
writeFileSync(CONFIG_PATH, `${JSON.stringify(raw, null, "\t")}\n`, {
|
|
9763
|
-
encoding: "utf8",
|
|
9764
|
-
mode: 0o600,
|
|
9765
|
-
});
|
|
9738
|
+
mutateConfigFile(CONFIG_PATH, () => ({ onlyActive: next2 }));
|
|
9766
9739
|
} catch {
|
|
9767
9740
|
// non-fatal: the in-memory flag still applies for this session
|
|
9768
9741
|
}
|
|
@@ -9821,14 +9794,7 @@ export default function piMultiAccount(pi: ExtensionAPI) {
|
|
|
9821
9794
|
|
|
9822
9795
|
config = { ...config, providerPriority: next };
|
|
9823
9796
|
try {
|
|
9824
|
-
|
|
9825
|
-
? JSON.parse(readFileSync(CONFIG_PATH, "utf8"))
|
|
9826
|
-
: {};
|
|
9827
|
-
raw.providerPriority = next;
|
|
9828
|
-
writeFileSync(CONFIG_PATH, `${JSON.stringify(raw, null, "\t")}\n`, {
|
|
9829
|
-
encoding: "utf8",
|
|
9830
|
-
mode: 0o600,
|
|
9831
|
-
});
|
|
9797
|
+
mutateConfigFile(CONFIG_PATH, () => ({ providerPriority: next }));
|
|
9832
9798
|
} catch {
|
|
9833
9799
|
// non-fatal: the ladder still applies for this session
|
|
9834
9800
|
}
|
|
@@ -10004,7 +9970,8 @@ export default function piMultiAccount(pi: ExtensionAPI) {
|
|
|
10004
9970
|
// list below, `switch` was effectively undiscoverable and `next` pressed repeatedly
|
|
10005
9971
|
// was the only way anyone found to reach a chosen account.
|
|
10006
9972
|
`Switch accounts: /multi-account best — jump straight to an account that can work now · /multi-account switch <provider> — e.g. /multi-account switch ${rotation.find((p) => p !== ctx.model?.provider) ?? rotation[0] ?? "<provider>"} · /multi-account next steps through the rotation in order`,
|
|
10007
|
-
`
|
|
9973
|
+
`Model pins: ${Object.entries(config.pinnedModels).map(([scope, id]) => `${scope}/${id}`).join(", ") || "none"} · /multi-account pins to inspect`,
|
|
9974
|
+
`Other commands: status | accounts [refresh] | best | priority [...] | limits [refresh] | models | pick | pin <provider/model> | unpin <provider-or-family> | pins [list] | save-default | log [N|on|off] | only-active [on|off] | rediscover | add [anthropic|codex|kimi|cursor|ollama|qwen] | remove [anthropic|codex|kimi|cursor|ollama|qwen|<provider-id>] | revive <provider|all> | clear | stop | reset | reload | enable | disable`,
|
|
10008
9975
|
].join("\n"),
|
|
10009
9976
|
"info",
|
|
10010
9977
|
);
|
|
@@ -10084,6 +10051,7 @@ export default function piMultiAccount(pi: ExtensionAPI) {
|
|
|
10084
10051
|
const models = ids.map((m) =>
|
|
10085
10052
|
family === "ollama" ? ollamaModelDef(m, baseId) : qwenModelDef(m, baseId),
|
|
10086
10053
|
);
|
|
10054
|
+
models.push(...configuredOperationModels(baseId));
|
|
10087
10055
|
pi.registerProvider(baseId, {
|
|
10088
10056
|
name: family === "ollama" ? "Ollama" : "Alibaba/Qwen",
|
|
10089
10057
|
baseUrl,
|
|
@@ -10799,8 +10767,10 @@ export default function piMultiAccount(pi: ExtensionAPI) {
|
|
|
10799
10767
|
// An existing child-facing placeholder (possibly published by another root)
|
|
10800
10768
|
// must also stay on its canonical loopback, even when this instance's proxy is off.
|
|
10801
10769
|
function numberedAnthropicBaseUrl(id: string): string {
|
|
10770
|
+
const raw = readAuthFileRaw()[id];
|
|
10771
|
+
if (raw?.type === "api_key" && !isChildFacingPlaceholderForSlot(raw, id)) return "https://api.anthropic.com";
|
|
10802
10772
|
if (typeof slotProxyPort === "number") return publishedRouteFor(slotProxyPort, id);
|
|
10803
|
-
if (isChildFacingPlaceholderForSlot(
|
|
10773
|
+
if (isChildFacingPlaceholderForSlot(raw, id)) return publishedRouteFor(SLOT_PROXY_PORT, id);
|
|
10804
10774
|
return "https://api.anthropic.com";
|
|
10805
10775
|
}
|
|
10806
10776
|
|
|
@@ -10824,6 +10794,8 @@ export default function piMultiAccount(pi: ExtensionAPI) {
|
|
|
10824
10794
|
* the canonical loopback instead, even if this sibling disabled its own proxy.
|
|
10825
10795
|
*/
|
|
10826
10796
|
function numberedCodexBaseUrl(id: string): string {
|
|
10797
|
+
const raw = readAuthFileRaw()[id];
|
|
10798
|
+
if (raw?.type === "api_key" && !isChildFacingPlaceholderForSlot(raw, id)) return "https://chatgpt.com/backend-api";
|
|
10827
10799
|
if (typeof slotProxyPort === "number") return publishedRouteFor(slotProxyPort, id);
|
|
10828
10800
|
if (!config.childProxy && !isChildFacingPlaceholderForSlot(readAuthFileRaw()[id], id)) {
|
|
10829
10801
|
return "https://chatgpt.com/backend-api";
|
|
@@ -10847,7 +10819,7 @@ export default function piMultiAccount(pi: ExtensionAPI) {
|
|
|
10847
10819
|
let entry = readAuthFile()[slotId];
|
|
10848
10820
|
if (!entry) return undefined;
|
|
10849
10821
|
if (entry.type !== "oauth" || typeof entry.access !== "string") return entry;
|
|
10850
|
-
const expiry =
|
|
10822
|
+
const expiry = oauthExpiryMs(entry);
|
|
10851
10823
|
// A minute of slack: a token that expires mid-flight fails the child's whole request, and
|
|
10852
10824
|
// refreshing one turn early costs nothing.
|
|
10853
10825
|
if (expiry !== undefined && expiry - 60_000 <= Date.now()) {
|
|
@@ -10884,10 +10856,9 @@ export default function piMultiAccount(pi: ExtensionAPI) {
|
|
|
10884
10856
|
return;
|
|
10885
10857
|
}
|
|
10886
10858
|
const parsed = parseProxyPath(req.url ?? "");
|
|
10887
|
-
const
|
|
10888
|
-
|
|
10889
|
-
|
|
10890
|
-
: undefined;
|
|
10859
|
+
const presentedEntry = parsed ? readAuthFile()[parsed.slotId] : undefined;
|
|
10860
|
+
const presentedSecret = presentedEntry?.type === "oauth" ? presentedEntry.access :
|
|
10861
|
+
presentedEntry?.type === "api_key" ? presentedEntry.key : undefined;
|
|
10891
10862
|
const verdict = admitRequest({
|
|
10892
10863
|
rawUrl: req.url ?? "",
|
|
10893
10864
|
headers: req.headers ?? {},
|
|
@@ -10920,9 +10891,9 @@ export default function piMultiAccount(pi: ExtensionAPI) {
|
|
|
10920
10891
|
let body: Buffer | undefined =
|
|
10921
10892
|
req.method === "GET" || req.method === "HEAD" ? undefined : await readRequestBody(req);
|
|
10922
10893
|
if (body && verdict.route.family === "anthropic" && credential?.type === "oauth") {
|
|
10923
|
-
// The
|
|
10924
|
-
//
|
|
10925
|
-
//
|
|
10894
|
+
// The extension-owned billing shaping also applies to bare children. Their native
|
|
10895
|
+
// host supplies OAuth identity/tool mapping through the token-shaped placeholder,
|
|
10896
|
+
// but does not load this extension's billing-header maintenance.
|
|
10926
10897
|
try {
|
|
10927
10898
|
const parsed = JSON.parse(body.toString("utf8"));
|
|
10928
10899
|
const reshaped = shapeAnthropicOAuthPayload(parsed);
|
|
@@ -10932,11 +10903,18 @@ export default function piMultiAccount(pi: ExtensionAPI) {
|
|
|
10932
10903
|
}
|
|
10933
10904
|
}
|
|
10934
10905
|
|
|
10906
|
+
if (res.destroyed) return;
|
|
10907
|
+
const upstreamAbort = new AbortController();
|
|
10908
|
+
const callerClosed = () => {
|
|
10909
|
+
if (!res.writableFinished) upstreamAbort.abort();
|
|
10910
|
+
};
|
|
10911
|
+
res.once("close", callerClosed);
|
|
10935
10912
|
try {
|
|
10936
10913
|
const upstream = await fetch(shaped.url, {
|
|
10937
10914
|
method: req.method ?? "POST",
|
|
10938
10915
|
headers: shaped.headers,
|
|
10939
10916
|
body: body && body.length ? new Uint8Array(body) : undefined,
|
|
10917
|
+
signal: upstreamAbort.signal,
|
|
10940
10918
|
});
|
|
10941
10919
|
const outgoing: Record<string, string> = {};
|
|
10942
10920
|
upstream.headers.forEach((value, name) => {
|
|
@@ -10948,7 +10926,7 @@ export default function piMultiAccount(pi: ExtensionAPI) {
|
|
|
10948
10926
|
if (upstream.body) {
|
|
10949
10927
|
await new Promise<void>((resolve) => {
|
|
10950
10928
|
const stream = Readable.fromWeb(upstream.body as any);
|
|
10951
|
-
stream.on("error", () => resolve());
|
|
10929
|
+
stream.on("error", () => { res.destroy(); resolve(); });
|
|
10952
10930
|
res.on("close", () => resolve());
|
|
10953
10931
|
stream.pipe(res);
|
|
10954
10932
|
res.on("finish", () => resolve());
|
|
@@ -10956,11 +10934,13 @@ export default function piMultiAccount(pi: ExtensionAPI) {
|
|
|
10956
10934
|
} else {
|
|
10957
10935
|
res.end();
|
|
10958
10936
|
}
|
|
10959
|
-
logEvent("slot_proxy_forwarded", {
|
|
10937
|
+
if (!upstreamAbort.signal.aborted) logEvent("slot_proxy_forwarded", {
|
|
10960
10938
|
slot: verdict.route.slotId,
|
|
10961
10939
|
status: upstream.status,
|
|
10962
10940
|
});
|
|
10963
10941
|
} catch (error) {
|
|
10942
|
+
// A caller cancellation is not an upstream/provider failure or a quota verdict.
|
|
10943
|
+
if (upstreamAbort.signal.aborted || res.destroyed) return;
|
|
10964
10944
|
const reason = error instanceof Error ? error.message : String(error);
|
|
10965
10945
|
logEvent("slot_proxy_upstream_failed", { slot: verdict.route.slotId, reason });
|
|
10966
10946
|
if (!res.headersSent) {
|
|
@@ -10969,6 +10949,8 @@ export default function piMultiAccount(pi: ExtensionAPI) {
|
|
|
10969
10949
|
} else {
|
|
10970
10950
|
res.end();
|
|
10971
10951
|
}
|
|
10952
|
+
} finally {
|
|
10953
|
+
res.off("close", callerClosed);
|
|
10972
10954
|
}
|
|
10973
10955
|
}
|
|
10974
10956
|
|
|
@@ -11227,12 +11209,14 @@ export default function piMultiAccount(pi: ExtensionAPI) {
|
|
|
11227
11209
|
for (const id of slots) {
|
|
11228
11210
|
const family = proxyFamilyOf(id);
|
|
11229
11211
|
if (!family) continue;
|
|
11230
|
-
const
|
|
11212
|
+
const oauth = parent[id]?.type === "oauth";
|
|
11213
|
+
const baseUrl = oauth ? publishedRouteFor(port, id) :
|
|
11214
|
+
family === "anthropic" ? "https://api.anthropic.com" : "https://chatgpt.com/backend-api";
|
|
11231
11215
|
if (family === "anthropic") {
|
|
11232
11216
|
provisionNativeSlot(id, {
|
|
11233
11217
|
api: "anthropic-messages",
|
|
11234
11218
|
baseUrl,
|
|
11235
|
-
apiKey: placeholderKeyFor(family),
|
|
11219
|
+
apiKey: oauth ? placeholderKeyFor(family) : undefined,
|
|
11236
11220
|
models: DEFAULT_ANTHROPIC_MODELS.map((modelId) => ({
|
|
11237
11221
|
...anthropicModelDef(modelId, id),
|
|
11238
11222
|
baseUrl,
|
|
@@ -11253,7 +11237,7 @@ export default function piMultiAccount(pi: ExtensionAPI) {
|
|
|
11253
11237
|
provisionNativeSlot(id, {
|
|
11254
11238
|
api: "openai-codex-responses",
|
|
11255
11239
|
baseUrl,
|
|
11256
|
-
apiKey: placeholderKeyFor(family),
|
|
11240
|
+
apiKey: oauth ? placeholderKeyFor(family) : undefined,
|
|
11257
11241
|
models,
|
|
11258
11242
|
});
|
|
11259
11243
|
}
|
|
@@ -12072,6 +12056,7 @@ export default function piMultiAccount(pi: ExtensionAPI) {
|
|
|
12072
12056
|
});
|
|
12073
12057
|
|
|
12074
12058
|
safeOn("message_end", async (event, ctx) => {
|
|
12059
|
+
if (!isAccountChatModel(ctx.model)) return;
|
|
12075
12060
|
const errorEpoch = chainEpoch;
|
|
12076
12061
|
const staleError = () => sessionClosed || errorEpoch !== chainEpoch || userAbortedChain || ctx.signal?.aborted;
|
|
12077
12062
|
const message = (event as any).message;
|
|
@@ -12287,6 +12272,7 @@ export default function piMultiAccount(pi: ExtensionAPI) {
|
|
|
12287
12272
|
});
|
|
12288
12273
|
|
|
12289
12274
|
safeOn("agent_end", async (event, ctx) => {
|
|
12275
|
+
if (!isAccountChatModel(ctx.model)) return;
|
|
12290
12276
|
if (!automaticFailoverEnabled()) return;
|
|
12291
12277
|
const stop = lastAssistantStopReason((event as any).messages ?? []);
|
|
12292
12278
|
// Our own watchdog aborted a wedged resumed turn — this is RECOVERY, not a user cancel.
|