@xyit/wellspring 0.0.0-stage → 1.1.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/LICENSE +21 -0
- package/README.md +853 -2
- package/dist/agent/pending-asks.d.ts +20 -0
- package/dist/agent/pending-asks.js +70 -0
- package/dist/agent/pending-asks.js.map +1 -0
- package/dist/agent/task-registry.d.ts +14 -0
- package/dist/agent/task-registry.js +80 -0
- package/dist/agent/task-registry.js.map +1 -0
- package/dist/agent/tool-context.d.ts +30 -0
- package/dist/agent/tool-context.js +10 -0
- package/dist/agent/tool-context.js.map +1 -0
- package/dist/agent/usage.d.ts +40 -0
- package/dist/agent/usage.js +122 -0
- package/dist/agent/usage.js.map +1 -0
- package/dist/api/host/agent.d.ts +165 -0
- package/dist/api/host/agent.js +5 -0
- package/dist/api/host/agent.js.map +1 -0
- package/dist/api/host/events.d.ts +2 -0
- package/dist/api/host/events.js +6 -0
- package/dist/api/host/events.js.map +1 -0
- package/dist/api/host/exec.d.ts +36 -0
- package/dist/api/host/exec.js +10 -0
- package/dist/api/host/exec.js.map +1 -0
- package/dist/api/host/fs.d.ts +24 -0
- package/dist/api/host/fs.js +7 -0
- package/dist/api/host/fs.js.map +1 -0
- package/dist/api/host/hooks.d.ts +210 -0
- package/dist/api/host/hooks.js +401 -0
- package/dist/api/host/hooks.js.map +1 -0
- package/dist/api/host/host.d.ts +36 -0
- package/dist/api/host/host.js +53 -0
- package/dist/api/host/host.js.map +1 -0
- package/dist/api/host/http.d.ts +58 -0
- package/dist/api/host/http.js +6 -0
- package/dist/api/host/http.js.map +1 -0
- package/dist/api/host/index.d.ts +15 -0
- package/dist/api/host/index.js +20 -0
- package/dist/api/host/index.js.map +1 -0
- package/dist/api/host/models.d.ts +65 -0
- package/dist/api/host/models.js +8 -0
- package/dist/api/host/models.js.map +1 -0
- package/dist/api/host/network.d.ts +22 -0
- package/dist/api/host/network.js +7 -0
- package/dist/api/host/network.js.map +1 -0
- package/dist/api/host/plugin.d.ts +138 -0
- package/dist/api/host/plugin.js +6 -0
- package/dist/api/host/plugin.js.map +1 -0
- package/dist/api/host/plugins.d.ts +49 -0
- package/dist/api/host/plugins.js +6 -0
- package/dist/api/host/plugins.js.map +1 -0
- package/dist/api/host/pty.d.ts +55 -0
- package/dist/api/host/pty.js +21 -0
- package/dist/api/host/pty.js.map +1 -0
- package/dist/api/host/session.d.ts +5 -0
- package/dist/api/host/session.js +52 -0
- package/dist/api/host/session.js.map +1 -0
- package/dist/api/host/version.d.ts +2 -0
- package/dist/api/host/version.js +3 -0
- package/dist/api/host/version.js.map +1 -0
- package/dist/api/host/workspace.d.ts +38 -0
- package/dist/api/host/workspace.js +9 -0
- package/dist/api/host/workspace.js.map +1 -0
- package/dist/api/plugin/http-client.d.ts +42 -0
- package/dist/api/plugin/http-client.js +364 -0
- package/dist/api/plugin/http-client.js.map +1 -0
- package/dist/api/plugin/http-protocol.d.ts +152 -0
- package/dist/api/plugin/http-protocol.js +49 -0
- package/dist/api/plugin/http-protocol.js.map +1 -0
- package/dist/api/result/result.d.ts +16 -0
- package/dist/api/result/result.js +43 -0
- package/dist/api/result/result.js.map +1 -0
- package/dist/commands/agent-turn.d.ts +63 -0
- package/dist/commands/agent-turn.js +687 -0
- package/dist/commands/agent-turn.js.map +1 -0
- package/dist/commands/ask-prompt.d.ts +6 -0
- package/dist/commands/ask-prompt.js +49 -0
- package/dist/commands/ask-prompt.js.map +1 -0
- package/dist/commands/config.d.ts +2 -0
- package/dist/commands/config.js +283 -0
- package/dist/commands/config.js.map +1 -0
- package/dist/commands/conversation.d.ts +51 -0
- package/dist/commands/conversation.js +630 -0
- package/dist/commands/conversation.js.map +1 -0
- package/dist/commands/doctor.d.ts +2 -0
- package/dist/commands/doctor.js +77 -0
- package/dist/commands/doctor.js.map +1 -0
- package/dist/commands/exec-approval.d.ts +3 -0
- package/dist/commands/exec-approval.js +80 -0
- package/dist/commands/exec-approval.js.map +1 -0
- package/dist/commands/help.d.ts +2 -0
- package/dist/commands/help.js +35 -0
- package/dist/commands/help.js.map +1 -0
- package/dist/commands/key.d.ts +2 -0
- package/dist/commands/key.js +226 -0
- package/dist/commands/key.js.map +1 -0
- package/dist/commands/plugin.d.ts +2 -0
- package/dist/commands/plugin.js +190 -0
- package/dist/commands/plugin.js.map +1 -0
- package/dist/commands/session.d.ts +2 -0
- package/dist/commands/session.js +167 -0
- package/dist/commands/session.js.map +1 -0
- package/dist/commands/shared.d.ts +35 -0
- package/dist/commands/shared.js +66 -0
- package/dist/commands/shared.js.map +1 -0
- package/dist/commands/start.d.ts +2 -0
- package/dist/commands/start.js +75 -0
- package/dist/commands/start.js.map +1 -0
- package/dist/commands/stop.d.ts +2 -0
- package/dist/commands/stop.js +24 -0
- package/dist/commands/stop.js.map +1 -0
- package/dist/commands/version.d.ts +2 -0
- package/dist/commands/version.js +33 -0
- package/dist/commands/version.js.map +1 -0
- package/dist/config/codes.d.ts +13 -0
- package/dist/config/codes.js +15 -0
- package/dist/config/codes.js.map +1 -0
- package/dist/config/credentials.d.ts +67 -0
- package/dist/config/credentials.js +214 -0
- package/dist/config/credentials.js.map +1 -0
- package/dist/config/defaults.d.ts +5 -0
- package/dist/config/defaults.js +31 -0
- package/dist/config/defaults.js.map +1 -0
- package/dist/config/index.d.ts +5 -0
- package/dist/config/index.js +10 -0
- package/dist/config/index.js.map +1 -0
- package/dist/config/loader.d.ts +10 -0
- package/dist/config/loader.js +68 -0
- package/dist/config/loader.js.map +1 -0
- package/dist/config/providers.d.ts +30 -0
- package/dist/config/providers.js +97 -0
- package/dist/config/providers.js.map +1 -0
- package/dist/config/types.d.ts +75 -0
- package/dist/config/types.js +4 -0
- package/dist/config/types.js.map +1 -0
- package/dist/config/validate.d.ts +4 -0
- package/dist/config/validate.js +391 -0
- package/dist/config/validate.js.map +1 -0
- package/dist/display/markdown.d.ts +7 -0
- package/dist/display/markdown.js +116 -0
- package/dist/display/markdown.js.map +1 -0
- package/dist/display/tool-label.d.ts +6 -0
- package/dist/display/tool-label.js +81 -0
- package/dist/display/tool-label.js.map +1 -0
- package/dist/event/event.d.ts +27 -0
- package/dist/event/event.js +152 -0
- package/dist/event/event.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +308 -0
- package/dist/index.js.map +1 -0
- package/dist/input/codes.d.ts +15 -0
- package/dist/input/codes.js +20 -0
- package/dist/input/codes.js.map +1 -0
- package/dist/input/commands.d.ts +10 -0
- package/dist/input/commands.js +65 -0
- package/dist/input/commands.js.map +1 -0
- package/dist/input/help.d.ts +8 -0
- package/dist/input/help.js +51 -0
- package/dist/input/help.js.map +1 -0
- package/dist/input/index.d.ts +6 -0
- package/dist/input/index.js +10 -0
- package/dist/input/index.js.map +1 -0
- package/dist/input/parse-options.d.ts +11 -0
- package/dist/input/parse-options.js +51 -0
- package/dist/input/parse-options.js.map +1 -0
- package/dist/input/parse.d.ts +3 -0
- package/dist/input/parse.js +119 -0
- package/dist/input/parse.js.map +1 -0
- package/dist/input/types.d.ts +17 -0
- package/dist/input/types.js +2 -0
- package/dist/input/types.js.map +1 -0
- package/dist/logger/elog4js.d.ts +65 -0
- package/dist/logger/elog4js.js +305 -0
- package/dist/logger/elog4js.js.map +1 -0
- package/dist/logger/logger.d.ts +37 -0
- package/dist/logger/logger.js +196 -0
- package/dist/logger/logger.js.map +1 -0
- package/dist/network/index.d.ts +2 -0
- package/dist/network/index.js +6 -0
- package/dist/network/index.js.map +1 -0
- package/dist/network/openai.d.ts +117 -0
- package/dist/network/openai.js +443 -0
- package/dist/network/openai.js.map +1 -0
- package/dist/network/proxy.d.ts +9 -0
- package/dist/network/proxy.js +72 -0
- package/dist/network/proxy.js.map +1 -0
- package/dist/network/web.d.ts +34 -0
- package/dist/network/web.js +152 -0
- package/dist/network/web.js.map +1 -0
- package/dist/platform/clipboard.d.ts +15 -0
- package/dist/platform/clipboard.js +82 -0
- package/dist/platform/clipboard.js.map +1 -0
- package/dist/platform/exec.d.ts +28 -0
- package/dist/platform/exec.js +95 -0
- package/dist/platform/exec.js.map +1 -0
- package/dist/platform/file.d.ts +38 -0
- package/dist/platform/file.js +197 -0
- package/dist/platform/file.js.map +1 -0
- package/dist/platform/index.d.ts +7 -0
- package/dist/platform/index.js +12 -0
- package/dist/platform/index.js.map +1 -0
- package/dist/platform/notification.d.ts +14 -0
- package/dist/platform/notification.js +82 -0
- package/dist/platform/notification.js.map +1 -0
- package/dist/platform/open-external.d.ts +10 -0
- package/dist/platform/open-external.js +105 -0
- package/dist/platform/open-external.js.map +1 -0
- package/dist/platform/os.d.ts +20 -0
- package/dist/platform/os.js +86 -0
- package/dist/platform/os.js.map +1 -0
- package/dist/platform/path.d.ts +32 -0
- package/dist/platform/path.js +154 -0
- package/dist/platform/path.js.map +1 -0
- package/dist/plugin/access-guard.d.ts +1 -0
- package/dist/plugin/access-guard.js +40 -0
- package/dist/plugin/access-guard.js.map +1 -0
- package/dist/plugin/agent-service.d.ts +25 -0
- package/dist/plugin/agent-service.js +809 -0
- package/dist/plugin/agent-service.js.map +1 -0
- package/dist/plugin/agent-tools.d.ts +12 -0
- package/dist/plugin/agent-tools.js +31 -0
- package/dist/plugin/agent-tools.js.map +1 -0
- package/dist/plugin/capabilities.d.ts +26 -0
- package/dist/plugin/capabilities.js +192 -0
- package/dist/plugin/capabilities.js.map +1 -0
- package/dist/plugin/console-guard.d.ts +2 -0
- package/dist/plugin/console-guard.js +52 -0
- package/dist/plugin/console-guard.js.map +1 -0
- package/dist/plugin/data-access.d.ts +15 -0
- package/dist/plugin/data-access.js +57 -0
- package/dist/plugin/data-access.js.map +1 -0
- package/dist/plugin/fs.d.ts +3 -0
- package/dist/plugin/fs.js +93 -0
- package/dist/plugin/fs.js.map +1 -0
- package/dist/plugin/host-alias.d.ts +2 -0
- package/dist/plugin/host-alias.js +57 -0
- package/dist/plugin/host-alias.js.map +1 -0
- package/dist/plugin/index.d.ts +7 -0
- package/dist/plugin/index.js +11 -0
- package/dist/plugin/index.js.map +1 -0
- package/dist/plugin/install.d.ts +17 -0
- package/dist/plugin/install.js +163 -0
- package/dist/plugin/install.js.map +1 -0
- package/dist/plugin/loader.d.ts +45 -0
- package/dist/plugin/loader.js +739 -0
- package/dist/plugin/loader.js.map +1 -0
- package/dist/plugin/manage.d.ts +31 -0
- package/dist/plugin/manage.js +271 -0
- package/dist/plugin/manage.js.map +1 -0
- package/dist/plugin/models-service.d.ts +15 -0
- package/dist/plugin/models-service.js +317 -0
- package/dist/plugin/models-service.js.map +1 -0
- package/dist/plugin/network-service.d.ts +10 -0
- package/dist/plugin/network-service.js +87 -0
- package/dist/plugin/network-service.js.map +1 -0
- package/dist/plugin/package-manager.d.ts +46 -0
- package/dist/plugin/package-manager.js +148 -0
- package/dist/plugin/package-manager.js.map +1 -0
- package/dist/plugin/paths.d.ts +6 -0
- package/dist/plugin/paths.js +15 -0
- package/dist/plugin/paths.js.map +1 -0
- package/dist/plugin/registry.d.ts +17 -0
- package/dist/plugin/registry.js +82 -0
- package/dist/plugin/registry.js.map +1 -0
- package/dist/plugin/runtime.d.ts +9 -0
- package/dist/plugin/runtime.js +43 -0
- package/dist/plugin/runtime.js.map +1 -0
- package/dist/plugin/scaffold.d.ts +13 -0
- package/dist/plugin/scaffold.js +132 -0
- package/dist/plugin/scaffold.js.map +1 -0
- package/dist/plugin/state.d.ts +6 -0
- package/dist/plugin/state.js +38 -0
- package/dist/plugin/state.js.map +1 -0
- package/dist/plugin/zip.d.ts +7 -0
- package/dist/plugin/zip.js +138 -0
- package/dist/plugin/zip.js.map +1 -0
- package/dist/sandbox/audit.d.ts +12 -0
- package/dist/sandbox/audit.js +20 -0
- package/dist/sandbox/audit.js.map +1 -0
- package/dist/sandbox/backends/bwrap.d.ts +28 -0
- package/dist/sandbox/backends/bwrap.js +197 -0
- package/dist/sandbox/backends/bwrap.js.map +1 -0
- package/dist/sandbox/backends/direct.d.ts +7 -0
- package/dist/sandbox/backends/direct.js +32 -0
- package/dist/sandbox/backends/direct.js.map +1 -0
- package/dist/sandbox/bridge-state.d.ts +13 -0
- package/dist/sandbox/bridge-state.js +12 -0
- package/dist/sandbox/bridge-state.js.map +1 -0
- package/dist/sandbox/index.d.ts +5 -0
- package/dist/sandbox/index.js +6 -0
- package/dist/sandbox/index.js.map +1 -0
- package/dist/sandbox/manager.d.ts +8 -0
- package/dist/sandbox/manager.js +325 -0
- package/dist/sandbox/manager.js.map +1 -0
- package/dist/sandbox/memory-limit.d.ts +44 -0
- package/dist/sandbox/memory-limit.js +193 -0
- package/dist/sandbox/memory-limit.js.map +1 -0
- package/dist/sandbox/plugin-client.d.ts +80 -0
- package/dist/sandbox/plugin-client.js +1601 -0
- package/dist/sandbox/plugin-client.js.map +1 -0
- package/dist/sandbox/plugin-host.d.ts +1 -0
- package/dist/sandbox/plugin-host.js +1001 -0
- package/dist/sandbox/plugin-host.js.map +1 -0
- package/dist/sandbox/protocol.d.ts +43 -0
- package/dist/sandbox/protocol.js +96 -0
- package/dist/sandbox/protocol.js.map +1 -0
- package/dist/sandbox/rpc-timeouts.d.ts +28 -0
- package/dist/sandbox/rpc-timeouts.js +15 -0
- package/dist/sandbox/rpc-timeouts.js.map +1 -0
- package/dist/sandbox/supervisor.d.ts +31 -0
- package/dist/sandbox/supervisor.js +114 -0
- package/dist/sandbox/supervisor.js.map +1 -0
- package/dist/sandbox/types.d.ts +124 -0
- package/dist/sandbox/types.js +17 -0
- package/dist/sandbox/types.js.map +1 -0
- package/dist/service/control.d.ts +32 -0
- package/dist/service/control.js +117 -0
- package/dist/service/control.js.map +1 -0
- package/dist/session/compress.d.ts +3 -0
- package/dist/session/compress.js +42 -0
- package/dist/session/compress.js.map +1 -0
- package/dist/session/index.d.ts +4 -0
- package/dist/session/index.js +8 -0
- package/dist/session/index.js.map +1 -0
- package/dist/session/registry.d.ts +4 -0
- package/dist/session/registry.js +12 -0
- package/dist/session/registry.js.map +1 -0
- package/dist/session/replay.d.ts +10 -0
- package/dist/session/replay.js +109 -0
- package/dist/session/replay.js.map +1 -0
- package/dist/session/session.d.ts +40 -0
- package/dist/session/session.js +108 -0
- package/dist/session/session.js.map +1 -0
- package/dist/session/store.d.ts +58 -0
- package/dist/session/store.js +382 -0
- package/dist/session/store.js.map +1 -0
- package/dist/session/types.d.ts +56 -0
- package/dist/session/types.js +3 -0
- package/dist/session/types.js.map +1 -0
- package/dist/workspace/index.d.ts +7 -0
- package/dist/workspace/index.js +17 -0
- package/dist/workspace/index.js.map +1 -0
- package/dist/workspace/registry.d.ts +25 -0
- package/dist/workspace/registry.js +293 -0
- package/dist/workspace/registry.js.map +1 -0
- package/package.json +73 -5
- package/scripts/build-node-pty.mjs +55 -0
- package/scripts/rewrite-dts.mjs +36 -0
- package/scripts/run-tests.mjs +41 -0
- package/scripts/verify-sandbox.ts +98 -0
- package/src/agent/pending-asks.ts +88 -0
- package/src/agent/task-registry.ts +108 -0
- package/src/agent/tool-context.ts +47 -0
- package/src/agent/usage.ts +170 -0
- package/src/api/host/agent.ts +162 -0
- package/src/api/host/events.ts +7 -0
- package/src/api/host/exec.ts +47 -0
- package/src/api/host/fs.ts +33 -0
- package/src/api/host/hooks.ts +635 -0
- package/src/api/host/host.ts +99 -0
- package/src/api/host/http.ts +79 -0
- package/src/api/host/index.ts +20 -0
- package/src/api/host/models.ts +81 -0
- package/src/api/host/network.ts +32 -0
- package/src/api/host/plugin.ts +158 -0
- package/src/api/host/plugins.ts +59 -0
- package/src/api/host/pty.ts +73 -0
- package/src/api/host/session.ts +61 -0
- package/src/api/host/version.ts +2 -0
- package/src/api/host/workspace.ts +51 -0
- package/src/api/plugin/http-client.ts +492 -0
- package/src/api/plugin/http-protocol.ts +231 -0
- package/src/api/result/result.ts +52 -0
- package/src/commands/agent-turn.ts +817 -0
- package/src/commands/ask-prompt.ts +52 -0
- package/src/commands/config.ts +306 -0
- package/src/commands/conversation.ts +777 -0
- package/src/commands/doctor.ts +92 -0
- package/src/commands/exec-approval.ts +91 -0
- package/src/commands/help.ts +41 -0
- package/src/commands/key.ts +281 -0
- package/src/commands/plugin.ts +222 -0
- package/src/commands/session.ts +179 -0
- package/src/commands/shared.ts +103 -0
- package/src/commands/start.ts +88 -0
- package/src/commands/stop.ts +24 -0
- package/src/commands/version.ts +40 -0
- package/src/config/codes.ts +17 -0
- package/src/config/credentials.ts +302 -0
- package/src/config/defaults.ts +32 -0
- package/src/config/index.ts +10 -0
- package/src/config/loader.ts +80 -0
- package/src/config/providers.ts +133 -0
- package/src/config/types.ts +88 -0
- package/src/config/validate.ts +458 -0
- package/src/display/markdown.ts +137 -0
- package/src/display/tool-label.ts +87 -0
- package/src/event/event.ts +171 -0
- package/src/index.ts +344 -0
- package/src/input/codes.ts +21 -0
- package/src/input/commands.ts +76 -0
- package/src/input/help.ts +60 -0
- package/src/input/index.ts +10 -0
- package/src/input/parse-options.ts +66 -0
- package/src/input/parse.ts +138 -0
- package/src/input/types.ts +19 -0
- package/src/logger/elog4js.ts +366 -0
- package/src/logger/logger.ts +251 -0
- package/src/network/index.ts +6 -0
- package/src/network/openai.ts +571 -0
- package/src/network/proxy.ts +80 -0
- package/src/network/web.ts +206 -0
- package/src/platform/clipboard.ts +107 -0
- package/src/platform/exec.ts +128 -0
- package/src/platform/file.ts +235 -0
- package/src/platform/index.ts +12 -0
- package/src/platform/notification.ts +108 -0
- package/src/platform/open-external.ts +132 -0
- package/src/platform/os.ts +101 -0
- package/src/platform/path.ts +214 -0
- package/src/plugin/access-guard.ts +46 -0
- package/src/plugin/agent-service.ts +969 -0
- package/src/plugin/agent-tools.ts +43 -0
- package/src/plugin/capabilities.ts +240 -0
- package/src/plugin/console-guard.ts +66 -0
- package/src/plugin/data-access.ts +70 -0
- package/src/plugin/fs.ts +109 -0
- package/src/plugin/host-alias.ts +61 -0
- package/src/plugin/index.ts +11 -0
- package/src/plugin/install.ts +213 -0
- package/src/plugin/loader.ts +952 -0
- package/src/plugin/manage.ts +338 -0
- package/src/plugin/models-service.ts +383 -0
- package/src/plugin/network-service.ts +102 -0
- package/src/plugin/package-manager.ts +214 -0
- package/src/plugin/paths.ts +19 -0
- package/src/plugin/registry.ts +103 -0
- package/src/plugin/runtime.ts +57 -0
- package/src/plugin/scaffold.ts +158 -0
- package/src/plugin/state.ts +52 -0
- package/src/plugin/zip.ts +156 -0
- package/src/sandbox/audit.ts +38 -0
- package/src/sandbox/backends/bwrap.ts +251 -0
- package/src/sandbox/backends/direct.ts +43 -0
- package/src/sandbox/bridge-state.ts +29 -0
- package/src/sandbox/index.ts +5 -0
- package/src/sandbox/manager.ts +406 -0
- package/src/sandbox/memory-limit.ts +245 -0
- package/src/sandbox/plugin-client.ts +1881 -0
- package/src/sandbox/plugin-host.ts +1282 -0
- package/src/sandbox/protocol.ts +142 -0
- package/src/sandbox/pty-bridge.py +154 -0
- package/src/sandbox/rpc-timeouts.ts +43 -0
- package/src/sandbox/supervisor.ts +172 -0
- package/src/sandbox/types.ts +149 -0
- package/src/service/control.ts +155 -0
- package/src/session/compress.ts +47 -0
- package/src/session/index.ts +8 -0
- package/src/session/registry.ts +17 -0
- package/src/session/replay.ts +132 -0
- package/src/session/session.ts +134 -0
- package/src/session/store.ts +468 -0
- package/src/session/types.ts +61 -0
- package/src/workspace/index.ts +21 -0
- package/src/workspace/registry.ts +356 -0
|
@@ -0,0 +1,383 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* 宿主模型服务:给 sandbox 插件提供自定义供应商与模型切换。
|
|
3
|
+
*
|
|
4
|
+
* 配置写在 app-setting.json 的 models 节(每次调用实时读写,改完下一轮对话生效),
|
|
5
|
+
* 每个供应商的 Key 写在 credentials.json;状态只返回掩码。
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { Logger } from "pino"
|
|
9
|
+
|
|
10
|
+
import type {
|
|
11
|
+
HostModelProvider,
|
|
12
|
+
HostModelProviderInput,
|
|
13
|
+
HostModelsApi,
|
|
14
|
+
HostModelsFetchResult,
|
|
15
|
+
HostModelsState,
|
|
16
|
+
HostModelsStateResult,
|
|
17
|
+
} from "../api/host/models.ts"
|
|
18
|
+
import { CONFIG_CODES } from "../config/codes.ts"
|
|
19
|
+
import {
|
|
20
|
+
clearProviderApiKey,
|
|
21
|
+
credentialsFile,
|
|
22
|
+
normalizeApiKey,
|
|
23
|
+
providerKeyStatusSync,
|
|
24
|
+
saveProviderApiKey,
|
|
25
|
+
} from "../config/credentials.ts"
|
|
26
|
+
import { DEFAULT_SETTINGS } from "../config/defaults.ts"
|
|
27
|
+
import { REASONING_LEVELS, type ReasoningLevel } from "../config/types.ts"
|
|
28
|
+
import { loadSettings, saveSettings, SETTING_FILE_NAME } from "../config/loader.ts"
|
|
29
|
+
import {
|
|
30
|
+
BUILTIN_PROVIDER_ID,
|
|
31
|
+
listModelProviders,
|
|
32
|
+
modelsSetting,
|
|
33
|
+
providerApiKey,
|
|
34
|
+
} from "../config/providers.ts"
|
|
35
|
+
import type { AppSetting, ModelProviderSetting } from "../config/types.ts"
|
|
36
|
+
import { OpenAIClient } from "../network/openai.ts"
|
|
37
|
+
import { createProxyFetch } from "../network/proxy.ts"
|
|
38
|
+
|
|
39
|
+
export interface HostModelsServiceOptions {
|
|
40
|
+
readonly settings: AppSetting
|
|
41
|
+
/** app-setting.json 的绝对路径 */
|
|
42
|
+
readonly settingsFile: string
|
|
43
|
+
/** 凭据文件所在 config 目录 */
|
|
44
|
+
readonly configDir: string
|
|
45
|
+
readonly logger: Logger
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export interface HostModelsService extends HostModelsApi {
|
|
49
|
+
state(): Promise<HostModelsState>
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const PROVIDER_ID = /^[a-z0-9][a-z0-9._-]{0,31}$/
|
|
53
|
+
const MAX_MODEL_CHARS = 120
|
|
54
|
+
|
|
55
|
+
function normalizeModels(value: readonly string[] | undefined): string[] {
|
|
56
|
+
const models: string[] = []
|
|
57
|
+
for (const item of value ?? []) {
|
|
58
|
+
if (typeof item !== "string") {
|
|
59
|
+
continue
|
|
60
|
+
}
|
|
61
|
+
const model = item.trim()
|
|
62
|
+
if (model === "" || model.length > MAX_MODEL_CHARS || models.includes(model)) {
|
|
63
|
+
continue
|
|
64
|
+
}
|
|
65
|
+
models.push(model)
|
|
66
|
+
}
|
|
67
|
+
return models
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function slugOf(name: string): string {
|
|
71
|
+
const slug = name
|
|
72
|
+
.toLowerCase()
|
|
73
|
+
.replace(/[^a-z0-9]+/g, "-")
|
|
74
|
+
.replace(/^-+|-+$/g, "")
|
|
75
|
+
.slice(0, 32)
|
|
76
|
+
return PROVIDER_ID.test(slug) ? slug : "provider"
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function uniqueId(base: string, taken: ReadonlySet<string>): string {
|
|
80
|
+
if (!taken.has(base) && base !== BUILTIN_PROVIDER_ID) {
|
|
81
|
+
return base
|
|
82
|
+
}
|
|
83
|
+
for (let index = 2; index < 100; index += 1) {
|
|
84
|
+
const candidate = `${base}-${index}`
|
|
85
|
+
if (
|
|
86
|
+
!taken.has(candidate) &&
|
|
87
|
+
candidate !== BUILTIN_PROVIDER_ID &&
|
|
88
|
+
PROVIDER_ID.test(candidate)
|
|
89
|
+
) {
|
|
90
|
+
return candidate
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return `provider-${Date.now().toString(36)}`
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export function createHostModelsService(options: HostModelsServiceOptions): HostModelsService {
|
|
97
|
+
const logger = options.logger.child({ module: "models" })
|
|
98
|
+
const settingsFile = options.settingsFile
|
|
99
|
+
const credentialsPath = credentialsFile(options.configDir)
|
|
100
|
+
|
|
101
|
+
const currentSettings = async (): Promise<AppSetting> => {
|
|
102
|
+
const loaded = await loadSettings(settingsFile)
|
|
103
|
+
return loaded.getData() ?? options.settings ?? DEFAULT_SETTINGS
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const stateOf = (settings: AppSetting): HostModelsState => {
|
|
107
|
+
const models = modelsSetting(settings)
|
|
108
|
+
const providers: HostModelProvider[] = listModelProviders(settings).map((provider) => {
|
|
109
|
+
const key = providerKeyStatusSync(
|
|
110
|
+
{ id: provider.id, apiKeyEnv: provider.apiKeyEnv },
|
|
111
|
+
credentialsPath,
|
|
112
|
+
)
|
|
113
|
+
return {
|
|
114
|
+
id: provider.id,
|
|
115
|
+
name: provider.name,
|
|
116
|
+
baseUrl: provider.baseUrl,
|
|
117
|
+
models: provider.models,
|
|
118
|
+
defaultModel: provider.defaultModel,
|
|
119
|
+
apiKeyEnv: provider.apiKeyEnv,
|
|
120
|
+
builtin: provider.builtin,
|
|
121
|
+
keyConfigured: key.configured,
|
|
122
|
+
keySource: key.source,
|
|
123
|
+
keyMasked: key.masked,
|
|
124
|
+
envVar: key.envVar,
|
|
125
|
+
keyStored: key.stored,
|
|
126
|
+
}
|
|
127
|
+
})
|
|
128
|
+
const activeProvider = providers.some((provider) => provider.id === models.activeProvider)
|
|
129
|
+
? models.activeProvider
|
|
130
|
+
: BUILTIN_PROVIDER_ID
|
|
131
|
+
return {
|
|
132
|
+
providers,
|
|
133
|
+
activeProvider,
|
|
134
|
+
activeModel: models.activeModel,
|
|
135
|
+
activeReasoning: models.activeReasoning,
|
|
136
|
+
reasoningLevels: REASONING_LEVELS,
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const saveProviderConfig = async (
|
|
141
|
+
settings: AppSetting,
|
|
142
|
+
provider: ModelProviderSetting,
|
|
143
|
+
): Promise<HostModelsStateResult> => {
|
|
144
|
+
const models = modelsSetting(settings)
|
|
145
|
+
const providers = [...models.providers]
|
|
146
|
+
const index = providers.findIndex((item) => item.id === provider.id)
|
|
147
|
+
if (index === -1) {
|
|
148
|
+
if (providers.length >= 20) {
|
|
149
|
+
return { ok: false, code: "models.too-many-providers", state: stateOf(settings) }
|
|
150
|
+
}
|
|
151
|
+
providers.push(provider)
|
|
152
|
+
} else {
|
|
153
|
+
providers[index] = provider
|
|
154
|
+
}
|
|
155
|
+
const next: AppSetting = {
|
|
156
|
+
...settings,
|
|
157
|
+
models: { ...models, providers },
|
|
158
|
+
}
|
|
159
|
+
const saved = await saveSettings(next, settingsFile)
|
|
160
|
+
if (!saved.isSuccess()) {
|
|
161
|
+
return {
|
|
162
|
+
ok: false,
|
|
163
|
+
code: saved.getCode() ?? CONFIG_CODES.invalid,
|
|
164
|
+
state: stateOf(settings),
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
return { ok: true, state: stateOf(next) }
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const fail = async (code: string): Promise<HostModelsStateResult> => ({
|
|
171
|
+
ok: false,
|
|
172
|
+
code,
|
|
173
|
+
state: stateOf(await currentSettings()),
|
|
174
|
+
})
|
|
175
|
+
|
|
176
|
+
return {
|
|
177
|
+
async state(): Promise<HostModelsState> {
|
|
178
|
+
return stateOf(await currentSettings())
|
|
179
|
+
},
|
|
180
|
+
|
|
181
|
+
async setActive(
|
|
182
|
+
providerId: string,
|
|
183
|
+
model: string | null,
|
|
184
|
+
reasoning?: string | null,
|
|
185
|
+
): Promise<HostModelsState> {
|
|
186
|
+
const settings = await currentSettings()
|
|
187
|
+
const models = modelsSetting(settings)
|
|
188
|
+
const id = providerId.trim()
|
|
189
|
+
const exists = listModelProviders(settings).some((provider) => provider.id === id)
|
|
190
|
+
if (!exists) {
|
|
191
|
+
throw new Error("models.provider-not-found")
|
|
192
|
+
}
|
|
193
|
+
const nextModel = typeof model === "string" && model.trim() !== "" ? model.trim() : null
|
|
194
|
+
if (nextModel !== null && nextModel.length > MAX_MODEL_CHARS) {
|
|
195
|
+
throw new Error("models.invalid-model")
|
|
196
|
+
}
|
|
197
|
+
const nextReasoning =
|
|
198
|
+
typeof reasoning === "string" && reasoning.trim() !== ""
|
|
199
|
+
? reasoning.trim()
|
|
200
|
+
: models.activeReasoning
|
|
201
|
+
if (!(REASONING_LEVELS as readonly string[]).includes(nextReasoning)) {
|
|
202
|
+
throw new Error("models.invalid-reasoning")
|
|
203
|
+
}
|
|
204
|
+
const next: AppSetting = {
|
|
205
|
+
...settings,
|
|
206
|
+
models: {
|
|
207
|
+
...models,
|
|
208
|
+
activeProvider: id,
|
|
209
|
+
activeModel: nextModel,
|
|
210
|
+
activeReasoning: nextReasoning as ReasoningLevel,
|
|
211
|
+
},
|
|
212
|
+
}
|
|
213
|
+
const saved = await saveSettings(next, settingsFile)
|
|
214
|
+
if (!saved.isSuccess()) {
|
|
215
|
+
throw new Error(saved.getCode() ?? CONFIG_CODES.invalid)
|
|
216
|
+
}
|
|
217
|
+
logger.info({ provider: id, model: nextModel, reasoning: nextReasoning }, "切换模型")
|
|
218
|
+
return stateOf(next)
|
|
219
|
+
},
|
|
220
|
+
|
|
221
|
+
async saveProvider(input: HostModelProviderInput): Promise<HostModelsStateResult> {
|
|
222
|
+
const settings = await currentSettings()
|
|
223
|
+
const models = modelsSetting(settings)
|
|
224
|
+
const name = typeof input.name === "string" ? input.name.trim() : ""
|
|
225
|
+
if (name === "" || name.length > 60) {
|
|
226
|
+
return await fail(CONFIG_CODES.invalid)
|
|
227
|
+
}
|
|
228
|
+
const explicitId =
|
|
229
|
+
typeof input.id === "string" && input.id.trim() !== "" ? input.id.trim() : ""
|
|
230
|
+
const taken = new Set(models.providers.map((provider) => provider.id))
|
|
231
|
+
let id = explicitId
|
|
232
|
+
if (id === "") {
|
|
233
|
+
id = uniqueId(slugOf(name), taken)
|
|
234
|
+
} else if (!PROVIDER_ID.test(id) || id === BUILTIN_PROVIDER_ID) {
|
|
235
|
+
return await fail(CONFIG_CODES.invalid)
|
|
236
|
+
}
|
|
237
|
+
const apiKey =
|
|
238
|
+
typeof input.apiKey === "string" && input.apiKey.trim() !== ""
|
|
239
|
+
? normalizeApiKey(input.apiKey)
|
|
240
|
+
: null
|
|
241
|
+
if (typeof input.apiKey === "string" && input.apiKey.trim() !== "" && apiKey === null) {
|
|
242
|
+
return { ok: false, code: CONFIG_CODES.invalidApiKey, state: stateOf(settings) }
|
|
243
|
+
}
|
|
244
|
+
const rawBaseUrl = input.baseUrl
|
|
245
|
+
const baseUrl =
|
|
246
|
+
typeof rawBaseUrl === "string" && rawBaseUrl.trim() !== ""
|
|
247
|
+
? rawBaseUrl.trim()
|
|
248
|
+
: null
|
|
249
|
+
const provider: ModelProviderSetting = {
|
|
250
|
+
id,
|
|
251
|
+
name,
|
|
252
|
+
baseUrl,
|
|
253
|
+
models: normalizeModels(input.models),
|
|
254
|
+
defaultModel:
|
|
255
|
+
typeof input.defaultModel === "string" && input.defaultModel.trim() !== ""
|
|
256
|
+
? input.defaultModel.trim()
|
|
257
|
+
: null,
|
|
258
|
+
apiKeyEnv:
|
|
259
|
+
typeof input.apiKeyEnv === "string" && input.apiKeyEnv.trim() !== ""
|
|
260
|
+
? input.apiKeyEnv.trim()
|
|
261
|
+
: null,
|
|
262
|
+
}
|
|
263
|
+
const result = await saveProviderConfig(settings, provider)
|
|
264
|
+
if (!result.ok) {
|
|
265
|
+
return result
|
|
266
|
+
}
|
|
267
|
+
if (apiKey !== null) {
|
|
268
|
+
const keyed = await saveProviderApiKey(id, apiKey, credentialsPath)
|
|
269
|
+
if (!keyed.isSuccess()) {
|
|
270
|
+
return await fail(keyed.getCode() ?? CONFIG_CODES.credentialsWriteFailed)
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
return { ok: true, state: stateOf(await currentSettings()) }
|
|
274
|
+
},
|
|
275
|
+
|
|
276
|
+
async removeProvider(id: string): Promise<HostModelsStateResult> {
|
|
277
|
+
const settings = await currentSettings()
|
|
278
|
+
const models = modelsSetting(settings)
|
|
279
|
+
if (id === BUILTIN_PROVIDER_ID) {
|
|
280
|
+
return await fail("models.builtin-provider")
|
|
281
|
+
}
|
|
282
|
+
const providers = models.providers.filter((provider) => provider.id !== id)
|
|
283
|
+
if (providers.length === models.providers.length) {
|
|
284
|
+
return await fail("models.provider-not-found")
|
|
285
|
+
}
|
|
286
|
+
const next: AppSetting = {
|
|
287
|
+
...settings,
|
|
288
|
+
models: {
|
|
289
|
+
...models,
|
|
290
|
+
providers,
|
|
291
|
+
activeProvider:
|
|
292
|
+
models.activeProvider === id ? BUILTIN_PROVIDER_ID : models.activeProvider,
|
|
293
|
+
activeModel: models.activeProvider === id ? null : models.activeModel,
|
|
294
|
+
},
|
|
295
|
+
}
|
|
296
|
+
const saved = await saveSettings(next, settingsFile)
|
|
297
|
+
if (!saved.isSuccess()) {
|
|
298
|
+
return await fail(saved.getCode() ?? CONFIG_CODES.invalid)
|
|
299
|
+
}
|
|
300
|
+
await clearProviderApiKey(id, credentialsPath)
|
|
301
|
+
logger.info({ provider: id }, "删除自定义供应商")
|
|
302
|
+
return { ok: true, state: stateOf(next) }
|
|
303
|
+
},
|
|
304
|
+
|
|
305
|
+
async fetchModels(id: string, candidate?: string): Promise<HostModelsFetchResult> {
|
|
306
|
+
const settings = await currentSettings()
|
|
307
|
+
const provider = listModelProviders(settings).find((item) => item.id === id)
|
|
308
|
+
if (provider === undefined) {
|
|
309
|
+
return { ok: false, code: "models.provider-not-found" }
|
|
310
|
+
}
|
|
311
|
+
let apiKey: string | undefined
|
|
312
|
+
if (typeof candidate === "string" && candidate.trim() !== "") {
|
|
313
|
+
const normalized = normalizeApiKey(candidate)
|
|
314
|
+
if (normalized === null) {
|
|
315
|
+
return { ok: false, code: CONFIG_CODES.invalidApiKey }
|
|
316
|
+
}
|
|
317
|
+
apiKey = normalized
|
|
318
|
+
} else {
|
|
319
|
+
apiKey = providerApiKey(provider, options.configDir) ?? undefined
|
|
320
|
+
}
|
|
321
|
+
if (apiKey === undefined) {
|
|
322
|
+
return { ok: false, code: "openai.missing-api-key" }
|
|
323
|
+
}
|
|
324
|
+
let proxyFetch: ReturnType<typeof createProxyFetch> | undefined
|
|
325
|
+
try {
|
|
326
|
+
proxyFetch = createProxyFetch(settings.network.proxy)
|
|
327
|
+
} catch {
|
|
328
|
+
proxyFetch = undefined
|
|
329
|
+
}
|
|
330
|
+
const created = OpenAIClient.create({
|
|
331
|
+
settings: {
|
|
332
|
+
...settings.openai,
|
|
333
|
+
baseUrl: provider.baseUrl,
|
|
334
|
+
model: provider.defaultModel ?? provider.models[0] ?? settings.openai.model,
|
|
335
|
+
},
|
|
336
|
+
apiKey,
|
|
337
|
+
credentialsFile: credentialsPath,
|
|
338
|
+
logger,
|
|
339
|
+
fetch: proxyFetch,
|
|
340
|
+
})
|
|
341
|
+
const client = created.getData()
|
|
342
|
+
if (!created.isSuccess() || client === null) {
|
|
343
|
+
return { ok: false, code: created.getCode() ?? "openai.missing-api-key" }
|
|
344
|
+
}
|
|
345
|
+
const listed = await client.listModels()
|
|
346
|
+
const data = listed.getData()
|
|
347
|
+
if (!listed.isSuccess() || data === null) {
|
|
348
|
+
return { ok: false, code: listed.getCode() ?? "openai.request-failed" }
|
|
349
|
+
}
|
|
350
|
+
return { ok: true, models: data }
|
|
351
|
+
},
|
|
352
|
+
|
|
353
|
+
async saveKey(id: string, apiKey: string): Promise<HostModelsStateResult> {
|
|
354
|
+
const settings = await currentSettings()
|
|
355
|
+
const exists = listModelProviders(settings).some((provider) => provider.id === id)
|
|
356
|
+
if (!exists) {
|
|
357
|
+
return await fail("models.provider-not-found")
|
|
358
|
+
}
|
|
359
|
+
const saved = await saveProviderApiKey(id, apiKey, credentialsPath)
|
|
360
|
+
if (!saved.isSuccess()) {
|
|
361
|
+
return {
|
|
362
|
+
ok: false,
|
|
363
|
+
code: saved.getCode() ?? CONFIG_CODES.invalidApiKey,
|
|
364
|
+
state: stateOf(settings),
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
return { ok: true, state: stateOf(settings) }
|
|
368
|
+
},
|
|
369
|
+
|
|
370
|
+
async clearKey(id: string): Promise<HostModelsState> {
|
|
371
|
+
const settings = await currentSettings()
|
|
372
|
+
const exists = listModelProviders(settings).some((provider) => provider.id === id)
|
|
373
|
+
if (!exists) {
|
|
374
|
+
throw new Error("models.provider-not-found")
|
|
375
|
+
}
|
|
376
|
+
const cleared = await clearProviderApiKey(id, credentialsPath)
|
|
377
|
+
if (!cleared.isSuccess()) {
|
|
378
|
+
throw new Error(cleared.getCode() ?? CONFIG_CODES.credentialsWriteFailed)
|
|
379
|
+
}
|
|
380
|
+
return stateOf(settings)
|
|
381
|
+
},
|
|
382
|
+
}
|
|
383
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* 宿主网络配置服务:读写 app-setting.json 的 network.proxy。
|
|
3
|
+
*
|
|
4
|
+
* 只写这一个字段,其余配置原样保留;写入前经过 validateSettings,
|
|
5
|
+
* 非法 URL / noProxy 不会落盘。模型调用、拉模型、网页搜索每轮都会重新读配置,
|
|
6
|
+
* 所以保存后不需要重启服务,下一轮请求生效。
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type { Logger } from "pino"
|
|
10
|
+
|
|
11
|
+
import type { HostNetworkApi, HostNetworkResult, HostNetworkState } from "../api/host/network.ts"
|
|
12
|
+
import { CONFIG_CODES, invalidCode } from "../config/codes.ts"
|
|
13
|
+
import { loadSettings, saveSettings } from "../config/loader.ts"
|
|
14
|
+
import type { AppSetting, ProxySetting } from "../config/types.ts"
|
|
15
|
+
|
|
16
|
+
const MAX_NO_PROXY = 100
|
|
17
|
+
const MAX_NO_PROXY_LENGTH = 256
|
|
18
|
+
|
|
19
|
+
export interface HostNetworkServiceOptions {
|
|
20
|
+
/** 启动时的配置;配置文件不存在或读取失败时回退到它 */
|
|
21
|
+
readonly settings: AppSetting
|
|
22
|
+
readonly settingsFile: string
|
|
23
|
+
readonly logger?: Logger
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function isHttpUrl(value: string): boolean {
|
|
27
|
+
try {
|
|
28
|
+
const url = new URL(value)
|
|
29
|
+
return url.protocol === "http:" || url.protocol === "https:"
|
|
30
|
+
} catch {
|
|
31
|
+
return false
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** 去空、去重、限长,避免把超大/空白的 noProxy 写进配置 */
|
|
36
|
+
function normalizeNoProxy(values: readonly string[] | undefined): string[] {
|
|
37
|
+
const result: string[] = []
|
|
38
|
+
for (const raw of values ?? []) {
|
|
39
|
+
if (typeof raw !== "string") continue
|
|
40
|
+
const value = raw.trim()
|
|
41
|
+
if (value === "" || value.length > MAX_NO_PROXY_LENGTH) continue
|
|
42
|
+
if (result.includes(value)) continue
|
|
43
|
+
if (result.length >= MAX_NO_PROXY) break
|
|
44
|
+
result.push(value)
|
|
45
|
+
}
|
|
46
|
+
return result
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function createHostNetworkService(options: HostNetworkServiceOptions): HostNetworkApi {
|
|
50
|
+
const currentSettings = async (): Promise<AppSetting> => {
|
|
51
|
+
try {
|
|
52
|
+
const loaded = await loadSettings(options.settingsFile)
|
|
53
|
+
return loaded.getData() ?? options.settings
|
|
54
|
+
} catch {
|
|
55
|
+
return options.settings
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const stateOf = (settings: AppSetting): HostNetworkState => ({
|
|
60
|
+
proxy: settings.network.proxy,
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
return {
|
|
64
|
+
async state(): Promise<HostNetworkState> {
|
|
65
|
+
return stateOf(await currentSettings())
|
|
66
|
+
},
|
|
67
|
+
|
|
68
|
+
async saveProxy(input): Promise<HostNetworkResult> {
|
|
69
|
+
const settings = await currentSettings()
|
|
70
|
+
const rawUrl = typeof input.url === "string" ? input.url.trim() : ""
|
|
71
|
+
if (rawUrl !== "" && !isHttpUrl(rawUrl)) {
|
|
72
|
+
return {
|
|
73
|
+
ok: false,
|
|
74
|
+
code: invalidCode("network.proxy.url"),
|
|
75
|
+
state: stateOf(settings),
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
let proxy: ProxySetting | null = null
|
|
79
|
+
if (rawUrl !== "") {
|
|
80
|
+
proxy = {
|
|
81
|
+
type: "http",
|
|
82
|
+
url: rawUrl,
|
|
83
|
+
noProxy: normalizeNoProxy(input.noProxy),
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
const next: AppSetting = { ...settings, network: { proxy } }
|
|
87
|
+
const saved = await saveSettings(next, options.settingsFile)
|
|
88
|
+
if (!saved.isSuccess()) {
|
|
89
|
+
return {
|
|
90
|
+
ok: false,
|
|
91
|
+
code: saved.getCode() ?? CONFIG_CODES.invalid,
|
|
92
|
+
state: stateOf(settings),
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
options.logger?.info(
|
|
96
|
+
{ proxy: proxy?.url ?? null, noProxy: proxy?.noProxy ?? [] },
|
|
97
|
+
proxy === null ? "已关闭 HTTP 代理" : "已更新 HTTP 代理",
|
|
98
|
+
)
|
|
99
|
+
return { ok: true, state: stateOf(next) }
|
|
100
|
+
},
|
|
101
|
+
}
|
|
102
|
+
}
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* 插件依赖安装:插件自己的 package.json 用 packageManager 字段声明包管理器。
|
|
3
|
+
*
|
|
4
|
+
* 只认白名单命令(npm / pnpm / yarn / bun),字段不会变成任意命令执行;
|
|
5
|
+
* 版本号只作声明,实际使用 PATH 里的同名命令(不安装新版本,也不走 corepack)。
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { spawn } from "node:child_process"
|
|
9
|
+
import { readdir } from "node:fs/promises"
|
|
10
|
+
|
|
11
|
+
export type PluginPackageManagerName = "npm" | "pnpm" | "yarn" | "bun"
|
|
12
|
+
|
|
13
|
+
export interface PluginPackageManager {
|
|
14
|
+
readonly name: PluginPackageManagerName
|
|
15
|
+
readonly version: string | null
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface PluginPackageJsonLike {
|
|
19
|
+
readonly packageManager?: unknown
|
|
20
|
+
readonly dependencies?: Record<string, unknown> | null
|
|
21
|
+
readonly optionalDependencies?: Record<string, unknown> | null
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface PluginDependencyInstallResult {
|
|
25
|
+
readonly manager: PluginPackageManager | null
|
|
26
|
+
readonly installed: boolean
|
|
27
|
+
readonly reason: string | null
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
interface ManagerCommand {
|
|
31
|
+
readonly binary: string
|
|
32
|
+
readonly args: readonly string[]
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const MANAGERS: ReadonlyMap<PluginPackageManagerName, ManagerCommand> = new Map([
|
|
36
|
+
["npm", { binary: "npm", args: ["install", "--no-audit", "--no-fund", "--omit=dev"] }],
|
|
37
|
+
["pnpm", { binary: "pnpm", args: ["install", "--prod"] }],
|
|
38
|
+
["yarn", { binary: "yarn", args: ["install", "--production"] }],
|
|
39
|
+
["bun", { binary: "bun", args: ["install", "--production"] }],
|
|
40
|
+
])
|
|
41
|
+
|
|
42
|
+
const INSTALL_TIMEOUT_MS = 5 * 60 * 1000
|
|
43
|
+
const MAX_OUTPUT_CHARS = 4000
|
|
44
|
+
|
|
45
|
+
function isManagerName(value: string): value is PluginPackageManagerName {
|
|
46
|
+
return MANAGERS.has(value as PluginPackageManagerName)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** 解析 "npm@11.20.0" / "pnpm" 这类声明;不合法返回 null */
|
|
50
|
+
export function parsePackageManager(value: unknown): PluginPackageManager | null {
|
|
51
|
+
if (typeof value !== "string") {
|
|
52
|
+
return null
|
|
53
|
+
}
|
|
54
|
+
const trimmed = value.trim()
|
|
55
|
+
const match = /^([A-Za-z][A-Za-z0-9-]*)(?:@(.+))?$/.exec(trimmed)
|
|
56
|
+
if (match === null) {
|
|
57
|
+
return null
|
|
58
|
+
}
|
|
59
|
+
const name = (match[1] ?? "").toLowerCase()
|
|
60
|
+
if (!isManagerName(name)) {
|
|
61
|
+
return null
|
|
62
|
+
}
|
|
63
|
+
const version = match[2] ?? null
|
|
64
|
+
// 允许 corepack 常见的 "pnpm@10.0.0+sha512..." 形式
|
|
65
|
+
if (version !== null && !/^[0-9A-Za-z][0-9A-Za-z.+-]*$/.test(version)) {
|
|
66
|
+
return null
|
|
67
|
+
}
|
|
68
|
+
return { name, version }
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** 没声明 packageManager 时按 lockfile 推断,兼容旧插件 */
|
|
72
|
+
export function lockfilePackageManager(files: readonly string[]): PluginPackageManager | null {
|
|
73
|
+
if (files.includes("pnpm-lock.yaml")) {
|
|
74
|
+
return { name: "pnpm", version: null }
|
|
75
|
+
}
|
|
76
|
+
if (files.includes("yarn.lock")) {
|
|
77
|
+
return { name: "yarn", version: null }
|
|
78
|
+
}
|
|
79
|
+
if (files.includes("bun.lockb") || files.includes("bun.lock")) {
|
|
80
|
+
return { name: "bun", version: null }
|
|
81
|
+
}
|
|
82
|
+
if (files.includes("package-lock.json")) {
|
|
83
|
+
return { name: "npm", version: null }
|
|
84
|
+
}
|
|
85
|
+
return null
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export function hasPluginDependencies(pkg: PluginPackageJsonLike): boolean {
|
|
89
|
+
return (
|
|
90
|
+
Object.keys(pkg.dependencies ?? {}).length > 0 ||
|
|
91
|
+
Object.keys(pkg.optionalDependencies ?? {}).length > 0
|
|
92
|
+
)
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** 实际执行的命令;Windows 下 .cmd 需要 shell */
|
|
96
|
+
export function pluginInstallCommand(manager: PluginPackageManager): {
|
|
97
|
+
readonly command: string
|
|
98
|
+
readonly args: readonly string[]
|
|
99
|
+
} {
|
|
100
|
+
const spec = MANAGERS.get(manager.name)
|
|
101
|
+
if (spec === undefined) {
|
|
102
|
+
throw new Error(`不支持的包管理器:${manager.name}`)
|
|
103
|
+
}
|
|
104
|
+
const command = process.platform === "win32" ? `${spec.binary}.cmd` : spec.binary
|
|
105
|
+
return { command, args: spec.args }
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export interface RunPackageInstallOptions {
|
|
109
|
+
readonly timeoutMs?: number
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/** 在插件目录里执行包管理器安装;失败时抛出带输出尾部的错误 */
|
|
113
|
+
export async function runPackageInstall(
|
|
114
|
+
directory: string,
|
|
115
|
+
manager: PluginPackageManager,
|
|
116
|
+
options: RunPackageInstallOptions = {},
|
|
117
|
+
): Promise<void> {
|
|
118
|
+
const { command, args } = pluginInstallCommand(manager)
|
|
119
|
+
const timeoutMs = options.timeoutMs ?? INSTALL_TIMEOUT_MS
|
|
120
|
+
await new Promise<void>((resolvePromise, rejectPromise) => {
|
|
121
|
+
let output = ""
|
|
122
|
+
let settled = false
|
|
123
|
+
const append = (chunk: Buffer): void => {
|
|
124
|
+
if (output.length >= MAX_OUTPUT_CHARS) {
|
|
125
|
+
return
|
|
126
|
+
}
|
|
127
|
+
output += chunk.toString("utf8")
|
|
128
|
+
}
|
|
129
|
+
const child = spawn(command, [...args], {
|
|
130
|
+
cwd: directory,
|
|
131
|
+
env: process.env,
|
|
132
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
133
|
+
// Windows 的 npm.cmd / pnpm.cmd 必须经 shell 才能启动;args 都是固定白名单
|
|
134
|
+
shell: process.platform === "win32",
|
|
135
|
+
})
|
|
136
|
+
const timer = setTimeout(() => {
|
|
137
|
+
if (settled) return
|
|
138
|
+
settled = true
|
|
139
|
+
child.kill("SIGKILL")
|
|
140
|
+
rejectPromise(
|
|
141
|
+
new Error(`${manager.name} 安装依赖超时(${Math.round(timeoutMs / 1000)}s)`),
|
|
142
|
+
)
|
|
143
|
+
}, timeoutMs)
|
|
144
|
+
child.stdout?.on("data", append)
|
|
145
|
+
child.stderr?.on("data", append)
|
|
146
|
+
child.once("error", (error) => {
|
|
147
|
+
if (settled) return
|
|
148
|
+
settled = true
|
|
149
|
+
clearTimeout(timer)
|
|
150
|
+
rejectPromise(new Error(`无法运行 ${manager.name}:${error.message}`))
|
|
151
|
+
})
|
|
152
|
+
child.once("close", (code) => {
|
|
153
|
+
if (settled) return
|
|
154
|
+
settled = true
|
|
155
|
+
clearTimeout(timer)
|
|
156
|
+
if (code === 0) {
|
|
157
|
+
resolvePromise()
|
|
158
|
+
return
|
|
159
|
+
}
|
|
160
|
+
const tail = output.trim().slice(-MAX_OUTPUT_CHARS)
|
|
161
|
+
rejectPromise(
|
|
162
|
+
new Error(
|
|
163
|
+
`${manager.name} 安装依赖失败(退出码 ${String(code)})` +
|
|
164
|
+
(tail === "" ? "" : `:${tail}`),
|
|
165
|
+
),
|
|
166
|
+
)
|
|
167
|
+
})
|
|
168
|
+
})
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
export interface InstallPluginDependenciesOptions {
|
|
172
|
+
/** 插件目录里的文件名;默认读目录,用于 lockfile 推断 */
|
|
173
|
+
readonly files?: readonly string[]
|
|
174
|
+
/** 测试注入;默认 runPackageInstall */
|
|
175
|
+
readonly run?: typeof runPackageInstall
|
|
176
|
+
readonly timeoutMs?: number
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* 安装插件依赖。
|
|
181
|
+
*
|
|
182
|
+
* - 没有 dependencies / optionalDependencies:直接跳过
|
|
183
|
+
* - 有依赖但没有 packageManager:先用 lockfile 推断;仍没有则报错
|
|
184
|
+
* (不允许猜 npm,避免在错误的目录里跑错包管理器)
|
|
185
|
+
*/
|
|
186
|
+
export async function installPluginDependencies(
|
|
187
|
+
directory: string,
|
|
188
|
+
pkg: PluginPackageJsonLike,
|
|
189
|
+
options: InstallPluginDependenciesOptions = {},
|
|
190
|
+
): Promise<PluginDependencyInstallResult> {
|
|
191
|
+
if (!hasPluginDependencies(pkg)) {
|
|
192
|
+
return { manager: null, installed: false, reason: null }
|
|
193
|
+
}
|
|
194
|
+
const declared = parsePackageManager(pkg.packageManager)
|
|
195
|
+
if (declared === null && pkg.packageManager !== undefined) {
|
|
196
|
+
throw new Error(
|
|
197
|
+
'package.json 的 packageManager 不合法,支持 "npm@11"、"pnpm@10"、"yarn@1"、"bun@1"',
|
|
198
|
+
)
|
|
199
|
+
}
|
|
200
|
+
const files = options.files ?? (await readdir(directory).catch(() => []))
|
|
201
|
+
const manager = declared ?? lockfilePackageManager(files)
|
|
202
|
+
if (manager === null) {
|
|
203
|
+
throw new Error(
|
|
204
|
+
'插件声明了 dependencies,但 package.json 缺少 packageManager(例如 "npm@11.20.0")',
|
|
205
|
+
)
|
|
206
|
+
}
|
|
207
|
+
const run = options.run ?? runPackageInstall
|
|
208
|
+
await run(directory, manager, { timeoutMs: options.timeoutMs ?? INSTALL_TIMEOUT_MS })
|
|
209
|
+
return { manager, installed: true, reason: null }
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
export function packageManagerLabel(manager: PluginPackageManager): string {
|
|
213
|
+
return manager.version === null ? manager.name : `${manager.name}@${manager.version}`
|
|
214
|
+
}
|