@earendil-works/pi-coding-agent 0.85.0 → 0.86.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 +104 -0
- package/README.md +2 -3
- package/dist/bun/cli.d.ts +3 -1
- package/dist/bun/cli.d.ts.map +1 -1
- package/dist/bun/cli.js +3 -12
- package/dist/bun/cli.js.map +1 -1
- package/dist/bun/sandbox-env-setup.d.ts +2 -0
- package/dist/bun/sandbox-env-setup.d.ts.map +1 -0
- package/dist/bun/sandbox-env-setup.js +4 -0
- package/dist/bun/sandbox-env-setup.js.map +1 -0
- package/dist/bundle/chunks/anthropic-messages-MYU5ZMRF.js +6 -0
- package/dist/bundle/chunks/azure-openai-responses-HUSQ3GP2.js +2 -0
- package/dist/bundle/chunks/bedrock-converse-stream.js +47 -38
- package/dist/bundle/chunks/chunk-7IOYH2SY.js +2 -0
- package/dist/bundle/chunks/chunk-7YM6BE7Y.js +1561 -0
- package/dist/bundle/chunks/chunk-A3JYRWB6.js +2 -0
- package/dist/bundle/chunks/chunk-DTD7JQ7Y.js +42 -0
- package/dist/bundle/chunks/chunk-FUXEF6JQ.js +44 -0
- package/dist/bundle/chunks/{chunk-IDDQWTHI.js → chunk-RCZIEVGO.js} +10 -1
- package/dist/bundle/chunks/chunk-S7SZN6Z3.js +2 -0
- package/dist/bundle/chunks/chunk-WIUZI2CJ.js +11 -0
- package/dist/bundle/chunks/github-copilot.js +1 -1
- package/dist/bundle/chunks/google-generative-ai-IMS5EK2Y.js +2 -0
- package/dist/bundle/chunks/google-vertex-O4YJEQUW.js +2 -0
- package/dist/bundle/chunks/jiti-loader-C3NU2SDH.js +21 -0
- package/dist/bundle/chunks/jiti-static-loader-E6ANQD5T.js +2 -0
- package/dist/bundle/chunks/mistral-conversations-NOSQHBEY.js +5 -0
- package/dist/bundle/chunks/{node-EM5FIVUN.js → node-O3RIBJRH.js} +1 -1
- package/dist/bundle/chunks/openai-codex-responses-QDSOTBBO.js +10 -0
- package/dist/bundle/chunks/openai-completions-KQPOPEVM.js +7 -0
- package/dist/bundle/chunks/openai-responses-HWEUZ6WZ.js +2 -0
- package/dist/bundle/chunks/{pi-messages-EGZIFQGG.js → pi-messages-GFKBWJHZ.js} +1 -1
- package/dist/bundle/chunks/virtual-modules-XW5APVUE.js +2 -0
- package/dist/bundle/cli.js +1 -1
- package/dist/bundle/index.js +1 -1
- package/dist/bundle/rpc-entry.js +1 -1
- package/dist/cli/args.d.ts.map +1 -1
- package/dist/cli/args.js +0 -2
- package/dist/cli/args.js.map +1 -1
- package/dist/cli/session-picker.d.ts +1 -1
- package/dist/cli/session-picker.d.ts.map +1 -1
- package/dist/cli/session-picker.js.map +1 -1
- package/dist/cli/setup.d.ts +2 -0
- package/dist/cli/setup.d.ts.map +1 -0
- package/dist/cli/setup.js +12 -0
- package/dist/cli/setup.js.map +1 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +3 -25
- package/dist/cli.js.map +1 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +1 -1
- package/dist/config.js.map +1 -1
- package/dist/core/agent-session.d.ts +56 -6
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +293 -155
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/bug-report-upload.d.ts +12 -0
- package/dist/core/bug-report-upload.d.ts.map +1 -0
- package/dist/core/bug-report-upload.js +22 -0
- package/dist/core/bug-report-upload.js.map +1 -0
- package/dist/core/bug-report.d.ts +176 -0
- package/dist/core/bug-report.d.ts.map +1 -0
- package/dist/core/bug-report.js +292 -0
- package/dist/core/bug-report.js.map +1 -0
- package/dist/core/cache-stats.d.ts.map +1 -1
- package/dist/core/cache-stats.js +12 -1
- package/dist/core/cache-stats.js.map +1 -1
- package/dist/core/cache-warmer.d.ts +102 -0
- package/dist/core/cache-warmer.d.ts.map +1 -0
- package/dist/core/cache-warmer.js +342 -0
- package/dist/core/cache-warmer.js.map +1 -0
- package/dist/core/compaction/branch-summarization.d.ts.map +1 -1
- package/dist/core/compaction/branch-summarization.js +2 -2
- package/dist/core/compaction/branch-summarization.js.map +1 -1
- package/dist/core/compaction/compaction.d.ts +2 -2
- package/dist/core/compaction/compaction.d.ts.map +1 -1
- package/dist/core/compaction/compaction.js +11 -12
- package/dist/core/compaction/compaction.js.map +1 -1
- package/dist/core/crash-log.d.ts +22 -0
- package/dist/core/crash-log.d.ts.map +1 -0
- package/dist/core/crash-log.js +71 -0
- package/dist/core/crash-log.js.map +1 -0
- package/dist/core/experimental.d.ts +0 -4
- package/dist/core/experimental.d.ts.map +1 -1
- package/dist/core/experimental.js +0 -4
- package/dist/core/experimental.js.map +1 -1
- package/dist/core/extensions/index.d.ts +1 -1
- package/dist/core/extensions/index.d.ts.map +1 -1
- package/dist/core/extensions/index.js.map +1 -1
- package/dist/core/extensions/jiti-loader.d.ts +2 -0
- package/dist/core/extensions/jiti-loader.d.ts.map +1 -0
- package/dist/core/extensions/jiti-loader.js +4 -0
- package/dist/core/extensions/jiti-loader.js.map +1 -0
- package/dist/core/extensions/jiti-static-loader.d.ts +2 -0
- package/dist/core/extensions/jiti-static-loader.d.ts.map +1 -0
- package/dist/core/extensions/jiti-static-loader.js +4 -0
- package/dist/core/extensions/jiti-static-loader.js.map +1 -0
- package/dist/core/extensions/loader.d.ts.map +1 -1
- package/dist/core/extensions/loader.js +38 -51
- package/dist/core/extensions/loader.js.map +1 -1
- package/dist/core/extensions/runner.d.ts +10 -7
- package/dist/core/extensions/runner.d.ts.map +1 -1
- package/dist/core/extensions/runner.js +85 -67
- package/dist/core/extensions/runner.js.map +1 -1
- package/dist/core/extensions/types.d.ts +60 -50
- package/dist/core/extensions/types.d.ts.map +1 -1
- package/dist/core/extensions/types.js.map +1 -1
- package/dist/core/extensions/virtual-modules.d.ts +3 -0
- package/dist/core/extensions/virtual-modules.d.ts.map +1 -0
- package/dist/core/extensions/virtual-modules.js +38 -0
- package/dist/core/extensions/virtual-modules.js.map +1 -0
- package/dist/core/extensions/wrapper.d.ts.map +1 -1
- package/dist/core/extensions/wrapper.js +1 -20
- package/dist/core/extensions/wrapper.js.map +1 -1
- package/dist/core/index.d.ts +2 -1
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js.map +1 -1
- package/dist/core/keybindings.d.ts +6 -1
- package/dist/core/keybindings.d.ts.map +1 -1
- package/dist/core/keybindings.js +5 -1
- package/dist/core/keybindings.js.map +1 -1
- package/dist/core/messages.d.ts.map +1 -1
- package/dist/core/messages.js +1 -0
- package/dist/core/messages.js.map +1 -1
- package/dist/core/model-config.d.ts +101 -20
- package/dist/core/model-config.d.ts.map +1 -1
- package/dist/core/model-config.js +25 -18
- package/dist/core/model-config.js.map +1 -1
- package/dist/core/model-registry.d.ts +5 -1
- package/dist/core/model-registry.d.ts.map +1 -1
- package/dist/core/model-registry.js +8 -0
- package/dist/core/model-registry.js.map +1 -1
- package/dist/core/model-resolver.d.ts.map +1 -1
- package/dist/core/model-resolver.js +1 -1
- package/dist/core/model-resolver.js.map +1 -1
- package/dist/core/model-runtime.d.ts.map +1 -1
- package/dist/core/model-runtime.js +5 -3
- package/dist/core/model-runtime.js.map +1 -1
- package/dist/core/provider-composer.d.ts +3 -2
- package/dist/core/provider-composer.d.ts.map +1 -1
- package/dist/core/provider-composer.js +2 -0
- package/dist/core/provider-composer.js.map +1 -1
- package/dist/core/radius.d.ts +3 -0
- package/dist/core/radius.d.ts.map +1 -1
- package/dist/core/radius.js +6 -0
- package/dist/core/radius.js.map +1 -1
- package/dist/core/sdk.d.ts.map +1 -1
- package/dist/core/sdk.js +60 -38
- package/dist/core/sdk.js.map +1 -1
- package/dist/core/session-export.d.ts +6 -2
- package/dist/core/session-export.d.ts.map +1 -1
- package/dist/core/session-export.js +14 -13
- package/dist/core/session-export.js.map +1 -1
- package/dist/core/session-manager.d.ts +29 -6
- package/dist/core/session-manager.d.ts.map +1 -1
- package/dist/core/session-manager.js +159 -96
- package/dist/core/session-manager.js.map +1 -1
- package/dist/core/settings-manager.d.ts +20 -4
- package/dist/core/settings-manager.d.ts.map +1 -1
- package/dist/core/settings-manager.js +43 -7
- package/dist/core/settings-manager.js.map +1 -1
- package/dist/core/slash-commands.d.ts.map +1 -1
- package/dist/core/slash-commands.js +1 -0
- package/dist/core/slash-commands.js.map +1 -1
- package/dist/core/system-prompt.d.ts +49 -6
- package/dist/core/system-prompt.d.ts.map +1 -1
- package/dist/core/system-prompt.js +121 -92
- package/dist/core/system-prompt.js.map +1 -1
- package/dist/core/tools/bash.d.ts +2 -1
- package/dist/core/tools/bash.d.ts.map +1 -1
- package/dist/core/tools/bash.js +10 -4
- package/dist/core/tools/bash.js.map +1 -1
- package/dist/core/tools/edit.d.ts.map +1 -1
- package/dist/core/tools/edit.js +1 -2
- package/dist/core/tools/edit.js.map +1 -1
- package/dist/core/tools/read.d.ts.map +1 -1
- package/dist/core/tools/read.js +1 -2
- package/dist/core/tools/read.js.map +1 -1
- package/dist/core/tools/renderers/bash.d.ts.map +1 -1
- package/dist/core/tools/renderers/bash.js +9 -1
- package/dist/core/tools/renderers/bash.js.map +1 -1
- package/dist/core/tools/write.d.ts.map +1 -1
- package/dist/core/tools/write.js +1 -2
- package/dist/core/tools/write.js.map +1 -1
- package/dist/core/usage-totals.d.ts +1 -1
- package/dist/core/usage-totals.d.ts.map +1 -1
- package/dist/core/usage-totals.js +5 -1
- package/dist/core/usage-totals.js.map +1 -1
- package/dist/extensions/llama/client.d.ts +2 -0
- package/dist/extensions/llama/client.d.ts.map +1 -1
- package/dist/extensions/llama/client.js +7 -3
- package/dist/extensions/llama/client.js.map +1 -1
- package/dist/extensions/llama/provider.d.ts.map +1 -1
- package/dist/extensions/llama/provider.js +20 -5
- package/dist/extensions/llama/provider.js.map +1 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +13 -131
- package/dist/main.js.map +1 -1
- package/dist/migrations.d.ts.map +1 -1
- package/dist/migrations.js +2 -2
- package/dist/migrations.js.map +1 -1
- package/dist/modes/interactive/bug-report.d.ts +14 -0
- package/dist/modes/interactive/bug-report.d.ts.map +1 -0
- package/dist/modes/interactive/bug-report.js +209 -0
- package/dist/modes/interactive/bug-report.js.map +1 -0
- package/dist/modes/interactive/chat-viewport.d.ts.map +1 -1
- package/dist/modes/interactive/chat-viewport.js +1 -1
- package/dist/modes/interactive/chat-viewport.js.map +1 -1
- package/dist/modes/interactive/components/branch-summary-message.d.ts.map +1 -1
- package/dist/modes/interactive/components/branch-summary-message.js +12 -5
- package/dist/modes/interactive/components/branch-summary-message.js.map +1 -1
- package/dist/modes/interactive/components/compaction-summary-message.d.ts.map +1 -1
- package/dist/modes/interactive/components/compaction-summary-message.js +12 -5
- package/dist/modes/interactive/components/compaction-summary-message.js.map +1 -1
- package/dist/modes/interactive/components/custom-editor.d.ts +3 -3
- package/dist/modes/interactive/components/custom-editor.d.ts.map +1 -1
- package/dist/modes/interactive/components/custom-editor.js.map +1 -1
- package/dist/modes/interactive/components/extension-input.d.ts +2 -0
- package/dist/modes/interactive/components/extension-input.d.ts.map +1 -1
- package/dist/modes/interactive/components/extension-input.js +6 -0
- package/dist/modes/interactive/components/extension-input.js.map +1 -1
- package/dist/modes/interactive/components/extension-selector.d.ts +1 -0
- package/dist/modes/interactive/components/extension-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/extension-selector.js +4 -0
- package/dist/modes/interactive/components/extension-selector.js.map +1 -1
- package/dist/modes/interactive/components/footer.d.ts.map +1 -1
- package/dist/modes/interactive/components/footer.js +4 -1
- package/dist/modes/interactive/components/footer.js.map +1 -1
- package/dist/modes/interactive/components/model-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/model-selector.js +5 -5
- package/dist/modes/interactive/components/model-selector.js.map +1 -1
- package/dist/modes/interactive/components/scoped-models-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/scoped-models-selector.js +8 -8
- package/dist/modes/interactive/components/scoped-models-selector.js.map +1 -1
- package/dist/modes/interactive/components/session-selector.d.ts +5 -5
- package/dist/modes/interactive/components/session-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/session-selector.js +74 -48
- package/dist/modes/interactive/components/session-selector.js.map +1 -1
- package/dist/modes/interactive/components/settings-selector.d.ts +3 -1
- package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/settings-selector.js +11 -0
- package/dist/modes/interactive/components/settings-selector.js.map +1 -1
- package/dist/modes/interactive/components/skill-invocation-message.d.ts.map +1 -1
- package/dist/modes/interactive/components/skill-invocation-message.js +11 -4
- package/dist/modes/interactive/components/skill-invocation-message.js.map +1 -1
- package/dist/modes/interactive/components/status-indicator.d.ts +2 -2
- package/dist/modes/interactive/components/status-indicator.d.ts.map +1 -1
- package/dist/modes/interactive/components/status-indicator.js +7 -7
- package/dist/modes/interactive/components/status-indicator.js.map +1 -1
- package/dist/modes/interactive/components/thinking-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/thinking-selector.js +4 -4
- package/dist/modes/interactive/components/thinking-selector.js.map +1 -1
- package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
- package/dist/modes/interactive/components/tool-execution.js +18 -9
- package/dist/modes/interactive/components/tool-execution.js.map +1 -1
- package/dist/modes/interactive/components/tree-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/tree-selector.js +2 -0
- package/dist/modes/interactive/components/tree-selector.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts +13 -1
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +220 -84
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/modes/interactive/session-share.d.ts +2 -0
- package/dist/modes/interactive/session-share.d.ts.map +1 -1
- package/dist/modes/interactive/session-share.js +8 -4
- package/dist/modes/interactive/session-share.js.map +1 -1
- package/dist/modes/interactive/tui-renderer.d.ts.map +1 -1
- package/dist/modes/interactive/tui-renderer.js +2 -2
- package/dist/modes/interactive/tui-renderer.js.map +1 -1
- package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-mode.js +2 -2
- package/dist/modes/rpc/rpc-mode.js.map +1 -1
- package/dist/utils/clipboard-command.d.ts +7 -0
- package/dist/utils/clipboard-command.d.ts.map +1 -0
- package/dist/utils/clipboard-command.js +45 -0
- package/dist/utils/clipboard-command.js.map +1 -0
- package/dist/utils/clipboard-image.d.ts.map +1 -1
- package/dist/utils/clipboard-image.js +52 -69
- package/dist/utils/clipboard-image.js.map +1 -1
- package/dist/utils/clipboard.d.ts.map +1 -1
- package/dist/utils/clipboard.js +61 -125
- package/dist/utils/clipboard.js.map +1 -1
- package/dist/utils/zip.d.ts +7 -0
- package/dist/utils/zip.d.ts.map +1 -0
- package/dist/utils/zip.js +60 -0
- package/dist/utils/zip.js.map +1 -0
- package/docs/compaction.md +39 -13
- package/docs/containerization.md +45 -0
- package/docs/custom-provider.md +22 -13
- package/docs/development.md +23 -4
- package/docs/docs.json +4 -0
- package/docs/environment-variables.md +3 -2
- package/docs/extensions.md +57 -43
- package/docs/json.md +4 -4
- package/docs/keybindings.md +2 -1
- package/docs/models.md +34 -3
- package/docs/providers.md +2 -2
- package/docs/sdk.md +4 -2
- package/docs/security.md +1 -1
- package/docs/session-format.md +82 -40
- package/docs/sessions.md +27 -0
- package/docs/settings.md +60 -1
- package/docs/termux.md +0 -1
- package/docs/tui.md +2 -2
- package/docs/usage.md +1 -0
- package/examples/extensions/README.md +0 -1
- package/examples/extensions/custom-provider-anthropic/index.ts +18 -12
- package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
- package/examples/extensions/custom-provider-anthropic/package.json +1 -1
- package/examples/extensions/custom-provider-gitlab-duo/index.ts +2 -2
- package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
- package/examples/extensions/gondolin/package-lock.json +2 -2
- package/examples/extensions/gondolin/package.json +1 -1
- package/examples/extensions/prompt-customizer.ts +19 -67
- package/examples/extensions/sandbox/package-lock.json +2 -2
- package/examples/extensions/sandbox/package.json +1 -1
- package/examples/extensions/with-deps/package-lock.json +2 -2
- package/examples/extensions/with-deps/package.json +1 -1
- package/npm-shrinkwrap.json +425 -778
- package/package.json +22 -27
- package/dist/bundle/chunks/anthropic-messages-QAQLEQIY.js +0 -6
- package/dist/bundle/chunks/azure-openai-responses-Q7MJBRE6.js +0 -2
- package/dist/bundle/chunks/chunk-2KVJKXS2.js +0 -50
- package/dist/bundle/chunks/chunk-AXIIZGTV.js +0 -2
- package/dist/bundle/chunks/chunk-EQ6DR6XK.js +0 -5
- package/dist/bundle/chunks/chunk-GMWCTUPB.js +0 -2
- package/dist/bundle/chunks/chunk-LZKJTKCT.js +0 -2
- package/dist/bundle/chunks/chunk-OUXLLA64.js +0 -11
- package/dist/bundle/chunks/chunk-R7UUJ4C2.js +0 -42
- package/dist/bundle/chunks/chunk-UAQELI3K.js +0 -2
- package/dist/bundle/chunks/chunk-WBE4J4AU.js +0 -9
- package/dist/bundle/chunks/chunk-WZB2R5YO.js +0 -1558
- package/dist/bundle/chunks/google-generative-ai-BSMTYDAM.js +0 -2
- package/dist/bundle/chunks/google-vertex-5C3HASOJ.js +0 -2
- package/dist/bundle/chunks/mistral-conversations-4M6BZBX6.js +0 -5
- package/dist/bundle/chunks/openai-codex-responses-YJOO52RW.js +0 -10
- package/dist/bundle/chunks/openai-completions-QOT3QX7D.js +0 -7
- package/dist/bundle/chunks/openai-responses-U7LNPQN4.js +0 -2
- package/dist/bundle/client.js +0 -2
- package/dist/bundle/coordinator.js +0 -3
- package/dist/cli/experimental/cli.d.ts +0 -10
- package/dist/cli/experimental/cli.d.ts.map +0 -1
- package/dist/cli/experimental/cli.js +0 -9
- package/dist/cli/experimental/cli.js.map +0 -1
- package/dist/cli/experimental/command-options.d.ts +0 -28
- package/dist/cli/experimental/command-options.d.ts.map +0 -1
- package/dist/cli/experimental/command-options.js +0 -84
- package/dist/cli/experimental/command-options.js.map +0 -1
- package/dist/cli/experimental/command.d.ts +0 -70
- package/dist/cli/experimental/command.d.ts.map +0 -1
- package/dist/cli/experimental/command.js +0 -144
- package/dist/cli/experimental/command.js.map +0 -1
- package/dist/cli/experimental/commands/client.d.ts +0 -19
- package/dist/cli/experimental/commands/client.d.ts.map +0 -1
- package/dist/cli/experimental/commands/client.js +0 -63
- package/dist/cli/experimental/commands/client.js.map +0 -1
- package/dist/cli/experimental/commands/server.d.ts +0 -17
- package/dist/cli/experimental/commands/server.d.ts.map +0 -1
- package/dist/cli/experimental/commands/server.js +0 -44
- package/dist/cli/experimental/commands/server.js.map +0 -1
- package/dist/client/index.d.ts +0 -2
- package/dist/client/index.d.ts.map +0 -1
- package/dist/client/index.js +0 -2
- package/dist/client/index.js.map +0 -1
- package/dist/experimental/client-runtime.d.ts +0 -43
- package/dist/experimental/client-runtime.d.ts.map +0 -1
- package/dist/experimental/client-runtime.js +0 -183
- package/dist/experimental/client-runtime.js.map +0 -1
- package/dist/experimental/client-tui-chat.d.ts +0 -14
- package/dist/experimental/client-tui-chat.d.ts.map +0 -1
- package/dist/experimental/client-tui-chat.js +0 -171
- package/dist/experimental/client-tui-chat.js.map +0 -1
- package/dist/experimental/client-tui.d.ts +0 -37
- package/dist/experimental/client-tui.d.ts.map +0 -1
- package/dist/experimental/client-tui.js +0 -702
- package/dist/experimental/client-tui.js.map +0 -1
- package/dist/experimental/client.d.ts +0 -25
- package/dist/experimental/client.d.ts.map +0 -1
- package/dist/experimental/client.js +0 -99
- package/dist/experimental/client.js.map +0 -1
- package/dist/experimental/coordinator-entry.d.ts +0 -3
- package/dist/experimental/coordinator-entry.d.ts.map +0 -1
- package/dist/experimental/coordinator-entry.js +0 -11
- package/dist/experimental/coordinator-entry.js.map +0 -1
- package/dist/experimental/coordinator.d.ts +0 -38
- package/dist/experimental/coordinator.d.ts.map +0 -1
- package/dist/experimental/coordinator.js +0 -570
- package/dist/experimental/coordinator.js.map +0 -1
- package/dist/experimental/mini/main.d.ts +0 -7
- package/dist/experimental/mini/main.d.ts.map +0 -1
- package/dist/experimental/mini/main.js +0 -21
- package/dist/experimental/mini/main.js.map +0 -1
- package/dist/experimental/mini/server/entry.d.ts +0 -3
- package/dist/experimental/mini/server/entry.d.ts.map +0 -1
- package/dist/experimental/mini/server/entry.js +0 -11
- package/dist/experimental/mini/server/entry.js.map +0 -1
- package/dist/experimental/mini/server/run.d.ts +0 -13
- package/dist/experimental/mini/server/run.d.ts.map +0 -1
- package/dist/experimental/mini/server/run.js +0 -150
- package/dist/experimental/mini/server/run.js.map +0 -1
- package/dist/experimental/mini/shared/protocol.d.ts +0 -127
- package/dist/experimental/mini/shared/protocol.d.ts.map +0 -1
- package/dist/experimental/mini/shared/protocol.js +0 -13
- package/dist/experimental/mini/shared/protocol.js.map +0 -1
- package/dist/experimental/mini/shared/rpc.d.ts +0 -49
- package/dist/experimental/mini/shared/rpc.d.ts.map +0 -1
- package/dist/experimental/mini/shared/rpc.js +0 -171
- package/dist/experimental/mini/shared/rpc.js.map +0 -1
- package/dist/experimental/mini/shared/transport.d.ts +0 -35
- package/dist/experimental/mini/shared/transport.d.ts.map +0 -1
- package/dist/experimental/mini/shared/transport.js +0 -94
- package/dist/experimental/mini/shared/transport.js.map +0 -1
- package/dist/experimental/mini/tui/run.d.ts +0 -9
- package/dist/experimental/mini/tui/run.d.ts.map +0 -1
- package/dist/experimental/mini/tui/run.js +0 -62
- package/dist/experimental/mini/tui/run.js.map +0 -1
- package/dist/experimental/mini/tui/session.d.ts +0 -26
- package/dist/experimental/mini/tui/session.d.ts.map +0 -1
- package/dist/experimental/mini/tui/session.js +0 -94
- package/dist/experimental/mini/tui/session.js.map +0 -1
- package/dist/experimental/mini/tui/view.d.ts +0 -10
- package/dist/experimental/mini/tui/view.d.ts.map +0 -1
- package/dist/experimental/mini/tui/view.js +0 -459
- package/dist/experimental/mini/tui/view.js.map +0 -1
- package/dist/experimental/mini/worker/entry.d.ts +0 -3
- package/dist/experimental/mini/worker/entry.d.ts.map +0 -1
- package/dist/experimental/mini/worker/entry.js +0 -10
- package/dist/experimental/mini/worker/entry.js.map +0 -1
- package/dist/experimental/mini/worker/lane-service.d.ts +0 -44
- package/dist/experimental/mini/worker/lane-service.d.ts.map +0 -1
- package/dist/experimental/mini/worker/lane-service.js +0 -97
- package/dist/experimental/mini/worker/lane-service.js.map +0 -1
- package/dist/experimental/mini/worker/models-service.d.ts +0 -19
- package/dist/experimental/mini/worker/models-service.d.ts.map +0 -1
- package/dist/experimental/mini/worker/models-service.js +0 -136
- package/dist/experimental/mini/worker/models-service.js.map +0 -1
- package/dist/experimental/mini/worker/run.d.ts +0 -14
- package/dist/experimental/mini/worker/run.d.ts.map +0 -1
- package/dist/experimental/mini/worker/run.js +0 -88
- package/dist/experimental/mini/worker/run.js.map +0 -1
- package/dist/experimental/plugin.d.ts +0 -4
- package/dist/experimental/plugin.d.ts.map +0 -1
- package/dist/experimental/plugin.js +0 -4
- package/dist/experimental/plugin.js.map +0 -1
- package/dist/experimental/plugins/bundled.d.ts +0 -7
- package/dist/experimental/plugins/bundled.d.ts.map +0 -1
- package/dist/experimental/plugins/bundled.js +0 -48
- package/dist/experimental/plugins/bundled.js.map +0 -1
- package/dist/experimental/plugins/package.d.ts +0 -18
- package/dist/experimental/plugins/package.d.ts.map +0 -1
- package/dist/experimental/plugins/package.js +0 -113
- package/dist/experimental/plugins/package.js.map +0 -1
- package/dist/experimental/process.d.ts +0 -20
- package/dist/experimental/process.d.ts.map +0 -1
- package/dist/experimental/process.js +0 -85
- package/dist/experimental/process.js.map +0 -1
- package/dist/experimental/radius-auth.d.ts +0 -17
- package/dist/experimental/radius-auth.d.ts.map +0 -1
- package/dist/experimental/radius-auth.js +0 -52
- package/dist/experimental/radius-auth.js.map +0 -1
- package/dist/experimental/radius-relay.d.ts +0 -90
- package/dist/experimental/radius-relay.d.ts.map +0 -1
- package/dist/experimental/radius-relay.js +0 -582
- package/dist/experimental/radius-relay.js.map +0 -1
- package/dist/experimental/server.d.ts +0 -75
- package/dist/experimental/server.d.ts.map +0 -1
- package/dist/experimental/server.js +0 -682
- package/dist/experimental/server.js.map +0 -1
- package/dist/experimental/services/agent-controller-provider.d.ts +0 -4
- package/dist/experimental/services/agent-controller-provider.d.ts.map +0 -1
- package/dist/experimental/services/agent-controller-provider.js +0 -84
- package/dist/experimental/services/agent-controller-provider.js.map +0 -1
- package/dist/experimental/services/agent-controller.d.ts +0 -57
- package/dist/experimental/services/agent-controller.d.ts.map +0 -1
- package/dist/experimental/services/agent-controller.js +0 -3
- package/dist/experimental/services/agent-controller.js.map +0 -1
- package/dist/experimental/services/connection.d.ts +0 -40
- package/dist/experimental/services/connection.d.ts.map +0 -1
- package/dist/experimental/services/connection.js +0 -300
- package/dist/experimental/services/connection.js.map +0 -1
- package/dist/experimental/services/models-provider.d.ts +0 -16
- package/dist/experimental/services/models-provider.d.ts.map +0 -1
- package/dist/experimental/services/models-provider.js +0 -116
- package/dist/experimental/services/models-provider.js.map +0 -1
- package/dist/experimental/services/models.d.ts +0 -36
- package/dist/experimental/services/models.d.ts.map +0 -1
- package/dist/experimental/services/models.js +0 -3
- package/dist/experimental/services/models.js.map +0 -1
- package/dist/experimental/services/plugins.d.ts +0 -16
- package/dist/experimental/services/plugins.d.ts.map +0 -1
- package/dist/experimental/services/plugins.js +0 -4
- package/dist/experimental/services/plugins.js.map +0 -1
- package/dist/experimental/services/presentation-ui.d.ts +0 -13
- package/dist/experimental/services/presentation-ui.d.ts.map +0 -1
- package/dist/experimental/services/presentation-ui.js +0 -3
- package/dist/experimental/services/presentation-ui.js.map +0 -1
- package/dist/experimental/services/server.d.ts +0 -19
- package/dist/experimental/services/server.d.ts.map +0 -1
- package/dist/experimental/services/server.js +0 -104
- package/dist/experimental/services/server.js.map +0 -1
- package/dist/experimental/services/sessions.d.ts +0 -28
- package/dist/experimental/services/sessions.d.ts.map +0 -1
- package/dist/experimental/services/sessions.js +0 -4
- package/dist/experimental/services/sessions.js.map +0 -1
- package/dist/experimental/services/slash-commands-provider.d.ts +0 -14
- package/dist/experimental/services/slash-commands-provider.d.ts.map +0 -1
- package/dist/experimental/services/slash-commands-provider.js +0 -200
- package/dist/experimental/services/slash-commands-provider.js.map +0 -1
- package/dist/experimental/services/slash-commands.d.ts +0 -24
- package/dist/experimental/services/slash-commands.d.ts.map +0 -1
- package/dist/experimental/services/slash-commands.js +0 -3
- package/dist/experimental/services/slash-commands.js.map +0 -1
- package/dist/experimental/services/transcript-provider.d.ts +0 -12
- package/dist/experimental/services/transcript-provider.d.ts.map +0 -1
- package/dist/experimental/services/transcript-provider.js +0 -109
- package/dist/experimental/services/transcript-provider.js.map +0 -1
- package/dist/experimental/services/transcript.d.ts +0 -13
- package/dist/experimental/services/transcript.d.ts.map +0 -1
- package/dist/experimental/services/transcript.js +0 -3
- package/dist/experimental/services/transcript.js.map +0 -1
- package/dist/experimental/services/worker.d.ts +0 -28
- package/dist/experimental/services/worker.d.ts.map +0 -1
- package/dist/experimental/services/worker.js +0 -107
- package/dist/experimental/services/worker.js.map +0 -1
- package/dist/experimental/session-worker-manager.d.ts +0 -24
- package/dist/experimental/session-worker-manager.d.ts.map +0 -1
- package/dist/experimental/session-worker-manager.js +0 -691
- package/dist/experimental/session-worker-manager.js.map +0 -1
- package/dist/experimental/session-worker.d.ts +0 -188
- package/dist/experimental/session-worker.d.ts.map +0 -1
- package/dist/experimental/session-worker.js +0 -785
- package/dist/experimental/session-worker.js.map +0 -1
- package/dist/experimental/source-resolver.d.ts +0 -2
- package/dist/experimental/source-resolver.d.ts.map +0 -1
- package/dist/experimental/source-resolver.js +0 -71
- package/dist/experimental/source-resolver.js.map +0 -1
- package/dist/utils/clipboard-native.d.ts +0 -11
- package/dist/utils/clipboard-native.d.ts.map +0 -1
- package/dist/utils/clipboard-native.js +0 -20
- package/dist/utils/clipboard-native.js.map +0 -1
- package/examples/extensions/kimi-deferred-tools.ts +0 -61
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
import { type FacetBundleArtifact } from "@earendil-works/chord/node";
|
|
2
|
-
import type { ServerId } from "@earendil-works/pi-protocol";
|
|
3
|
-
export interface ConfiguredServerPluginPackage {
|
|
4
|
-
readonly manifestPath: string;
|
|
5
|
-
build(): Promise<readonly FacetBundleArtifact[]>;
|
|
6
|
-
}
|
|
7
|
-
/** Persist an explicit plugin package selection or restore it for a later server generation. */
|
|
8
|
-
export declare function restoreServerPluginPackageProfile(directory: string, serverId: ServerId, configuredPackagePaths?: readonly string[]): Promise<readonly string[]>;
|
|
9
|
-
/** Read the explicit plugin selection stored for one durable Session. */
|
|
10
|
-
export declare function readSessionPluginPackageProfile(directory: string, serverId: ServerId, sessionPath: string): Promise<readonly string[] | undefined>;
|
|
11
|
-
/** Remove the plugin selection stored for one deleted Session. */
|
|
12
|
-
export declare function removeSessionPluginPackageProfile(directory: string, serverId: ServerId, sessionPath: string): Promise<void>;
|
|
13
|
-
/** Persist the plugin selection for one durable Session. */
|
|
14
|
-
export declare function writeSessionPluginPackageProfile(directory: string, serverId: ServerId, sessionPath: string, packagePaths: readonly string[]): Promise<void>;
|
|
15
|
-
/** Create a serialized server-owned builder for one configured plugin package. */
|
|
16
|
-
export declare function createServerPluginPackage(directory: string, serverId: ServerId, packagePath: string): ConfiguredServerPluginPackage;
|
|
17
|
-
export declare function normalizePluginPackagePaths(packagePaths: readonly string[]): readonly string[];
|
|
18
|
-
//# sourceMappingURL=package.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"package.d.ts","sourceRoot":"","sources":["../../../src/experimental/plugins/package.ts"],"names":[],"mappings":"AAIA,OAAO,EAEN,KAAK,mBAAmB,EAExB,MAAM,4BAA4B,CAAC;AACpC,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,6BAA6B,CAAC;AAK5D,MAAM,WAAW,6BAA6B;IAC7C,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,KAAK,IAAI,OAAO,CAAC,SAAS,mBAAmB,EAAE,CAAC,CAAC;CACjD;AAED,gGAAgG;AAChG,wBAAsB,iCAAiC,CACtD,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,QAAQ,EAClB,sBAAsB,CAAC,EAAE,SAAS,MAAM,EAAE,GACxC,OAAO,CAAC,SAAS,MAAM,EAAE,CAAC,CAS5B;AAED,yEAAyE;AACzE,wBAAgB,+BAA+B,CAC9C,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,QAAQ,EAClB,WAAW,EAAE,MAAM,GACjB,OAAO,CAAC,SAAS,MAAM,EAAE,GAAG,SAAS,CAAC,CAExC;AAED,kEAAkE;AAClE,wBAAsB,iCAAiC,CACtD,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,QAAQ,EAClB,WAAW,EAAE,MAAM,GACjB,OAAO,CAAC,IAAI,CAAC,CAEf;AAED,4DAA4D;AAC5D,wBAAgB,gCAAgC,CAC/C,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,QAAQ,EAClB,WAAW,EAAE,MAAM,EACnB,YAAY,EAAE,SAAS,MAAM,EAAE,GAC7B,OAAO,CAAC,IAAI,CAAC,CAMf;AAED,kFAAkF;AAClF,wBAAgB,yBAAyB,CACxC,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,QAAQ,EAClB,WAAW,EAAE,MAAM,GACjB,6BAA6B,CAwB/B;AAoDD,wBAAgB,2BAA2B,CAAC,YAAY,EAAE,SAAS,MAAM,EAAE,GAAG,SAAS,MAAM,EAAE,CAS9F","sourcesContent":["import { createHash } from \"node:crypto\";\nimport { readFile, rm, writeFile } from \"node:fs/promises\";\nimport { basename, dirname, join, resolve } from \"node:path\";\nimport { bundleFacetPackage } from \"@earendil-works/chord/bundler\";\nimport {\n\tFACET_BUNDLE_MANIFEST_FILE,\n\ttype FacetBundleArtifact,\n\treadFacetBundleArtifact,\n} from \"@earendil-works/chord/node\";\nimport type { ServerId } from \"@earendil-works/pi-protocol\";\n\nconst PLUGIN_PACKAGE_PROFILE_VERSION = 1;\nconst DEFAULT_PLUGIN_FACETS = Object.freeze({ session: \"src/session.ts\", tui: \"src/tui.ts\" });\n\nexport interface ConfiguredServerPluginPackage {\n\treadonly manifestPath: string;\n\tbuild(): Promise<readonly FacetBundleArtifact[]>;\n}\n\n/** Persist an explicit plugin package selection or restore it for a later server generation. */\nexport async function restoreServerPluginPackageProfile(\n\tdirectory: string,\n\tserverId: ServerId,\n\tconfiguredPackagePaths?: readonly string[],\n): Promise<readonly string[]> {\n\tconst path = join(directory, `plugin-packages-${serverId}.json`);\n\tif (configuredPackagePaths === undefined) {\n\t\treturn (await readPluginPackageProfile(path, false)) ?? [];\n\t}\n\tconst packagePaths = normalizePluginPackagePaths(configuredPackagePaths);\n\tif (packagePaths.length === 0) await rm(path, { force: true });\n\telse await writePluginPackageProfile(path, packagePaths);\n\treturn packagePaths;\n}\n\n/** Read the explicit plugin selection stored for one durable Session. */\nexport function readSessionPluginPackageProfile(\n\tdirectory: string,\n\tserverId: ServerId,\n\tsessionPath: string,\n): Promise<readonly string[] | undefined> {\n\treturn readPluginPackageProfile(sessionPluginProfilePath(directory, serverId, sessionPath), true, sessionPath);\n}\n\n/** Remove the plugin selection stored for one deleted Session. */\nexport async function removeSessionPluginPackageProfile(\n\tdirectory: string,\n\tserverId: ServerId,\n\tsessionPath: string,\n): Promise<void> {\n\tawait rm(sessionPluginProfilePath(directory, serverId, sessionPath), { force: true });\n}\n\n/** Persist the plugin selection for one durable Session. */\nexport function writeSessionPluginPackageProfile(\n\tdirectory: string,\n\tserverId: ServerId,\n\tsessionPath: string,\n\tpackagePaths: readonly string[],\n): Promise<void> {\n\treturn writePluginPackageProfile(\n\t\tsessionPluginProfilePath(directory, serverId, sessionPath),\n\t\tnormalizePluginPackagePaths(packagePaths),\n\t\tsessionPath,\n\t);\n}\n\n/** Create a serialized server-owned builder for one configured plugin package. */\nexport function createServerPluginPackage(\n\tdirectory: string,\n\tserverId: ServerId,\n\tpackagePath: string,\n): ConfiguredServerPluginPackage {\n\tconst normalizedPackagePath = resolve(packagePath);\n\tconst outdir = join(directory, \"plugin-builds\", serverId, pluginBuildDirectoryName(normalizedPackagePath));\n\tconst manifestPath = join(outdir, FACET_BUNDLE_MANIFEST_FILE);\n\tlet buildTail = Promise.resolve();\n\treturn {\n\t\tmanifestPath,\n\t\tbuild() {\n\t\t\tconst operation = buildTail.then(async () => {\n\t\t\t\tconst result = await bundleFacetPackage({\n\t\t\t\t\tpackagePath: normalizedPackagePath,\n\t\t\t\t\toutdir,\n\t\t\t\t\tdefaultFacets: DEFAULT_PLUGIN_FACETS,\n\t\t\t\t});\n\t\t\t\tif (result.manifest.entries.tui === undefined) return [];\n\t\t\t\treturn [await readFacetBundleArtifact({ manifestPath: result.manifestPath, entry: \"tui\" })];\n\t\t\t});\n\t\t\tbuildTail = operation.then(\n\t\t\t\t() => undefined,\n\t\t\t\t() => undefined,\n\t\t\t);\n\t\t\treturn operation;\n\t\t},\n\t};\n}\n\nasync function readPluginPackageProfile(\n\tpath: string,\n\tallowEmpty: boolean,\n\tsessionPath?: string,\n): Promise<readonly string[] | undefined> {\n\tlet parsed: unknown;\n\ttry {\n\t\tparsed = JSON.parse(await readFile(path, \"utf8\"));\n\t} catch (error) {\n\t\tif (error instanceof Error && \"code\" in error && error.code === \"ENOENT\") return undefined;\n\t\tthrow new Error(`Could not read experimental plugin package profile ${path}`, { cause: error });\n\t}\n\tif (\n\t\ttypeof parsed !== \"object\" ||\n\t\tparsed === null ||\n\t\tArray.isArray(parsed) ||\n\t\tObject.keys(parsed).some(\n\t\t\t(key) => key !== \"version\" && key !== \"packagePaths\" && !(key === \"sessionPath\" && sessionPath !== undefined),\n\t\t) ||\n\t\t!(\"version\" in parsed) ||\n\t\tparsed.version !== PLUGIN_PACKAGE_PROFILE_VERSION ||\n\t\t(sessionPath === undefined\n\t\t\t? \"sessionPath\" in parsed\n\t\t\t: !(\"sessionPath\" in parsed) || parsed.sessionPath !== sessionPath) ||\n\t\t!(\"packagePaths\" in parsed) ||\n\t\t!Array.isArray(parsed.packagePaths) ||\n\t\t(!allowEmpty && parsed.packagePaths.length === 0) ||\n\t\tparsed.packagePaths.some((packagePath) => typeof packagePath !== \"string\" || packagePath.length === 0)\n\t) {\n\t\tthrow new Error(`Invalid experimental plugin package profile ${path}`);\n\t}\n\treturn normalizePluginPackagePaths(parsed.packagePaths as readonly string[]);\n}\n\nfunction writePluginPackageProfile(path: string, packagePaths: readonly string[], sessionPath?: string): Promise<void> {\n\treturn writeFile(\n\t\tpath,\n\t\t`${JSON.stringify(\n\t\t\t{\n\t\t\t\tversion: PLUGIN_PACKAGE_PROFILE_VERSION,\n\t\t\t\t...(sessionPath === undefined ? {} : { sessionPath }),\n\t\t\t\tpackagePaths,\n\t\t\t},\n\t\t\tnull,\n\t\t\t2,\n\t\t)}\\n`,\n\t\t{ encoding: \"utf8\", mode: 0o600 },\n\t);\n}\n\nexport function normalizePluginPackagePaths(packagePaths: readonly string[]): readonly string[] {\n\tconst normalized = packagePaths.map((packagePath) => {\n\t\tif (packagePath.length === 0) throw new Error(\"Plugin package path must not be empty\");\n\t\treturn resolve(packagePath);\n\t});\n\tif (new Set(normalized).size !== normalized.length) {\n\t\tthrow new Error(\"Plugin package paths must be unique\");\n\t}\n\treturn Object.freeze(normalized);\n}\n\nfunction sessionPluginProfilePath(directory: string, serverId: ServerId, sessionPath: string): string {\n\tconst hash = createHash(\"sha256\").update(sessionPath).digest(\"hex\").slice(0, 24);\n\treturn join(directory, `session-plugin-packages-${serverId}-${hash}.json`);\n}\n\nfunction pluginBuildDirectoryName(packagePath: string): string {\n\tconst packageDirectory = basename(packagePath) === \"package.json\" ? dirname(packagePath) : packagePath;\n\tconst base = basename(packageDirectory).replaceAll(/[^a-zA-Z0-9._-]/gu, \"-\") || \"plugin\";\n\tconst label = base.endsWith(\"-plugin\") ? base : `${base}-plugin`;\n\tconst hash = createHash(\"sha256\").update(packagePath).digest(\"hex\").slice(0, 12);\n\treturn `${label}-${hash}`;\n}\n"]}
|
|
@@ -1,113 +0,0 @@
|
|
|
1
|
-
import { createHash } from "node:crypto";
|
|
2
|
-
import { readFile, rm, writeFile } from "node:fs/promises";
|
|
3
|
-
import { basename, dirname, join, resolve } from "node:path";
|
|
4
|
-
import { bundleFacetPackage } from "@earendil-works/chord/bundler";
|
|
5
|
-
import { FACET_BUNDLE_MANIFEST_FILE, readFacetBundleArtifact, } from "@earendil-works/chord/node";
|
|
6
|
-
const PLUGIN_PACKAGE_PROFILE_VERSION = 1;
|
|
7
|
-
const DEFAULT_PLUGIN_FACETS = Object.freeze({ session: "src/session.ts", tui: "src/tui.ts" });
|
|
8
|
-
/** Persist an explicit plugin package selection or restore it for a later server generation. */
|
|
9
|
-
export async function restoreServerPluginPackageProfile(directory, serverId, configuredPackagePaths) {
|
|
10
|
-
const path = join(directory, `plugin-packages-${serverId}.json`);
|
|
11
|
-
if (configuredPackagePaths === undefined) {
|
|
12
|
-
return (await readPluginPackageProfile(path, false)) ?? [];
|
|
13
|
-
}
|
|
14
|
-
const packagePaths = normalizePluginPackagePaths(configuredPackagePaths);
|
|
15
|
-
if (packagePaths.length === 0)
|
|
16
|
-
await rm(path, { force: true });
|
|
17
|
-
else
|
|
18
|
-
await writePluginPackageProfile(path, packagePaths);
|
|
19
|
-
return packagePaths;
|
|
20
|
-
}
|
|
21
|
-
/** Read the explicit plugin selection stored for one durable Session. */
|
|
22
|
-
export function readSessionPluginPackageProfile(directory, serverId, sessionPath) {
|
|
23
|
-
return readPluginPackageProfile(sessionPluginProfilePath(directory, serverId, sessionPath), true, sessionPath);
|
|
24
|
-
}
|
|
25
|
-
/** Remove the plugin selection stored for one deleted Session. */
|
|
26
|
-
export async function removeSessionPluginPackageProfile(directory, serverId, sessionPath) {
|
|
27
|
-
await rm(sessionPluginProfilePath(directory, serverId, sessionPath), { force: true });
|
|
28
|
-
}
|
|
29
|
-
/** Persist the plugin selection for one durable Session. */
|
|
30
|
-
export function writeSessionPluginPackageProfile(directory, serverId, sessionPath, packagePaths) {
|
|
31
|
-
return writePluginPackageProfile(sessionPluginProfilePath(directory, serverId, sessionPath), normalizePluginPackagePaths(packagePaths), sessionPath);
|
|
32
|
-
}
|
|
33
|
-
/** Create a serialized server-owned builder for one configured plugin package. */
|
|
34
|
-
export function createServerPluginPackage(directory, serverId, packagePath) {
|
|
35
|
-
const normalizedPackagePath = resolve(packagePath);
|
|
36
|
-
const outdir = join(directory, "plugin-builds", serverId, pluginBuildDirectoryName(normalizedPackagePath));
|
|
37
|
-
const manifestPath = join(outdir, FACET_BUNDLE_MANIFEST_FILE);
|
|
38
|
-
let buildTail = Promise.resolve();
|
|
39
|
-
return {
|
|
40
|
-
manifestPath,
|
|
41
|
-
build() {
|
|
42
|
-
const operation = buildTail.then(async () => {
|
|
43
|
-
const result = await bundleFacetPackage({
|
|
44
|
-
packagePath: normalizedPackagePath,
|
|
45
|
-
outdir,
|
|
46
|
-
defaultFacets: DEFAULT_PLUGIN_FACETS,
|
|
47
|
-
});
|
|
48
|
-
if (result.manifest.entries.tui === undefined)
|
|
49
|
-
return [];
|
|
50
|
-
return [await readFacetBundleArtifact({ manifestPath: result.manifestPath, entry: "tui" })];
|
|
51
|
-
});
|
|
52
|
-
buildTail = operation.then(() => undefined, () => undefined);
|
|
53
|
-
return operation;
|
|
54
|
-
},
|
|
55
|
-
};
|
|
56
|
-
}
|
|
57
|
-
async function readPluginPackageProfile(path, allowEmpty, sessionPath) {
|
|
58
|
-
let parsed;
|
|
59
|
-
try {
|
|
60
|
-
parsed = JSON.parse(await readFile(path, "utf8"));
|
|
61
|
-
}
|
|
62
|
-
catch (error) {
|
|
63
|
-
if (error instanceof Error && "code" in error && error.code === "ENOENT")
|
|
64
|
-
return undefined;
|
|
65
|
-
throw new Error(`Could not read experimental plugin package profile ${path}`, { cause: error });
|
|
66
|
-
}
|
|
67
|
-
if (typeof parsed !== "object" ||
|
|
68
|
-
parsed === null ||
|
|
69
|
-
Array.isArray(parsed) ||
|
|
70
|
-
Object.keys(parsed).some((key) => key !== "version" && key !== "packagePaths" && !(key === "sessionPath" && sessionPath !== undefined)) ||
|
|
71
|
-
!("version" in parsed) ||
|
|
72
|
-
parsed.version !== PLUGIN_PACKAGE_PROFILE_VERSION ||
|
|
73
|
-
(sessionPath === undefined
|
|
74
|
-
? "sessionPath" in parsed
|
|
75
|
-
: !("sessionPath" in parsed) || parsed.sessionPath !== sessionPath) ||
|
|
76
|
-
!("packagePaths" in parsed) ||
|
|
77
|
-
!Array.isArray(parsed.packagePaths) ||
|
|
78
|
-
(!allowEmpty && parsed.packagePaths.length === 0) ||
|
|
79
|
-
parsed.packagePaths.some((packagePath) => typeof packagePath !== "string" || packagePath.length === 0)) {
|
|
80
|
-
throw new Error(`Invalid experimental plugin package profile ${path}`);
|
|
81
|
-
}
|
|
82
|
-
return normalizePluginPackagePaths(parsed.packagePaths);
|
|
83
|
-
}
|
|
84
|
-
function writePluginPackageProfile(path, packagePaths, sessionPath) {
|
|
85
|
-
return writeFile(path, `${JSON.stringify({
|
|
86
|
-
version: PLUGIN_PACKAGE_PROFILE_VERSION,
|
|
87
|
-
...(sessionPath === undefined ? {} : { sessionPath }),
|
|
88
|
-
packagePaths,
|
|
89
|
-
}, null, 2)}\n`, { encoding: "utf8", mode: 0o600 });
|
|
90
|
-
}
|
|
91
|
-
export function normalizePluginPackagePaths(packagePaths) {
|
|
92
|
-
const normalized = packagePaths.map((packagePath) => {
|
|
93
|
-
if (packagePath.length === 0)
|
|
94
|
-
throw new Error("Plugin package path must not be empty");
|
|
95
|
-
return resolve(packagePath);
|
|
96
|
-
});
|
|
97
|
-
if (new Set(normalized).size !== normalized.length) {
|
|
98
|
-
throw new Error("Plugin package paths must be unique");
|
|
99
|
-
}
|
|
100
|
-
return Object.freeze(normalized);
|
|
101
|
-
}
|
|
102
|
-
function sessionPluginProfilePath(directory, serverId, sessionPath) {
|
|
103
|
-
const hash = createHash("sha256").update(sessionPath).digest("hex").slice(0, 24);
|
|
104
|
-
return join(directory, `session-plugin-packages-${serverId}-${hash}.json`);
|
|
105
|
-
}
|
|
106
|
-
function pluginBuildDirectoryName(packagePath) {
|
|
107
|
-
const packageDirectory = basename(packagePath) === "package.json" ? dirname(packagePath) : packagePath;
|
|
108
|
-
const base = basename(packageDirectory).replaceAll(/[^a-zA-Z0-9._-]/gu, "-") || "plugin";
|
|
109
|
-
const label = base.endsWith("-plugin") ? base : `${base}-plugin`;
|
|
110
|
-
const hash = createHash("sha256").update(packagePath).digest("hex").slice(0, 12);
|
|
111
|
-
return `${label}-${hash}`;
|
|
112
|
-
}
|
|
113
|
-
//# sourceMappingURL=package.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"package.js","sourceRoot":"","sources":["../../../src/experimental/plugins/package.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAC3D,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAC7D,OAAO,EAAE,kBAAkB,EAAE,MAAM,+BAA+B,CAAC;AACnE,OAAO,EACN,0BAA0B,EAE1B,uBAAuB,GACvB,MAAM,4BAA4B,CAAC;AAGpC,MAAM,8BAA8B,GAAG,CAAC,CAAC;AACzC,MAAM,qBAAqB,GAAG,MAAM,CAAC,MAAM,CAAC,EAAE,OAAO,EAAE,gBAAgB,EAAE,GAAG,EAAE,YAAY,EAAE,CAAC,CAAC;AAO9F,gGAAgG;AAChG,MAAM,CAAC,KAAK,UAAU,iCAAiC,CACtD,SAAiB,EACjB,QAAkB,EAClB,sBAA0C,EACb;IAC7B,MAAM,IAAI,GAAG,IAAI,CAAC,SAAS,EAAE,mBAAmB,QAAQ,OAAO,CAAC,CAAC;IACjE,IAAI,sBAAsB,KAAK,SAAS,EAAE,CAAC;QAC1C,OAAO,CAAC,MAAM,wBAAwB,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC,IAAI,EAAE,CAAC;IAC5D,CAAC;IACD,MAAM,YAAY,GAAG,2BAA2B,CAAC,sBAAsB,CAAC,CAAC;IACzE,IAAI,YAAY,CAAC,MAAM,KAAK,CAAC;QAAE,MAAM,EAAE,CAAC,IAAI,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;;QAC1D,MAAM,yBAAyB,CAAC,IAAI,EAAE,YAAY,CAAC,CAAC;IACzD,OAAO,YAAY,CAAC;AAAA,CACpB;AAED,yEAAyE;AACzE,MAAM,UAAU,+BAA+B,CAC9C,SAAiB,EACjB,QAAkB,EAClB,WAAmB,EACsB;IACzC,OAAO,wBAAwB,CAAC,wBAAwB,CAAC,SAAS,EAAE,QAAQ,EAAE,WAAW,CAAC,EAAE,IAAI,EAAE,WAAW,CAAC,CAAC;AAAA,CAC/G;AAED,kEAAkE;AAClE,MAAM,CAAC,KAAK,UAAU,iCAAiC,CACtD,SAAiB,EACjB,QAAkB,EAClB,WAAmB,EACH;IAChB,MAAM,EAAE,CAAC,wBAAwB,CAAC,SAAS,EAAE,QAAQ,EAAE,WAAW,CAAC,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;AAAA,CACtF;AAED,4DAA4D;AAC5D,MAAM,UAAU,gCAAgC,CAC/C,SAAiB,EACjB,QAAkB,EAClB,WAAmB,EACnB,YAA+B,EACf;IAChB,OAAO,yBAAyB,CAC/B,wBAAwB,CAAC,SAAS,EAAE,QAAQ,EAAE,WAAW,CAAC,EAC1D,2BAA2B,CAAC,YAAY,CAAC,EACzC,WAAW,CACX,CAAC;AAAA,CACF;AAED,kFAAkF;AAClF,MAAM,UAAU,yBAAyB,CACxC,SAAiB,EACjB,QAAkB,EAClB,WAAmB,EACa;IAChC,MAAM,qBAAqB,GAAG,OAAO,CAAC,WAAW,CAAC,CAAC;IACnD,MAAM,MAAM,GAAG,IAAI,CAAC,SAAS,EAAE,eAAe,EAAE,QAAQ,EAAE,wBAAwB,CAAC,qBAAqB,CAAC,CAAC,CAAC;IAC3G,MAAM,YAAY,GAAG,IAAI,CAAC,MAAM,EAAE,0BAA0B,CAAC,CAAC;IAC9D,IAAI,SAAS,GAAG,OAAO,CAAC,OAAO,EAAE,CAAC;IAClC,OAAO;QACN,YAAY;QACZ,KAAK,GAAG;YACP,MAAM,SAAS,GAAG,SAAS,CAAC,IAAI,CAAC,KAAK,IAAI,EAAE,CAAC;gBAC5C,MAAM,MAAM,GAAG,MAAM,kBAAkB,CAAC;oBACvC,WAAW,EAAE,qBAAqB;oBAClC,MAAM;oBACN,aAAa,EAAE,qBAAqB;iBACpC,CAAC,CAAC;gBACH,IAAI,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,GAAG,KAAK,SAAS;oBAAE,OAAO,EAAE,CAAC;gBACzD,OAAO,CAAC,MAAM,uBAAuB,CAAC,EAAE,YAAY,EAAE,MAAM,CAAC,YAAY,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC;YAAA,CAC5F,CAAC,CAAC;YACH,SAAS,GAAG,SAAS,CAAC,IAAI,CACzB,GAAG,EAAE,CAAC,SAAS,EACf,GAAG,EAAE,CAAC,SAAS,CACf,CAAC;YACF,OAAO,SAAS,CAAC;QAAA,CACjB;KACD,CAAC;AAAA,CACF;AAED,KAAK,UAAU,wBAAwB,CACtC,IAAY,EACZ,UAAmB,EACnB,WAAoB,EACqB;IACzC,IAAI,MAAe,CAAC;IACpB,IAAI,CAAC;QACJ,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC;IACnD,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QAChB,IAAI,KAAK,YAAY,KAAK,IAAI,MAAM,IAAI,KAAK,IAAI,KAAK,CAAC,IAAI,KAAK,QAAQ;YAAE,OAAO,SAAS,CAAC;QAC3F,MAAM,IAAI,KAAK,CAAC,sDAAsD,IAAI,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,CAAC;IACjG,CAAC;IACD,IACC,OAAO,MAAM,KAAK,QAAQ;QAC1B,MAAM,KAAK,IAAI;QACf,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC;QACrB,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,IAAI,CACvB,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,KAAK,SAAS,IAAI,GAAG,KAAK,cAAc,IAAI,CAAC,CAAC,GAAG,KAAK,aAAa,IAAI,WAAW,KAAK,SAAS,CAAC,CAC7G;QACD,CAAC,CAAC,SAAS,IAAI,MAAM,CAAC;QACtB,MAAM,CAAC,OAAO,KAAK,8BAA8B;QACjD,CAAC,WAAW,KAAK,SAAS;YACzB,CAAC,CAAC,aAAa,IAAI,MAAM;YACzB,CAAC,CAAC,CAAC,CAAC,aAAa,IAAI,MAAM,CAAC,IAAI,MAAM,CAAC,WAAW,KAAK,WAAW,CAAC;QACpE,CAAC,CAAC,cAAc,IAAI,MAAM,CAAC;QAC3B,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,YAAY,CAAC;QACnC,CAAC,CAAC,UAAU,IAAI,MAAM,CAAC,YAAY,CAAC,MAAM,KAAK,CAAC,CAAC;QACjD,MAAM,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC,WAAW,EAAE,EAAE,CAAC,OAAO,WAAW,KAAK,QAAQ,IAAI,WAAW,CAAC,MAAM,KAAK,CAAC,CAAC,EACrG,CAAC;QACF,MAAM,IAAI,KAAK,CAAC,+CAA+C,IAAI,EAAE,CAAC,CAAC;IACxE,CAAC;IACD,OAAO,2BAA2B,CAAC,MAAM,CAAC,YAAiC,CAAC,CAAC;AAAA,CAC7E;AAED,SAAS,yBAAyB,CAAC,IAAY,EAAE,YAA+B,EAAE,WAAoB,EAAiB;IACtH,OAAO,SAAS,CACf,IAAI,EACJ,GAAG,IAAI,CAAC,SAAS,CAChB;QACC,OAAO,EAAE,8BAA8B;QACvC,GAAG,CAAC,WAAW,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,CAAC;QACrD,YAAY;KACZ,EACD,IAAI,EACJ,CAAC,CACD,IAAI,EACL,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,CACjC,CAAC;AAAA,CACF;AAED,MAAM,UAAU,2BAA2B,CAAC,YAA+B,EAAqB;IAC/F,MAAM,UAAU,GAAG,YAAY,CAAC,GAAG,CAAC,CAAC,WAAW,EAAE,EAAE,CAAC;QACpD,IAAI,WAAW,CAAC,MAAM,KAAK,CAAC;YAAE,MAAM,IAAI,KAAK,CAAC,uCAAuC,CAAC,CAAC;QACvF,OAAO,OAAO,CAAC,WAAW,CAAC,CAAC;IAAA,CAC5B,CAAC,CAAC;IACH,IAAI,IAAI,GAAG,CAAC,UAAU,CAAC,CAAC,IAAI,KAAK,UAAU,CAAC,MAAM,EAAE,CAAC;QACpD,MAAM,IAAI,KAAK,CAAC,qCAAqC,CAAC,CAAC;IACxD,CAAC;IACD,OAAO,MAAM,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;AAAA,CACjC;AAED,SAAS,wBAAwB,CAAC,SAAiB,EAAE,QAAkB,EAAE,WAAmB,EAAU;IACrG,MAAM,IAAI,GAAG,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IACjF,OAAO,IAAI,CAAC,SAAS,EAAE,2BAA2B,QAAQ,IAAI,IAAI,OAAO,CAAC,CAAC;AAAA,CAC3E;AAED,SAAS,wBAAwB,CAAC,WAAmB,EAAU;IAC9D,MAAM,gBAAgB,GAAG,QAAQ,CAAC,WAAW,CAAC,KAAK,cAAc,CAAC,CAAC,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC;IACvG,MAAM,IAAI,GAAG,QAAQ,CAAC,gBAAgB,CAAC,CAAC,UAAU,CAAC,mBAAmB,EAAE,GAAG,CAAC,IAAI,QAAQ,CAAC;IACzF,MAAM,KAAK,GAAG,IAAI,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,IAAI,SAAS,CAAC;IACjE,MAAM,IAAI,GAAG,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IACjF,OAAO,GAAG,KAAK,IAAI,IAAI,EAAE,CAAC;AAAA,CAC1B","sourcesContent":["import { createHash } from \"node:crypto\";\nimport { readFile, rm, writeFile } from \"node:fs/promises\";\nimport { basename, dirname, join, resolve } from \"node:path\";\nimport { bundleFacetPackage } from \"@earendil-works/chord/bundler\";\nimport {\n\tFACET_BUNDLE_MANIFEST_FILE,\n\ttype FacetBundleArtifact,\n\treadFacetBundleArtifact,\n} from \"@earendil-works/chord/node\";\nimport type { ServerId } from \"@earendil-works/pi-protocol\";\n\nconst PLUGIN_PACKAGE_PROFILE_VERSION = 1;\nconst DEFAULT_PLUGIN_FACETS = Object.freeze({ session: \"src/session.ts\", tui: \"src/tui.ts\" });\n\nexport interface ConfiguredServerPluginPackage {\n\treadonly manifestPath: string;\n\tbuild(): Promise<readonly FacetBundleArtifact[]>;\n}\n\n/** Persist an explicit plugin package selection or restore it for a later server generation. */\nexport async function restoreServerPluginPackageProfile(\n\tdirectory: string,\n\tserverId: ServerId,\n\tconfiguredPackagePaths?: readonly string[],\n): Promise<readonly string[]> {\n\tconst path = join(directory, `plugin-packages-${serverId}.json`);\n\tif (configuredPackagePaths === undefined) {\n\t\treturn (await readPluginPackageProfile(path, false)) ?? [];\n\t}\n\tconst packagePaths = normalizePluginPackagePaths(configuredPackagePaths);\n\tif (packagePaths.length === 0) await rm(path, { force: true });\n\telse await writePluginPackageProfile(path, packagePaths);\n\treturn packagePaths;\n}\n\n/** Read the explicit plugin selection stored for one durable Session. */\nexport function readSessionPluginPackageProfile(\n\tdirectory: string,\n\tserverId: ServerId,\n\tsessionPath: string,\n): Promise<readonly string[] | undefined> {\n\treturn readPluginPackageProfile(sessionPluginProfilePath(directory, serverId, sessionPath), true, sessionPath);\n}\n\n/** Remove the plugin selection stored for one deleted Session. */\nexport async function removeSessionPluginPackageProfile(\n\tdirectory: string,\n\tserverId: ServerId,\n\tsessionPath: string,\n): Promise<void> {\n\tawait rm(sessionPluginProfilePath(directory, serverId, sessionPath), { force: true });\n}\n\n/** Persist the plugin selection for one durable Session. */\nexport function writeSessionPluginPackageProfile(\n\tdirectory: string,\n\tserverId: ServerId,\n\tsessionPath: string,\n\tpackagePaths: readonly string[],\n): Promise<void> {\n\treturn writePluginPackageProfile(\n\t\tsessionPluginProfilePath(directory, serverId, sessionPath),\n\t\tnormalizePluginPackagePaths(packagePaths),\n\t\tsessionPath,\n\t);\n}\n\n/** Create a serialized server-owned builder for one configured plugin package. */\nexport function createServerPluginPackage(\n\tdirectory: string,\n\tserverId: ServerId,\n\tpackagePath: string,\n): ConfiguredServerPluginPackage {\n\tconst normalizedPackagePath = resolve(packagePath);\n\tconst outdir = join(directory, \"plugin-builds\", serverId, pluginBuildDirectoryName(normalizedPackagePath));\n\tconst manifestPath = join(outdir, FACET_BUNDLE_MANIFEST_FILE);\n\tlet buildTail = Promise.resolve();\n\treturn {\n\t\tmanifestPath,\n\t\tbuild() {\n\t\t\tconst operation = buildTail.then(async () => {\n\t\t\t\tconst result = await bundleFacetPackage({\n\t\t\t\t\tpackagePath: normalizedPackagePath,\n\t\t\t\t\toutdir,\n\t\t\t\t\tdefaultFacets: DEFAULT_PLUGIN_FACETS,\n\t\t\t\t});\n\t\t\t\tif (result.manifest.entries.tui === undefined) return [];\n\t\t\t\treturn [await readFacetBundleArtifact({ manifestPath: result.manifestPath, entry: \"tui\" })];\n\t\t\t});\n\t\t\tbuildTail = operation.then(\n\t\t\t\t() => undefined,\n\t\t\t\t() => undefined,\n\t\t\t);\n\t\t\treturn operation;\n\t\t},\n\t};\n}\n\nasync function readPluginPackageProfile(\n\tpath: string,\n\tallowEmpty: boolean,\n\tsessionPath?: string,\n): Promise<readonly string[] | undefined> {\n\tlet parsed: unknown;\n\ttry {\n\t\tparsed = JSON.parse(await readFile(path, \"utf8\"));\n\t} catch (error) {\n\t\tif (error instanceof Error && \"code\" in error && error.code === \"ENOENT\") return undefined;\n\t\tthrow new Error(`Could not read experimental plugin package profile ${path}`, { cause: error });\n\t}\n\tif (\n\t\ttypeof parsed !== \"object\" ||\n\t\tparsed === null ||\n\t\tArray.isArray(parsed) ||\n\t\tObject.keys(parsed).some(\n\t\t\t(key) => key !== \"version\" && key !== \"packagePaths\" && !(key === \"sessionPath\" && sessionPath !== undefined),\n\t\t) ||\n\t\t!(\"version\" in parsed) ||\n\t\tparsed.version !== PLUGIN_PACKAGE_PROFILE_VERSION ||\n\t\t(sessionPath === undefined\n\t\t\t? \"sessionPath\" in parsed\n\t\t\t: !(\"sessionPath\" in parsed) || parsed.sessionPath !== sessionPath) ||\n\t\t!(\"packagePaths\" in parsed) ||\n\t\t!Array.isArray(parsed.packagePaths) ||\n\t\t(!allowEmpty && parsed.packagePaths.length === 0) ||\n\t\tparsed.packagePaths.some((packagePath) => typeof packagePath !== \"string\" || packagePath.length === 0)\n\t) {\n\t\tthrow new Error(`Invalid experimental plugin package profile ${path}`);\n\t}\n\treturn normalizePluginPackagePaths(parsed.packagePaths as readonly string[]);\n}\n\nfunction writePluginPackageProfile(path: string, packagePaths: readonly string[], sessionPath?: string): Promise<void> {\n\treturn writeFile(\n\t\tpath,\n\t\t`${JSON.stringify(\n\t\t\t{\n\t\t\t\tversion: PLUGIN_PACKAGE_PROFILE_VERSION,\n\t\t\t\t...(sessionPath === undefined ? {} : { sessionPath }),\n\t\t\t\tpackagePaths,\n\t\t\t},\n\t\t\tnull,\n\t\t\t2,\n\t\t)}\\n`,\n\t\t{ encoding: \"utf8\", mode: 0o600 },\n\t);\n}\n\nexport function normalizePluginPackagePaths(packagePaths: readonly string[]): readonly string[] {\n\tconst normalized = packagePaths.map((packagePath) => {\n\t\tif (packagePath.length === 0) throw new Error(\"Plugin package path must not be empty\");\n\t\treturn resolve(packagePath);\n\t});\n\tif (new Set(normalized).size !== normalized.length) {\n\t\tthrow new Error(\"Plugin package paths must be unique\");\n\t}\n\treturn Object.freeze(normalized);\n}\n\nfunction sessionPluginProfilePath(directory: string, serverId: ServerId, sessionPath: string): string {\n\tconst hash = createHash(\"sha256\").update(sessionPath).digest(\"hex\").slice(0, 24);\n\treturn join(directory, `session-plugin-packages-${serverId}-${hash}.json`);\n}\n\nfunction pluginBuildDirectoryName(packagePath: string): string {\n\tconst packageDirectory = basename(packagePath) === \"package.json\" ? dirname(packagePath) : packagePath;\n\tconst base = basename(packageDirectory).replaceAll(/[^a-zA-Z0-9._-]/gu, \"-\") || \"plugin\";\n\tconst label = base.endsWith(\"-plugin\") ? base : `${base}-plugin`;\n\tconst hash = createHash(\"sha256\").update(packagePath).digest(\"hex\").slice(0, 12);\n\treturn `${label}-${hash}`;\n}\n"]}
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import { type ChildProcess } from "node:child_process";
|
|
2
|
-
export declare const INTERNAL_PROCESS_ENV = "__PI_INTERNAL_SPAWN";
|
|
3
|
-
export type InternalProcessRole = "coordinator" | "server" | "session-worker";
|
|
4
|
-
/** Detect a directly executed source or unbundled internal-process module. */
|
|
5
|
-
export declare function isDirectInternalProcessEntry(moduleUrl: string): boolean;
|
|
6
|
-
/** Read and validate an internal process role without consuming it. */
|
|
7
|
-
export declare function getInternalProcessRole(): InternalProcessRole | undefined;
|
|
8
|
-
/** Read, validate, and remove the role so descendants do not inherit it. */
|
|
9
|
-
export declare function consumeInternalProcessRole(): InternalProcessRole | undefined;
|
|
10
|
-
export interface InternalProcessSpawnOptions {
|
|
11
|
-
readonly entryUrl?: URL;
|
|
12
|
-
readonly env?: NodeJS.ProcessEnv;
|
|
13
|
-
}
|
|
14
|
-
/** Spawn a detached Pi-owned process consistently across Node and compiled Bun. */
|
|
15
|
-
export declare function spawnInternalProcess(role: InternalProcessRole, args: readonly string[], options?: InternalProcessSpawnOptions): ChildProcess;
|
|
16
|
-
/** Force a spawned internal process to exit and wait until it can no longer take ownership. */
|
|
17
|
-
export declare function terminateInternalProcess(child: ChildProcess): Promise<void>;
|
|
18
|
-
export declare const MAX_CONTROL_LINE_BYTES: number;
|
|
19
|
-
export declare function encodeControlLine(message: unknown): string;
|
|
20
|
-
//# sourceMappingURL=process.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"process.d.ts","sourceRoot":"","sources":["../../src/experimental/process.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,YAAY,EAAS,MAAM,oBAAoB,CAAC;AAK9D,eAAO,MAAM,oBAAoB,wBAAwB,CAAC;AAE1D,MAAM,MAAM,mBAAmB,GAAG,aAAa,GAAG,QAAQ,GAAG,gBAAgB,CAAC;AAE9E,8EAA8E;AAC9E,wBAAgB,4BAA4B,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAOvE;AAED,uEAAuE;AACvE,wBAAgB,sBAAsB,IAAI,mBAAmB,GAAG,SAAS,CAKxE;AAED,4EAA4E;AAC5E,wBAAgB,0BAA0B,IAAI,mBAAmB,GAAG,SAAS,CAI5E;AAED,MAAM,WAAW,2BAA2B;IAC3C,QAAQ,CAAC,QAAQ,CAAC,EAAE,GAAG,CAAC;IACxB,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;CACjC;AAED,mFAAmF;AACnF,wBAAgB,oBAAoB,CACnC,IAAI,EAAE,mBAAmB,EACzB,IAAI,EAAE,SAAS,MAAM,EAAE,EACvB,OAAO,GAAE,2BAAgC,GACvC,YAAY,CAyBd;AAED,+FAA+F;AAC/F,wBAAsB,wBAAwB,CAAC,KAAK,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CAQjF;AAkBD,eAAO,MAAM,sBAAsB,QAAoB,CAAC;AAExD,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,OAAO,GAAG,MAAM,CAI1D","sourcesContent":["import { type ChildProcess, spawn } from \"node:child_process\";\nimport { join, resolve } from \"node:path\";\nimport { fileURLToPath, pathToFileURL } from \"node:url\";\nimport { getPackageDir, isBunBinary, isBundledNode } from \"../config.ts\";\n\nexport const INTERNAL_PROCESS_ENV = \"__PI_INTERNAL_SPAWN\";\n\nexport type InternalProcessRole = \"coordinator\" | \"server\" | \"session-worker\";\n\n/** Detect a directly executed source or unbundled internal-process module. */\nexport function isDirectInternalProcessEntry(moduleUrl: string): boolean {\n\treturn (\n\t\t!isBunBinary &&\n\t\t!isBundledNode &&\n\t\tprocess.argv[1] !== undefined &&\n\t\tresolve(process.argv[1]) === fileURLToPath(moduleUrl)\n\t);\n}\n\n/** Read and validate an internal process role without consuming it. */\nexport function getInternalProcessRole(): InternalProcessRole | undefined {\n\tconst role = process.env[INTERNAL_PROCESS_ENV];\n\tif (role === undefined) return undefined;\n\tif (role === \"coordinator\" || role === \"server\" || role === \"session-worker\") return role;\n\tthrow new Error(`Unsupported internal process role: ${role}`);\n}\n\n/** Read, validate, and remove the role so descendants do not inherit it. */\nexport function consumeInternalProcessRole(): InternalProcessRole | undefined {\n\tconst role = getInternalProcessRole();\n\tdelete process.env[INTERNAL_PROCESS_ENV];\n\treturn role;\n}\n\nexport interface InternalProcessSpawnOptions {\n\treadonly entryUrl?: URL;\n\treadonly env?: NodeJS.ProcessEnv;\n}\n\n/** Spawn a detached Pi-owned process consistently across Node and compiled Bun. */\nexport function spawnInternalProcess(\n\trole: InternalProcessRole,\n\targs: readonly string[],\n\toptions: InternalProcessSpawnOptions = {},\n): ChildProcess {\n\tif (isBunBinary && options.entryUrl) {\n\t\tthrow new Error(\"A compiled Bun executable cannot launch an external internal-process entrypoint\");\n\t}\n\tconst entryUrl = defaultEntryUrl(role, options.entryUrl);\n\tconst sourceRuntimeArgs = import.meta.url.endsWith(\".ts\")\n\t\t? [\"--import\", fileURLToPath(new URL(\"source-resolver.ts\", import.meta.url))]\n\t\t: [];\n\tconst child = spawn(\n\t\tprocess.execPath,\n\t\tisBunBinary ? [...args] : [...sourceRuntimeArgs, fileURLToPath(entryUrl), ...args],\n\t\t{\n\t\t\tcwd: process.cwd(),\n\t\t\tdetached: true,\n\t\t\tenv: {\n\t\t\t\t...process.env,\n\t\t\t\t...options.env,\n\t\t\t\t[INTERNAL_PROCESS_ENV]: role,\n\t\t\t},\n\t\t\tstdio: \"ignore\",\n\t\t\twindowsHide: true,\n\t\t},\n\t);\n\tchild.unref();\n\treturn child;\n}\n\n/** Force a spawned internal process to exit and wait until it can no longer take ownership. */\nexport async function terminateInternalProcess(child: ChildProcess): Promise<void> {\n\tif (child.pid === undefined || child.exitCode !== null || child.signalCode !== null) return;\n\tconst terminated = new Promise<void>((resolve) => {\n\t\tchild.once(\"exit\", () => resolve());\n\t\tchild.once(\"error\", () => resolve());\n\t});\n\tchild.kill(\"SIGKILL\");\n\tawait terminated;\n}\n\nfunction defaultEntryUrl(role: InternalProcessRole, override: URL | undefined): URL {\n\tif (override) return override;\n\tif (isBundledNode) {\n\t\tconst entry = role === \"coordinator\" ? \"coordinator.js\" : \"cli.js\";\n\t\treturn pathToFileURL(join(getPackageDir(), \"dist\", \"bundle\", entry));\n\t}\n\tconst javaScript = import.meta.url.endsWith(\".js\");\n\tif (role === \"coordinator\") {\n\t\treturn new URL(javaScript ? \"coordinator.js\" : \"coordinator.ts\", import.meta.url);\n\t}\n\tif (role === \"server\") {\n\t\treturn new URL(javaScript ? \"server.js\" : \"server.ts\", import.meta.url);\n\t}\n\treturn new URL(javaScript ? \"session-worker.js\" : \"session-worker.ts\", import.meta.url);\n}\n\nexport const MAX_CONTROL_LINE_BYTES = 128 * 1024 * 1024;\n\nexport function encodeControlLine(message: unknown): string {\n\tconst line = `${JSON.stringify(message)}\\n`;\n\tif (Buffer.byteLength(line) > MAX_CONTROL_LINE_BYTES) throw new Error(\"Internal control message is too large\");\n\treturn line;\n}\n"]}
|
|
@@ -1,85 +0,0 @@
|
|
|
1
|
-
import { spawn } from "node:child_process";
|
|
2
|
-
import { join, resolve } from "node:path";
|
|
3
|
-
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
4
|
-
import { getPackageDir, isBunBinary, isBundledNode } from "../config.js";
|
|
5
|
-
export const INTERNAL_PROCESS_ENV = "__PI_INTERNAL_SPAWN";
|
|
6
|
-
/** Detect a directly executed source or unbundled internal-process module. */
|
|
7
|
-
export function isDirectInternalProcessEntry(moduleUrl) {
|
|
8
|
-
return (!isBunBinary &&
|
|
9
|
-
!isBundledNode &&
|
|
10
|
-
process.argv[1] !== undefined &&
|
|
11
|
-
resolve(process.argv[1]) === fileURLToPath(moduleUrl));
|
|
12
|
-
}
|
|
13
|
-
/** Read and validate an internal process role without consuming it. */
|
|
14
|
-
export function getInternalProcessRole() {
|
|
15
|
-
const role = process.env[INTERNAL_PROCESS_ENV];
|
|
16
|
-
if (role === undefined)
|
|
17
|
-
return undefined;
|
|
18
|
-
if (role === "coordinator" || role === "server" || role === "session-worker")
|
|
19
|
-
return role;
|
|
20
|
-
throw new Error(`Unsupported internal process role: ${role}`);
|
|
21
|
-
}
|
|
22
|
-
/** Read, validate, and remove the role so descendants do not inherit it. */
|
|
23
|
-
export function consumeInternalProcessRole() {
|
|
24
|
-
const role = getInternalProcessRole();
|
|
25
|
-
delete process.env[INTERNAL_PROCESS_ENV];
|
|
26
|
-
return role;
|
|
27
|
-
}
|
|
28
|
-
/** Spawn a detached Pi-owned process consistently across Node and compiled Bun. */
|
|
29
|
-
export function spawnInternalProcess(role, args, options = {}) {
|
|
30
|
-
if (isBunBinary && options.entryUrl) {
|
|
31
|
-
throw new Error("A compiled Bun executable cannot launch an external internal-process entrypoint");
|
|
32
|
-
}
|
|
33
|
-
const entryUrl = defaultEntryUrl(role, options.entryUrl);
|
|
34
|
-
const sourceRuntimeArgs = import.meta.url.endsWith(".ts")
|
|
35
|
-
? ["--import", fileURLToPath(new URL("source-resolver.ts", import.meta.url))]
|
|
36
|
-
: [];
|
|
37
|
-
const child = spawn(process.execPath, isBunBinary ? [...args] : [...sourceRuntimeArgs, fileURLToPath(entryUrl), ...args], {
|
|
38
|
-
cwd: process.cwd(),
|
|
39
|
-
detached: true,
|
|
40
|
-
env: {
|
|
41
|
-
...process.env,
|
|
42
|
-
...options.env,
|
|
43
|
-
[INTERNAL_PROCESS_ENV]: role,
|
|
44
|
-
},
|
|
45
|
-
stdio: "ignore",
|
|
46
|
-
windowsHide: true,
|
|
47
|
-
});
|
|
48
|
-
child.unref();
|
|
49
|
-
return child;
|
|
50
|
-
}
|
|
51
|
-
/** Force a spawned internal process to exit and wait until it can no longer take ownership. */
|
|
52
|
-
export async function terminateInternalProcess(child) {
|
|
53
|
-
if (child.pid === undefined || child.exitCode !== null || child.signalCode !== null)
|
|
54
|
-
return;
|
|
55
|
-
const terminated = new Promise((resolve) => {
|
|
56
|
-
child.once("exit", () => resolve());
|
|
57
|
-
child.once("error", () => resolve());
|
|
58
|
-
});
|
|
59
|
-
child.kill("SIGKILL");
|
|
60
|
-
await terminated;
|
|
61
|
-
}
|
|
62
|
-
function defaultEntryUrl(role, override) {
|
|
63
|
-
if (override)
|
|
64
|
-
return override;
|
|
65
|
-
if (isBundledNode) {
|
|
66
|
-
const entry = role === "coordinator" ? "coordinator.js" : "cli.js";
|
|
67
|
-
return pathToFileURL(join(getPackageDir(), "dist", "bundle", entry));
|
|
68
|
-
}
|
|
69
|
-
const javaScript = import.meta.url.endsWith(".js");
|
|
70
|
-
if (role === "coordinator") {
|
|
71
|
-
return new URL(javaScript ? "coordinator.js" : "coordinator.ts", import.meta.url);
|
|
72
|
-
}
|
|
73
|
-
if (role === "server") {
|
|
74
|
-
return new URL(javaScript ? "server.js" : "server.ts", import.meta.url);
|
|
75
|
-
}
|
|
76
|
-
return new URL(javaScript ? "session-worker.js" : "session-worker.ts", import.meta.url);
|
|
77
|
-
}
|
|
78
|
-
export const MAX_CONTROL_LINE_BYTES = 128 * 1024 * 1024;
|
|
79
|
-
export function encodeControlLine(message) {
|
|
80
|
-
const line = `${JSON.stringify(message)}\n`;
|
|
81
|
-
if (Buffer.byteLength(line) > MAX_CONTROL_LINE_BYTES)
|
|
82
|
-
throw new Error("Internal control message is too large");
|
|
83
|
-
return line;
|
|
84
|
-
}
|
|
85
|
-
//# sourceMappingURL=process.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"process.js","sourceRoot":"","sources":["../../src/experimental/process.ts"],"names":[],"mappings":"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","sourcesContent":["import { type ChildProcess, spawn } from \"node:child_process\";\nimport { join, resolve } from \"node:path\";\nimport { fileURLToPath, pathToFileURL } from \"node:url\";\nimport { getPackageDir, isBunBinary, isBundledNode } from \"../config.ts\";\n\nexport const INTERNAL_PROCESS_ENV = \"__PI_INTERNAL_SPAWN\";\n\nexport type InternalProcessRole = \"coordinator\" | \"server\" | \"session-worker\";\n\n/** Detect a directly executed source or unbundled internal-process module. */\nexport function isDirectInternalProcessEntry(moduleUrl: string): boolean {\n\treturn (\n\t\t!isBunBinary &&\n\t\t!isBundledNode &&\n\t\tprocess.argv[1] !== undefined &&\n\t\tresolve(process.argv[1]) === fileURLToPath(moduleUrl)\n\t);\n}\n\n/** Read and validate an internal process role without consuming it. */\nexport function getInternalProcessRole(): InternalProcessRole | undefined {\n\tconst role = process.env[INTERNAL_PROCESS_ENV];\n\tif (role === undefined) return undefined;\n\tif (role === \"coordinator\" || role === \"server\" || role === \"session-worker\") return role;\n\tthrow new Error(`Unsupported internal process role: ${role}`);\n}\n\n/** Read, validate, and remove the role so descendants do not inherit it. */\nexport function consumeInternalProcessRole(): InternalProcessRole | undefined {\n\tconst role = getInternalProcessRole();\n\tdelete process.env[INTERNAL_PROCESS_ENV];\n\treturn role;\n}\n\nexport interface InternalProcessSpawnOptions {\n\treadonly entryUrl?: URL;\n\treadonly env?: NodeJS.ProcessEnv;\n}\n\n/** Spawn a detached Pi-owned process consistently across Node and compiled Bun. */\nexport function spawnInternalProcess(\n\trole: InternalProcessRole,\n\targs: readonly string[],\n\toptions: InternalProcessSpawnOptions = {},\n): ChildProcess {\n\tif (isBunBinary && options.entryUrl) {\n\t\tthrow new Error(\"A compiled Bun executable cannot launch an external internal-process entrypoint\");\n\t}\n\tconst entryUrl = defaultEntryUrl(role, options.entryUrl);\n\tconst sourceRuntimeArgs = import.meta.url.endsWith(\".ts\")\n\t\t? [\"--import\", fileURLToPath(new URL(\"source-resolver.ts\", import.meta.url))]\n\t\t: [];\n\tconst child = spawn(\n\t\tprocess.execPath,\n\t\tisBunBinary ? [...args] : [...sourceRuntimeArgs, fileURLToPath(entryUrl), ...args],\n\t\t{\n\t\t\tcwd: process.cwd(),\n\t\t\tdetached: true,\n\t\t\tenv: {\n\t\t\t\t...process.env,\n\t\t\t\t...options.env,\n\t\t\t\t[INTERNAL_PROCESS_ENV]: role,\n\t\t\t},\n\t\t\tstdio: \"ignore\",\n\t\t\twindowsHide: true,\n\t\t},\n\t);\n\tchild.unref();\n\treturn child;\n}\n\n/** Force a spawned internal process to exit and wait until it can no longer take ownership. */\nexport async function terminateInternalProcess(child: ChildProcess): Promise<void> {\n\tif (child.pid === undefined || child.exitCode !== null || child.signalCode !== null) return;\n\tconst terminated = new Promise<void>((resolve) => {\n\t\tchild.once(\"exit\", () => resolve());\n\t\tchild.once(\"error\", () => resolve());\n\t});\n\tchild.kill(\"SIGKILL\");\n\tawait terminated;\n}\n\nfunction defaultEntryUrl(role: InternalProcessRole, override: URL | undefined): URL {\n\tif (override) return override;\n\tif (isBundledNode) {\n\t\tconst entry = role === \"coordinator\" ? \"coordinator.js\" : \"cli.js\";\n\t\treturn pathToFileURL(join(getPackageDir(), \"dist\", \"bundle\", entry));\n\t}\n\tconst javaScript = import.meta.url.endsWith(\".js\");\n\tif (role === \"coordinator\") {\n\t\treturn new URL(javaScript ? \"coordinator.js\" : \"coordinator.ts\", import.meta.url);\n\t}\n\tif (role === \"server\") {\n\t\treturn new URL(javaScript ? \"server.js\" : \"server.ts\", import.meta.url);\n\t}\n\treturn new URL(javaScript ? \"session-worker.js\" : \"session-worker.ts\", import.meta.url);\n}\n\nexport const MAX_CONTROL_LINE_BYTES = 128 * 1024 * 1024;\n\nexport function encodeControlLine(message: unknown): string {\n\tconst line = `${JSON.stringify(message)}\\n`;\n\tif (Buffer.byteLength(line) > MAX_CONTROL_LINE_BYTES) throw new Error(\"Internal control message is too large\");\n\treturn line;\n}\n"]}
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import type { AuthInput } from "../cli/experimental/command-options.ts";
|
|
2
|
-
export declare const ENV_RADIUS_GATEWAY = "PI_RADIUS_GATEWAY";
|
|
3
|
-
export interface RadiusRelayAuth {
|
|
4
|
-
readonly gateway: string;
|
|
5
|
-
readonly token: string;
|
|
6
|
-
}
|
|
7
|
-
/** Resolve explicit or stored Radius credentials anew for every relay connection attempt. */
|
|
8
|
-
export declare class RadiusRelayAuthResolver {
|
|
9
|
-
#private;
|
|
10
|
-
constructor(input?: AuthInput, gateway?: string);
|
|
11
|
-
get gateway(): string;
|
|
12
|
-
resolve(options: {
|
|
13
|
-
readonly required: boolean;
|
|
14
|
-
readonly signal?: AbortSignal;
|
|
15
|
-
}): Promise<RadiusRelayAuth | undefined>;
|
|
16
|
-
}
|
|
17
|
-
//# sourceMappingURL=radius-auth.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"radius-auth.d.ts","sourceRoot":"","sources":["../../src/experimental/radius-auth.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,wCAAwC,CAAC;AAIxE,eAAO,MAAM,kBAAkB,sBAAsB,CAAC;AAEtD,MAAM,WAAW,eAAe;IAC/B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;CACvB;AAED,6FAA6F;AAC7F,qBAAa,uBAAuB;;IAKnC,YAAY,KAAK,CAAC,EAAE,SAAS,EAAE,OAAO,SAA4D,EAGjG;IAED,IAAI,OAAO,IAAI,MAAM,CAEpB;IAEK,OAAO,CAAC,OAAO,EAAE;QACtB,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;QAC3B,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,CAAC;KAC9B,GAAG,OAAO,CAAC,eAAe,GAAG,SAAS,CAAC,CAqBvC;CAYD","sourcesContent":["import { readFile } from \"node:fs/promises\";\nimport { DEFAULT_RADIUS_GATEWAY, normalizeRadiusGatewayUrl } from \"@earendil-works/pi-ai/providers/radius-config\";\nimport { getAuthCredential } from \"../cli/auth-command.ts\";\nimport type { AuthInput } from \"../cli/experimental/command-options.ts\";\nimport { ModelRuntime } from \"../core/model-runtime.ts\";\nimport { resolvePath } from \"../utils/paths.ts\";\n\nexport const ENV_RADIUS_GATEWAY = \"PI_RADIUS_GATEWAY\";\n\nexport interface RadiusRelayAuth {\n\treadonly gateway: string;\n\treadonly token: string;\n}\n\n/** Resolve explicit or stored Radius credentials anew for every relay connection attempt. */\nexport class RadiusRelayAuthResolver {\n\treadonly #input: AuthInput | undefined;\n\treadonly #gateway: string;\n\t#modelRuntime: Promise<ModelRuntime> | undefined;\n\n\tconstructor(input?: AuthInput, gateway = process.env[ENV_RADIUS_GATEWAY] ?? DEFAULT_RADIUS_GATEWAY) {\n\t\tthis.#input = input;\n\t\tthis.#gateway = normalizeRadiusGatewayUrl(gateway);\n\t}\n\n\tget gateway(): string {\n\t\treturn this.#gateway;\n\t}\n\n\tasync resolve(options: {\n\t\treadonly required: boolean;\n\t\treadonly signal?: AbortSignal;\n\t}): Promise<RadiusRelayAuth | undefined> {\n\t\toptions.signal?.throwIfAborted();\n\t\tif (process.env.PI_OFFLINE !== undefined) {\n\t\t\tif (options.required) throw new Error(\"Radius relay connections are unavailable in offline mode\");\n\t\t\treturn undefined;\n\t\t}\n\n\t\tconst explicit = await this.#explicitToken(options.signal);\n\t\tif (explicit !== undefined) return { gateway: this.#gateway, token: explicit };\n\n\t\tthis.#modelRuntime ??= ModelRuntime.create({ refreshOnCreate: false, allowModelNetwork: false });\n\t\tconst runtime = await this.#modelRuntime;\n\t\toptions.signal?.throwIfAborted();\n\t\tconst token = getAuthCredential(\n\t\t\tawait runtime.getAuth(\"radius\", { minOAuthValidityMs: 5 * 60_000, signal: options.signal }),\n\t\t);\n\t\tif (token !== undefined && token.length > 0) return { gateway: this.#gateway, token };\n\t\tif (options.required) {\n\t\t\tthrow new Error(\"Radius authentication is required; start Pi and run /login radius, then retry\");\n\t\t}\n\t\treturn undefined;\n\t}\n\n\tasync #explicitToken(signal: AbortSignal | undefined): Promise<string | undefined> {\n\t\tif (this.#input === undefined) return undefined;\n\t\tconst value =\n\t\t\tthis.#input.type === \"token\"\n\t\t\t\t? this.#input.token\n\t\t\t\t: await readFile(resolvePath(this.#input.path), { encoding: \"utf8\", signal });\n\t\tconst token = value.trim();\n\t\tif (token.length === 0) throw new Error(\"Radius authentication token must not be empty\");\n\t\treturn token;\n\t}\n}\n"]}
|
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
import { readFile } from "node:fs/promises";
|
|
2
|
-
import { DEFAULT_RADIUS_GATEWAY, normalizeRadiusGatewayUrl } from "@earendil-works/pi-ai/providers/radius-config";
|
|
3
|
-
import { getAuthCredential } from "../cli/auth-command.js";
|
|
4
|
-
import { ModelRuntime } from "../core/model-runtime.js";
|
|
5
|
-
import { resolvePath } from "../utils/paths.js";
|
|
6
|
-
export const ENV_RADIUS_GATEWAY = "PI_RADIUS_GATEWAY";
|
|
7
|
-
/** Resolve explicit or stored Radius credentials anew for every relay connection attempt. */
|
|
8
|
-
export class RadiusRelayAuthResolver {
|
|
9
|
-
#input;
|
|
10
|
-
#gateway;
|
|
11
|
-
#modelRuntime;
|
|
12
|
-
constructor(input, gateway = process.env[ENV_RADIUS_GATEWAY] ?? DEFAULT_RADIUS_GATEWAY) {
|
|
13
|
-
this.#input = input;
|
|
14
|
-
this.#gateway = normalizeRadiusGatewayUrl(gateway);
|
|
15
|
-
}
|
|
16
|
-
get gateway() {
|
|
17
|
-
return this.#gateway;
|
|
18
|
-
}
|
|
19
|
-
async resolve(options) {
|
|
20
|
-
options.signal?.throwIfAborted();
|
|
21
|
-
if (process.env.PI_OFFLINE !== undefined) {
|
|
22
|
-
if (options.required)
|
|
23
|
-
throw new Error("Radius relay connections are unavailable in offline mode");
|
|
24
|
-
return undefined;
|
|
25
|
-
}
|
|
26
|
-
const explicit = await this.#explicitToken(options.signal);
|
|
27
|
-
if (explicit !== undefined)
|
|
28
|
-
return { gateway: this.#gateway, token: explicit };
|
|
29
|
-
this.#modelRuntime ??= ModelRuntime.create({ refreshOnCreate: false, allowModelNetwork: false });
|
|
30
|
-
const runtime = await this.#modelRuntime;
|
|
31
|
-
options.signal?.throwIfAborted();
|
|
32
|
-
const token = getAuthCredential(await runtime.getAuth("radius", { minOAuthValidityMs: 5 * 60_000, signal: options.signal }));
|
|
33
|
-
if (token !== undefined && token.length > 0)
|
|
34
|
-
return { gateway: this.#gateway, token };
|
|
35
|
-
if (options.required) {
|
|
36
|
-
throw new Error("Radius authentication is required; start Pi and run /login radius, then retry");
|
|
37
|
-
}
|
|
38
|
-
return undefined;
|
|
39
|
-
}
|
|
40
|
-
async #explicitToken(signal) {
|
|
41
|
-
if (this.#input === undefined)
|
|
42
|
-
return undefined;
|
|
43
|
-
const value = this.#input.type === "token"
|
|
44
|
-
? this.#input.token
|
|
45
|
-
: await readFile(resolvePath(this.#input.path), { encoding: "utf8", signal });
|
|
46
|
-
const token = value.trim();
|
|
47
|
-
if (token.length === 0)
|
|
48
|
-
throw new Error("Radius authentication token must not be empty");
|
|
49
|
-
return token;
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
//# sourceMappingURL=radius-auth.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"radius-auth.js","sourceRoot":"","sources":["../../src/experimental/radius-auth.ts"],"names":[],"mappings":"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","sourcesContent":["import { readFile } from \"node:fs/promises\";\nimport { DEFAULT_RADIUS_GATEWAY, normalizeRadiusGatewayUrl } from \"@earendil-works/pi-ai/providers/radius-config\";\nimport { getAuthCredential } from \"../cli/auth-command.ts\";\nimport type { AuthInput } from \"../cli/experimental/command-options.ts\";\nimport { ModelRuntime } from \"../core/model-runtime.ts\";\nimport { resolvePath } from \"../utils/paths.ts\";\n\nexport const ENV_RADIUS_GATEWAY = \"PI_RADIUS_GATEWAY\";\n\nexport interface RadiusRelayAuth {\n\treadonly gateway: string;\n\treadonly token: string;\n}\n\n/** Resolve explicit or stored Radius credentials anew for every relay connection attempt. */\nexport class RadiusRelayAuthResolver {\n\treadonly #input: AuthInput | undefined;\n\treadonly #gateway: string;\n\t#modelRuntime: Promise<ModelRuntime> | undefined;\n\n\tconstructor(input?: AuthInput, gateway = process.env[ENV_RADIUS_GATEWAY] ?? DEFAULT_RADIUS_GATEWAY) {\n\t\tthis.#input = input;\n\t\tthis.#gateway = normalizeRadiusGatewayUrl(gateway);\n\t}\n\n\tget gateway(): string {\n\t\treturn this.#gateway;\n\t}\n\n\tasync resolve(options: {\n\t\treadonly required: boolean;\n\t\treadonly signal?: AbortSignal;\n\t}): Promise<RadiusRelayAuth | undefined> {\n\t\toptions.signal?.throwIfAborted();\n\t\tif (process.env.PI_OFFLINE !== undefined) {\n\t\t\tif (options.required) throw new Error(\"Radius relay connections are unavailable in offline mode\");\n\t\t\treturn undefined;\n\t\t}\n\n\t\tconst explicit = await this.#explicitToken(options.signal);\n\t\tif (explicit !== undefined) return { gateway: this.#gateway, token: explicit };\n\n\t\tthis.#modelRuntime ??= ModelRuntime.create({ refreshOnCreate: false, allowModelNetwork: false });\n\t\tconst runtime = await this.#modelRuntime;\n\t\toptions.signal?.throwIfAborted();\n\t\tconst token = getAuthCredential(\n\t\t\tawait runtime.getAuth(\"radius\", { minOAuthValidityMs: 5 * 60_000, signal: options.signal }),\n\t\t);\n\t\tif (token !== undefined && token.length > 0) return { gateway: this.#gateway, token };\n\t\tif (options.required) {\n\t\t\tthrow new Error(\"Radius authentication is required; start Pi and run /login radius, then retry\");\n\t\t}\n\t\treturn undefined;\n\t}\n\n\tasync #explicitToken(signal: AbortSignal | undefined): Promise<string | undefined> {\n\t\tif (this.#input === undefined) return undefined;\n\t\tconst value =\n\t\t\tthis.#input.type === \"token\"\n\t\t\t\t? this.#input.token\n\t\t\t\t: await readFile(resolvePath(this.#input.path), { encoding: \"utf8\", signal });\n\t\tconst token = value.trim();\n\t\tif (token.length === 0) throw new Error(\"Radius authentication token must not be empty\");\n\t\treturn token;\n\t}\n}\n"]}
|
|
@@ -1,90 +0,0 @@
|
|
|
1
|
-
import type { ByteTransportFactory, Client } from "@earendil-works/pi-client";
|
|
2
|
-
import { type ServerId } from "@earendil-works/pi-protocol";
|
|
3
|
-
import type { Server } from "@earendil-works/pi-server";
|
|
4
|
-
import type { RadiusRelayAuthResolver } from "./radius-auth.ts";
|
|
5
|
-
export declare const RADIUS_RELAY_HOST_SUBPROTOCOL = "pi-session-relay.host.v1";
|
|
6
|
-
export declare const RADIUS_RELAY_CLIENT_SUBPROTOCOL = "pi-session-relay.client.v1";
|
|
7
|
-
interface RadiusRelayMessageEvent extends Event {
|
|
8
|
-
readonly data: unknown;
|
|
9
|
-
}
|
|
10
|
-
interface RadiusRelayCloseEvent extends Event {
|
|
11
|
-
readonly code: number;
|
|
12
|
-
readonly reason: string;
|
|
13
|
-
}
|
|
14
|
-
interface RadiusRelayErrorEvent extends Event {
|
|
15
|
-
readonly error?: unknown;
|
|
16
|
-
readonly message?: string;
|
|
17
|
-
}
|
|
18
|
-
export interface RadiusRelayWebSocket {
|
|
19
|
-
binaryType: "arraybuffer" | "blob";
|
|
20
|
-
readonly bufferedAmount: number;
|
|
21
|
-
readonly protocol: string;
|
|
22
|
-
readonly readyState: number;
|
|
23
|
-
readonly OPEN: number;
|
|
24
|
-
send(data: string | ArrayBuffer): void;
|
|
25
|
-
close(code?: number, reason?: string): void;
|
|
26
|
-
addEventListener(type: "open", listener: (event: Event) => void, options?: {
|
|
27
|
-
once?: boolean;
|
|
28
|
-
}): void;
|
|
29
|
-
addEventListener(type: "message", listener: (event: RadiusRelayMessageEvent) => void, options?: {
|
|
30
|
-
once?: boolean;
|
|
31
|
-
}): void;
|
|
32
|
-
addEventListener(type: "close", listener: (event: RadiusRelayCloseEvent) => void, options?: {
|
|
33
|
-
once?: boolean;
|
|
34
|
-
}): void;
|
|
35
|
-
addEventListener(type: "error", listener: (event: RadiusRelayErrorEvent) => void, options?: {
|
|
36
|
-
once?: boolean;
|
|
37
|
-
}): void;
|
|
38
|
-
removeEventListener(type: "open", listener: (event: Event) => void): void;
|
|
39
|
-
removeEventListener(type: "message", listener: (event: RadiusRelayMessageEvent) => void): void;
|
|
40
|
-
removeEventListener(type: "close", listener: (event: RadiusRelayCloseEvent) => void): void;
|
|
41
|
-
removeEventListener(type: "error", listener: (event: RadiusRelayErrorEvent) => void): void;
|
|
42
|
-
}
|
|
43
|
-
export type RadiusRelayWebSocketFactory = (options: {
|
|
44
|
-
readonly url: string;
|
|
45
|
-
readonly protocol: string;
|
|
46
|
-
readonly authorization: string;
|
|
47
|
-
}) => RadiusRelayWebSocket;
|
|
48
|
-
export type RadiusRelayHostStatus = {
|
|
49
|
-
readonly status: "not_authenticated";
|
|
50
|
-
} | {
|
|
51
|
-
readonly status: "connecting";
|
|
52
|
-
} | {
|
|
53
|
-
readonly status: "connected";
|
|
54
|
-
} | {
|
|
55
|
-
readonly status: "retrying";
|
|
56
|
-
readonly error: string;
|
|
57
|
-
};
|
|
58
|
-
export interface RadiusRelayHostOptions {
|
|
59
|
-
readonly serverId: ServerId;
|
|
60
|
-
readonly server: Pick<Server, "accept">;
|
|
61
|
-
readonly auth: RadiusRelayAuthResolver;
|
|
62
|
-
readonly webSocketFactory?: RadiusRelayWebSocketFactory;
|
|
63
|
-
readonly onStatus?: (status: RadiusRelayHostStatus) => void;
|
|
64
|
-
}
|
|
65
|
-
/** Maintain the experimental server's multiplexed, authenticated Radius host connection. */
|
|
66
|
-
export declare class RadiusRelayHost {
|
|
67
|
-
#private;
|
|
68
|
-
constructor(options: RadiusRelayHostOptions);
|
|
69
|
-
start(): void;
|
|
70
|
-
close(): Promise<void>;
|
|
71
|
-
}
|
|
72
|
-
export declare function createRadiusClientTransportFactory(options: {
|
|
73
|
-
readonly serverId: ServerId;
|
|
74
|
-
readonly auth: RadiusRelayAuthResolver;
|
|
75
|
-
readonly webSocketFactory?: RadiusRelayWebSocketFactory;
|
|
76
|
-
}): ByteTransportFactory;
|
|
77
|
-
type RadiusReconnectClient = Pick<Client, "attachment" | "connected" | "connectionState" | "disconnect" | "onAttachmentChange" | "onConnectionStateChange" | "reconnect">;
|
|
78
|
-
/** Reconnect one established Radius client and restore its last selected Session. */
|
|
79
|
-
export declare class RadiusClientReconnect {
|
|
80
|
-
#private;
|
|
81
|
-
constructor(client: RadiusReconnectClient, reattach: (sessionId: string) => Promise<void>);
|
|
82
|
-
dispose(): Promise<void>;
|
|
83
|
-
}
|
|
84
|
-
export declare function encodeRelayDataFrame(connectionId: string, payload: Uint8Array): ArrayBuffer;
|
|
85
|
-
export declare function parseRelayDataFrame(frame: ArrayBuffer): {
|
|
86
|
-
readonly connectionId: string;
|
|
87
|
-
readonly payload: ArrayBuffer;
|
|
88
|
-
} | undefined;
|
|
89
|
-
export {};
|
|
90
|
-
//# sourceMappingURL=radius-relay.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"radius-relay.d.ts","sourceRoot":"","sources":["../../src/experimental/radius-relay.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ByteTransport, ByteTransportFactory, ByteTransportHandlers, Client } from \"@earendil-works/pi-client\";\nimport { DEFAULT_MAX_FRAME_LENGTH, type ServerId } from \"@earendil-works/pi-protocol\";\nimport type { Server } from \"@earendil-works/pi-server\";\nimport { WebSocket } from \"undici\";\nimport type { RadiusRelayAuthResolver } from \"./radius-auth.ts\";\n\nexport const RADIUS_RELAY_HOST_SUBPROTOCOL = \"pi-session-relay.host.v1\";\nexport const RADIUS_RELAY_CLIENT_SUBPROTOCOL = \"pi-session-relay.client.v1\";\n\nconst RELAY_DATA_HEADER_BYTES = 18;\nconst RELAY_DATA_FRAME_VERSION = 1;\nconst RELAY_DATA_FRAME_TYPE = 1;\nconst CONNECTION_ID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/u;\nconst MAX_PENDING_BYTES = DEFAULT_MAX_FRAME_LENGTH * 4;\nconst DRAIN_THRESHOLD_BYTES = 1024 * 1024;\nconst HOST_RETRY_INITIAL_MS = 1_000;\nconst HOST_RETRY_MAX_MS = 30_000;\nconst MISSING_AUTH_RETRY_MS = 30_000;\nconst CLIENT_RETRY_INITIAL_MS = 1_000;\nconst CLIENT_RETRY_MAX_MS = 30_000;\n// Undici implements the browser WebSocket API, which permits callers to send\n// only code 1000 or application codes from 3000 through 4999. RFC protocol\n// codes such as 1002 and 1011 may be received but cannot be passed to close().\nconst LOCAL_PROTOCOL_ERROR_CLOSE_CODE = 4000;\nconst LOCAL_TRANSPORT_ERROR_CLOSE_CODE = 4001;\n\ninterface RadiusRelayMessageEvent extends Event {\n\treadonly data: unknown;\n}\n\ninterface RadiusRelayCloseEvent extends Event {\n\treadonly code: number;\n\treadonly reason: string;\n}\n\ninterface RadiusRelayErrorEvent extends Event {\n\treadonly error?: unknown;\n\treadonly message?: string;\n}\n\nexport interface RadiusRelayWebSocket {\n\tbinaryType: \"arraybuffer\" | \"blob\";\n\treadonly bufferedAmount: number;\n\treadonly protocol: string;\n\treadonly readyState: number;\n\treadonly OPEN: number;\n\tsend(data: string | ArrayBuffer): void;\n\tclose(code?: number, reason?: string): void;\n\taddEventListener(type: \"open\", listener: (event: Event) => void, options?: { once?: boolean }): void;\n\taddEventListener(\n\t\ttype: \"message\",\n\t\tlistener: (event: RadiusRelayMessageEvent) => void,\n\t\toptions?: { once?: boolean },\n\t): void;\n\taddEventListener(\n\t\ttype: \"close\",\n\t\tlistener: (event: RadiusRelayCloseEvent) => void,\n\t\toptions?: { once?: boolean },\n\t): void;\n\taddEventListener(\n\t\ttype: \"error\",\n\t\tlistener: (event: RadiusRelayErrorEvent) => void,\n\t\toptions?: { once?: boolean },\n\t): void;\n\tremoveEventListener(type: \"open\", listener: (event: Event) => void): void;\n\tremoveEventListener(type: \"message\", listener: (event: RadiusRelayMessageEvent) => void): void;\n\tremoveEventListener(type: \"close\", listener: (event: RadiusRelayCloseEvent) => void): void;\n\tremoveEventListener(type: \"error\", listener: (event: RadiusRelayErrorEvent) => void): void;\n}\n\nexport type RadiusRelayWebSocketFactory = (options: {\n\treadonly url: string;\n\treadonly protocol: string;\n\treadonly authorization: string;\n}) => RadiusRelayWebSocket;\n\ninterface RelayByteConnection {\n\treadonly closed: boolean;\n\tsend(chunk: Uint8Array): Promise<void>;\n\tclose(finalChunk?: Uint8Array): Promise<void>;\n}\n\ninterface RelayByteConnectionHandler {\n\tonData(chunk: Uint8Array): void;\n\tonClose(): void;\n\tonError(error: Error): void;\n}\n\nexport type RadiusRelayHostStatus =\n\t| { readonly status: \"not_authenticated\" }\n\t| { readonly status: \"connecting\" }\n\t| { readonly status: \"connected\" }\n\t| { readonly status: \"retrying\"; readonly error: string };\n\nexport interface RadiusRelayHostOptions {\n\treadonly serverId: ServerId;\n\treadonly server: Pick<Server, \"accept\">;\n\treadonly auth: RadiusRelayAuthResolver;\n\treadonly webSocketFactory?: RadiusRelayWebSocketFactory;\n\treadonly onStatus?: (status: RadiusRelayHostStatus) => void;\n}\n\n/** Maintain the experimental server's multiplexed, authenticated Radius host connection. */\nexport class RadiusRelayHost {\n\treadonly #options: RadiusRelayHostOptions;\n\treadonly #abortController = new AbortController();\n\treadonly #connections = new Map<\n\t\tstring,\n\t\t{ connection: RelayServerByteConnection; handler: RelayByteConnectionHandler }\n\t>();\n\t#socket: RadiusRelayWebSocket | undefined;\n\t#writer: OrderedWebSocketWriter | undefined;\n\t#loop: Promise<void> | undefined;\n\t#closed = false;\n\n\tconstructor(options: RadiusRelayHostOptions) {\n\t\tthis.#options = options;\n\t}\n\n\tstart(): void {\n\t\tif (this.#loop !== undefined) return;\n\t\tthis.#loop = this.#run();\n\t}\n\n\tasync close(): Promise<void> {\n\t\tif (this.#closed) return;\n\t\tthis.#closed = true;\n\t\tthis.#abortController.abort();\n\t\tthis.#writer?.close();\n\t\tthis.#socket?.close(1000, \"Pi server stopped\");\n\t\tthis.#dropConnections();\n\t\tawait this.#loop;\n\t}\n\n\tasync #run(): Promise<void> {\n\t\tlet retryMs = HOST_RETRY_INITIAL_MS;\n\t\twhile (!this.#closed) {\n\t\t\ttry {\n\t\t\t\tconst auth = await this.#options.auth.resolve({\n\t\t\t\t\trequired: false,\n\t\t\t\t\tsignal: this.#abortController.signal,\n\t\t\t\t});\n\t\t\t\tif (auth === undefined) {\n\t\t\t\t\tthis.#options.onStatus?.({ status: \"not_authenticated\" });\n\t\t\t\t\tawait delay(MISSING_AUTH_RETRY_MS, this.#abortController.signal, true);\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tthis.#options.onStatus?.({ status: \"connecting\" });\n\t\t\t\tconst socket = await openRadiusRelayWebSocket(\n\t\t\t\t\trelayWebSocketUrl(auth.gateway, this.#options.serverId),\n\t\t\t\t\tRADIUS_RELAY_HOST_SUBPROTOCOL,\n\t\t\t\t\tauth.token,\n\t\t\t\t\tthis.#options.webSocketFactory ?? defaultWebSocketFactory,\n\t\t\t\t\tthis.#abortController.signal,\n\t\t\t\t);\n\t\t\t\tretryMs = HOST_RETRY_INITIAL_MS;\n\t\t\t\tthis.#options.onStatus?.({ status: \"connected\" });\n\t\t\t\tawait this.#serve(socket);\n\t\t\t\tif (!this.#closed) throw new Error(\"Radius relay host disconnected\");\n\t\t\t} catch (error) {\n\t\t\t\tif (this.#closed || this.#abortController.signal.aborted) break;\n\t\t\t\tthis.#options.onStatus?.({ status: \"retrying\", error: errorMessage(error) });\n\t\t\t\ttry {\n\t\t\t\t\tawait delay(retryMs, this.#abortController.signal, true);\n\t\t\t\t} catch {\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tretryMs = Math.min(retryMs * 2, HOST_RETRY_MAX_MS);\n\t\t\t}\n\t\t}\n\t}\n\n\t#serve(socket: RadiusRelayWebSocket): Promise<void> {\n\t\tthis.#socket = socket;\n\t\tthis.#writer = new OrderedWebSocketWriter(socket);\n\t\treturn new Promise<void>((resolve, reject) => {\n\t\t\tlet terminal = false;\n\t\t\tconst cleanup = (): void => {\n\t\t\t\tsocket.removeEventListener(\"message\", onMessage);\n\t\t\t\tsocket.removeEventListener(\"close\", onClose);\n\t\t\t\tsocket.removeEventListener(\"error\", onError);\n\t\t\t\tthis.#writer?.close();\n\t\t\t\tif (this.#socket === socket) {\n\t\t\t\t\tthis.#socket = undefined;\n\t\t\t\t\tthis.#writer = undefined;\n\t\t\t\t}\n\t\t\t};\n\t\t\tconst finish = (error?: Error): void => {\n\t\t\t\tif (terminal) return;\n\t\t\t\tterminal = true;\n\t\t\t\tcleanup();\n\t\t\t\tthis.#dropConnections(error);\n\t\t\t\tif (error) reject(error);\n\t\t\t\telse resolve();\n\t\t\t};\n\t\t\tconst onMessage = (event: RadiusRelayMessageEvent): void => {\n\t\t\t\ttry {\n\t\t\t\t\tthis.#handleHostMessage(event.data);\n\t\t\t\t} catch (error) {\n\t\t\t\t\tcloseWebSocket(socket, LOCAL_PROTOCOL_ERROR_CLOSE_CODE, \"Radius relay protocol error\");\n\t\t\t\t\tfinish(toError(error));\n\t\t\t\t}\n\t\t\t};\n\t\t\tconst onClose = (event: RadiusRelayCloseEvent): void => {\n\t\t\t\tfinish(\n\t\t\t\t\tevent.code === 1000\n\t\t\t\t\t\t? undefined\n\t\t\t\t\t\t: new Error(`Radius relay host closed (${event.code}${event.reason ? `: ${event.reason}` : \"\"})`),\n\t\t\t\t);\n\t\t\t};\n\t\t\tconst onError = (event: RadiusRelayErrorEvent): void => finish(webSocketError(event));\n\t\t\tsocket.addEventListener(\"message\", onMessage);\n\t\t\tsocket.addEventListener(\"close\", onClose);\n\t\t\tsocket.addEventListener(\"error\", onError);\n\t\t});\n\t}\n\n\t#handleHostMessage(value: unknown): void {\n\t\tif (typeof value === \"string\") {\n\t\t\tconst control = parseHostControlMessage(value);\n\t\t\tswitch (control.type) {\n\t\t\t\tcase \"ping\":\n\t\t\t\t\tvoid this.#sendControl({ version: 1, type: \"pong\" }).catch(() => {\n\t\t\t\t\t\tif (this.#socket !== undefined) {\n\t\t\t\t\t\t\tcloseWebSocket(this.#socket, LOCAL_TRANSPORT_ERROR_CLOSE_CODE, \"Radius relay send failed\");\n\t\t\t\t\t\t}\n\t\t\t\t\t});\n\t\t\t\t\treturn;\n\t\t\t\tcase \"pong\":\n\t\t\t\t\treturn;\n\t\t\t\tcase \"connection_open\":\n\t\t\t\t\tthis.#openConnection(control.connection_id);\n\t\t\t\t\treturn;\n\t\t\t\tcase \"connection_close\":\n\t\t\t\t\tthis.#remoteCloseConnection(control.connection_id);\n\t\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\t\tif (!(value instanceof ArrayBuffer)) throw new Error(\"Radius relay data message must be binary\");\n\t\tconst frame = parseRelayDataFrame(value);\n\t\tif (!frame) throw new Error(\"Invalid Radius relay data frame\");\n\t\tconst active = this.#connections.get(frame.connectionId);\n\t\tif (!active) {\n\t\t\tvoid this.#sendControl({\n\t\t\t\tversion: 1,\n\t\t\t\ttype: \"connection_close\",\n\t\t\t\tconnection_id: frame.connectionId,\n\t\t\t\tcode: 1000,\n\t\t\t});\n\t\t\treturn;\n\t\t}\n\t\tactive.handler.onData(new Uint8Array(frame.payload));\n\t}\n\n\t#openConnection(connectionId: string): void {\n\t\tif (this.#connections.has(connectionId)) throw new Error(\"Radius relay reused a connection ID\");\n\t\tconst connection = new RelayServerByteConnection(\n\t\t\t(chunk) => this.#sendData(connectionId, chunk),\n\t\t\t(finalChunk) => this.#serverCloseConnection(connectionId, finalChunk),\n\t\t);\n\t\tconst handler = this.#options.server.accept(connection);\n\t\tif (connection.closed) {\n\t\t\tvoid this.#sendControl({ version: 1, type: \"connection_close\", connection_id: connectionId, code: 1012 });\n\t\t} else {\n\t\t\tthis.#connections.set(connectionId, { connection, handler });\n\t\t}\n\t}\n\n\t#remoteCloseConnection(connectionId: string): void {\n\t\tconst active = this.#connections.get(connectionId);\n\t\tif (!active) return;\n\t\tthis.#connections.delete(connectionId);\n\t\tactive.connection.markClosed();\n\t\tactive.handler.onClose();\n\t}\n\n\tasync #serverCloseConnection(connectionId: string, finalChunk?: Uint8Array): Promise<void> {\n\t\tif (!this.#connections.delete(connectionId)) return;\n\t\tif (finalChunk !== undefined) await this.#requireWriter().send(encodeRelayDataFrame(connectionId, finalChunk));\n\t\tawait this.#sendControl({\n\t\t\tversion: 1,\n\t\t\ttype: \"connection_close\",\n\t\t\tconnection_id: connectionId,\n\t\t\tcode: 1000,\n\t\t});\n\t}\n\n\t#sendData(connectionId: string, chunk: Uint8Array): Promise<void> {\n\t\tif (!this.#connections.has(connectionId)) return Promise.reject(new Error(\"Radius relay connection is closed\"));\n\t\treturn this.#requireWriter().send(encodeRelayDataFrame(connectionId, chunk));\n\t}\n\n\t#sendControl(message: HostOutputControlMessage): Promise<void> {\n\t\treturn this.#requireWriter().send(JSON.stringify(message));\n\t}\n\n\t#requireWriter(): OrderedWebSocketWriter {\n\t\tif (!this.#writer) throw new Error(\"Radius relay host is disconnected\");\n\t\treturn this.#writer;\n\t}\n\n\t#dropConnections(error?: Error): void {\n\t\tconst connections = [...this.#connections.values()];\n\t\tthis.#connections.clear();\n\t\tfor (const active of connections) {\n\t\t\tactive.connection.markClosed();\n\t\t\tif (error) active.handler.onError(error);\n\t\t\telse active.handler.onClose();\n\t\t}\n\t}\n}\n\nexport function createRadiusClientTransportFactory(options: {\n\treadonly serverId: ServerId;\n\treadonly auth: RadiusRelayAuthResolver;\n\treadonly webSocketFactory?: RadiusRelayWebSocketFactory;\n}): ByteTransportFactory {\n\treturn async (handlers) => {\n\t\tconst auth = await options.auth.resolve({ required: true });\n\t\tif (!auth) throw new Error(\"Radius authentication is required\");\n\t\tconst socket = await openRadiusRelayWebSocket(\n\t\t\trelayWebSocketUrl(auth.gateway, options.serverId),\n\t\t\tRADIUS_RELAY_CLIENT_SUBPROTOCOL,\n\t\t\tauth.token,\n\t\t\toptions.webSocketFactory ?? defaultWebSocketFactory,\n\t\t);\n\t\treturn new RadiusClientByteTransport(socket, handlers);\n\t};\n}\n\ntype RadiusReconnectClient = Pick<\n\tClient,\n\t| \"attachment\"\n\t| \"connected\"\n\t| \"connectionState\"\n\t| \"disconnect\"\n\t| \"onAttachmentChange\"\n\t| \"onConnectionStateChange\"\n\t| \"reconnect\"\n>;\n\n/** Reconnect one established Radius client and restore its last selected Session. */\nexport class RadiusClientReconnect {\n\treadonly #client: RadiusReconnectClient;\n\treadonly #reattach: (sessionId: string) => Promise<void>;\n\treadonly #abortController = new AbortController();\n\treadonly #removeConnectionListener: () => void;\n\treadonly #removeAttachmentListener: () => void;\n\t#desiredSessionId: string | undefined;\n\t#reconnecting: Promise<void> | undefined;\n\t#disposed = false;\n\n\tconstructor(client: RadiusReconnectClient, reattach: (sessionId: string) => Promise<void>) {\n\t\tthis.#client = client;\n\t\tthis.#reattach = reattach;\n\t\tthis.#desiredSessionId = client.attachment?.sessionId;\n\t\tthis.#removeAttachmentListener = client.onAttachmentChange((attachment) => {\n\t\t\tif (attachment !== undefined) this.#desiredSessionId = attachment.sessionId;\n\t\t\telse if (client.connected) this.#desiredSessionId = undefined;\n\t\t});\n\t\tthis.#removeConnectionListener = client.onConnectionStateChange(({ state }) => {\n\t\t\tif (state === \"disconnected\" && !this.#disposed) this.#startReconnect();\n\t\t});\n\t}\n\n\tasync dispose(): Promise<void> {\n\t\tif (this.#disposed) return;\n\t\tthis.#disposed = true;\n\t\tthis.#removeConnectionListener();\n\t\tthis.#removeAttachmentListener();\n\t\tthis.#abortController.abort();\n\t\tif (this.#client.connectionState !== \"disconnected\") {\n\t\t\tthis.#client.disconnect(\"Radius reconnect stopped\");\n\t\t}\n\t\tawait this.#reconnecting;\n\t}\n\n\t#startReconnect(): void {\n\t\tif (this.#reconnecting !== undefined) return;\n\t\tthis.#reconnecting = this.#runReconnect().finally(() => {\n\t\t\tthis.#reconnecting = undefined;\n\t\t});\n\t}\n\n\tasync #runReconnect(): Promise<void> {\n\t\tlet retryMs = CLIENT_RETRY_INITIAL_MS;\n\t\twhile (!this.#disposed && !this.#client.connected) {\n\t\t\ttry {\n\t\t\t\tawait delay(retryMs, this.#abortController.signal, false);\n\t\t\t\tawait this.#client.reconnect();\n\t\t\t\tconst sessionId = this.#desiredSessionId;\n\t\t\t\tif (sessionId !== undefined) await this.#reattach(sessionId);\n\t\t\t\treturn;\n\t\t\t} catch (error) {\n\t\t\t\tif (this.#disposed || this.#abortController.signal.aborted) return;\n\t\t\t\tif (this.#client.connected) this.#client.disconnect(errorMessage(error));\n\t\t\t\tretryMs = Math.min(retryMs * 2, CLIENT_RETRY_MAX_MS);\n\t\t\t}\n\t\t}\n\t}\n}\n\nclass RelayServerByteConnection implements RelayByteConnection {\n\treadonly #sendChunk: (chunk: Uint8Array) => Promise<void>;\n\treadonly #closeConnection: (finalChunk?: Uint8Array) => Promise<void>;\n\t#closed = false;\n\n\tconstructor(\n\t\tsendChunk: (chunk: Uint8Array) => Promise<void>,\n\t\tcloseConnection: (finalChunk?: Uint8Array) => Promise<void>,\n\t) {\n\t\tthis.#sendChunk = sendChunk;\n\t\tthis.#closeConnection = closeConnection;\n\t}\n\n\tget closed(): boolean {\n\t\treturn this.#closed;\n\t}\n\n\tsend(chunk: Uint8Array): Promise<void> {\n\t\tif (this.#closed) return Promise.reject(new Error(\"Radius relay connection is closed\"));\n\t\treturn this.#sendChunk(chunk);\n\t}\n\n\tasync close(finalChunk?: Uint8Array): Promise<void> {\n\t\tif (this.#closed) return;\n\t\tthis.#closed = true;\n\t\tawait this.#closeConnection(finalChunk);\n\t}\n\n\tmarkClosed(): void {\n\t\tthis.#closed = true;\n\t}\n}\n\nclass RadiusClientByteTransport implements ByteTransport {\n\treadonly #socket: RadiusRelayWebSocket;\n\treadonly #handlers: ByteTransportHandlers;\n\treadonly #writer: OrderedWebSocketWriter;\n\treadonly #onMessage: (event: RadiusRelayMessageEvent) => void;\n\treadonly #onClose: (event: RadiusRelayCloseEvent) => void;\n\treadonly #onError: (event: RadiusRelayErrorEvent) => void;\n\t#closed = false;\n\n\tconstructor(socket: RadiusRelayWebSocket, handlers: ByteTransportHandlers) {\n\t\tthis.#socket = socket;\n\t\tthis.#handlers = handlers;\n\t\tthis.#writer = new OrderedWebSocketWriter(socket);\n\t\tthis.#onMessage = (event) => {\n\t\t\tif (this.#closed) return;\n\t\t\tif (!(event.data instanceof ArrayBuffer)) {\n\t\t\t\tthis.#fail(new Error(\"Radius relay client received a non-binary message\"));\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tthis.#handlers.onData(new Uint8Array(event.data));\n\t\t};\n\t\tthis.#onClose = () => {\n\t\t\tif (this.#markClosed()) this.#handlers.onClose();\n\t\t};\n\t\tthis.#onError = (event) => this.#fail(webSocketError(event));\n\t\tsocket.addEventListener(\"message\", this.#onMessage);\n\t\tsocket.addEventListener(\"close\", this.#onClose);\n\t\tsocket.addEventListener(\"error\", this.#onError);\n\t}\n\n\tsend(chunk: Uint8Array): Promise<void> {\n\t\tif (!(chunk instanceof Uint8Array))\n\t\t\treturn Promise.reject(new TypeError(\"Radius relay chunks must be Uint8Array\"));\n\t\tif (this.#closed) return Promise.reject(new Error(\"Radius relay client is closed\"));\n\t\treturn this.#writer.send(chunk.slice().buffer);\n\t}\n\n\tclose(): void {\n\t\tif (!this.#markClosed()) return;\n\t\tthis.#socket.close(1000, \"Pi client closed\");\n\t}\n\n\t#fail(error: Error): void {\n\t\tif (!this.#markClosed()) return;\n\t\tcloseWebSocket(this.#socket, LOCAL_TRANSPORT_ERROR_CLOSE_CODE, \"Radius relay transport error\");\n\t\tthis.#handlers.onError(error);\n\t}\n\n\t#markClosed(): boolean {\n\t\tif (this.#closed) return false;\n\t\tthis.#closed = true;\n\t\tthis.#writer.close();\n\t\tthis.#socket.removeEventListener(\"message\", this.#onMessage);\n\t\tthis.#socket.removeEventListener(\"close\", this.#onClose);\n\t\tthis.#socket.removeEventListener(\"error\", this.#onError);\n\t\treturn true;\n\t}\n}\n\nclass OrderedWebSocketWriter {\n\treadonly #socket: RadiusRelayWebSocket;\n\t#tail: Promise<void> = Promise.resolve();\n\t#pendingBytes = 0;\n\t#closed = false;\n\n\tconstructor(socket: RadiusRelayWebSocket) {\n\t\tthis.#socket = socket;\n\t}\n\n\tsend(value: string | ArrayBuffer): Promise<void> {\n\t\tif (this.#closed) return Promise.reject(new Error(\"Radius relay WebSocket is closed\"));\n\t\tconst copied = typeof value === \"string\" ? value : value.slice(0);\n\t\tconst byteLength = typeof copied === \"string\" ? Buffer.byteLength(copied) : copied.byteLength;\n\t\tif (this.#pendingBytes + byteLength > MAX_PENDING_BYTES) {\n\t\t\treturn Promise.reject(new Error(\"Radius relay exceeded its pending byte limit\"));\n\t\t}\n\t\tthis.#pendingBytes += byteLength;\n\t\tconst operation = this.#tail.then(async () => {\n\t\t\tif (this.#closed || this.#socket.readyState !== this.#socket.OPEN) {\n\t\t\t\tthrow new Error(\"Radius relay WebSocket is closed\");\n\t\t\t}\n\t\t\tthis.#socket.send(copied);\n\t\t\twhile (this.#socket.bufferedAmount > DRAIN_THRESHOLD_BYTES) {\n\t\t\t\tif (this.#closed || this.#socket.readyState !== this.#socket.OPEN) {\n\t\t\t\t\tthrow new Error(\"Radius relay WebSocket closed during write\");\n\t\t\t\t}\n\t\t\t\tawait delay(5, undefined, false);\n\t\t\t}\n\t\t});\n\t\tconst tracked = operation.finally(() => {\n\t\t\tthis.#pendingBytes -= byteLength;\n\t\t});\n\t\tthis.#tail = tracked.catch(() => {});\n\t\treturn tracked;\n\t}\n\n\tclose(): void {\n\t\tthis.#closed = true;\n\t}\n}\n\ntype HostInputControlMessage =\n\t| { readonly version: 1; readonly type: \"ping\" | \"pong\" }\n\t| { readonly version: 1; readonly type: \"connection_open\"; readonly connection_id: string }\n\t| {\n\t\t\treadonly version: 1;\n\t\t\treadonly type: \"connection_close\";\n\t\t\treadonly connection_id: string;\n\t\t\treadonly code?: number;\n\t };\n\ntype HostOutputControlMessage =\n\t| { readonly version: 1; readonly type: \"pong\" }\n\t| {\n\t\t\treadonly version: 1;\n\t\t\treadonly type: \"connection_close\";\n\t\t\treadonly connection_id: string;\n\t\t\treadonly code?: number;\n\t };\n\nfunction parseHostControlMessage(value: string): HostInputControlMessage {\n\tlet parsed: unknown;\n\ttry {\n\t\tparsed = JSON.parse(value);\n\t} catch {\n\t\tthrow new Error(\"Invalid Radius relay control message\");\n\t}\n\tif (!parsed || typeof parsed !== \"object\" || Array.isArray(parsed)) {\n\t\tthrow new Error(\"Invalid Radius relay control message\");\n\t}\n\tconst message = parsed as { version?: unknown; type?: unknown; connection_id?: unknown; code?: unknown };\n\tif (message.version !== 1) throw new Error(\"Unsupported Radius relay control version\");\n\tif (message.type === \"ping\" || message.type === \"pong\") return { version: 1, type: message.type };\n\tif (\n\t\t(message.type === \"connection_open\" || message.type === \"connection_close\") &&\n\t\ttypeof message.connection_id === \"string\" &&\n\t\tCONNECTION_ID_PATTERN.test(message.connection_id) &&\n\t\t(message.code === undefined ||\n\t\t\t(typeof message.code === \"number\" &&\n\t\t\t\tNumber.isInteger(message.code) &&\n\t\t\t\tmessage.code >= 1000 &&\n\t\t\t\tmessage.code <= 4999))\n\t) {\n\t\treturn message.type === \"connection_open\"\n\t\t\t? { version: 1, type: \"connection_open\", connection_id: message.connection_id }\n\t\t\t: {\n\t\t\t\t\tversion: 1,\n\t\t\t\t\ttype: \"connection_close\",\n\t\t\t\t\tconnection_id: message.connection_id,\n\t\t\t\t\t...(message.code === undefined ? {} : { code: message.code }),\n\t\t\t\t};\n\t}\n\tthrow new Error(\"Invalid Radius relay control message\");\n}\n\nexport function encodeRelayDataFrame(connectionId: string, payload: Uint8Array): ArrayBuffer {\n\tif (!CONNECTION_ID_PATTERN.test(connectionId)) throw new TypeError(\"Invalid Radius relay connection ID\");\n\tconst frame = new Uint8Array(RELAY_DATA_HEADER_BYTES + payload.byteLength);\n\tframe[0] = RELAY_DATA_FRAME_VERSION;\n\tframe[1] = RELAY_DATA_FRAME_TYPE;\n\tconst hex = connectionId.replaceAll(\"-\", \"\");\n\tfor (let index = 0; index < 16; index++) {\n\t\tframe[index + 2] = Number.parseInt(hex.slice(index * 2, index * 2 + 2), 16);\n\t}\n\tframe.set(payload, RELAY_DATA_HEADER_BYTES);\n\treturn frame.buffer;\n}\n\nexport function parseRelayDataFrame(\n\tframe: ArrayBuffer,\n): { readonly connectionId: string; readonly payload: ArrayBuffer } | undefined {\n\tif (frame.byteLength < RELAY_DATA_HEADER_BYTES) return undefined;\n\tconst bytes = new Uint8Array(frame);\n\tif (bytes[0] !== RELAY_DATA_FRAME_VERSION || bytes[1] !== RELAY_DATA_FRAME_TYPE) return undefined;\n\tlet hex = \"\";\n\tfor (const byte of bytes.subarray(2, RELAY_DATA_HEADER_BYTES)) hex += byte.toString(16).padStart(2, \"0\");\n\tconst connectionId = `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;\n\tif (!CONNECTION_ID_PATTERN.test(connectionId)) return undefined;\n\treturn { connectionId, payload: frame.slice(RELAY_DATA_HEADER_BYTES) };\n}\n\nfunction relayWebSocketUrl(gateway: string, serverId: ServerId): string {\n\tconst url = new URL(`/v1/session-relays/${serverId}/connect`, gateway);\n\tif (url.protocol === \"https:\") url.protocol = \"wss:\";\n\telse if (url.protocol === \"http:\") url.protocol = \"ws:\";\n\telse throw new Error(`Unsupported Radius gateway protocol: ${url.protocol}`);\n\treturn url.toString();\n}\n\nfunction defaultWebSocketFactory(options: {\n\treadonly url: string;\n\treadonly protocol: string;\n\treadonly authorization: string;\n}): RadiusRelayWebSocket {\n\treturn new WebSocket(options.url, {\n\t\tprotocols: [options.protocol],\n\t\theaders: { authorization: options.authorization },\n\t}) as unknown as RadiusRelayWebSocket;\n}\n\nfunction openRadiusRelayWebSocket(\n\turl: string,\n\tprotocol: string,\n\ttoken: string,\n\tfactory: RadiusRelayWebSocketFactory,\n\tsignal?: AbortSignal,\n): Promise<RadiusRelayWebSocket> {\n\tsignal?.throwIfAborted();\n\treturn new Promise((resolve, reject) => {\n\t\tconst socket = factory({ url, protocol, authorization: `Bearer ${token}` });\n\t\tsocket.binaryType = \"arraybuffer\";\n\t\tlet settled = false;\n\t\tconst cleanup = (): void => {\n\t\t\tsocket.removeEventListener(\"open\", onOpen);\n\t\t\tsocket.removeEventListener(\"close\", onClose);\n\t\t\tsocket.removeEventListener(\"error\", onError);\n\t\t\tsignal?.removeEventListener(\"abort\", onAbort);\n\t\t};\n\t\tconst fail = (error: Error): void => {\n\t\t\tif (settled) return;\n\t\t\tsettled = true;\n\t\t\tcleanup();\n\t\t\tcloseWebSocket(socket, 1000, \"Radius relay connection failed\");\n\t\t\treject(error);\n\t\t};\n\t\tconst onOpen = (): void => {\n\t\t\tif (settled) return;\n\t\t\tif (socket.protocol !== protocol) {\n\t\t\t\tfail(new Error(`Radius relay selected unexpected WebSocket protocol ${JSON.stringify(socket.protocol)}`));\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tsettled = true;\n\t\t\tcleanup();\n\t\t\tresolve(socket);\n\t\t};\n\t\tconst onClose = (event: RadiusRelayCloseEvent): void =>\n\t\t\tfail(new Error(`Radius relay closed before connecting (${event.code})`));\n\t\tconst onError = (event: RadiusRelayErrorEvent): void => fail(webSocketError(event));\n\t\tconst onAbort = (): void => fail(new Error(\"Radius relay connection cancelled\"));\n\t\tsocket.addEventListener(\"open\", onOpen, { once: true });\n\t\tsocket.addEventListener(\"close\", onClose, { once: true });\n\t\tsocket.addEventListener(\"error\", onError, { once: true });\n\t\tsignal?.addEventListener(\"abort\", onAbort, { once: true });\n\t});\n}\n\nfunction closeWebSocket(socket: RadiusRelayWebSocket, code: number, reason: string): void {\n\ttry {\n\t\tsocket.close(code, reason);\n\t} catch {\n\t\t// A WebSocket can reject close() while its opening handshake is still pending.\n\t}\n}\n\nfunction webSocketError(event: RadiusRelayErrorEvent): Error {\n\tif (event.error instanceof Error && event.error.message.trim().length > 0) return event.error;\n\treturn new Error(event.message?.trim() || \"Radius WebSocket connection failed\");\n}\n\nfunction delay(milliseconds: number, signal: AbortSignal | undefined, unref: boolean): Promise<void> {\n\tsignal?.throwIfAborted();\n\treturn new Promise((resolve, reject) => {\n\t\tconst timer = setTimeout(() => {\n\t\t\tcleanup();\n\t\t\tresolve();\n\t\t}, milliseconds);\n\t\tif (unref) timer.unref();\n\t\tconst onAbort = (): void => {\n\t\t\tclearTimeout(timer);\n\t\t\tcleanup();\n\t\t\ttry {\n\t\t\t\tsignal?.throwIfAborted();\n\t\t\t} catch (error) {\n\t\t\t\treject(error);\n\t\t\t}\n\t\t};\n\t\tconst cleanup = (): void => signal?.removeEventListener(\"abort\", onAbort);\n\t\tsignal?.addEventListener(\"abort\", onAbort, { once: true });\n\t});\n}\n\nfunction toError(error: unknown): Error {\n\treturn error instanceof Error ? error : new Error(String(error));\n}\n\nfunction errorMessage(error: unknown): string {\n\treturn toError(error).message;\n}\n"]}
|