@murphai/murph 0.2.3 → 0.2.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +51 -0
- package/README.md +9 -1
- package/config.schema.json +95 -35
- package/dist/cli-entry.js +1 -1
- package/dist/cli-entry.js.map +1 -1
- package/dist/commands/inbox.d.ts.map +1 -1
- package/dist/commands/inbox.js +5 -40
- package/dist/commands/inbox.js.map +1 -1
- package/dist/commands/route.d.ts +3 -0
- package/dist/commands/route.d.ts.map +1 -0
- package/dist/commands/route.js +114 -0
- package/dist/commands/route.js.map +1 -0
- package/dist/foreground-terminal-logging.d.ts.map +1 -1
- package/dist/foreground-terminal-logging.js +0 -12
- package/dist/foreground-terminal-logging.js.map +1 -1
- package/dist/incur.generated.d.ts +17 -4
- package/dist/incur.generated.d.ts.map +1 -1
- package/dist/runner-vault-cli-command-manifest.d.ts.map +1 -1
- package/dist/runner-vault-cli-command-manifest.js +2 -0
- package/dist/runner-vault-cli-command-manifest.js.map +1 -1
- package/dist/vault-cli-command-manifest.d.ts +94 -0
- package/dist/vault-cli-command-manifest.d.ts.map +1 -1
- package/dist/vault-cli-command-manifest.js +19 -1
- package/dist/vault-cli-command-manifest.js.map +1 -1
- package/node_modules/@murphai/assistant-cli/dist/assistant/automation/run-loop.d.ts.map +1 -1
- package/node_modules/@murphai/assistant-cli/dist/assistant/automation/run-loop.js +0 -1
- package/node_modules/@murphai/assistant-cli/dist/assistant/automation/run-loop.js.map +1 -1
- package/node_modules/@murphai/assistant-cli/dist/assistant-daemon-client.d.ts.map +1 -1
- package/node_modules/@murphai/assistant-cli/dist/assistant-daemon-client.js +0 -1
- package/node_modules/@murphai/assistant-cli/dist/assistant-daemon-client.js.map +1 -1
- package/node_modules/@murphai/assistant-cli/dist/commands/assistant.d.ts.map +1 -1
- package/node_modules/@murphai/assistant-cli/dist/commands/assistant.js +20 -26
- package/node_modules/@murphai/assistant-cli/dist/commands/assistant.js.map +1 -1
- package/node_modules/@murphai/assistant-cli/dist/run-terminal-logging.d.ts.map +1 -1
- package/node_modules/@murphai/assistant-cli/dist/run-terminal-logging.js +11 -27
- package/node_modules/@murphai/assistant-cli/dist/run-terminal-logging.js.map +1 -1
- package/node_modules/@murphai/assistant-engine/dist/assistant/automation/auto-reply-retry.d.ts +6 -0
- package/node_modules/@murphai/assistant-engine/dist/assistant/automation/auto-reply-retry.d.ts.map +1 -0
- package/node_modules/@murphai/assistant-engine/dist/assistant/automation/auto-reply-retry.js +48 -0
- package/node_modules/@murphai/assistant-engine/dist/assistant/automation/auto-reply-retry.js.map +1 -0
- package/node_modules/@murphai/assistant-engine/dist/assistant/automation/reply.d.ts +1 -2
- package/node_modules/@murphai/assistant-engine/dist/assistant/automation/reply.d.ts.map +1 -1
- package/node_modules/@murphai/assistant-engine/dist/assistant/automation/reply.js +25 -72
- package/node_modules/@murphai/assistant-engine/dist/assistant/automation/reply.js.map +1 -1
- package/node_modules/@murphai/assistant-engine/dist/assistant/automation/routing.d.ts +1 -0
- package/node_modules/@murphai/assistant-engine/dist/assistant/automation/routing.d.ts.map +1 -1
- package/node_modules/@murphai/assistant-engine/dist/assistant/automation/routing.js +4 -1
- package/node_modules/@murphai/assistant-engine/dist/assistant/automation/routing.js.map +1 -1
- package/node_modules/@murphai/assistant-engine/dist/assistant/automation/run-loop.d.ts +6 -2
- package/node_modules/@murphai/assistant-engine/dist/assistant/automation/run-loop.d.ts.map +1 -1
- package/node_modules/@murphai/assistant-engine/dist/assistant/automation/run-loop.js +151 -117
- package/node_modules/@murphai/assistant-engine/dist/assistant/automation/run-loop.js.map +1 -1
- package/node_modules/@murphai/assistant-engine/dist/assistant/automation/scanner.d.ts +1 -1
- package/node_modules/@murphai/assistant-engine/dist/assistant/automation/scanner.d.ts.map +1 -1
- package/node_modules/@murphai/assistant-engine/dist/assistant/automation/scanner.js +64 -98
- package/node_modules/@murphai/assistant-engine/dist/assistant/automation/scanner.js.map +1 -1
- package/node_modules/@murphai/assistant-engine/dist/assistant/automation/shared.d.ts +19 -10
- package/node_modules/@murphai/assistant-engine/dist/assistant/automation/shared.d.ts.map +1 -1
- package/node_modules/@murphai/assistant-engine/dist/assistant/automation/shared.js +43 -10
- package/node_modules/@murphai/assistant-engine/dist/assistant/automation/shared.js.map +1 -1
- package/node_modules/@murphai/assistant-engine/dist/assistant/automation/startup-recovery.d.ts +3 -2
- package/node_modules/@murphai/assistant-engine/dist/assistant/automation/startup-recovery.d.ts.map +1 -1
- package/node_modules/@murphai/assistant-engine/dist/assistant/automation/startup-recovery.js +40 -12
- package/node_modules/@murphai/assistant-engine/dist/assistant/automation/startup-recovery.js.map +1 -1
- package/node_modules/@murphai/assistant-engine/dist/assistant/automation-state.d.ts +11 -0
- package/node_modules/@murphai/assistant-engine/dist/assistant/automation-state.d.ts.map +1 -0
- package/node_modules/@murphai/assistant-engine/dist/assistant/automation-state.js +29 -0
- package/node_modules/@murphai/assistant-engine/dist/assistant/automation-state.js.map +1 -0
- package/node_modules/@murphai/assistant-engine/dist/assistant/automation.d.ts +1 -1
- package/node_modules/@murphai/assistant-engine/dist/assistant/automation.d.ts.map +1 -1
- package/node_modules/@murphai/assistant-engine/dist/assistant/automation.js +1 -1
- package/node_modules/@murphai/assistant-engine/dist/assistant/automation.js.map +1 -1
- package/node_modules/@murphai/assistant-engine/dist/assistant/channel-adapters.d.ts +2 -2
- package/node_modules/@murphai/assistant-engine/dist/assistant/channel-adapters.d.ts.map +1 -1
- package/node_modules/@murphai/assistant-engine/dist/assistant/channel-adapters.js +2 -2
- package/node_modules/@murphai/assistant-engine/dist/assistant/channel-adapters.js.map +1 -1
- package/node_modules/@murphai/assistant-engine/dist/assistant/channels/descriptors.d.ts.map +1 -1
- package/node_modules/@murphai/assistant-engine/dist/assistant/channels/descriptors.js +2 -25
- package/node_modules/@murphai/assistant-engine/dist/assistant/channels/descriptors.js.map +1 -1
- package/node_modules/@murphai/assistant-engine/dist/assistant/channels/registry.d.ts +1 -5
- package/node_modules/@murphai/assistant-engine/dist/assistant/channels/registry.d.ts.map +1 -1
- package/node_modules/@murphai/assistant-engine/dist/assistant/channels/registry.js +0 -3
- package/node_modules/@murphai/assistant-engine/dist/assistant/channels/registry.js.map +1 -1
- package/node_modules/@murphai/assistant-engine/dist/assistant/channels/runtime.d.ts +1 -6
- package/node_modules/@murphai/assistant-engine/dist/assistant/channels/runtime.d.ts.map +1 -1
- package/node_modules/@murphai/assistant-engine/dist/assistant/channels/runtime.js +0 -74
- package/node_modules/@murphai/assistant-engine/dist/assistant/channels/runtime.js.map +1 -1
- package/node_modules/@murphai/assistant-engine/dist/assistant/channels/types.d.ts +1 -16
- package/node_modules/@murphai/assistant-engine/dist/assistant/channels/types.d.ts.map +1 -1
- package/node_modules/@murphai/assistant-engine/dist/assistant/local-service.d.ts.map +1 -1
- package/node_modules/@murphai/assistant-engine/dist/assistant/local-service.js +9 -0
- package/node_modules/@murphai/assistant-engine/dist/assistant/local-service.js.map +1 -1
- package/node_modules/@murphai/assistant-engine/dist/assistant/status.d.ts.map +1 -1
- package/node_modules/@murphai/assistant-engine/dist/assistant/status.js +1 -4
- package/node_modules/@murphai/assistant-engine/dist/assistant/status.js.map +1 -1
- package/node_modules/@murphai/assistant-engine/dist/assistant/store/persistence.d.ts +1 -1
- package/node_modules/@murphai/assistant-engine/dist/assistant/store/persistence.js +2 -5
- package/node_modules/@murphai/assistant-engine/dist/assistant/store/persistence.js.map +1 -1
- package/node_modules/@murphai/assistant-engine/dist/assistant/system-prompt.js +7 -2
- package/node_modules/@murphai/assistant-engine/dist/assistant/system-prompt.js.map +1 -1
- package/node_modules/@murphai/assistant-engine/dist/assistant/turns.d.ts +1 -0
- package/node_modules/@murphai/assistant-engine/dist/assistant/turns.d.ts.map +1 -1
- package/node_modules/@murphai/assistant-engine/dist/assistant/turns.js +21 -7
- package/node_modules/@murphai/assistant-engine/dist/assistant/turns.js.map +1 -1
- package/node_modules/@murphai/assistant-engine/dist/assistant-state.d.ts +1 -0
- package/node_modules/@murphai/assistant-engine/dist/assistant-state.d.ts.map +1 -1
- package/node_modules/@murphai/assistant-engine/dist/assistant-state.js +1 -0
- package/node_modules/@murphai/assistant-engine/dist/assistant-state.js.map +1 -1
- package/node_modules/@murphai/assistant-engine/dist/index.d.ts +1 -0
- package/node_modules/@murphai/assistant-engine/dist/index.d.ts.map +1 -1
- package/node_modules/@murphai/assistant-engine/dist/index.js +1 -0
- package/node_modules/@murphai/assistant-engine/dist/index.js.map +1 -1
- package/node_modules/@murphai/assistant-engine/dist/mapbox-route.d.ts +142 -0
- package/node_modules/@murphai/assistant-engine/dist/mapbox-route.d.ts.map +1 -0
- package/node_modules/@murphai/assistant-engine/dist/mapbox-route.js +670 -0
- package/node_modules/@murphai/assistant-engine/dist/mapbox-route.js.map +1 -0
- package/node_modules/@murphai/assistant-engine/dist/outbound-channel.d.ts +2 -2
- package/node_modules/@murphai/assistant-engine/dist/outbound-channel.d.ts.map +1 -1
- package/node_modules/@murphai/assistant-engine/dist/outbound-channel.js +2 -2
- package/node_modules/@murphai/assistant-engine/dist/outbound-channel.js.map +1 -1
- package/node_modules/@murphai/assistant-engine/package.json +2 -2
- package/node_modules/@murphai/assistantd/dist/http-protocol.d.ts.map +1 -1
- package/node_modules/@murphai/assistantd/dist/http-protocol.js +0 -1
- package/node_modules/@murphai/assistantd/dist/http-protocol.js.map +1 -1
- package/node_modules/@murphai/assistantd/dist/service.d.ts +0 -1
- package/node_modules/@murphai/assistantd/dist/service.d.ts.map +1 -1
- package/node_modules/@murphai/assistantd/dist/service.js +0 -1
- package/node_modules/@murphai/assistantd/dist/service.js.map +1 -1
- package/node_modules/@murphai/assistantd/package.json +1 -1
- package/node_modules/@murphai/core/dist/automation.js +1 -1
- package/node_modules/@murphai/core/dist/index.d.ts +1 -1
- package/node_modules/@murphai/core/dist/index.d.ts.map +1 -1
- package/node_modules/@murphai/core/dist/index.js +1 -1
- package/node_modules/@murphai/core/dist/index.js.map +1 -1
- package/node_modules/@murphai/core/dist/preferences.d.ts +11 -2
- package/node_modules/@murphai/core/dist/preferences.d.ts.map +1 -1
- package/node_modules/@murphai/core/dist/preferences.js +65 -5
- package/node_modules/@murphai/core/dist/preferences.js.map +1 -1
- package/node_modules/@murphai/core/dist/public-mutations.d.ts +2 -1
- package/node_modules/@murphai/core/dist/public-mutations.d.ts.map +1 -1
- package/node_modules/@murphai/core/dist/public-mutations.js +4 -1
- package/node_modules/@murphai/core/dist/public-mutations.js.map +1 -1
- package/node_modules/@murphai/core/package.json +1 -1
- package/node_modules/@murphai/device-syncd/dist/service.d.ts +1 -1
- package/node_modules/@murphai/device-syncd/dist/service.d.ts.map +1 -1
- package/node_modules/@murphai/device-syncd/dist/service.js +2 -4
- package/node_modules/@murphai/device-syncd/dist/service.js.map +1 -1
- package/node_modules/@murphai/gateway-local/package.json +1 -1
- package/node_modules/@murphai/importers/package.json +3 -1
- package/node_modules/@murphai/inbox-services/dist/inbox-app/bootstrap-doctor-strategies.d.ts +2 -2
- package/node_modules/@murphai/inbox-services/dist/inbox-app/bootstrap-doctor-strategies.d.ts.map +1 -1
- package/node_modules/@murphai/inbox-services/dist/inbox-app/bootstrap-doctor-strategies.js +0 -46
- package/node_modules/@murphai/inbox-services/dist/inbox-app/bootstrap-doctor-strategies.js.map +1 -1
- package/node_modules/@murphai/inbox-services/dist/inbox-app/environment.d.ts +0 -2
- package/node_modules/@murphai/inbox-services/dist/inbox-app/environment.d.ts.map +1 -1
- package/node_modules/@murphai/inbox-services/dist/inbox-app/environment.js +0 -58
- package/node_modules/@murphai/inbox-services/dist/inbox-app/environment.js.map +1 -1
- package/node_modules/@murphai/inbox-services/dist/inbox-app/runtime.d.ts.map +1 -1
- package/node_modules/@murphai/inbox-services/dist/inbox-app/runtime.js +52 -47
- package/node_modules/@murphai/inbox-services/dist/inbox-app/runtime.js.map +1 -1
- package/node_modules/@murphai/inbox-services/dist/inbox-app/sources.d.ts +1 -1
- package/node_modules/@murphai/inbox-services/dist/inbox-app/sources.d.ts.map +1 -1
- package/node_modules/@murphai/inbox-services/dist/inbox-app/sources.js +0 -18
- package/node_modules/@murphai/inbox-services/dist/inbox-app/sources.js.map +1 -1
- package/node_modules/@murphai/inbox-services/dist/inbox-app/types.d.ts +8 -26
- package/node_modules/@murphai/inbox-services/dist/inbox-app/types.d.ts.map +1 -1
- package/node_modules/@murphai/inbox-services/dist/inbox-services/connectors.d.ts +1 -4
- package/node_modules/@murphai/inbox-services/dist/inbox-services/connectors.d.ts.map +1 -1
- package/node_modules/@murphai/inbox-services/dist/inbox-services/connectors.js +2 -19
- package/node_modules/@murphai/inbox-services/dist/inbox-services/connectors.js.map +1 -1
- package/node_modules/@murphai/inbox-services/dist/inbox-services/shared.d.ts.map +1 -1
- package/node_modules/@murphai/inbox-services/dist/inbox-services/shared.js +1 -4
- package/node_modules/@murphai/inbox-services/dist/inbox-services/shared.js.map +1 -1
- package/node_modules/@murphai/inbox-services/dist/index.d.ts +1 -1
- package/node_modules/@murphai/inbox-services/dist/index.d.ts.map +1 -1
- package/node_modules/@murphai/inbox-services/package.json +1 -1
- package/node_modules/@murphai/inboxd/README.md +1 -3
- package/node_modules/@murphai/inboxd/dist/connectors/email/normalize-parsed.js +34 -22
- package/node_modules/@murphai/inboxd/dist/connectors/email/normalize-parsed.js.map +1 -1
- package/node_modules/@murphai/inboxd/dist/connectors/email/normalize.d.ts +5 -2
- package/node_modules/@murphai/inboxd/dist/connectors/email/normalize.d.ts.map +1 -1
- package/node_modules/@murphai/inboxd/dist/connectors/email/normalize.js +32 -30
- package/node_modules/@murphai/inboxd/dist/connectors/email/normalize.js.map +1 -1
- package/node_modules/@murphai/inboxd/dist/indexing/persist.d.ts.map +1 -1
- package/node_modules/@murphai/inboxd/dist/indexing/persist.js +3 -19
- package/node_modules/@murphai/inboxd/dist/indexing/persist.js.map +1 -1
- package/node_modules/@murphai/inboxd/dist/parsers.d.ts +3 -0
- package/node_modules/@murphai/inboxd/dist/parsers.d.ts.map +1 -1
- package/node_modules/@murphai/inboxd/dist/parsers.js +8 -2
- package/node_modules/@murphai/inboxd/dist/parsers.js.map +1 -1
- package/node_modules/@murphai/inboxd/package.json +1 -1
- package/node_modules/@murphai/operator-config/dist/assistant-cli-contracts.d.ts +28 -21
- package/node_modules/@murphai/operator-config/dist/assistant-cli-contracts.d.ts.map +1 -1
- package/node_modules/@murphai/operator-config/dist/assistant-cli-contracts.js +14 -9
- package/node_modules/@murphai/operator-config/dist/assistant-cli-contracts.js.map +1 -1
- package/node_modules/@murphai/operator-config/dist/inbox-cli-contracts.d.ts +1 -14
- package/node_modules/@murphai/operator-config/dist/inbox-cli-contracts.d.ts.map +1 -1
- package/node_modules/@murphai/operator-config/dist/inbox-cli-contracts.js +1 -2
- package/node_modules/@murphai/operator-config/dist/inbox-cli-contracts.js.map +1 -1
- package/node_modules/@murphai/operator-config/dist/index.d.ts +0 -1
- package/node_modules/@murphai/operator-config/dist/index.d.ts.map +1 -1
- package/node_modules/@murphai/operator-config/dist/index.js +0 -1
- package/node_modules/@murphai/operator-config/dist/index.js.map +1 -1
- package/node_modules/@murphai/operator-config/dist/setup-cli-contracts.d.ts +4 -7
- package/node_modules/@murphai/operator-config/dist/setup-cli-contracts.d.ts.map +1 -1
- package/node_modules/@murphai/operator-config/dist/setup-cli-contracts.js +6 -2
- package/node_modules/@murphai/operator-config/dist/setup-cli-contracts.js.map +1 -1
- package/node_modules/@murphai/operator-config/dist/setup-runtime-env.d.ts.map +1 -1
- package/node_modules/@murphai/operator-config/dist/setup-runtime-env.js +4 -25
- package/node_modules/@murphai/operator-config/dist/setup-runtime-env.js.map +1 -1
- package/node_modules/@murphai/operator-config/package.json +2 -7
- package/node_modules/@murphai/query/package.json +1 -1
- package/node_modules/@murphai/runtime-state/dist/node/index.d.ts +1 -1
- package/node_modules/@murphai/runtime-state/dist/node/index.d.ts.map +1 -1
- package/node_modules/@murphai/runtime-state/dist/node/index.js +1 -1
- package/node_modules/@murphai/runtime-state/dist/node/index.js.map +1 -1
- package/node_modules/@murphai/setup-cli/dist/setup-cli.d.ts +1 -0
- package/node_modules/@murphai/setup-cli/dist/setup-cli.d.ts.map +1 -1
- package/node_modules/@murphai/setup-cli/dist/setup-cli.js +56 -17
- package/node_modules/@murphai/setup-cli/dist/setup-cli.js.map +1 -1
- package/node_modules/@murphai/setup-cli/dist/setup-services/channels.d.ts +1 -1
- package/node_modules/@murphai/setup-cli/dist/setup-services/channels.d.ts.map +1 -1
- package/node_modules/@murphai/setup-cli/dist/setup-services/channels.js +105 -99
- package/node_modules/@murphai/setup-cli/dist/setup-services/channels.js.map +1 -1
- package/node_modules/@murphai/setup-cli/dist/setup-services/wearables.d.ts +9 -0
- package/node_modules/@murphai/setup-cli/dist/setup-services/wearables.d.ts.map +1 -0
- package/node_modules/@murphai/setup-cli/dist/setup-services/wearables.js +50 -0
- package/node_modules/@murphai/setup-cli/dist/setup-services/wearables.js.map +1 -0
- package/node_modules/@murphai/setup-cli/dist/setup-services.d.ts +1 -1
- package/node_modules/@murphai/setup-cli/dist/setup-services.d.ts.map +1 -1
- package/node_modules/@murphai/setup-cli/dist/setup-services.js +9 -0
- package/node_modules/@murphai/setup-cli/dist/setup-services.js.map +1 -1
- package/node_modules/@murphai/setup-cli/dist/setup-wizard-options.d.ts.map +1 -1
- package/node_modules/@murphai/setup-cli/dist/setup-wizard-options.js +5 -14
- package/node_modules/@murphai/setup-cli/dist/setup-wizard-options.js.map +1 -1
- package/node_modules/@murphai/setup-cli/dist/setup-wizard.js +6 -6
- package/node_modules/@murphai/setup-cli/dist/setup-wizard.js.map +1 -1
- package/node_modules/@murphai/vault-usecases/dist/index.d.ts +1 -0
- package/node_modules/@murphai/vault-usecases/dist/index.d.ts.map +1 -1
- package/node_modules/@murphai/vault-usecases/dist/index.js +1 -0
- package/node_modules/@murphai/vault-usecases/dist/index.js.map +1 -1
- package/node_modules/@murphai/vault-usecases/dist/preferences.d.ts +24 -0
- package/node_modules/@murphai/vault-usecases/dist/preferences.d.ts.map +1 -0
- package/node_modules/@murphai/vault-usecases/dist/preferences.js +30 -0
- package/node_modules/@murphai/vault-usecases/dist/preferences.js.map +1 -0
- package/node_modules/@murphai/vault-usecases/dist/usecases/workout-measurement.d.ts +1 -1
- package/node_modules/@murphai/vault-usecases/package.json +1 -1
- package/package.json +4 -7
- package/node_modules/@murphai/inboxd-imessage/README.md +0 -7
- package/node_modules/@murphai/inboxd-imessage/dist/connector.d.ts +0 -40
- package/node_modules/@murphai/inboxd-imessage/dist/connector.d.ts.map +0 -1
- package/node_modules/@murphai/inboxd-imessage/dist/connector.js +0 -334
- package/node_modules/@murphai/inboxd-imessage/dist/connector.js.map +0 -1
- package/node_modules/@murphai/inboxd-imessage/dist/index.d.ts +0 -5
- package/node_modules/@murphai/inboxd-imessage/dist/index.d.ts.map +0 -1
- package/node_modules/@murphai/inboxd-imessage/dist/index.js +0 -3
- package/node_modules/@murphai/inboxd-imessage/dist/index.js.map +0 -1
- package/node_modules/@murphai/inboxd-imessage/dist/normalize.d.ts +0 -60
- package/node_modules/@murphai/inboxd-imessage/dist/normalize.d.ts.map +0 -1
- package/node_modules/@murphai/inboxd-imessage/dist/normalize.js +0 -147
- package/node_modules/@murphai/inboxd-imessage/dist/normalize.js.map +0 -1
- package/node_modules/@murphai/inboxd-imessage/dist/shared-runtime.d.ts +0 -4
- package/node_modules/@murphai/inboxd-imessage/dist/shared-runtime.d.ts.map +0 -1
- package/node_modules/@murphai/inboxd-imessage/dist/shared-runtime.js +0 -172
- package/node_modules/@murphai/inboxd-imessage/dist/shared-runtime.js.map +0 -1
- package/node_modules/@murphai/inboxd-imessage/package.json +0 -31
- package/node_modules/@murphai/operator-config/dist/imessage-readiness.d.ts +0 -22
- package/node_modules/@murphai/operator-config/dist/imessage-readiness.d.ts.map +0 -1
- package/node_modules/@murphai/operator-config/dist/imessage-readiness.js +0 -83
- package/node_modules/@murphai/operator-config/dist/imessage-readiness.js.map +0 -1
|
@@ -0,0 +1,670 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared Mapbox route estimation helper used by the CLI surface.
|
|
3
|
+
*
|
|
4
|
+
* Privacy posture:
|
|
5
|
+
* - the access token stays in env only
|
|
6
|
+
* - forward geocoding stays temporary and is not cached by this helper
|
|
7
|
+
* - route geometry is only returned when explicitly requested
|
|
8
|
+
* - elevation, when requested, is approximate and derived from bounded contour samples
|
|
9
|
+
*/
|
|
10
|
+
import { errorMessage, normalizeNullableString } from '@murphai/operator-config/text/shared';
|
|
11
|
+
import { z } from 'zod';
|
|
12
|
+
const MAPBOX_DIRECTIONS_API_VERSION = 'v5';
|
|
13
|
+
const MAPBOX_GEOCODING_API_VERSION = 'v6';
|
|
14
|
+
const DEFAULT_MAPBOX_TIMEOUT_MS = 10_000;
|
|
15
|
+
const MAX_MAPBOX_TIMEOUT_MS = 30_000;
|
|
16
|
+
const DEFAULT_ELEVATION_QUERY_RADIUS_METERS = 50;
|
|
17
|
+
const DEFAULT_ELEVATION_SAMPLE_SPACING_METERS = 500;
|
|
18
|
+
const DEFAULT_MAX_ELEVATION_SAMPLES = 16;
|
|
19
|
+
const MAX_ELEVATION_SAMPLES = 24;
|
|
20
|
+
const MAX_ROUTE_COORDINATES = 25;
|
|
21
|
+
const MAX_ROUTE_WAYPOINTS = MAX_ROUTE_COORDINATES - 2;
|
|
22
|
+
const coordinateLiteralPattern = /^\s*(-?(?:\d+(?:\.\d+)?|\.\d+))\s*,\s*(-?(?:\d+(?:\.\d+)?|\.\d+))\s*$/u;
|
|
23
|
+
export const mapboxRouteProfileValues = [
|
|
24
|
+
'walking',
|
|
25
|
+
'cycling',
|
|
26
|
+
'driving',
|
|
27
|
+
'driving-traffic',
|
|
28
|
+
];
|
|
29
|
+
export const mapboxRouteProfileSchema = z.enum(mapboxRouteProfileValues);
|
|
30
|
+
const longitudeSchema = z.number().gte(-180).lte(180);
|
|
31
|
+
const latitudeSchema = z.number().gte(-90).lte(90);
|
|
32
|
+
const coordinateTupleSchema = z.tuple([longitudeSchema, latitudeSchema]);
|
|
33
|
+
const coordinatePointSchema = z.object({
|
|
34
|
+
longitude: longitudeSchema,
|
|
35
|
+
latitude: latitudeSchema,
|
|
36
|
+
name: z.string().min(1).optional(),
|
|
37
|
+
});
|
|
38
|
+
const pointRoleSchema = z.enum(['origin', 'waypoint', 'destination']);
|
|
39
|
+
const pointSourceSchema = z.enum([
|
|
40
|
+
'coordinates',
|
|
41
|
+
'coordinate-literal',
|
|
42
|
+
'geocoded-query',
|
|
43
|
+
]);
|
|
44
|
+
const elevationSourceSchema = z.enum(['none', 'mapbox-terrain-v2-contour']);
|
|
45
|
+
export const mapboxRouteLocationInputSchema = z.union([
|
|
46
|
+
z.string().min(1),
|
|
47
|
+
coordinatePointSchema,
|
|
48
|
+
]);
|
|
49
|
+
export const mapboxRouteEstimateInputSchema = z.object({
|
|
50
|
+
origin: mapboxRouteLocationInputSchema,
|
|
51
|
+
destination: mapboxRouteLocationInputSchema,
|
|
52
|
+
waypoints: z.array(mapboxRouteLocationInputSchema).max(MAX_ROUTE_WAYPOINTS).optional(),
|
|
53
|
+
profile: mapboxRouteProfileSchema.optional(),
|
|
54
|
+
includeGeometry: z.boolean().optional(),
|
|
55
|
+
includeElevation: z.boolean().optional(),
|
|
56
|
+
country: z
|
|
57
|
+
.array(z.string().regex(/^[A-Za-z]{2}$/u))
|
|
58
|
+
.max(10)
|
|
59
|
+
.optional(),
|
|
60
|
+
language: z.string().min(1).max(10).optional(),
|
|
61
|
+
elevationSampleSpacingMeters: z.number().positive().max(10_000).optional(),
|
|
62
|
+
maxElevationSamples: z.number().int().positive().max(MAX_ELEVATION_SAMPLES).optional(),
|
|
63
|
+
});
|
|
64
|
+
const routeGeometrySchema = z
|
|
65
|
+
.object({
|
|
66
|
+
type: z.literal('LineString'),
|
|
67
|
+
coordinates: z.array(coordinateTupleSchema).min(2),
|
|
68
|
+
})
|
|
69
|
+
.strict();
|
|
70
|
+
const resolvedPointSchema = z
|
|
71
|
+
.object({
|
|
72
|
+
role: pointRoleSchema,
|
|
73
|
+
source: pointSourceSchema,
|
|
74
|
+
displayName: z.string().min(1),
|
|
75
|
+
longitude: longitudeSchema,
|
|
76
|
+
latitude: latitudeSchema,
|
|
77
|
+
routableLongitude: longitudeSchema,
|
|
78
|
+
routableLatitude: latitudeSchema,
|
|
79
|
+
accuracy: z.string().min(1).nullable(),
|
|
80
|
+
matchType: z.string().min(1).nullable(),
|
|
81
|
+
routablePointName: z.string().min(1).nullable(),
|
|
82
|
+
})
|
|
83
|
+
.strict();
|
|
84
|
+
const routeLegSchema = z
|
|
85
|
+
.object({
|
|
86
|
+
index: z.number().int().nonnegative(),
|
|
87
|
+
distanceMeters: z.number().nonnegative(),
|
|
88
|
+
durationSeconds: z.number().nonnegative(),
|
|
89
|
+
summary: z.string().min(1).nullable(),
|
|
90
|
+
})
|
|
91
|
+
.strict();
|
|
92
|
+
const elevationSummarySchema = z
|
|
93
|
+
.object({
|
|
94
|
+
approximate: z.literal(true),
|
|
95
|
+
source: z.literal('mapbox-terrain-v2-contour'),
|
|
96
|
+
sampleCount: z.number().int().positive(),
|
|
97
|
+
resolvedSampleCount: z.number().int().positive(),
|
|
98
|
+
sampleSpacingMeters: z.number().positive(),
|
|
99
|
+
queryRadiusMeters: z.number().positive(),
|
|
100
|
+
gainMeters: z.number(),
|
|
101
|
+
lossMeters: z.number(),
|
|
102
|
+
netChangeMeters: z.number(),
|
|
103
|
+
minimumMeters: z.number(),
|
|
104
|
+
maximumMeters: z.number(),
|
|
105
|
+
startMeters: z.number(),
|
|
106
|
+
endMeters: z.number(),
|
|
107
|
+
notes: z.array(z.string().min(1)),
|
|
108
|
+
})
|
|
109
|
+
.strict();
|
|
110
|
+
const routeSummarySchema = z
|
|
111
|
+
.object({
|
|
112
|
+
distanceMeters: z.number().nonnegative(),
|
|
113
|
+
distanceKilometers: z.number().nonnegative(),
|
|
114
|
+
durationSeconds: z.number().nonnegative(),
|
|
115
|
+
durationMinutes: z.number().nonnegative(),
|
|
116
|
+
})
|
|
117
|
+
.strict();
|
|
118
|
+
const providerMetadataSchema = z
|
|
119
|
+
.object({
|
|
120
|
+
name: z.literal('mapbox'),
|
|
121
|
+
directionsApiVersion: z.literal(MAPBOX_DIRECTIONS_API_VERSION),
|
|
122
|
+
geocodingApiVersion: z.literal(MAPBOX_GEOCODING_API_VERSION),
|
|
123
|
+
elevationSource: elevationSourceSchema,
|
|
124
|
+
})
|
|
125
|
+
.strict();
|
|
126
|
+
const routePrivacySchema = z
|
|
127
|
+
.object({
|
|
128
|
+
tokenSource: z.literal('env'),
|
|
129
|
+
persistedByTool: z.literal(false),
|
|
130
|
+
geocodingStorage: z.enum(['temporary', 'not-used']),
|
|
131
|
+
geocodedPointCount: z.number().int().nonnegative(),
|
|
132
|
+
geometryIncluded: z.boolean(),
|
|
133
|
+
})
|
|
134
|
+
.strict();
|
|
135
|
+
export const mapboxRouteEstimateResultSchema = z
|
|
136
|
+
.object({
|
|
137
|
+
provider: providerMetadataSchema,
|
|
138
|
+
profile: mapboxRouteProfileSchema,
|
|
139
|
+
summary: routeSummarySchema,
|
|
140
|
+
points: z.array(resolvedPointSchema).min(2),
|
|
141
|
+
legs: z.array(routeLegSchema),
|
|
142
|
+
elevation: elevationSummarySchema.nullable(),
|
|
143
|
+
geometry: routeGeometrySchema.nullable(),
|
|
144
|
+
privacy: routePrivacySchema,
|
|
145
|
+
warnings: z.array(z.string().min(1)),
|
|
146
|
+
})
|
|
147
|
+
.strict();
|
|
148
|
+
export async function estimateMapboxRoute(rawInput, dependencies = {}) {
|
|
149
|
+
const input = mapboxRouteEstimateInputSchema.parse(rawInput);
|
|
150
|
+
const env = dependencies.env ?? process.env;
|
|
151
|
+
const fetchImpl = dependencies.fetchImpl ?? fetch;
|
|
152
|
+
const accessToken = readMapboxAccessToken(env);
|
|
153
|
+
if (!accessToken) {
|
|
154
|
+
throw new Error('Mapbox routing is not configured. Set MAPBOX_ACCESS_TOKEN in the runtime environment before using route estimation.');
|
|
155
|
+
}
|
|
156
|
+
const timeoutMs = resolveMapboxTimeoutMs(env);
|
|
157
|
+
const wantsGeometry = Boolean(input.includeGeometry || input.includeElevation);
|
|
158
|
+
const warnings = [];
|
|
159
|
+
const resolvedPoints = await resolveRoutePoints({
|
|
160
|
+
accessToken,
|
|
161
|
+
country: input.country,
|
|
162
|
+
destination: input.destination,
|
|
163
|
+
fetchImpl,
|
|
164
|
+
language: input.language,
|
|
165
|
+
origin: input.origin,
|
|
166
|
+
timeoutMs,
|
|
167
|
+
waypoints: input.waypoints ?? [],
|
|
168
|
+
});
|
|
169
|
+
const directionsRoute = await requestDirections({
|
|
170
|
+
accessToken,
|
|
171
|
+
fetchImpl,
|
|
172
|
+
points: resolvedPoints,
|
|
173
|
+
profile: input.profile ?? 'walking',
|
|
174
|
+
timeoutMs,
|
|
175
|
+
wantsGeometry,
|
|
176
|
+
});
|
|
177
|
+
const geometry = wantsGeometry
|
|
178
|
+
? normalizeRouteGeometry(directionsRoute.geometry)
|
|
179
|
+
: null;
|
|
180
|
+
let elevation = null;
|
|
181
|
+
if (input.includeElevation) {
|
|
182
|
+
if (!geometry) {
|
|
183
|
+
warnings.push('Elevation was requested but the routed geometry was unavailable.');
|
|
184
|
+
}
|
|
185
|
+
else {
|
|
186
|
+
elevation = await summarizeRouteElevation({
|
|
187
|
+
accessToken,
|
|
188
|
+
fetchImpl,
|
|
189
|
+
geometry,
|
|
190
|
+
maxSamples: input.maxElevationSamples ?? DEFAULT_MAX_ELEVATION_SAMPLES,
|
|
191
|
+
sampleSpacingMeters: input.elevationSampleSpacingMeters ??
|
|
192
|
+
DEFAULT_ELEVATION_SAMPLE_SPACING_METERS,
|
|
193
|
+
timeoutMs,
|
|
194
|
+
});
|
|
195
|
+
if (!elevation) {
|
|
196
|
+
warnings.push('Elevation is unavailable for this route. When returned, it is only an approximation based on sampled contour queries.');
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
if (input.includeElevation) {
|
|
201
|
+
warnings.push('Elevation is approximate and based on sampled contour queries against Mapbox Terrain rather than a full-resolution trail profile.');
|
|
202
|
+
}
|
|
203
|
+
const result = {
|
|
204
|
+
provider: {
|
|
205
|
+
name: 'mapbox',
|
|
206
|
+
directionsApiVersion: MAPBOX_DIRECTIONS_API_VERSION,
|
|
207
|
+
geocodingApiVersion: MAPBOX_GEOCODING_API_VERSION,
|
|
208
|
+
elevationSource: input.includeElevation ? 'mapbox-terrain-v2-contour' : 'none',
|
|
209
|
+
},
|
|
210
|
+
profile: input.profile ?? 'walking',
|
|
211
|
+
summary: {
|
|
212
|
+
distanceMeters: roundTo(directionsRoute.distance ?? 0, 1),
|
|
213
|
+
distanceKilometers: roundTo((directionsRoute.distance ?? 0) / 1000, 3),
|
|
214
|
+
durationSeconds: roundTo(directionsRoute.duration ?? 0, 1),
|
|
215
|
+
durationMinutes: roundTo((directionsRoute.duration ?? 0) / 60, 1),
|
|
216
|
+
},
|
|
217
|
+
points: resolvedPoints,
|
|
218
|
+
legs: (directionsRoute.legs ?? []).map((leg, index) => ({
|
|
219
|
+
index,
|
|
220
|
+
distanceMeters: roundTo(leg.distance ?? 0, 1),
|
|
221
|
+
durationSeconds: roundTo(leg.duration ?? 0, 1),
|
|
222
|
+
summary: normalizeNullableString(leg.summary) ?? null,
|
|
223
|
+
})),
|
|
224
|
+
elevation,
|
|
225
|
+
geometry: input.includeGeometry ? geometry : null,
|
|
226
|
+
privacy: {
|
|
227
|
+
tokenSource: 'env',
|
|
228
|
+
persistedByTool: false,
|
|
229
|
+
geocodingStorage: resolvedPoints.some((point) => point.source === 'geocoded-query')
|
|
230
|
+
? 'temporary'
|
|
231
|
+
: 'not-used',
|
|
232
|
+
geocodedPointCount: resolvedPoints.filter((point) => point.source === 'geocoded-query').length,
|
|
233
|
+
geometryIncluded: Boolean(input.includeGeometry && geometry),
|
|
234
|
+
},
|
|
235
|
+
warnings,
|
|
236
|
+
};
|
|
237
|
+
return mapboxRouteEstimateResultSchema.parse(result);
|
|
238
|
+
}
|
|
239
|
+
async function resolveRoutePoints(input) {
|
|
240
|
+
const sources = [
|
|
241
|
+
{ role: 'origin', value: input.origin },
|
|
242
|
+
...input.waypoints.map((value) => ({
|
|
243
|
+
role: 'waypoint',
|
|
244
|
+
value,
|
|
245
|
+
})),
|
|
246
|
+
{ role: 'destination', value: input.destination },
|
|
247
|
+
];
|
|
248
|
+
return await Promise.all(sources.map(async ({ role, value }) => await resolveRoutePoint(value, {
|
|
249
|
+
accessToken: input.accessToken,
|
|
250
|
+
country: input.country,
|
|
251
|
+
fetchImpl: input.fetchImpl,
|
|
252
|
+
language: input.language,
|
|
253
|
+
role,
|
|
254
|
+
timeoutMs: input.timeoutMs,
|
|
255
|
+
})));
|
|
256
|
+
}
|
|
257
|
+
async function resolveRoutePoint(input, options) {
|
|
258
|
+
if (typeof input !== 'string') {
|
|
259
|
+
return buildCoordinatePoint(input, options.role, 'coordinates');
|
|
260
|
+
}
|
|
261
|
+
const coordinateLiteral = parseCoordinateLiteral(input);
|
|
262
|
+
if (coordinateLiteral) {
|
|
263
|
+
return buildCoordinatePoint(coordinateLiteral, options.role, 'coordinate-literal');
|
|
264
|
+
}
|
|
265
|
+
return await geocodeRoutePoint(input, options);
|
|
266
|
+
}
|
|
267
|
+
function buildCoordinatePoint(input, role, source) {
|
|
268
|
+
const displayName = normalizeNullableString(input.name) ??
|
|
269
|
+
formatCoordinateLiteral(input.longitude, input.latitude);
|
|
270
|
+
return {
|
|
271
|
+
role,
|
|
272
|
+
source,
|
|
273
|
+
displayName,
|
|
274
|
+
longitude: input.longitude,
|
|
275
|
+
latitude: input.latitude,
|
|
276
|
+
routableLongitude: input.longitude,
|
|
277
|
+
routableLatitude: input.latitude,
|
|
278
|
+
accuracy: null,
|
|
279
|
+
matchType: null,
|
|
280
|
+
routablePointName: null,
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
async function geocodeRoutePoint(query, input) {
|
|
284
|
+
const url = new URL(`https://api.mapbox.com/search/geocode/${MAPBOX_GEOCODING_API_VERSION}/forward`);
|
|
285
|
+
url.searchParams.set('q', query);
|
|
286
|
+
url.searchParams.set('access_token', input.accessToken);
|
|
287
|
+
url.searchParams.set('limit', '1');
|
|
288
|
+
url.searchParams.set('autocomplete', 'false');
|
|
289
|
+
url.searchParams.set('permanent', 'false');
|
|
290
|
+
if (input.country && input.country.length > 0) {
|
|
291
|
+
url.searchParams.set('country', input.country.join(','));
|
|
292
|
+
}
|
|
293
|
+
if (input.language) {
|
|
294
|
+
url.searchParams.set('language', input.language);
|
|
295
|
+
}
|
|
296
|
+
const payload = await fetchMapboxJson({
|
|
297
|
+
fetchImpl: input.fetchImpl,
|
|
298
|
+
timeoutMs: input.timeoutMs,
|
|
299
|
+
url,
|
|
300
|
+
requestLabel: `${input.role} geocoding`,
|
|
301
|
+
});
|
|
302
|
+
const feature = payload.features?.[0];
|
|
303
|
+
if (!feature) {
|
|
304
|
+
throw new Error(`Mapbox could not geocode the ${input.role}.`);
|
|
305
|
+
}
|
|
306
|
+
const featureCoordinates = readFeatureCoordinates(feature);
|
|
307
|
+
if (!featureCoordinates) {
|
|
308
|
+
throw new Error(`Mapbox returned an unusable coordinate for the ${input.role}.`);
|
|
309
|
+
}
|
|
310
|
+
const routablePoint = selectRoutablePoint(feature);
|
|
311
|
+
const displayName = readFeatureDisplayName(feature);
|
|
312
|
+
const accuracy = normalizeNullableString(feature.properties?.coordinates?.accuracy) ?? null;
|
|
313
|
+
const matchType = normalizeNullableString(feature.properties?.feature_type) ?? null;
|
|
314
|
+
return {
|
|
315
|
+
role: input.role,
|
|
316
|
+
source: 'geocoded-query',
|
|
317
|
+
displayName,
|
|
318
|
+
longitude: featureCoordinates.longitude,
|
|
319
|
+
latitude: featureCoordinates.latitude,
|
|
320
|
+
routableLongitude: routablePoint?.longitude ?? featureCoordinates.longitude,
|
|
321
|
+
routableLatitude: routablePoint?.latitude ?? featureCoordinates.latitude,
|
|
322
|
+
accuracy,
|
|
323
|
+
matchType,
|
|
324
|
+
routablePointName: normalizeNullableString(routablePoint?.name) ?? null,
|
|
325
|
+
};
|
|
326
|
+
}
|
|
327
|
+
function readFeatureCoordinates(feature) {
|
|
328
|
+
const propertiesCoordinates = feature.properties?.coordinates;
|
|
329
|
+
const longitude = typeof propertiesCoordinates?.longitude === 'number'
|
|
330
|
+
? propertiesCoordinates.longitude
|
|
331
|
+
: feature.geometry?.coordinates?.[0];
|
|
332
|
+
const latitude = typeof propertiesCoordinates?.latitude === 'number'
|
|
333
|
+
? propertiesCoordinates.latitude
|
|
334
|
+
: feature.geometry?.coordinates?.[1];
|
|
335
|
+
if (typeof longitude !== 'number' || typeof latitude !== 'number') {
|
|
336
|
+
return null;
|
|
337
|
+
}
|
|
338
|
+
return {
|
|
339
|
+
longitude,
|
|
340
|
+
latitude,
|
|
341
|
+
};
|
|
342
|
+
}
|
|
343
|
+
function selectRoutablePoint(feature) {
|
|
344
|
+
const routablePoints = feature.properties?.coordinates?.routable_points ?? [];
|
|
345
|
+
const selectedPoint = routablePoints.find((point) => normalizeNullableString(point.name)?.toLowerCase() === 'default') ?? routablePoints[0];
|
|
346
|
+
if (!selectedPoint ||
|
|
347
|
+
typeof selectedPoint.longitude !== 'number' ||
|
|
348
|
+
typeof selectedPoint.latitude !== 'number') {
|
|
349
|
+
return null;
|
|
350
|
+
}
|
|
351
|
+
return {
|
|
352
|
+
longitude: selectedPoint.longitude,
|
|
353
|
+
latitude: selectedPoint.latitude,
|
|
354
|
+
name: normalizeNullableString(selectedPoint.name) ?? undefined,
|
|
355
|
+
};
|
|
356
|
+
}
|
|
357
|
+
function readFeatureDisplayName(feature) {
|
|
358
|
+
const fullAddress = normalizeNullableString(feature.properties?.full_address);
|
|
359
|
+
if (fullAddress) {
|
|
360
|
+
return fullAddress;
|
|
361
|
+
}
|
|
362
|
+
const preferredName = normalizeNullableString(feature.properties?.name_preferred) ??
|
|
363
|
+
normalizeNullableString(feature.properties?.name);
|
|
364
|
+
const placeFormatted = normalizeNullableString(feature.properties?.place_formatted);
|
|
365
|
+
const composite = [preferredName, placeFormatted].filter(Boolean).join(', ');
|
|
366
|
+
return composite || 'Resolved location';
|
|
367
|
+
}
|
|
368
|
+
async function requestDirections(input) {
|
|
369
|
+
const coordinatePath = input.points
|
|
370
|
+
.map((point) => `${point.routableLongitude},${point.routableLatitude}`)
|
|
371
|
+
.join(';');
|
|
372
|
+
const url = new URL(`https://api.mapbox.com/directions/${MAPBOX_DIRECTIONS_API_VERSION}/mapbox/${input.profile}/${coordinatePath}`);
|
|
373
|
+
url.searchParams.set('access_token', input.accessToken);
|
|
374
|
+
url.searchParams.set('alternatives', 'false');
|
|
375
|
+
url.searchParams.set('steps', 'false');
|
|
376
|
+
url.searchParams.set('overview', input.wantsGeometry ? 'full' : 'false');
|
|
377
|
+
if (input.wantsGeometry) {
|
|
378
|
+
url.searchParams.set('geometries', 'geojson');
|
|
379
|
+
}
|
|
380
|
+
const payload = await fetchMapboxJson({
|
|
381
|
+
fetchImpl: input.fetchImpl,
|
|
382
|
+
timeoutMs: input.timeoutMs,
|
|
383
|
+
url,
|
|
384
|
+
requestLabel: 'directions',
|
|
385
|
+
});
|
|
386
|
+
if (payload.code !== 'Ok') {
|
|
387
|
+
throw new Error(normalizeNullableString(payload.message) ??
|
|
388
|
+
'Mapbox did not return a route for these points.');
|
|
389
|
+
}
|
|
390
|
+
const route = payload.routes?.[0];
|
|
391
|
+
if (!route) {
|
|
392
|
+
throw new Error('Mapbox did not return a route for these points.');
|
|
393
|
+
}
|
|
394
|
+
return route;
|
|
395
|
+
}
|
|
396
|
+
function normalizeRouteGeometry(geometry) {
|
|
397
|
+
if (geometry?.type !== 'LineString' ||
|
|
398
|
+
!Array.isArray(geometry.coordinates) ||
|
|
399
|
+
geometry.coordinates.length < 2) {
|
|
400
|
+
return null;
|
|
401
|
+
}
|
|
402
|
+
const coordinates = geometry.coordinates.flatMap((point) => {
|
|
403
|
+
if (Array.isArray(point) &&
|
|
404
|
+
point.length >= 2 &&
|
|
405
|
+
typeof point[0] === 'number' &&
|
|
406
|
+
typeof point[1] === 'number') {
|
|
407
|
+
return [[point[0], point[1]]];
|
|
408
|
+
}
|
|
409
|
+
return [];
|
|
410
|
+
});
|
|
411
|
+
if (coordinates.length < 2) {
|
|
412
|
+
return null;
|
|
413
|
+
}
|
|
414
|
+
return {
|
|
415
|
+
type: 'LineString',
|
|
416
|
+
coordinates,
|
|
417
|
+
};
|
|
418
|
+
}
|
|
419
|
+
async function summarizeRouteElevation(input) {
|
|
420
|
+
const totalDistanceMeters = measureLineDistanceMeters(input.geometry.coordinates);
|
|
421
|
+
const sampleCount = resolveElevationSampleCount({
|
|
422
|
+
maxSamples: input.maxSamples,
|
|
423
|
+
sampleSpacingMeters: input.sampleSpacingMeters,
|
|
424
|
+
totalDistanceMeters,
|
|
425
|
+
});
|
|
426
|
+
const sampledPoints = sampleLineCoordinates(input.geometry.coordinates, sampleCount);
|
|
427
|
+
const elevations = await Promise.all(sampledPoints.map(async ([longitude, latitude]) => ({
|
|
428
|
+
elevationMeters: await queryElevationAtPoint({
|
|
429
|
+
accessToken: input.accessToken,
|
|
430
|
+
fetchImpl: input.fetchImpl,
|
|
431
|
+
latitude,
|
|
432
|
+
longitude,
|
|
433
|
+
timeoutMs: input.timeoutMs,
|
|
434
|
+
}),
|
|
435
|
+
latitude,
|
|
436
|
+
longitude,
|
|
437
|
+
})));
|
|
438
|
+
const knownSamples = elevations.filter((sample) => typeof sample.elevationMeters === 'number');
|
|
439
|
+
if (knownSamples.length < 2) {
|
|
440
|
+
return null;
|
|
441
|
+
}
|
|
442
|
+
let gainMeters = 0;
|
|
443
|
+
let lossMeters = 0;
|
|
444
|
+
let previousSample = knownSamples[0];
|
|
445
|
+
for (const sample of knownSamples.slice(1)) {
|
|
446
|
+
const delta = sample.elevationMeters - previousSample.elevationMeters;
|
|
447
|
+
if (delta > 0) {
|
|
448
|
+
gainMeters += delta;
|
|
449
|
+
}
|
|
450
|
+
else if (delta < 0) {
|
|
451
|
+
lossMeters += Math.abs(delta);
|
|
452
|
+
}
|
|
453
|
+
previousSample = sample;
|
|
454
|
+
}
|
|
455
|
+
const elevationValues = knownSamples.map((sample) => sample.elevationMeters);
|
|
456
|
+
const notes = [
|
|
457
|
+
'Approximate profile from Mapbox Terrain contour features sampled along the routed line.',
|
|
458
|
+
'Contour-derived elevations are quantized and may understate short, steep changes.',
|
|
459
|
+
];
|
|
460
|
+
if (knownSamples.length !== elevations.length) {
|
|
461
|
+
notes.push('Some sampled points did not return nearby contour data, so the profile is partially interpolated from nearby samples only.');
|
|
462
|
+
}
|
|
463
|
+
return {
|
|
464
|
+
approximate: true,
|
|
465
|
+
source: 'mapbox-terrain-v2-contour',
|
|
466
|
+
sampleCount: elevations.length,
|
|
467
|
+
resolvedSampleCount: knownSamples.length,
|
|
468
|
+
sampleSpacingMeters: roundTo(input.sampleSpacingMeters, 1),
|
|
469
|
+
queryRadiusMeters: DEFAULT_ELEVATION_QUERY_RADIUS_METERS,
|
|
470
|
+
gainMeters: roundTo(gainMeters, 1),
|
|
471
|
+
lossMeters: roundTo(lossMeters, 1),
|
|
472
|
+
netChangeMeters: roundTo(knownSamples[knownSamples.length - 1].elevationMeters - knownSamples[0].elevationMeters, 1),
|
|
473
|
+
minimumMeters: Math.min(...elevationValues),
|
|
474
|
+
maximumMeters: Math.max(...elevationValues),
|
|
475
|
+
startMeters: knownSamples[0].elevationMeters,
|
|
476
|
+
endMeters: knownSamples[knownSamples.length - 1].elevationMeters,
|
|
477
|
+
notes,
|
|
478
|
+
};
|
|
479
|
+
}
|
|
480
|
+
function resolveElevationSampleCount(input) {
|
|
481
|
+
if (input.totalDistanceMeters <= 0) {
|
|
482
|
+
return 1;
|
|
483
|
+
}
|
|
484
|
+
const desiredSamples = Math.ceil(input.totalDistanceMeters / input.sampleSpacingMeters) + 1;
|
|
485
|
+
return Math.max(2, Math.min(input.maxSamples, desiredSamples));
|
|
486
|
+
}
|
|
487
|
+
function sampleLineCoordinates(coordinates, sampleCount) {
|
|
488
|
+
if (coordinates.length === 0) {
|
|
489
|
+
return [];
|
|
490
|
+
}
|
|
491
|
+
if (coordinates.length === 1 || sampleCount <= 1) {
|
|
492
|
+
return [[coordinates[0][0], coordinates[0][1]]];
|
|
493
|
+
}
|
|
494
|
+
const cumulativeDistances = [0];
|
|
495
|
+
for (let index = 1; index < coordinates.length; index += 1) {
|
|
496
|
+
cumulativeDistances.push(cumulativeDistances[index - 1] +
|
|
497
|
+
haversineMeters(coordinates[index - 1], coordinates[index]));
|
|
498
|
+
}
|
|
499
|
+
const totalDistanceMeters = cumulativeDistances[cumulativeDistances.length - 1];
|
|
500
|
+
if (totalDistanceMeters <= 0) {
|
|
501
|
+
return [[coordinates[0][0], coordinates[0][1]]];
|
|
502
|
+
}
|
|
503
|
+
const samples = [];
|
|
504
|
+
let segmentIndex = 0;
|
|
505
|
+
for (let sampleIndex = 0; sampleIndex < sampleCount; sampleIndex += 1) {
|
|
506
|
+
const targetDistance = sampleCount === 1
|
|
507
|
+
? 0
|
|
508
|
+
: (totalDistanceMeters * sampleIndex) / (sampleCount - 1);
|
|
509
|
+
while (segmentIndex < cumulativeDistances.length - 2 &&
|
|
510
|
+
cumulativeDistances[segmentIndex + 1] < targetDistance) {
|
|
511
|
+
segmentIndex += 1;
|
|
512
|
+
}
|
|
513
|
+
const start = coordinates[segmentIndex];
|
|
514
|
+
const end = coordinates[Math.min(segmentIndex + 1, coordinates.length - 1)];
|
|
515
|
+
const segmentStartDistance = cumulativeDistances[segmentIndex];
|
|
516
|
+
const segmentEndDistance = cumulativeDistances[Math.min(segmentIndex + 1, cumulativeDistances.length - 1)];
|
|
517
|
+
const ratio = segmentEndDistance > segmentStartDistance
|
|
518
|
+
? (targetDistance - segmentStartDistance) /
|
|
519
|
+
(segmentEndDistance - segmentStartDistance)
|
|
520
|
+
: 0;
|
|
521
|
+
samples.push(interpolateCoordinate(start, end, ratio));
|
|
522
|
+
}
|
|
523
|
+
return dedupeAdjacentCoordinates(samples);
|
|
524
|
+
}
|
|
525
|
+
function interpolateCoordinate(start, end, ratio) {
|
|
526
|
+
const safeRatio = Math.max(0, Math.min(1, ratio));
|
|
527
|
+
return [
|
|
528
|
+
start[0] + (end[0] - start[0]) * safeRatio,
|
|
529
|
+
start[1] + (end[1] - start[1]) * safeRatio,
|
|
530
|
+
];
|
|
531
|
+
}
|
|
532
|
+
function dedupeAdjacentCoordinates(coordinates) {
|
|
533
|
+
const deduped = [];
|
|
534
|
+
for (const coordinate of coordinates) {
|
|
535
|
+
const previous = deduped[deduped.length - 1];
|
|
536
|
+
if (previous && previous[0] === coordinate[0] && previous[1] === coordinate[1]) {
|
|
537
|
+
continue;
|
|
538
|
+
}
|
|
539
|
+
deduped.push([coordinate[0], coordinate[1]]);
|
|
540
|
+
}
|
|
541
|
+
return deduped;
|
|
542
|
+
}
|
|
543
|
+
async function queryElevationAtPoint(input) {
|
|
544
|
+
const url = new URL(`https://api.mapbox.com/v4/mapbox.mapbox-terrain-v2/tilequery/${input.longitude},${input.latitude}.json`);
|
|
545
|
+
url.searchParams.set('access_token', input.accessToken);
|
|
546
|
+
url.searchParams.set('radius', String(DEFAULT_ELEVATION_QUERY_RADIUS_METERS));
|
|
547
|
+
url.searchParams.set('limit', '10');
|
|
548
|
+
url.searchParams.set('layers', 'contour');
|
|
549
|
+
url.searchParams.set('geometry', 'linestring');
|
|
550
|
+
const payload = await fetchMapboxJson({
|
|
551
|
+
allowNotFound: true,
|
|
552
|
+
fetchImpl: input.fetchImpl,
|
|
553
|
+
timeoutMs: input.timeoutMs,
|
|
554
|
+
url,
|
|
555
|
+
requestLabel: 'terrain elevation',
|
|
556
|
+
});
|
|
557
|
+
if (!payload) {
|
|
558
|
+
return null;
|
|
559
|
+
}
|
|
560
|
+
const feature = payload.features?.find((candidate) => {
|
|
561
|
+
const elevation = candidate.properties?.ele;
|
|
562
|
+
return typeof elevation === 'number' || typeof elevation === 'string';
|
|
563
|
+
});
|
|
564
|
+
const candidateElevation = feature?.properties?.ele;
|
|
565
|
+
if (typeof candidateElevation === 'number') {
|
|
566
|
+
return candidateElevation;
|
|
567
|
+
}
|
|
568
|
+
if (typeof candidateElevation === 'string') {
|
|
569
|
+
const parsed = Number.parseFloat(candidateElevation);
|
|
570
|
+
return Number.isFinite(parsed) ? parsed : null;
|
|
571
|
+
}
|
|
572
|
+
return null;
|
|
573
|
+
}
|
|
574
|
+
async function fetchMapboxJson(input) {
|
|
575
|
+
let response;
|
|
576
|
+
try {
|
|
577
|
+
response = await input.fetchImpl(input.url, {
|
|
578
|
+
headers: {
|
|
579
|
+
accept: 'application/json',
|
|
580
|
+
},
|
|
581
|
+
signal: AbortSignal.timeout(input.timeoutMs),
|
|
582
|
+
});
|
|
583
|
+
}
|
|
584
|
+
catch (error) {
|
|
585
|
+
throw new Error(`Mapbox ${input.requestLabel} request failed: ${errorMessage(error)}.`);
|
|
586
|
+
}
|
|
587
|
+
if (input.allowNotFound && response.status === 404) {
|
|
588
|
+
return null;
|
|
589
|
+
}
|
|
590
|
+
if (!response.ok) {
|
|
591
|
+
throw new Error(`Mapbox ${input.requestLabel} request failed (${await describeFailedMapboxResponse(response)}).`);
|
|
592
|
+
}
|
|
593
|
+
return (await response.json());
|
|
594
|
+
}
|
|
595
|
+
async function describeFailedMapboxResponse(response) {
|
|
596
|
+
const fallback = `HTTP ${response.status}`;
|
|
597
|
+
try {
|
|
598
|
+
const payload = (await response.json());
|
|
599
|
+
const message = typeof payload.message === 'string'
|
|
600
|
+
? normalizeNullableString(payload.message)
|
|
601
|
+
: null;
|
|
602
|
+
return message ? `${fallback}: ${message}` : fallback;
|
|
603
|
+
}
|
|
604
|
+
catch {
|
|
605
|
+
return fallback;
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
function parseCoordinateLiteral(value) {
|
|
609
|
+
const match = coordinateLiteralPattern.exec(value);
|
|
610
|
+
if (!match) {
|
|
611
|
+
return null;
|
|
612
|
+
}
|
|
613
|
+
const longitude = Number.parseFloat(match[1] ?? '');
|
|
614
|
+
const latitude = Number.parseFloat(match[2] ?? '');
|
|
615
|
+
if (!Number.isFinite(longitude) || !Number.isFinite(latitude)) {
|
|
616
|
+
return null;
|
|
617
|
+
}
|
|
618
|
+
if (longitude < -180 || longitude > 180 || latitude < -90 || latitude > 90) {
|
|
619
|
+
return null;
|
|
620
|
+
}
|
|
621
|
+
return {
|
|
622
|
+
longitude,
|
|
623
|
+
latitude,
|
|
624
|
+
};
|
|
625
|
+
}
|
|
626
|
+
function formatCoordinateLiteral(longitude, latitude) {
|
|
627
|
+
return `${longitude.toFixed(6)}, ${latitude.toFixed(6)}`;
|
|
628
|
+
}
|
|
629
|
+
function readMapboxAccessToken(env) {
|
|
630
|
+
return normalizeNullableString(env.MAPBOX_ACCESS_TOKEN);
|
|
631
|
+
}
|
|
632
|
+
function resolveMapboxTimeoutMs(env) {
|
|
633
|
+
const configured = normalizeNullableString(env.MURPH_MAPBOX_TIMEOUT_MS);
|
|
634
|
+
if (!configured) {
|
|
635
|
+
return DEFAULT_MAPBOX_TIMEOUT_MS;
|
|
636
|
+
}
|
|
637
|
+
const parsed = Number.parseInt(configured, 10);
|
|
638
|
+
if (!Number.isInteger(parsed) || parsed <= 0) {
|
|
639
|
+
return DEFAULT_MAPBOX_TIMEOUT_MS;
|
|
640
|
+
}
|
|
641
|
+
return Math.min(parsed, MAX_MAPBOX_TIMEOUT_MS);
|
|
642
|
+
}
|
|
643
|
+
function measureLineDistanceMeters(coordinates) {
|
|
644
|
+
let totalDistance = 0;
|
|
645
|
+
for (let index = 1; index < coordinates.length; index += 1) {
|
|
646
|
+
totalDistance += haversineMeters(coordinates[index - 1], coordinates[index]);
|
|
647
|
+
}
|
|
648
|
+
return totalDistance;
|
|
649
|
+
}
|
|
650
|
+
function haversineMeters(start, end) {
|
|
651
|
+
const earthRadiusMeters = 6_371_000;
|
|
652
|
+
const startLatitudeRadians = toRadians(start[1]);
|
|
653
|
+
const endLatitudeRadians = toRadians(end[1]);
|
|
654
|
+
const deltaLatitudeRadians = toRadians(end[1] - start[1]);
|
|
655
|
+
const deltaLongitudeRadians = toRadians(end[0] - start[0]);
|
|
656
|
+
const haversineValue = Math.sin(deltaLatitudeRadians / 2) ** 2 +
|
|
657
|
+
Math.cos(startLatitudeRadians) *
|
|
658
|
+
Math.cos(endLatitudeRadians) *
|
|
659
|
+
Math.sin(deltaLongitudeRadians / 2) ** 2;
|
|
660
|
+
const centralAngle = 2 * Math.atan2(Math.sqrt(haversineValue), Math.sqrt(1 - haversineValue));
|
|
661
|
+
return earthRadiusMeters * centralAngle;
|
|
662
|
+
}
|
|
663
|
+
function toRadians(value) {
|
|
664
|
+
return (value * Math.PI) / 180;
|
|
665
|
+
}
|
|
666
|
+
function roundTo(value, decimals) {
|
|
667
|
+
const factor = 10 ** decimals;
|
|
668
|
+
return Math.round(value * factor) / factor;
|
|
669
|
+
}
|
|
670
|
+
//# sourceMappingURL=mapbox-route.js.map
|