dsh-agent-plugins-market 0.9.0 → 0.9.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +64 -154
- package/README.zh.md +74 -164
- package/assets/dsh-agent-plugins.png +0 -0
- package/client/client.js +5371 -1085
- package/client/client.js.map +1 -1
- package/lib/index.js +2 -257
- package/lib/{application → packages/market-bundle/src/application}/catalog.js +207 -41
- package/lib/{application → packages/market-bundle/src/application}/details.js +73 -29
- package/lib/packages/market-bundle/src/application/extension-inventory.js +408 -0
- package/lib/packages/market-bundle/src/application/extension-project.js +64 -0
- package/lib/packages/market-bundle/src/application/resource-inventory.js +96 -0
- package/lib/packages/market-bundle/src/application/server-config.js +9 -0
- package/lib/packages/market-bundle/src/application/suite-queries.js +86 -0
- package/lib/packages/market-bundle/src/index.js +372 -0
- package/lib/{runtime/host → packages/market-bundle/src/platform}/feedback-tool.js +2 -2
- package/lib/{runtime/host → packages/market-bundle/src/platform}/settings-namespace.js +55 -7
- package/lib/packages/market-bundle/src/platform/settings-schema.js +25 -0
- package/lib/packages/market-bundle/src/routes-extension-presets.js +161 -0
- package/lib/packages/market-bundle/src/routes-resources.js +127 -0
- package/lib/{routes.js → packages/market-bundle/src/routes.js} +225 -21
- package/lib/packages/market-bundle/src/runtime-adapters.js +57 -0
- package/lib/packages/market-bundle/src/session-extension.js +337 -0
- package/lib/{application → packages/market-catalog/src/application}/catalog-context.js +7 -4
- package/lib/{application → packages/market-catalog/src/application}/install-store.js +3 -3
- package/lib/{application → packages/market-catalog/src/application}/snapshot-cache.js +100 -11
- package/lib/{application → packages/market-catalog/src/application}/source-store.js +24 -11
- package/lib/{application → packages/market-catalog/src/application}/state/state-store.js +1 -1
- package/lib/{application → packages/market-catalog/src/application}/state/storage-migration.js +1 -1
- package/lib/packages/market-catalog/src/index.js +19 -0
- package/lib/{catalog → packages/market-catalog/src/scanning}/archive.js +1 -1
- package/lib/{catalog → packages/market-catalog/src/scanning}/codex-mcp.js +1 -1
- package/lib/{catalog → packages/market-catalog/src/scanning}/component-files.js +2 -2
- package/lib/{catalog → packages/market-catalog/src/scanning}/manifests.js +2 -2
- package/lib/{catalog → packages/market-catalog/src/scanning}/native-project.js +1 -1
- package/lib/{catalog → packages/market-catalog/src/scanning}/paths.js +1 -1
- package/lib/{catalog → packages/market-catalog/src/scanning}/plugin-variables.js +21 -1
- package/lib/{catalog → packages/market-catalog/src/scanning}/scan-resolvers.js +35 -2
- package/lib/{catalog → packages/market-catalog/src/scanning}/skills-parse.js +40 -10
- package/lib/{catalog → packages/market-catalog/src/scanning}/validate.js +15 -2
- package/lib/packages/market-contracts/src/contracts/extension-presets.js +125 -0
- package/lib/{contracts → packages/market-contracts/src/contracts}/market.js +33 -5
- package/lib/packages/market-contracts/src/contracts/mcp-overrides.js +2 -0
- package/lib/packages/market-contracts/src/contracts/resource-window.js +29 -0
- package/lib/packages/market-contracts/src/contracts/settings.js +130 -0
- package/lib/packages/market-contracts/src/contracts/surface-toggles.js +29 -0
- package/lib/packages/market-contracts/src/contracts/translation.js +28 -0
- package/lib/packages/market-contracts/src/index.js +4 -0
- package/lib/{model → packages/market-contracts/src/model}/layouts.js +11 -0
- package/lib/{application → packages/market-contracts/src/ports}/ports.js +3 -0
- package/lib/{application/mcp/mcp-redaction.js → packages/market-contracts/src/redaction.js} +17 -1
- package/lib/{application → packages/market-lsp/src/application}/lsp/lsp-direct-config.js +2 -2
- package/lib/{application → packages/market-lsp/src/application}/lsp/lsp-server-state.js +1 -1
- package/lib/{application → packages/market-lsp/src/application}/lsp/lsp-status.js +20 -3
- package/lib/{application → packages/market-lsp/src/application}/lsp/profile-seam.js +1 -1
- package/lib/packages/market-lsp/src/application/lsp/validation.js +46 -0
- package/lib/{application → packages/market-lsp/src/application}/lsp-service.js +61 -3
- package/lib/packages/market-lsp/src/index.js +8 -0
- package/lib/{runtime → packages/market-lsp/src/runtime}/lsp/lsp-mounts.js +79 -40
- package/lib/{application → packages/market-mcp/src/application}/mcp/mcp-backend.js +0 -16
- package/lib/{application → packages/market-mcp/src/application}/mcp/mcp-config.js +104 -21
- package/lib/{application → packages/market-mcp/src/application}/mcp/mcp-direct-config.js +12 -4
- package/lib/{application → packages/market-mcp/src/application}/mcp/mcp-overrides.js +4 -4
- package/lib/{application → packages/market-mcp/src/application}/mcp/mcp-status.js +49 -10
- package/lib/packages/market-mcp/src/application/mcp/validation.js +63 -0
- package/lib/{application → packages/market-mcp/src/application}/mcp-service.js +111 -91
- package/lib/packages/market-mcp/src/index.js +10 -0
- package/lib/{runtime → packages/market-mcp/src/runtime}/mcp/bridge/bridge.js +12 -9
- package/lib/{runtime → packages/market-mcp/src/runtime}/mcp/bridge/connection.js +1 -1
- package/lib/{runtime → packages/market-mcp/src/runtime}/mcp/bridge/oauth.js +1 -1
- package/lib/packages/market-mcp/src/runtime/mcp/bridge/tool-ownership.js +26 -0
- package/lib/{runtime → packages/market-mcp/src/runtime}/mcp/bridge/tools.js +1 -1
- package/lib/{runtime → packages/market-mcp/src/runtime}/mcp/mcp-auth-record.js +35 -4
- package/lib/{runtime → packages/market-mcp/src/runtime}/mcp/mcp-mounts.js +35 -4
- package/lib/packages/market-runtime/src/adapter-contracts.js +1 -0
- package/lib/packages/market-runtime/src/application/command-resources.js +74 -0
- package/lib/packages/market-runtime/src/application/extension-authorization.js +27 -0
- package/lib/packages/market-runtime/src/application/extension-hook-selection.js +56 -0
- package/lib/packages/market-runtime/src/application/extension-suite-declarations.js +57 -0
- package/lib/packages/market-runtime/src/application/extension-suite-selection.js +84 -0
- package/lib/packages/market-runtime/src/application/fs-probe.js +43 -0
- package/lib/packages/market-runtime/src/application/panel-resources.js +370 -0
- package/lib/{application → packages/market-runtime/src/application}/panels/user-hooks.js +2 -2
- package/lib/{application → packages/market-runtime/src/application}/panels/user-store.js +53 -13
- package/lib/{application → packages/market-runtime/src/application}/project-agent-roles.js +20 -11
- package/lib/packages/market-runtime/src/application/state/extension-presets.js +144 -0
- package/lib/packages/market-runtime/src/application/state/resource-favorites.js +90 -0
- package/lib/packages/market-runtime/src/application/state/resource-filters.js +2 -0
- package/lib/packages/market-runtime/src/application/state/surface-toggles.js +10 -0
- package/lib/packages/market-runtime/src/application/state/workspace-policy.js +56 -0
- package/lib/packages/market-runtime/src/catalog-port.js +1 -0
- package/lib/packages/market-runtime/src/extension-service.js +1 -0
- package/lib/packages/market-runtime/src/index.js +31 -0
- package/lib/{runtime → packages/market-runtime/src/runtime}/agents/agent-role-router.js +74 -79
- package/lib/packages/market-runtime/src/runtime/agents/agent-teams-seat.js +58 -0
- package/lib/{runtime → packages/market-runtime/src/runtime}/agents/subagent-catalog.js +89 -21
- package/lib/packages/market-runtime/src/runtime/agents/team-coordination.js +51 -0
- package/lib/packages/market-runtime/src/runtime/agents/teammate-role-runtime.js +241 -0
- package/lib/packages/market-runtime/src/runtime/agents/teammate-role-tool.js +130 -0
- package/lib/{runtime → packages/market-runtime/src/runtime}/core/reconcile-scheduler.js +2 -2
- package/lib/packages/market-runtime/src/runtime/core/reconciler.js +457 -0
- package/lib/packages/market-runtime/src/runtime/host/command-name-allocator.js +168 -0
- package/lib/packages/market-runtime/src/runtime/host/extension-progress.js +120 -0
- package/lib/packages/market-runtime/src/runtime/host/extension-runtime.js +405 -0
- package/lib/packages/market-runtime/src/runtime/host/extension-session-state.js +839 -0
- package/lib/packages/market-runtime/src/runtime/host/extension-tool-gates.js +96 -0
- package/lib/{runtime → packages/market-runtime/src/runtime}/host/failure-detail.js +1 -1
- package/lib/{runtime → packages/market-runtime/src/runtime}/host/host-locale.js +2 -0
- package/lib/packages/market-runtime/src/runtime/host/menu-row-identities.js +38 -0
- package/lib/packages/market-runtime/src/runtime/host/resource-filter-service.js +81 -0
- package/lib/packages/market-runtime/src/runtime/host/scoped-contributors.js +304 -0
- package/lib/packages/market-runtime/src/runtime/host/surface-toggle-service.js +15 -0
- package/lib/packages/market-runtime/src/runtime/panels/user-commands.js +211 -0
- package/lib/{runtime → packages/market-runtime/src/runtime}/panels/user-panels.js +34 -14
- package/lib/packages/market-runtime/src/runtime/surfaces/commands-mounts.js +256 -0
- package/lib/packages/market-runtime/src/runtime/surfaces/extension-hooks.js +294 -0
- package/lib/packages/market-runtime/src/runtime/surfaces/hook-dry-run.js +122 -0
- package/lib/{runtime → packages/market-runtime/src/runtime}/surfaces/hooks-mounts.js +1 -1
- package/lib/{runtime → packages/market-runtime/src/runtime}/surfaces/project-runtime.js +23 -13
- package/lib/packages/market-runtime/src/runtime/surfaces/skills-provider.js +241 -0
- package/lib/packages/market-translation/src/application/state/translation-cache.js +122 -0
- package/lib/packages/market-translation/src/application/translation/chain.js +99 -0
- package/lib/packages/market-translation/src/application/translation/document.js +384 -0
- package/lib/packages/market-translation/src/application/translation/localizer.js +642 -0
- package/lib/packages/market-translation/src/application/translation/service.js +113 -0
- package/lib/packages/market-translation/src/application/translation/unit.js +19 -0
- package/lib/packages/market-translation/src/index.js +4 -0
- package/lib/packages/market-translation/src/runtime/host/llm-translator.js +278 -0
- package/lib/packages/market-translation/src/runtime/host/machine-translator.js +152 -0
- package/lib/packages/market-translation/src/runtime/host/text-masking.js +96 -0
- package/lib/packages/market-translation/src/runtime/host/translation-providers.js +116 -0
- package/lib/types/index.d.ts +2 -130
- package/lib/types/{application → packages/market-bundle/src/application}/catalog.d.ts +117 -22
- package/lib/types/packages/market-bundle/src/application/details.d.ts +24 -0
- package/lib/types/packages/market-bundle/src/application/extension-inventory.d.ts +45 -0
- package/lib/types/packages/market-bundle/src/application/extension-project.d.ts +26 -0
- package/lib/types/packages/market-bundle/src/application/resource-inventory.d.ts +42 -0
- package/lib/types/packages/market-bundle/src/application/server-config.d.ts +9 -0
- package/lib/types/packages/market-bundle/src/application/suite-queries.d.ts +84 -0
- package/lib/types/packages/market-bundle/src/index.d.ts +125 -0
- package/lib/types/{runtime/host → packages/market-bundle/src/platform}/settings-namespace.d.ts +44 -9
- package/lib/types/{application/mcp/mcp-backend.d.ts → packages/market-bundle/src/platform/settings-schema.d.ts} +13 -28
- package/lib/types/packages/market-bundle/src/routes-extension-presets.d.ts +15 -0
- package/lib/types/packages/market-bundle/src/routes-resources.d.ts +17 -0
- package/lib/types/packages/market-bundle/src/routes.d.ts +32 -0
- package/lib/types/packages/market-bundle/src/runtime-adapters.d.ts +42 -0
- package/lib/types/packages/market-bundle/src/session-extension.d.ts +62 -0
- package/lib/types/{application → packages/market-catalog/src/application}/catalog-context.d.ts +11 -3
- package/lib/types/{application → packages/market-catalog/src/application}/install-store.d.ts +1 -1
- package/lib/types/{application → packages/market-catalog/src/application}/regions.d.ts +1 -1
- package/lib/types/{application → packages/market-catalog/src/application}/snapshot-cache.d.ts +44 -2
- package/lib/types/{application → packages/market-catalog/src/application}/source-store.d.ts +12 -3
- package/lib/types/{application → packages/market-catalog/src/application}/state/state-store.d.ts +1 -1
- package/lib/types/packages/market-catalog/src/index.d.ts +22 -0
- package/lib/types/{catalog → packages/market-catalog/src/scanning}/archive.d.ts +1 -1
- package/lib/types/{catalog → packages/market-catalog/src/scanning}/codex-mcp.d.ts +1 -1
- package/lib/types/{catalog → packages/market-catalog/src/scanning}/component-files.d.ts +1 -1
- package/lib/types/{catalog → packages/market-catalog/src/scanning}/lsp-spec.d.ts +1 -1
- package/lib/types/{catalog → packages/market-catalog/src/scanning}/manifests.d.ts +3 -3
- package/lib/types/{catalog → packages/market-catalog/src/scanning}/native-project.d.ts +2 -2
- package/lib/types/{catalog → packages/market-catalog/src/scanning}/plugin-variables.d.ts +12 -1
- package/lib/types/{catalog → packages/market-catalog/src/scanning}/project-config.d.ts +2 -2
- package/lib/types/{catalog → packages/market-catalog/src/scanning}/project-hooks.d.ts +2 -2
- package/lib/types/{catalog → packages/market-catalog/src/scanning}/scan-pipeline.d.ts +1 -1
- package/lib/types/{catalog → packages/market-catalog/src/scanning}/scan-resolvers.d.ts +3 -3
- package/lib/types/{catalog → packages/market-catalog/src/scanning}/source-catalog.d.ts +1 -1
- package/lib/types/{catalog → packages/market-catalog/src/scanning}/suite-components.d.ts +1 -1
- package/lib/types/{catalog → packages/market-catalog/src/scanning}/suite-scanner.d.ts +1 -1
- package/lib/types/{catalog → packages/market-catalog/src/scanning}/surfaces.d.ts +1 -1
- package/lib/types/{catalog → packages/market-catalog/src/scanning}/validate.d.ts +1 -1
- package/lib/types/packages/market-contracts/src/contracts/extension-presets.d.ts +237 -0
- package/lib/types/{contracts → packages/market-contracts/src/contracts}/market.d.ts +87 -16
- package/lib/types/packages/market-contracts/src/contracts/mcp-overrides.d.ts +39 -0
- package/lib/types/{contracts → packages/market-contracts/src/contracts}/mcp-status.d.ts +9 -0
- package/lib/types/packages/market-contracts/src/contracts/resource-window.d.ts +70 -0
- package/lib/types/packages/market-contracts/src/contracts/settings.d.ts +121 -0
- package/lib/types/packages/market-contracts/src/contracts/surface-toggles.d.ts +28 -0
- package/lib/types/packages/market-contracts/src/contracts/translation.d.ts +81 -0
- package/lib/types/{runtime → packages/market-contracts/src}/host/plugin-message-source.d.ts +1 -1
- package/lib/types/packages/market-contracts/src/index.d.ts +5 -0
- package/lib/types/{model → packages/market-contracts/src/model}/layouts.d.ts +7 -0
- package/lib/types/{model → packages/market-contracts/src/model}/types.d.ts +2 -2
- package/lib/types/packages/market-contracts/src/ports/ports.d.ts +183 -0
- package/lib/types/{application → packages/market-contracts/src/ports}/queries.d.ts +27 -6
- package/lib/types/{application/mcp/mcp-redaction.d.ts → packages/market-contracts/src/redaction.d.ts} +2 -0
- package/lib/types/{application → packages/market-lsp/src/application}/lsp/lsp-direct-config.d.ts +1 -1
- package/lib/types/{application → packages/market-lsp/src/application}/lsp/lsp-status.d.ts +21 -5
- package/lib/types/packages/market-lsp/src/application/lsp/validation.d.ts +8 -0
- package/lib/types/{application → packages/market-lsp/src/application}/lsp-service.d.ts +19 -6
- package/lib/types/packages/market-lsp/src/index.d.ts +8 -0
- package/lib/types/{runtime → packages/market-lsp/src/runtime}/lsp/lsp-mounts.d.ts +26 -6
- package/lib/types/packages/market-mcp/src/application/mcp/mcp-backend.d.ts +28 -0
- package/lib/types/{application → packages/market-mcp/src/application}/mcp/mcp-bridge-config.d.ts +1 -1
- package/lib/types/{application → packages/market-mcp/src/application}/mcp/mcp-config.d.ts +2 -2
- package/lib/types/{application → packages/market-mcp/src/application}/mcp/mcp-direct-config.d.ts +6 -1
- package/lib/types/{application → packages/market-mcp/src/application}/mcp/mcp-overrides.d.ts +4 -38
- package/lib/types/packages/market-mcp/src/application/mcp/mcp-status.d.ts +40 -0
- package/lib/types/{application/server-config.d.ts → packages/market-mcp/src/application/mcp/validation.d.ts} +2 -10
- package/lib/types/{application → packages/market-mcp/src/application}/mcp-service.d.ts +22 -16
- package/lib/types/packages/market-mcp/src/index.d.ts +12 -0
- package/lib/types/packages/market-mcp/src/runtime/mcp/bridge/tool-ownership.d.ts +14 -0
- package/lib/types/{runtime → packages/market-mcp/src/runtime}/mcp/mcp-auth-record.d.ts +4 -0
- package/lib/types/{runtime → packages/market-mcp/src/runtime}/mcp/mcp-mounts.d.ts +19 -3
- package/lib/types/packages/market-runtime/src/adapter-contracts.d.ts +89 -0
- package/lib/types/{application → packages/market-runtime/src/application}/agent-roles.d.ts +2 -0
- package/lib/types/packages/market-runtime/src/application/command-resources.d.ts +26 -0
- package/lib/types/packages/market-runtime/src/application/extension-authorization.d.ts +12 -0
- package/lib/types/packages/market-runtime/src/application/extension-hook-selection.d.ts +23 -0
- package/lib/types/packages/market-runtime/src/application/extension-suite-declarations.d.ts +10 -0
- package/lib/types/packages/market-runtime/src/application/extension-suite-selection.d.ts +30 -0
- package/lib/types/packages/market-runtime/src/application/fs-probe.d.ts +12 -0
- package/lib/types/packages/market-runtime/src/application/panel-resources.d.ts +100 -0
- package/lib/types/{application → packages/market-runtime/src/application}/panels/user-hooks.d.ts +1 -1
- package/lib/types/packages/market-runtime/src/application/project-agent-roles.d.ts +10 -0
- package/lib/types/packages/market-runtime/src/application/state/extension-presets.d.ts +24 -0
- package/lib/types/packages/market-runtime/src/application/state/resource-favorites.d.ts +9 -0
- package/lib/types/packages/market-runtime/src/application/state/resource-filters.d.ts +2 -0
- package/lib/types/packages/market-runtime/src/application/state/surface-toggles.d.ts +6 -0
- package/lib/types/packages/market-runtime/src/application/state/workspace-policy.d.ts +19 -0
- package/lib/types/packages/market-runtime/src/catalog-port.d.ts +30 -0
- package/lib/types/packages/market-runtime/src/extension-service.d.ts +14 -0
- package/lib/types/packages/market-runtime/src/index.d.ts +41 -0
- package/lib/types/{runtime → packages/market-runtime/src/runtime}/agents/agent-role-router.d.ts +66 -31
- package/lib/types/packages/market-runtime/src/runtime/agents/agent-teams-seat.d.ts +41 -0
- package/lib/types/{runtime → packages/market-runtime/src/runtime}/agents/subagent-catalog.d.ts +34 -2
- package/lib/types/packages/market-runtime/src/runtime/agents/team-coordination.d.ts +8 -0
- package/lib/types/packages/market-runtime/src/runtime/agents/teammate-role-runtime.d.ts +74 -0
- package/lib/types/packages/market-runtime/src/runtime/agents/teammate-role-tool.d.ts +6 -0
- package/lib/types/{runtime → packages/market-runtime/src/runtime}/core/reconcile-scheduler.d.ts +5 -2
- package/lib/types/packages/market-runtime/src/runtime/core/reconciler.d.ts +141 -0
- package/lib/types/packages/market-runtime/src/runtime/host/command-name-allocator.d.ts +132 -0
- package/lib/types/packages/market-runtime/src/runtime/host/extension-progress.d.ts +91 -0
- package/lib/types/packages/market-runtime/src/runtime/host/extension-runtime.d.ts +77 -0
- package/lib/types/packages/market-runtime/src/runtime/host/extension-session-state.d.ts +242 -0
- package/lib/types/packages/market-runtime/src/runtime/host/extension-tool-gates.d.ts +38 -0
- package/lib/types/{runtime → packages/market-runtime/src/runtime}/host/host-locale.d.ts +1 -1
- package/lib/types/packages/market-runtime/src/runtime/host/menu-row-identities.d.ts +50 -0
- package/lib/types/{runtime → packages/market-runtime/src/runtime}/host/model-catalog.d.ts +1 -1
- package/lib/types/packages/market-runtime/src/runtime/host/resource-filter-service.d.ts +34 -0
- package/lib/types/packages/market-runtime/src/runtime/host/scoped-contributors.d.ts +58 -0
- package/lib/types/packages/market-runtime/src/runtime/host/surface-toggle-service.d.ts +10 -0
- package/lib/types/packages/market-runtime/src/runtime/panels/user-commands.d.ts +74 -0
- package/lib/types/{runtime → packages/market-runtime/src/runtime}/panels/user-panels.d.ts +7 -1
- package/lib/types/packages/market-runtime/src/runtime/surfaces/commands-mounts.d.ts +81 -0
- package/lib/types/packages/market-runtime/src/runtime/surfaces/extension-hooks.d.ts +59 -0
- package/lib/types/packages/market-runtime/src/runtime/surfaces/hook-dry-run.d.ts +91 -0
- package/lib/types/{runtime → packages/market-runtime/src/runtime}/surfaces/hooks-mounts.d.ts +1 -1
- package/lib/types/{runtime → packages/market-runtime/src/runtime}/surfaces/project-runtime.d.ts +8 -5
- package/lib/types/packages/market-runtime/src/runtime/surfaces/skills-provider.d.ts +71 -0
- package/lib/types/packages/market-translation/src/application/state/translation-cache.d.ts +56 -0
- package/lib/types/packages/market-translation/src/application/translation/chain.d.ts +47 -0
- package/lib/types/packages/market-translation/src/application/translation/document.d.ts +54 -0
- package/lib/types/packages/market-translation/src/application/translation/localizer.d.ts +325 -0
- package/lib/types/packages/market-translation/src/application/translation/service.d.ts +89 -0
- package/lib/types/packages/market-translation/src/application/translation/unit.d.ts +35 -0
- package/lib/types/packages/market-translation/src/index.d.ts +4 -0
- package/lib/types/packages/market-translation/src/runtime/host/llm-translator.d.ts +30 -0
- package/lib/types/packages/market-translation/src/runtime/host/machine-translator.d.ts +52 -0
- package/lib/types/packages/market-translation/src/runtime/host/text-masking.d.ts +42 -0
- package/lib/types/packages/market-translation/src/runtime/host/translation-providers.d.ts +42 -0
- package/locale/en.json +6 -0
- package/locale/zh.json +6 -0
- package/package.json +47 -10
- package/schemas/README.md +4 -4
- package/schemas/agent-plugins/spec.md +2 -2
- package/schemas/com.deepseek.harness/spec.md +1 -1
- package/schemas/skill-collection/spec.md +10 -2
- package/lib/application/panel-resources.js +0 -161
- package/lib/application/server-config.js +0 -109
- package/lib/contracts/settings.js +0 -63
- package/lib/runtime/core/reconciler.js +0 -100
- package/lib/runtime/panels/user-commands.js +0 -105
- package/lib/runtime/surfaces/commands-mounts.js +0 -186
- package/lib/runtime/surfaces/skills-provider.js +0 -160
- package/lib/types/application/details.d.ts +0 -7
- package/lib/types/application/mcp/mcp-status.d.ts +0 -24
- package/lib/types/application/panel-resources.d.ts +0 -28
- package/lib/types/application/ports.d.ts +0 -86
- package/lib/types/application/project-agent-roles.d.ts +0 -3
- package/lib/types/contracts/settings.d.ts +0 -59
- package/lib/types/routes.d.ts +0 -16
- package/lib/types/runtime/core/reconciler.d.ts +0 -58
- package/lib/types/runtime/panels/user-commands.d.ts +0 -27
- package/lib/types/runtime/surfaces/commands-mounts.d.ts +0 -48
- package/lib/types/runtime/surfaces/skills-provider.d.ts +0 -23
- /package/lib/{application → packages/market-catalog/src/application}/deadline.js +0 -0
- /package/lib/{application → packages/market-catalog/src/application}/json-file.js +0 -0
- /package/lib/{application → packages/market-catalog/src/application}/regions.js +0 -0
- /package/lib/{catalog → packages/market-catalog/src/scanning}/fs-probes.js +0 -0
- /package/lib/{catalog → packages/market-catalog/src/scanning}/git.js +0 -0
- /package/lib/{catalog → packages/market-catalog/src/scanning}/lsp-spec.js +0 -0
- /package/lib/{catalog → packages/market-catalog/src/scanning}/project-config.js +0 -0
- /package/lib/{catalog → packages/market-catalog/src/scanning}/project-hooks.js +0 -0
- /package/lib/{catalog → packages/market-catalog/src/scanning}/scan-pipeline.js +0 -0
- /package/lib/{catalog → packages/market-catalog/src/scanning}/source-catalog.js +0 -0
- /package/lib/{catalog → packages/market-catalog/src/scanning}/suite-components.js +0 -0
- /package/lib/{catalog → packages/market-catalog/src/scanning}/suite-scanner.js +0 -0
- /package/lib/{catalog → packages/market-catalog/src/scanning}/surfaces.js +0 -0
- /package/lib/{contracts → packages/market-contracts/src/contracts}/lsp-status.js +0 -0
- /package/lib/{contracts → packages/market-contracts/src/contracts}/lsp.js +0 -0
- /package/lib/{contracts → packages/market-contracts/src/contracts}/mcp-status.js +0 -0
- /package/lib/{contracts → packages/market-contracts/src/contracts}/mcp.js +0 -0
- /package/lib/{runtime → packages/market-contracts/src}/host/plugin-message-source.js +0 -0
- /package/lib/{model → packages/market-contracts/src/model}/command-names.js +0 -0
- /package/lib/{model → packages/market-contracts/src/model}/preset-source.js +0 -0
- /package/lib/{model → packages/market-contracts/src/model}/types.js +0 -0
- /package/lib/{application → packages/market-contracts/src/ports}/queries.js +0 -0
- /package/lib/{application → packages/market-mcp/src/application}/mcp/mcp-bridge-config.js +0 -0
- /package/lib/{runtime → packages/market-mcp/src/runtime}/mcp/bridge/host-contract.js +0 -0
- /package/lib/{runtime → packages/market-mcp/src/runtime}/mcp/bridge/host-seams.js +0 -0
- /package/lib/{runtime → packages/market-mcp/src/runtime}/mcp/bridge/plugin-identity.js +0 -0
- /package/lib/{runtime → packages/market-mcp/src/runtime}/mcp/bridge/projection.js +0 -0
- /package/lib/{runtime → packages/market-mcp/src/runtime}/mcp/bridge/transport.js +0 -0
- /package/lib/{runtime → packages/market-mcp/src/runtime}/mcp/mcp-credentials.js +0 -0
- /package/lib/{application → packages/market-runtime/src/application}/agent-roles.js +0 -0
- /package/lib/{runtime → packages/market-runtime/src/runtime}/agents/agent-role-names.js +0 -0
- /package/lib/{runtime → packages/market-runtime/src/runtime}/core/context.js +0 -0
- /package/lib/{runtime → packages/market-runtime/src/runtime}/core/mount-lifecycle.js +0 -0
- /package/lib/{runtime → packages/market-runtime/src/runtime}/core/source-auto-update.js +0 -0
- /package/lib/{runtime → packages/market-runtime/src/runtime}/core/timer-seat.js +0 -0
- /package/lib/{runtime → packages/market-runtime/src/runtime}/host/model-catalog.js +0 -0
- /package/lib/{runtime → packages/market-runtime/src/runtime}/host/shell-path.js +0 -0
- /package/lib/{runtime → packages/market-runtime/src/runtime}/host/tool-registry-observer.js +0 -0
- /package/lib/{runtime → packages/market-runtime/src/runtime}/surfaces/dynamic-context.js +0 -0
- /package/lib/types/{runtime/host → packages/market-bundle/src/platform}/feedback-tool.d.ts +0 -0
- /package/lib/types/{application → packages/market-catalog/src/application}/deadline.d.ts +0 -0
- /package/lib/types/{application → packages/market-catalog/src/application}/json-file.d.ts +0 -0
- /package/lib/types/{application → packages/market-catalog/src/application}/state/storage-migration.d.ts +0 -0
- /package/lib/types/{catalog → packages/market-catalog/src/scanning}/fs-probes.d.ts +0 -0
- /package/lib/types/{catalog → packages/market-catalog/src/scanning}/git.d.ts +0 -0
- /package/lib/types/{catalog → packages/market-catalog/src/scanning}/paths.d.ts +0 -0
- /package/lib/types/{catalog → packages/market-catalog/src/scanning}/skills-parse.d.ts +0 -0
- /package/lib/types/{contracts → packages/market-contracts/src/contracts}/lsp-status.d.ts +0 -0
- /package/lib/types/{contracts → packages/market-contracts/src/contracts}/lsp.d.ts +0 -0
- /package/lib/types/{contracts → packages/market-contracts/src/contracts}/mcp.d.ts +0 -0
- /package/lib/types/{model → packages/market-contracts/src/model}/command-names.d.ts +0 -0
- /package/lib/types/{model → packages/market-contracts/src/model}/preset-source.d.ts +0 -0
- /package/lib/types/{application → packages/market-lsp/src/application}/lsp/lsp-server-state.d.ts +0 -0
- /package/lib/types/{application → packages/market-lsp/src/application}/lsp/profile-seam.d.ts +0 -0
- /package/lib/types/{runtime → packages/market-mcp/src/runtime}/mcp/bridge/bridge.d.ts +0 -0
- /package/lib/types/{runtime → packages/market-mcp/src/runtime}/mcp/bridge/connection.d.ts +0 -0
- /package/lib/types/{runtime → packages/market-mcp/src/runtime}/mcp/bridge/host-contract.d.ts +0 -0
- /package/lib/types/{runtime → packages/market-mcp/src/runtime}/mcp/bridge/host-seams.d.ts +0 -0
- /package/lib/types/{runtime → packages/market-mcp/src/runtime}/mcp/bridge/oauth.d.ts +0 -0
- /package/lib/types/{runtime → packages/market-mcp/src/runtime}/mcp/bridge/plugin-identity.d.ts +0 -0
- /package/lib/types/{runtime → packages/market-mcp/src/runtime}/mcp/bridge/projection.d.ts +0 -0
- /package/lib/types/{runtime → packages/market-mcp/src/runtime}/mcp/bridge/tools.d.ts +0 -0
- /package/lib/types/{runtime → packages/market-mcp/src/runtime}/mcp/bridge/transport.d.ts +0 -0
- /package/lib/types/{runtime → packages/market-mcp/src/runtime}/mcp/mcp-credentials.d.ts +0 -0
- /package/lib/types/{application → packages/market-runtime/src/application}/panels/user-store.d.ts +0 -0
- /package/lib/types/{runtime → packages/market-runtime/src/runtime}/agents/agent-role-names.d.ts +0 -0
- /package/lib/types/{runtime → packages/market-runtime/src/runtime}/core/context.d.ts +0 -0
- /package/lib/types/{runtime → packages/market-runtime/src/runtime}/core/mount-lifecycle.d.ts +0 -0
- /package/lib/types/{runtime → packages/market-runtime/src/runtime}/core/source-auto-update.d.ts +0 -0
- /package/lib/types/{runtime → packages/market-runtime/src/runtime}/core/timer-seat.d.ts +0 -0
- /package/lib/types/{runtime → packages/market-runtime/src/runtime}/host/failure-detail.d.ts +0 -0
- /package/lib/types/{runtime → packages/market-runtime/src/runtime}/host/shell-path.d.ts +0 -0
- /package/lib/types/{runtime → packages/market-runtime/src/runtime}/host/tool-registry-observer.d.ts +0 -0
- /package/lib/types/{runtime → packages/market-runtime/src/runtime}/surfaces/dynamic-context.d.ts +0 -0
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { chunkDocument, translateMarkdownDocument } from './document.js';
|
|
2
|
+
import { TranslationLocalizer } from './localizer.js';
|
|
3
|
+
import { collectUnits } from './unit.js';
|
|
4
|
+
export class TranslationService {
|
|
5
|
+
localizer;
|
|
6
|
+
constructor(options) {
|
|
7
|
+
this.localizer = new TranslationLocalizer(options);
|
|
8
|
+
}
|
|
9
|
+
/** Read the persisted cache once, before the first {@link translateFields} read. */
|
|
10
|
+
load() {
|
|
11
|
+
return this.localizer.load();
|
|
12
|
+
}
|
|
13
|
+
/** Cancel timers and drop queued work; in-flight calls finish and are persisted. */
|
|
14
|
+
dispose() {
|
|
15
|
+
this.localizer.dispose();
|
|
16
|
+
}
|
|
17
|
+
/** Drop every cached translation, in memory and on disk. */
|
|
18
|
+
clear() {
|
|
19
|
+
return this.localizer.clear();
|
|
20
|
+
}
|
|
21
|
+
/** Apply a live display-preference change: off cancels unfinished work, on forgets failures. */
|
|
22
|
+
onEnabledChanged() {
|
|
23
|
+
this.localizer.onEnabledChanged();
|
|
24
|
+
}
|
|
25
|
+
/** Wait until the queue drains or the deadline passes. */
|
|
26
|
+
settle(deadlineMs) {
|
|
27
|
+
return this.localizer.settle(deadlineMs);
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Translate one entity's description for a surface.
|
|
31
|
+
*
|
|
32
|
+
* Only the description is a translatable field: a name is an identity the user
|
|
33
|
+
* types, greps, and matches against upstream documentation, so the collector
|
|
34
|
+
* hands back no unit for one. Nothing here waits on a provider: an uncached
|
|
35
|
+
* field answers with the upstream text and reports itself pending.
|
|
36
|
+
*
|
|
37
|
+
* A description longer than one chunk travels in chunks, exactly as a
|
|
38
|
+
* document body does ({@link localizeText}), so the size of an upstream field
|
|
39
|
+
* is never a reason for a provider call to be cut off.
|
|
40
|
+
*
|
|
41
|
+
* Exposed for the panel and status surfaces that build their own rows; it is
|
|
42
|
+
* not part of the HTTP route surface.
|
|
43
|
+
* @param surface - the surface the entity belongs to.
|
|
44
|
+
* @param id - the entity's stable identity inside that surface.
|
|
45
|
+
* @param fields - the entity's upstream name and description.
|
|
46
|
+
* @param locale - the host locale this read resolved once; there is no
|
|
47
|
+
* default, because a default would re-read the host preference per entity and
|
|
48
|
+
* make a read's cost scale with its row count.
|
|
49
|
+
* @returns the translated fields and how many of them are still pending.
|
|
50
|
+
*/
|
|
51
|
+
translateFields(surface, id, fields, locale) {
|
|
52
|
+
const translated = {};
|
|
53
|
+
let pending = 0;
|
|
54
|
+
for (const unit of collectUnits(surface, id, fields)) {
|
|
55
|
+
const result = this.localizeText(unit.surface, unit.id, unit.role ?? 'description', unit.text, locale);
|
|
56
|
+
pending += result.pending;
|
|
57
|
+
if (result.text !== unit.text)
|
|
58
|
+
translated.translatedDescription = result.text;
|
|
59
|
+
}
|
|
60
|
+
return { fields: translated, pending };
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Translate one authored text, chunk by chunk, and reassemble it exactly.
|
|
64
|
+
*
|
|
65
|
+
* Every text the layer sends travels the way a document does: split on blank
|
|
66
|
+
* lines into chunks one request may carry, fenced code passed through
|
|
67
|
+
* verbatim, and the authored text between chunks re-emitted rather than
|
|
68
|
+
* synthesized — so a text nothing was translated for comes back byte for
|
|
69
|
+
* byte, and a translated one keeps the author's own blank lines. A
|
|
70
|
+
* description is chunked for the same reason a document is: the model hop
|
|
71
|
+
* sizes one call's output from the source it carries, and a text that exceeds
|
|
72
|
+
* that budget is cut off mid-generation, which fails the batch and retires the
|
|
73
|
+
* provider for the rest of the session.
|
|
74
|
+
* @param surface - the surface the entity belongs to.
|
|
75
|
+
* @param id - the entity's stable identity inside that surface.
|
|
76
|
+
* @param role - which text of the entity this is; the cache key's role slot.
|
|
77
|
+
* @param text - the authored text.
|
|
78
|
+
* @param locale - the interface language this read resolved once.
|
|
79
|
+
* @returns the reassembled text and how many of its chunks are still in flight.
|
|
80
|
+
*/
|
|
81
|
+
localizeText(surface, id, role, text, locale) {
|
|
82
|
+
const chunks = chunkDocument(text);
|
|
83
|
+
if (chunks.length === 0)
|
|
84
|
+
return { text, pending: 0 };
|
|
85
|
+
let pending = 0;
|
|
86
|
+
const parts = [];
|
|
87
|
+
for (const chunk of chunks) {
|
|
88
|
+
if (!chunk.translatable) {
|
|
89
|
+
parts.push(chunk.text, chunk.separator);
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
const result = this.localizer.localize({ surface, id, role, text: chunk.text }, locale);
|
|
93
|
+
if (result.pending)
|
|
94
|
+
pending += 1;
|
|
95
|
+
parts.push(result.text, chunk.separator);
|
|
96
|
+
}
|
|
97
|
+
return { text: parts.join(''), pending };
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Translate paragraphs in one complete Markdown tree. The provider receives
|
|
101
|
+
* prose only. Links, code, math, references and nested containers remain local.
|
|
102
|
+
* The result includes a translated document and paragraph-paired Markdown.
|
|
103
|
+
* Unchanged output preserves the source bytes. Changed output preserves the
|
|
104
|
+
* parsed structure and uses canonical Markdown formatting. Pending paragraphs
|
|
105
|
+
* remain authored until complete. The retry flag resets only this document
|
|
106
|
+
* failure budgets and the provider breaker, without clearing cached successes.
|
|
107
|
+
*/
|
|
108
|
+
translateDocument(surface, id, text, locale, retry = false) {
|
|
109
|
+
if (retry)
|
|
110
|
+
this.localizer.retryDocument(surface, id);
|
|
111
|
+
return translateMarkdownDocument(text, (paragraph, parts) => this.localizer.localizeParagraph({ surface, id, role: 'document', text: paragraph }, parts, locale));
|
|
112
|
+
}
|
|
113
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Collect units for one entity, skipping empty or undefined text.
|
|
3
|
+
*
|
|
4
|
+
* Descriptions only: a name is an identifier the user types and matches against
|
|
5
|
+
* upstream documentation, and translating one made it harder to recognize than
|
|
6
|
+
* the original. The text is carried verbatim — the emptiness check trims, but
|
|
7
|
+
* the unit does not, so the cache key stays tied to what upstream authored.
|
|
8
|
+
* @param surface - the surface the entity belongs to.
|
|
9
|
+
* @param id - the entity's stable identity inside that surface.
|
|
10
|
+
* @param fields - the entity's upstream name and description.
|
|
11
|
+
* @returns the units to translate.
|
|
12
|
+
*/
|
|
13
|
+
export function collectUnits(surface, id, fields) {
|
|
14
|
+
return isTranslatable(fields.description) ? [{ surface, id, text: fields.description }] : [];
|
|
15
|
+
}
|
|
16
|
+
/** Whether a field holds text worth sending to a translation backend. */
|
|
17
|
+
function isTranslatable(text) {
|
|
18
|
+
return typeof text === 'string' && text.trim() !== '';
|
|
19
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
/** Explicit cross-domain entry surface. Internal modules are not import targets for siblings. */
|
|
2
|
+
export { TranslationService } from './application/translation/service.js';
|
|
3
|
+
export { createLlmTranslator } from './runtime/host/llm-translator.js';
|
|
4
|
+
export { createTranslationProviders } from './runtime/host/translation-providers.js';
|
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Translate a batch of texts through the host's own LLM service.
|
|
3
|
+
*
|
|
4
|
+
* This is the only place the translation layer talks to a model. It follows the
|
|
5
|
+
* harness auxiliary-call shape (packages/session/session-title-llm): resolve a
|
|
6
|
+
* route, stream through ctx.llm.stream, assemble the chunks with
|
|
7
|
+
* BlockAssembler, and reject on a terminal finish reason.
|
|
8
|
+
*
|
|
9
|
+
* The route is the user default model, read at call time from
|
|
10
|
+
* agentDefaultModel -- the same selection the harness uses for its own
|
|
11
|
+
* auxiliary calls, so the market never invents a model the deployment did not
|
|
12
|
+
* configure. purpose is deliberately omitted: the published
|
|
13
|
+
* GenerateOptions.purpose accepts only compaction and session-title, and
|
|
14
|
+
* neither describes a translation. Omitting it keeps the call on the ordinary
|
|
15
|
+
* route with no model-hidden transport metadata.
|
|
16
|
+
* @module runtime/host/llm-translator
|
|
17
|
+
*/
|
|
18
|
+
import { BlockAssembler, createUserMessage } from '@deepseek-ai/dsh-llm';
|
|
19
|
+
import { pluginMarketSource } from '../../../../market-contracts/src/host/plugin-message-source.js';
|
|
20
|
+
/** Bound the call: a stalled model must not pin a translation slot forever. */
|
|
21
|
+
const TRANSLATE_TIMEOUT_MS = 30_000;
|
|
22
|
+
/**
|
|
23
|
+
* Conservative output allowances, not tokenizer measurements. English output
|
|
24
|
+
* can expand Chinese source substantially; its 1.5 allowance is paired with
|
|
25
|
+
* the localizer's smaller English-target batches. Chinese retains the 0.6
|
|
26
|
+
* estimate. Both remain bounded by MAX_OUTPUT_TOKENS_CAP.
|
|
27
|
+
*/
|
|
28
|
+
const OUTPUT_TOKENS_PER_SOURCE_CHAR = { zh: 0.6, en: 1.5 };
|
|
29
|
+
/** Floor for one call, so a short text still has room for its own output. */
|
|
30
|
+
const MIN_OUTPUT_TOKENS = 512;
|
|
31
|
+
/** Ceiling for one call, so a large one cannot ask for an unbounded generation. */
|
|
32
|
+
export const MAX_OUTPUT_TOKENS_CAP = 4_096;
|
|
33
|
+
/**
|
|
34
|
+
* The output budget for one call carrying this much source text.
|
|
35
|
+
*
|
|
36
|
+
* Sized from the source rather than from the text count: what a call has to
|
|
37
|
+
* answer is the expansion of the text it was handed, so a batch of one long
|
|
38
|
+
* text and a batch of twenty short ones are budgeted by the same rule.
|
|
39
|
+
* @param sourceChars - characters in the user turn this call will send.
|
|
40
|
+
* @param locale - target language; English uses the larger expansion allowance.
|
|
41
|
+
* @returns the output token ceiling to ask the route for.
|
|
42
|
+
*/
|
|
43
|
+
export function outputTokenBudget(sourceChars, locale = 'zh') {
|
|
44
|
+
const ratio = locale.toLowerCase().startsWith('en') ? OUTPUT_TOKENS_PER_SOURCE_CHAR.en : OUTPUT_TOKENS_PER_SOURCE_CHAR.zh;
|
|
45
|
+
return Math.min(Math.max(Math.ceil(sourceChars * ratio), MIN_OUTPUT_TOKENS), MAX_OUTPUT_TOKENS_CAP);
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Batch separator: a line holding only two percent signs.
|
|
49
|
+
*
|
|
50
|
+
* A model asked to translate several segments at once needs an unambiguous
|
|
51
|
+
* boundary, and this one cannot appear inside ordinary prose.
|
|
52
|
+
*/
|
|
53
|
+
const BATCH_SEPARATOR = '\n\n%%\n\n';
|
|
54
|
+
const BATCH_SEPARATOR_PATTERN = /\r?\n[ \t]*%%[ \t]*\r?\n/;
|
|
55
|
+
/**
|
|
56
|
+
* The language name a target tag is asked for in.
|
|
57
|
+
*
|
|
58
|
+
* A model reads a language name, not a tag: `zh` alone is answered in
|
|
59
|
+
* Traditional as often as in Simplified. The layer resolves the interface
|
|
60
|
+
* preference to the language the market actually renders ({@link
|
|
61
|
+
* resolveTranslationTarget}) before the chain is called, so the tags that reach
|
|
62
|
+
* here are few; the table names the spellings a caller may still hand in,
|
|
63
|
+
* because a tag that names a script should be answered in that script rather
|
|
64
|
+
* than left for the model to interpret.
|
|
65
|
+
*/
|
|
66
|
+
const TARGET_LANGUAGE_NAMES = {
|
|
67
|
+
zh: 'Simplified Chinese',
|
|
68
|
+
'zh-hans': 'Simplified Chinese',
|
|
69
|
+
'zh-cn': 'Simplified Chinese',
|
|
70
|
+
'zh-sg': 'Simplified Chinese',
|
|
71
|
+
'zh-hant': 'Traditional Chinese',
|
|
72
|
+
'zh-tw': 'Traditional Chinese',
|
|
73
|
+
'zh-hk': 'Traditional Chinese',
|
|
74
|
+
en: 'English'
|
|
75
|
+
};
|
|
76
|
+
/** The language to translate into, named for the prompt. */
|
|
77
|
+
function targetLanguageName(locale) {
|
|
78
|
+
return TARGET_LANGUAGE_NAMES[locale.toLowerCase()] ?? locale;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Stable instruction; the target language and the batch flag are the variables.
|
|
82
|
+
* @param locale - target locale id.
|
|
83
|
+
* @param batched - whether the user turn carries several segments.
|
|
84
|
+
* @returns the system prompt for this call.
|
|
85
|
+
*/
|
|
86
|
+
function systemPrompt(locale, batched) {
|
|
87
|
+
const lines = [
|
|
88
|
+
'You translate plugin marketplace text for a developer tool.',
|
|
89
|
+
'Translate the user text into ' + targetLanguageName(locale) + '.',
|
|
90
|
+
'Preserve product names, package names, acronyms (MCP, LSP, DSH, CLI), and URLs exactly as written.',
|
|
91
|
+
'Keep the tone factual and concise. Output the translation alone: no quotes, no notes, no alternatives.'
|
|
92
|
+
];
|
|
93
|
+
if (batched) {
|
|
94
|
+
lines.push('The input holds several segments separated by a line containing only two percent signs.', 'Return the same number of segments in the same order, separated the same way.', 'Never use that separator for anything else, and never merge or split segments.');
|
|
95
|
+
}
|
|
96
|
+
return lines.join(' ');
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* The user turn carrying the text.
|
|
100
|
+
*
|
|
101
|
+
* Built through the host factory rather than as a literal: a UserMessage
|
|
102
|
+
* carries a stable id and a source tag, and hand-writing one would fabricate
|
|
103
|
+
* both. The source is this plugin declared producer identity -- the turn is an
|
|
104
|
+
* instruction this plugin sends, not text the user typed.
|
|
105
|
+
* @param text - the masked text to translate.
|
|
106
|
+
* @returns the user message to send.
|
|
107
|
+
*/
|
|
108
|
+
function userMessage(text) {
|
|
109
|
+
return createUserMessage({ content: [{ type: 'text', text }], source: pluginMarketSource() });
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Read the route the deployment selected, or undefined.
|
|
113
|
+
* @param host - the plugin context, read structurally.
|
|
114
|
+
* @returns the resolved route, or undefined when no model is configured.
|
|
115
|
+
*/
|
|
116
|
+
function readRoute(host) {
|
|
117
|
+
try {
|
|
118
|
+
const service = host.get?.('agentDefaultModel');
|
|
119
|
+
const selection = service?.currentSelection();
|
|
120
|
+
if (selection === undefined)
|
|
121
|
+
return undefined;
|
|
122
|
+
if (typeof selection.provider !== 'string' || typeof selection.model !== 'string')
|
|
123
|
+
return undefined;
|
|
124
|
+
if (selection.provider.length === 0 || selection.model.length === 0)
|
|
125
|
+
return undefined;
|
|
126
|
+
const effort = typeof selection.reasoningEffort === 'string' && selection.reasoningEffort.length > 0 ? selection.reasoningEffort : undefined;
|
|
127
|
+
return { provider: selection.provider, model: selection.model, ...(effort === undefined ? {} : { reasoningEffort: effort }) };
|
|
128
|
+
}
|
|
129
|
+
catch {
|
|
130
|
+
// A half-provisioned service is indistinguishable from an absent one.
|
|
131
|
+
return undefined;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Choose the reasoning effort for one translation call.
|
|
136
|
+
*
|
|
137
|
+
* Translating a sentence is mechanical, and the DeepSeek adapter default is
|
|
138
|
+
* high -- leaving the effort unset would spend reasoning tokens on every one of
|
|
139
|
+
* hundreds of texts. So the call asks for off first and keeps the user own
|
|
140
|
+
* selection only if the route refuses it: an unsupported effort rejects at
|
|
141
|
+
* validation rather than reaching the provider, and a model that genuinely
|
|
142
|
+
* requires reasoning still gets a working call instead of a failure.
|
|
143
|
+
*
|
|
144
|
+
* The user selection is forwarded rather than overridden when it is already
|
|
145
|
+
* explicit, because that is a choice they made for this deployment.
|
|
146
|
+
* @param llm - live LLM runtime owning effort validation, when it exposes it.
|
|
147
|
+
* @param route - the resolved provider/model route.
|
|
148
|
+
* @param signal - cancellation for the capability lookup.
|
|
149
|
+
* @returns the effort to send, or undefined to accept the adapter default.
|
|
150
|
+
*/
|
|
151
|
+
async function chooseEffort(llm, route, signal) {
|
|
152
|
+
if (route.reasoningEffort !== undefined)
|
|
153
|
+
return route.reasoningEffort;
|
|
154
|
+
if (llm.resolveCallConfig === undefined)
|
|
155
|
+
return undefined;
|
|
156
|
+
try {
|
|
157
|
+
await llm.resolveCallConfig({ provider: route.provider, model: route.model, reasoningEffort: 'off' }, signal);
|
|
158
|
+
return 'off';
|
|
159
|
+
}
|
|
160
|
+
catch {
|
|
161
|
+
// The route does not accept off; fall back to its own default.
|
|
162
|
+
return undefined;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Translate a terminal finish reason into an auxiliary-call failure.
|
|
167
|
+
* @param finish - the assembled finish reason.
|
|
168
|
+
* @returns the error to throw, or undefined when the stream ended normally.
|
|
169
|
+
*/
|
|
170
|
+
function finishError(finish) {
|
|
171
|
+
switch (finish.kind) {
|
|
172
|
+
case 'stop':
|
|
173
|
+
return undefined;
|
|
174
|
+
case 'error':
|
|
175
|
+
case 'aborted': {
|
|
176
|
+
const error = new Error(finish.failure.message);
|
|
177
|
+
error.code = finish.failure.code;
|
|
178
|
+
return error;
|
|
179
|
+
}
|
|
180
|
+
case 'max-tokens':
|
|
181
|
+
return new Error('translation reached the output token limit');
|
|
182
|
+
case 'tool-calls':
|
|
183
|
+
return new Error('translation unexpectedly requested a tool');
|
|
184
|
+
default:
|
|
185
|
+
return new Error('translation stopped for an unsupported reason: ' + String(finish.kind));
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Collect the assistant text from one stream, rejecting on a terminal failure.
|
|
190
|
+
* @param stream - the chunk stream from the LLM service.
|
|
191
|
+
* @param signal - cancellation, checked between chunks.
|
|
192
|
+
* @returns the assembled assistant text.
|
|
193
|
+
*/
|
|
194
|
+
async function collectText(stream, signal) {
|
|
195
|
+
const assembler = new BlockAssembler();
|
|
196
|
+
for await (const chunk of stream) {
|
|
197
|
+
signal.throwIfAborted();
|
|
198
|
+
assembler.push(chunk);
|
|
199
|
+
}
|
|
200
|
+
signal.throwIfAborted();
|
|
201
|
+
const terminal = finishError(assembler.finish);
|
|
202
|
+
if (terminal !== undefined)
|
|
203
|
+
throw terminal;
|
|
204
|
+
const blocks = assembler.blocks();
|
|
205
|
+
const text = blocks
|
|
206
|
+
.filter((block) => block.type === 'text')
|
|
207
|
+
.map(block => block.text)
|
|
208
|
+
.join(' ')
|
|
209
|
+
.trim();
|
|
210
|
+
if (text.length === 0)
|
|
211
|
+
throw new Error('the model returned no text');
|
|
212
|
+
return text;
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Build the model-backed provider over one plugin context.
|
|
216
|
+
*
|
|
217
|
+
* Every read is deferred to the call, because the LLM and default-model
|
|
218
|
+
* services provision after apply() returns; a value captured here would stay
|
|
219
|
+
* undefined for the life of the process. available() therefore re-resolves the
|
|
220
|
+
* route on each question, and a deployment with no model configured never
|
|
221
|
+
* queues work nothing can complete.
|
|
222
|
+
* @param host - the plugin context, read structurally so tests can pass a stub.
|
|
223
|
+
* @returns a provider bound to this context.
|
|
224
|
+
*/
|
|
225
|
+
export function createLlmTranslator(host) {
|
|
226
|
+
return {
|
|
227
|
+
id: 'llm',
|
|
228
|
+
available: () => readRoute(host) !== undefined && host.get?.('llm') !== undefined,
|
|
229
|
+
async translate(request) {
|
|
230
|
+
request.signal.throwIfAborted();
|
|
231
|
+
const llm = host.get?.('llm');
|
|
232
|
+
const route = readRoute(host);
|
|
233
|
+
if (llm === undefined || route === undefined)
|
|
234
|
+
throw new Error('the DSH model service is unavailable');
|
|
235
|
+
const texts = request.texts;
|
|
236
|
+
if (texts.length === 0)
|
|
237
|
+
return [];
|
|
238
|
+
const batched = texts.length > 1;
|
|
239
|
+
// One controller merges caller cancellation with the call deadline, so a
|
|
240
|
+
// stalled provider cannot hold a translation slot open.
|
|
241
|
+
const controller = new AbortController();
|
|
242
|
+
const onAbort = () => controller.abort();
|
|
243
|
+
request.signal.addEventListener('abort', onAbort, { once: true });
|
|
244
|
+
const timeout = setTimeout(onAbort, TRANSLATE_TIMEOUT_MS);
|
|
245
|
+
timeout.unref?.();
|
|
246
|
+
try {
|
|
247
|
+
const effort = await chooseEffort(llm, route, controller.signal);
|
|
248
|
+
controller.signal.throwIfAborted();
|
|
249
|
+
// The budget is sized from what this call actually carries: the batch
|
|
250
|
+
// separator is part of the turn, and a text longer than any earlier
|
|
251
|
+
// batch is what the response has to cover.
|
|
252
|
+
const source = batched ? texts.join(BATCH_SEPARATOR) : (texts[0] ?? '');
|
|
253
|
+
const options = {
|
|
254
|
+
provider: route.provider,
|
|
255
|
+
model: route.model,
|
|
256
|
+
system: systemPrompt(request.locale, batched),
|
|
257
|
+
messages: [userMessage(source)],
|
|
258
|
+
maxTokens: outputTokenBudget(source.length, request.locale),
|
|
259
|
+
temperature: 0,
|
|
260
|
+
...(effort === undefined ? {} : { reasoningEffort: effort }),
|
|
261
|
+
signal: controller.signal
|
|
262
|
+
};
|
|
263
|
+
const answer = await collectText(llm.stream(options), controller.signal);
|
|
264
|
+
if (!batched)
|
|
265
|
+
return [answer];
|
|
266
|
+
const segments = answer.split(BATCH_SEPARATOR_PATTERN).map(segment => segment.trim());
|
|
267
|
+
if (segments.length !== texts.length) {
|
|
268
|
+
throw new Error('the model returned ' + String(segments.length) + ' of ' + String(texts.length) + ' segments');
|
|
269
|
+
}
|
|
270
|
+
return segments;
|
|
271
|
+
}
|
|
272
|
+
finally {
|
|
273
|
+
clearTimeout(timeout);
|
|
274
|
+
request.signal.removeEventListener('abort', onAbort);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
};
|
|
278
|
+
}
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
/** Google's public web-translation endpoint, the one the Translate widget calls. */
|
|
2
|
+
const GOOGLE_ENDPOINT = 'https://translate-pa.googleapis.com/v1/translateHtml';
|
|
3
|
+
/**
|
|
4
|
+
* The key the endpoint above expects. It is a public, browser-embedded key
|
|
5
|
+
* rather than a credential issued to this plugin — the same value ships inside
|
|
6
|
+
* every page that renders Google's translate widget, so it identifies the
|
|
7
|
+
* calling application and grants nothing.
|
|
8
|
+
*/
|
|
9
|
+
const GOOGLE_API_KEY = 'AIzaSyATBXajvzQLTDHEQbcpq0Ihe0vWDHmO520';
|
|
10
|
+
/** Microsoft's consumer endpoint, the one Edge's translate feature calls. */
|
|
11
|
+
const MICROSOFT_ENDPOINT = 'https://edge.microsoft.com/translate/translatetext';
|
|
12
|
+
/** Auto-detect each request item; user-authored text can be Chinese or English. */
|
|
13
|
+
const SOURCE_LANGUAGE = 'auto';
|
|
14
|
+
/** How much of an error body to quote before it stops being diagnostic. */
|
|
15
|
+
const ERROR_BODY_LIMIT = 200;
|
|
16
|
+
/**
|
|
17
|
+
* Google's target language id.
|
|
18
|
+
*
|
|
19
|
+
* Google reads a bare `zh` as Simplified Chinese already, so the host's own
|
|
20
|
+
* value needs no qualification. A tag that names a script or region more
|
|
21
|
+
* precisely than the host does — `zh-Hant`, `ja` — is forwarded as authored
|
|
22
|
+
* rather than flattened to its primary subtag, which would silently answer in
|
|
23
|
+
* the wrong script.
|
|
24
|
+
*/
|
|
25
|
+
function googleTargetLanguage(locale) {
|
|
26
|
+
return locale.toLowerCase() === 'zh' ? 'zh' : locale;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Microsoft's target language id.
|
|
30
|
+
*
|
|
31
|
+
* This endpoint picks the script from the tag itself, so the host's bare `zh`
|
|
32
|
+
* — which names no script — has to be qualified to reach Simplified Chinese.
|
|
33
|
+
* A tag that already names one is forwarded as authored, so `zh-Hant` still
|
|
34
|
+
* answers in Traditional.
|
|
35
|
+
*/
|
|
36
|
+
function microsoftTargetLanguage(locale) {
|
|
37
|
+
return locale.toLowerCase() === 'zh' ? 'zh-Hans' : locale;
|
|
38
|
+
}
|
|
39
|
+
/** One error's message, whatever was thrown. */
|
|
40
|
+
function reason(error) {
|
|
41
|
+
return error instanceof Error ? error.message : String(error);
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* POST a JSON body and read the JSON answer, with the endpoint named in every
|
|
45
|
+
* failure so a chain log says which hop broke.
|
|
46
|
+
*/
|
|
47
|
+
async function postJson(provider, url, init) {
|
|
48
|
+
let response;
|
|
49
|
+
try {
|
|
50
|
+
response = await fetch(url, init);
|
|
51
|
+
}
|
|
52
|
+
catch (error) {
|
|
53
|
+
throw new Error(`${provider} translate: ${reason(error)}`, { cause: error });
|
|
54
|
+
}
|
|
55
|
+
if (!response.ok) {
|
|
56
|
+
const detail = await response.text().catch(() => '');
|
|
57
|
+
throw new Error(`${provider} translate: HTTP ${String(response.status)}${detail === '' ? '' : `: ${detail.slice(0, ERROR_BODY_LIMIT)}`}`);
|
|
58
|
+
}
|
|
59
|
+
try {
|
|
60
|
+
const payload = await response.json();
|
|
61
|
+
return payload;
|
|
62
|
+
}
|
|
63
|
+
catch (error) {
|
|
64
|
+
throw new Error(`${provider} translate: unreadable response body: ${reason(error)}`, { cause: error });
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
/** Read the first translation out of one Microsoft answer entry, or undefined. */
|
|
68
|
+
function microsoftEntryText(entry) {
|
|
69
|
+
if (typeof entry !== 'object' || entry === null)
|
|
70
|
+
return undefined;
|
|
71
|
+
const translations = entry['translations'];
|
|
72
|
+
if (!Array.isArray(translations))
|
|
73
|
+
return undefined;
|
|
74
|
+
const first = translations[0];
|
|
75
|
+
if (typeof first !== 'object' || first === null)
|
|
76
|
+
return undefined;
|
|
77
|
+
const text = first['text'];
|
|
78
|
+
return typeof text === 'string' ? text : undefined;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Accept one answer batch only when it lines up with the request.
|
|
82
|
+
*
|
|
83
|
+
* A batch endpoint that answers short has dropped a unit somewhere, and a
|
|
84
|
+
* positionally aligned result would silently pair translations with the wrong
|
|
85
|
+
* entity. Rejecting the whole batch costs one fallback hop and keeps every
|
|
86
|
+
* answer attached to its own text.
|
|
87
|
+
*/
|
|
88
|
+
function requireBatch(provider, value, expected) {
|
|
89
|
+
const expectedText = `${provider} translate: expected ${String(expected)} results`;
|
|
90
|
+
if (!Array.isArray(value))
|
|
91
|
+
throw new Error(`${expectedText}, got not an array`);
|
|
92
|
+
if (value.length !== expected)
|
|
93
|
+
throw new Error(`${expectedText}, got ${String(value.length)}`);
|
|
94
|
+
// A well-sized batch whose entries carry no text is a different fault from a
|
|
95
|
+
// short batch, and the two need different words to be diagnosable.
|
|
96
|
+
const unreadable = value.filter(item => typeof item !== 'string').length;
|
|
97
|
+
if (unreadable > 0)
|
|
98
|
+
throw new Error(`${expectedText}, but ${String(unreadable)} carried no text`);
|
|
99
|
+
return value;
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Translate one batch through Google's public endpoint.
|
|
103
|
+
*
|
|
104
|
+
* The body is the endpoint's own protobuf-JSON envelope: a single triple of
|
|
105
|
+
* texts, source language and target language, tagged `wt_lib` so the answer
|
|
106
|
+
* comes back as a plain array rather than as the widget's HTML payload.
|
|
107
|
+
* @param request - the batch, its target locale and cancellation.
|
|
108
|
+
* @returns the translations and the provider id.
|
|
109
|
+
* @throws when the request fails, the endpoint refuses it, or the answer does not line up.
|
|
110
|
+
*/
|
|
111
|
+
export async function googleTranslate(request) {
|
|
112
|
+
if (request.texts.length === 0)
|
|
113
|
+
return { texts: [], provider: 'google' };
|
|
114
|
+
const payload = await postJson('google', GOOGLE_ENDPOINT, {
|
|
115
|
+
method: 'POST',
|
|
116
|
+
headers: {
|
|
117
|
+
'content-type': 'application/json+protobuf',
|
|
118
|
+
'x-goog-api-key': GOOGLE_API_KEY
|
|
119
|
+
},
|
|
120
|
+
body: JSON.stringify([[request.texts, SOURCE_LANGUAGE, googleTargetLanguage(request.locale)], 'wt_lib']),
|
|
121
|
+
signal: request.signal
|
|
122
|
+
});
|
|
123
|
+
// The answer is one array of arrays; the outer one carries the batch.
|
|
124
|
+
const batch = Array.isArray(payload) ? payload[0] : undefined;
|
|
125
|
+
return { texts: requireBatch('google', batch, request.texts.length), provider: 'google' };
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Translate one batch through Microsoft's public endpoint.
|
|
129
|
+
*
|
|
130
|
+
* The body is the bare string array, and the languages ride the query string.
|
|
131
|
+
* `isEnterpriseClient=false` selects the consumer backend, which is the one
|
|
132
|
+
* that answers without authentication.
|
|
133
|
+
* @param request - the batch, its target locale and cancellation.
|
|
134
|
+
* @returns the translations and the provider id.
|
|
135
|
+
* @throws when the request fails, the endpoint refuses it, or the answer does not line up.
|
|
136
|
+
*/
|
|
137
|
+
export async function microsoftTranslate(request) {
|
|
138
|
+
if (request.texts.length === 0)
|
|
139
|
+
return { texts: [], provider: 'microsoft' };
|
|
140
|
+
const query = new URLSearchParams({
|
|
141
|
+
to: microsoftTargetLanguage(request.locale),
|
|
142
|
+
isEnterpriseClient: 'false'
|
|
143
|
+
});
|
|
144
|
+
const payload = await postJson('microsoft', `${MICROSOFT_ENDPOINT}?${query.toString()}`, {
|
|
145
|
+
method: 'POST',
|
|
146
|
+
headers: { 'content-type': 'application/json' },
|
|
147
|
+
body: JSON.stringify(request.texts),
|
|
148
|
+
signal: request.signal
|
|
149
|
+
});
|
|
150
|
+
const texts = Array.isArray(payload) ? payload.map(microsoftEntryText) : payload;
|
|
151
|
+
return { texts: requireBatch('microsoft', texts, request.texts.length), provider: 'microsoft' };
|
|
152
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mask the spans a machine engine corrupts, then restore them from its answer.
|
|
3
|
+
*
|
|
4
|
+
* Neither keyless endpoint leaves technical text alone: Google's HTML aligner
|
|
5
|
+
* escapes a bare `<`, and Microsoft folds angle brackets into pseudo-tags. The
|
|
6
|
+
* spans those engines damage — inline code, URLs, tag fragments and `${VAR}`
|
|
7
|
+
* paths — are exactly what a catalog description is made of, and the fixed terms
|
|
8
|
+
* are the ones an engine likes to paraphrase. Sending a bracketed token instead
|
|
9
|
+
* keeps the payload plain, and the restore table puts the original text back.
|
|
10
|
+
*
|
|
11
|
+
* The same table is the integrity check. An answer is accepted only when every
|
|
12
|
+
* placeholder it recorded comes back exactly once and nothing placeholder-shaped
|
|
13
|
+
* was invented, so a truncated or rewritten answer is discarded rather than
|
|
14
|
+
* rendered with a stray `⟦C1⟧` in it.
|
|
15
|
+
* @module runtime/host/text-masking
|
|
16
|
+
*/
|
|
17
|
+
/**
|
|
18
|
+
* The masking passes, widest span first.
|
|
19
|
+
*
|
|
20
|
+
* Order is load-bearing, in two places. Inline code runs first because its body
|
|
21
|
+
* may contain every other span, and angle-bracket fragments run before URLs
|
|
22
|
+
* because a markdown autolink is one fragment: masking the URL first would
|
|
23
|
+
* leave its bare `<` behind for the engine to escape, while masking the
|
|
24
|
+
* fragment first keeps the whole span intact.
|
|
25
|
+
*/
|
|
26
|
+
const MASK_RULES = [
|
|
27
|
+
{ letter: 'D', pattern: /⟪d\d+⟫/g },
|
|
28
|
+
{ letter: 'C', pattern: /`[^`]*`/g },
|
|
29
|
+
{ letter: 'T', pattern: /<[^>]{0,200}>/g },
|
|
30
|
+
{ letter: 'U', pattern: /https?:\/\/\S+/g },
|
|
31
|
+
{ letter: 'V', pattern: /\$\{[A-Z_][A-Z0-9_]*\}/g },
|
|
32
|
+
{ letter: 'H', pattern: /[<>&]/g },
|
|
33
|
+
{ letter: 'K', pattern: /\b(?:MCP|LSP|DSH|CLI|API|JSON|HTTP|URL|SDK)\b/g }
|
|
34
|
+
];
|
|
35
|
+
/** The placeholder shape: a category letter and a 1-based index between U+27E6 and U+27E7. */
|
|
36
|
+
const PLACEHOLDER_PATTERN = '⟦[A-Z]\\d+⟧';
|
|
37
|
+
/** Every placeholder-shaped token in one string, in order of appearance. */
|
|
38
|
+
function placeholderTokens(text) {
|
|
39
|
+
return text.match(new RegExp(PLACEHOLDER_PATTERN, 'g')) ?? [];
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Replace every span the engines damage with a numbered placeholder.
|
|
43
|
+
* @param input - the upstream text, exactly as authored.
|
|
44
|
+
* @returns the text to send, and the table that restores it.
|
|
45
|
+
*/
|
|
46
|
+
export function maskText(input) {
|
|
47
|
+
const placeholders = new Map();
|
|
48
|
+
let text = input;
|
|
49
|
+
for (const rule of MASK_RULES) {
|
|
50
|
+
let index = 0;
|
|
51
|
+
text = text
|
|
52
|
+
.split(/(⟦[A-Z]\d+⟧)/g)
|
|
53
|
+
.map(part => {
|
|
54
|
+
if (/^⟦[A-Z]\d+⟧$/.test(part))
|
|
55
|
+
return part;
|
|
56
|
+
return part.replace(rule.pattern, match => {
|
|
57
|
+
index += 1;
|
|
58
|
+
const placeholder = `⟦${rule.letter}${index}⟧`;
|
|
59
|
+
placeholders.set(placeholder, match);
|
|
60
|
+
return placeholder;
|
|
61
|
+
});
|
|
62
|
+
})
|
|
63
|
+
.join('');
|
|
64
|
+
}
|
|
65
|
+
return { text, placeholders };
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Put every masked span back, or reject the answer.
|
|
69
|
+
*
|
|
70
|
+
* An answer is rejected — `undefined`, so the caller discards the item instead
|
|
71
|
+
* of caching it — when a placeholder is missing, arrives twice, or when the
|
|
72
|
+
* engine invented one of its own. A short answer that dropped a placeholder
|
|
73
|
+
* would otherwise restore a sentence with a hole in it, and an invented one
|
|
74
|
+
* would render as literal brackets.
|
|
75
|
+
* @param masked - the text the engine returned.
|
|
76
|
+
* @param placeholders - the table {@link maskText} produced for that text.
|
|
77
|
+
* @returns the restored text, or `undefined` when the answer cannot be trusted.
|
|
78
|
+
*/
|
|
79
|
+
export function unmaskText(masked, placeholders) {
|
|
80
|
+
const seen = new Map();
|
|
81
|
+
for (const token of placeholderTokens(masked))
|
|
82
|
+
seen.set(token, (seen.get(token) ?? 0) + 1);
|
|
83
|
+
// Counting both directions rejects a dropped, duplicated and invented
|
|
84
|
+
// placeholder with one comparison: the answer carries exactly the table's
|
|
85
|
+
// keys, each of them once.
|
|
86
|
+
if (seen.size !== placeholders.size)
|
|
87
|
+
return undefined;
|
|
88
|
+
for (const [token, count] of seen) {
|
|
89
|
+
if (count !== 1 || !placeholders.has(token))
|
|
90
|
+
return undefined;
|
|
91
|
+
}
|
|
92
|
+
let restored = masked;
|
|
93
|
+
for (const [placeholder, original] of placeholders)
|
|
94
|
+
restored = restored.split(placeholder).join(original);
|
|
95
|
+
return restored;
|
|
96
|
+
}
|