@xyit/wellspring 0.0.0-stage → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +829 -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 +45 -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 +188 -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 +12 -0
- package/dist/plugin/install.js +114 -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 +265 -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/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 +126 -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/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 +55 -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 +218 -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 +149 -0
- package/src/plugin/loader.ts +952 -0
- package/src/plugin/manage.ts +329 -0
- package/src/plugin/models-service.ts +383 -0
- package/src/plugin/network-service.ts +102 -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 +152 -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,19 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* 插件目录与命名规则。
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { join } from "node:path"
|
|
6
|
+
|
|
7
|
+
import { getPaths, type ResolvedPaths } from "../platform/path.ts"
|
|
8
|
+
|
|
9
|
+
/** 插件目录名/包名:字母数字开头,允许 . _ -,最长 64 */
|
|
10
|
+
export const PLUGIN_NAME = /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/
|
|
11
|
+
|
|
12
|
+
/** 插件根目录:<dataDir>/plugins */
|
|
13
|
+
export function pluginsRoot(paths: ResolvedPaths = getPaths()): string {
|
|
14
|
+
return join(paths.dataDir, "plugins")
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function isValidPluginName(name: string): boolean {
|
|
18
|
+
return PLUGIN_NAME.test(name)
|
|
19
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* 插件命令注册表。
|
|
3
|
+
* 内置命令保留,插件命令挂在顶级;重名拒绝,别名同样参与冲突检查。
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { PluginCommand } from "../api/host/plugin.ts"
|
|
7
|
+
|
|
8
|
+
/** 内置命令名,插件不可占用 */
|
|
9
|
+
export const RESERVED_COMMANDS: ReadonlySet<string> = new Set([
|
|
10
|
+
"chat",
|
|
11
|
+
"run",
|
|
12
|
+
"session",
|
|
13
|
+
"config",
|
|
14
|
+
"plugin",
|
|
15
|
+
"doctor",
|
|
16
|
+
"version",
|
|
17
|
+
"help",
|
|
18
|
+
])
|
|
19
|
+
|
|
20
|
+
const COMMAND_NAME = /^[a-z][a-z0-9-]*$/
|
|
21
|
+
|
|
22
|
+
export interface RegisteredPluginCommand {
|
|
23
|
+
readonly pluginName: string
|
|
24
|
+
readonly command: PluginCommand
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export class PluginRegistry {
|
|
28
|
+
readonly #reserved: ReadonlySet<string>
|
|
29
|
+
readonly #commands = new Map<string, RegisteredPluginCommand>()
|
|
30
|
+
readonly #aliases = new Map<string, RegisteredPluginCommand>()
|
|
31
|
+
readonly #pluginNames = new Set<string>()
|
|
32
|
+
|
|
33
|
+
constructor(reserved: ReadonlySet<string> = RESERVED_COMMANDS) {
|
|
34
|
+
this.#reserved = reserved
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** 注册插件命令;非法、保留名或重名时抛错 */
|
|
38
|
+
register(pluginName: string, command: PluginCommand): void {
|
|
39
|
+
if (
|
|
40
|
+
!COMMAND_NAME.test(command.name) ||
|
|
41
|
+
command.name.length < 2 ||
|
|
42
|
+
command.name.length > 32
|
|
43
|
+
) {
|
|
44
|
+
throw new Error(`插件命令名不合法:${command.name}`)
|
|
45
|
+
}
|
|
46
|
+
if (this.#reserved.has(command.name)) {
|
|
47
|
+
throw new Error(`插件不能覆盖内置命令:${command.name}`)
|
|
48
|
+
}
|
|
49
|
+
if (this.#commands.has(command.name) || this.#aliases.has(command.name)) {
|
|
50
|
+
throw new Error(`插件命令重名:${command.name}`)
|
|
51
|
+
}
|
|
52
|
+
if (typeof command.summary !== "string" || command.summary.trim() === "") {
|
|
53
|
+
throw new Error(`插件命令缺少 summary:${command.name}`)
|
|
54
|
+
}
|
|
55
|
+
if (typeof command.run !== "function") {
|
|
56
|
+
throw new Error(`插件命令缺少 run:${command.name}`)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const record: RegisteredPluginCommand = { pluginName, command }
|
|
60
|
+
this.#commands.set(command.name, record)
|
|
61
|
+
this.#pluginNames.add(pluginName)
|
|
62
|
+
|
|
63
|
+
for (const alias of command.aliases ?? []) {
|
|
64
|
+
if (!COMMAND_NAME.test(alias) || alias.length < 2 || alias.length > 32) {
|
|
65
|
+
throw new Error(`插件命令别名不合法:${alias}`)
|
|
66
|
+
}
|
|
67
|
+
if (
|
|
68
|
+
this.#reserved.has(alias) ||
|
|
69
|
+
this.#commands.has(alias) ||
|
|
70
|
+
this.#aliases.has(alias)
|
|
71
|
+
) {
|
|
72
|
+
throw new Error(`插件命令别名冲突:${alias}`)
|
|
73
|
+
}
|
|
74
|
+
this.#aliases.set(alias, record)
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
resolve(name: string): RegisteredPluginCommand | undefined {
|
|
79
|
+
return this.#commands.get(name) ?? this.#aliases.get(name)
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
list(): readonly RegisteredPluginCommand[] {
|
|
83
|
+
return [...this.#commands.values()]
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
removePlugin(pluginName: string): void {
|
|
87
|
+
for (const [name, record] of this.#commands) {
|
|
88
|
+
if (record.pluginName === pluginName) {
|
|
89
|
+
this.#commands.delete(name)
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
for (const [alias, record] of this.#aliases) {
|
|
93
|
+
if (record.pluginName === pluginName) {
|
|
94
|
+
this.#aliases.delete(alias)
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
this.#pluginNames.delete(pluginName)
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
pluginNames(): readonly string[] {
|
|
101
|
+
return [...this.#pluginNames]
|
|
102
|
+
}
|
|
103
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* 插件运行时目录:
|
|
3
|
+
* - 数据:<dataDir>/plugin-data/<id>/
|
|
4
|
+
* - 配置:<configDir>/plugins/<id>.json(插件自己的 JSON 配置)
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { join } from "node:path"
|
|
8
|
+
|
|
9
|
+
import type { PluginConfig } from "../api/host/plugin.ts"
|
|
10
|
+
import { ensureDir, readJsonIfExists } from "../platform/file.ts"
|
|
11
|
+
import { getPaths, type ResolvedPaths } from "../platform/path.ts"
|
|
12
|
+
import { PLUGIN_NAME } from "./paths.ts"
|
|
13
|
+
|
|
14
|
+
// eslint-disable-next-line no-control-regex
|
|
15
|
+
const CONTROL_CHARS = /[\u0000-\u001f\u007f]/
|
|
16
|
+
|
|
17
|
+
function assertPluginId(id: string): void {
|
|
18
|
+
if (!PLUGIN_NAME.test(id) || CONTROL_CHARS.test(id)) {
|
|
19
|
+
throw new Error(`插件 id 不合法:${id}`)
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** 插件专属数据目录,平台会确保存在 */
|
|
24
|
+
export function pluginDataDir(id: string, paths: ResolvedPaths = getPaths()): string {
|
|
25
|
+
assertPluginId(id)
|
|
26
|
+
return join(paths.dataDir, "plugin-data", id)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** 插件专属配置文件 */
|
|
30
|
+
export function pluginConfigFile(id: string, paths: ResolvedPaths = getPaths()): string {
|
|
31
|
+
assertPluginId(id)
|
|
32
|
+
return join(paths.configDir, "plugins", `${id}.json`)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export async function ensurePluginDataDir(
|
|
36
|
+
id: string,
|
|
37
|
+
paths: ResolvedPaths = getPaths(),
|
|
38
|
+
): Promise<string> {
|
|
39
|
+
const dir = pluginDataDir(id, paths)
|
|
40
|
+
await ensureDir(dir)
|
|
41
|
+
return dir
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** 读取插件配置;文件不存在时返回空对象 */
|
|
45
|
+
export async function loadPluginConfig(
|
|
46
|
+
id: string,
|
|
47
|
+
paths: ResolvedPaths = getPaths(),
|
|
48
|
+
): Promise<PluginConfig> {
|
|
49
|
+
const value = await readJsonIfExists<unknown>(pluginConfigFile(id, paths))
|
|
50
|
+
if (value === null) {
|
|
51
|
+
return {}
|
|
52
|
+
}
|
|
53
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
54
|
+
throw new Error(`插件配置必须是 JSON 对象:${pluginConfigFile(id, paths)}`)
|
|
55
|
+
}
|
|
56
|
+
return value as Record<string, unknown>
|
|
57
|
+
}
|