@danceiny/gotry 0.0.1-rc.21 → 0.0.1-rc.24
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 +102 -194
- package/README.zh-CN.md +115 -210
- package/bin/gotry-backend.js +53 -0
- package/bin/gotry-bootstrap.js +466 -35
- package/bin/gotry-inner.js +255 -36
- package/bin/gotry-process-liveness.d.ts +27 -0
- package/bin/gotry-process-liveness.js +97 -0
- package/bin/gotry-runtime-resolution.d.ts +1 -1
- package/bin/gotry-runtime-resolution.js +2 -2
- package/client/client.js +174 -0
- package/cordis.gotry-patch.yml +70 -93
- package/dist/capabilities/agent-reach-deep.js +45 -122
- package/dist/capabilities/agent-reach.js +63 -148
- package/dist/capabilities/anything.js +89 -75
- package/dist/capabilities/artifacts.js +123 -104
- package/dist/capabilities/channel-health.js +65 -45
- package/dist/capabilities/channel-registry.js +108 -87
- package/dist/capabilities/doctor.js +357 -235
- package/dist/capabilities/effect.js +223 -309
- package/dist/capabilities/fact-log.js +28 -14
- package/dist/capabilities/flyai.js +245 -156
- package/dist/capabilities/ground-transfer.js +509 -0
- package/dist/capabilities/hbcli.js +144 -148
- package/dist/capabilities/hotelbyte-transaction.js +194 -0
- package/dist/capabilities/incident-log.js +131 -61
- package/dist/capabilities/model-override.js +30 -10
- package/dist/capabilities/opensky.js +54 -43
- package/dist/capabilities/resilience.js +50 -32
- package/dist/capabilities/session/action-cache.js +50 -44
- package/dist/capabilities/session/adapters/ctrip-flight.js +119 -62
- package/dist/capabilities/session/adapters/ctrip-hotel.js +125 -110
- package/dist/capabilities/session/adapters/dida-portal.js +206 -0
- package/dist/capabilities/session/adapters/meituan-local.js +35 -54
- package/dist/capabilities/session/adapters/rail-12306.js +169 -112
- package/dist/capabilities/session/benchmark.js +85 -149
- package/dist/capabilities/session/extension-bridge.js +320 -259
- package/dist/capabilities/session/extension-channel.js +57 -76
- package/dist/capabilities/session/extension-distribution.js +115 -119
- package/dist/capabilities/session/extract.js +31 -21
- package/dist/capabilities/session/golden-score.js +55 -33
- package/dist/capabilities/session/health-watch.js +146 -154
- package/dist/capabilities/session/read-guard.js +55 -47
- package/dist/capabilities/session/static-flight-golden.js +49 -36
- package/dist/capabilities/session/transport.js +54 -61
- package/dist/capabilities/session/wizard.js +63 -93
- package/dist/capabilities/session-consent.js +143 -47
- package/dist/capabilities/session-login.js +95 -126
- package/dist/capabilities/session-search.js +391 -253
- package/dist/capabilities/sponsor-plugin.js +341 -0
- package/dist/capabilities/visa-policy.js +87 -73
- package/dist/capabilities/weather.js +163 -235
- package/dist/scripts/action-cache-tests.js +24 -59
- package/dist/scripts/agent-planning-turn-deadline-e2e.js +91 -142
- package/dist/scripts/agent-planning-turn-deadline-tests.js +80 -176
- package/dist/scripts/agent-reach-deep-tests.js +30 -40
- package/dist/scripts/agent-reach-tests.js +26 -18
- package/dist/scripts/agent-reach-wrapper-tests.js +46 -50
- package/dist/scripts/anything-tests.js +58 -62
- package/dist/scripts/artifact-client-contract-tests.js +69 -0
- package/dist/scripts/artifacts-capability-tests.js +235 -0
- package/dist/scripts/async-collect.js +26 -15
- package/dist/scripts/benchmark-environment-bridge-e2e.js +287 -684
- package/dist/scripts/benchmark-environment-bridge-tests.js +818 -3123
- package/dist/scripts/booking-copilot-availability-ledger-binding-tests.js +66 -279
- package/dist/scripts/booking-copilot-availability-policy-tests.js +153 -1257
- package/dist/scripts/booking-copilot-bin-proof-tests.js +22 -45
- package/dist/scripts/booking-copilot-crossrepo-fixture-server.js +64 -76
- package/dist/scripts/booking-copilot-dsh-core-proof-tests.js +69 -199
- package/dist/scripts/booking-copilot-dsh-planner-proof-tests.js +393 -759
- package/dist/scripts/booking-copilot-dsh-plugin-proof-tests.js +33 -75
- package/dist/scripts/booking-copilot-event-sequence-concurrency-proof-tests.js +60 -110
- package/dist/scripts/booking-copilot-gap-code-contract-proof-tests.js +28 -66
- package/dist/scripts/booking-copilot-operation-ledger-concurrency-proof-tests.js +72 -271
- package/dist/scripts/booking-copilot-receipt-ledger-concurrency-proof-tests.js +73 -275
- package/dist/scripts/booking-copilot-runtime-proof-tests.js +784 -3398
- package/dist/scripts/booking-copilot-server-proof-tests.js +63 -168
- package/dist/scripts/booking-copilot-startup-proof-tests.js +38 -124
- package/dist/scripts/booking-executor-tests.js +80 -0
- package/dist/scripts/booking-recovery-chain-tests.js +182 -0
- package/dist/scripts/booking-saga-tests.js +61 -123
- package/dist/scripts/booking-surface-contract-proof-tests.js +152 -1167
- package/dist/scripts/bootstrap-tests.js +1383 -128
- package/dist/scripts/build-changelog.js +89 -130
- package/dist/scripts/build-metrics-report.js +135 -143
- package/dist/scripts/changelog-tests.js +35 -82
- package/dist/scripts/channel-probe-tests.js +44 -116
- package/dist/scripts/channel-probe.js +84 -167
- package/dist/scripts/channel-registry-tests.js +97 -185
- package/dist/scripts/companion-tests.js +25 -70
- package/dist/scripts/dida-cookie-probe.js +87 -0
- package/dist/scripts/dida-sw-probe.js +114 -0
- package/dist/scripts/diff-test.js +17 -12
- package/dist/scripts/doctor-tests.js +428 -120
- package/dist/scripts/dsh-artifact-e2e.js +150 -0
- package/dist/scripts/dsh-artifact-web-e2e.js +443 -0
- package/dist/scripts/dsh-http-proxy-sse-proof.js +90 -0
- package/dist/scripts/dsh-runtime-closure-tests.js +110 -153
- package/dist/scripts/dsh-runtime-closure.js +75 -63
- package/dist/scripts/dsh-session-v3-migration-proof.js +106 -0
- package/dist/scripts/dsh-subprocess-local-proof.js +149 -0
- package/dist/scripts/dsh-target-closure-proof.js +178 -0
- package/dist/scripts/effect-tests.js +165 -552
- package/dist/scripts/engine-run.js +1 -3
- package/dist/scripts/engine-tests.js +16 -10
- package/dist/scripts/evaluation-cadence-tests.js +69 -242
- package/dist/scripts/evaluation-contract-tests.js +193 -499
- package/dist/scripts/extension-distribution-cli.js +19 -15
- package/dist/scripts/extension-distribution-tests.js +98 -211
- package/dist/scripts/extension-tests.js +366 -389
- package/dist/scripts/fact-gate-tests.js +725 -263
- package/dist/scripts/fixtures/extension-bridge-unref-child.mjs +21 -0
- package/dist/scripts/flight-hotel-anchor-363-registered-e2e.js +169 -0
- package/dist/scripts/flight-malformed-tests.js +159 -0
- package/dist/scripts/flyai-tests.js +325 -68
- package/dist/scripts/g5-guard-tests.js +131 -0
- package/dist/scripts/g5-guard.js +189 -0
- package/dist/scripts/gen-lunar-anchors.js +106 -0
- package/dist/scripts/gotry-backend-kernel-tests.js +79 -0
- package/dist/scripts/ground-transfer-tests.js +956 -0
- package/dist/scripts/hbcli-e2e-tests.js +71 -90
- package/dist/scripts/hbcli-live-optin-tests.js +96 -0
- package/dist/scripts/hbcli-release-contract-tests.js +122 -0
- package/dist/scripts/hbcli-tests.js +41 -115
- package/dist/scripts/health-watch-cli.js +42 -28
- package/dist/scripts/hotel-date-gate-tests.js +496 -0
- package/dist/scripts/hotelbyte-reconcile-tests.js +141 -0
- package/dist/scripts/hotelbyte-spawn-e2e-tests.js +373 -0
- package/dist/scripts/i18n-tests.js +16 -9
- package/dist/scripts/incident-tests.js +281 -87
- package/dist/scripts/issue-194-job-id-guard-tests.js +106 -0
- package/dist/scripts/issue-233-cancel-refund-commission-tests.js +392 -0
- package/dist/scripts/issue-271-liveness-tests.js +547 -0
- package/dist/scripts/issue-289-model-retry-real-tests.js +296 -0
- package/dist/scripts/issue-289-model-retry-tests.js +441 -0
- package/dist/scripts/issue-289-web-retry-e2e.js +630 -0
- package/dist/scripts/issue-338-home-city-e2e.js +577 -0
- package/dist/scripts/issue343-real-entry-e2e.js +160 -0
- package/dist/scripts/journey-tests.js +22 -27
- package/dist/scripts/kernel-manifest-evidence.js +109 -0
- package/dist/scripts/kernel-manifest-gate.js +126 -0
- package/dist/scripts/kernel-manifest-tests.js +212 -0
- package/dist/scripts/kernel-manifest-trace-entry.js +7 -0
- package/dist/scripts/kernel-manifest-trace-hook.mjs +28 -0
- package/dist/scripts/kernel-manifest.js +369 -0
- package/dist/scripts/ledger-repair-apply-tests.js +316 -0
- package/dist/scripts/ledger-repair-plan-tests.js +375 -0
- package/dist/scripts/ledger-tests.js +325 -340
- package/dist/scripts/ledger-workflow-crash.js +10 -20
- package/dist/scripts/map-tools-vendor-package-proof.js +206 -134
- package/dist/scripts/memory-capture-tests.js +242 -61
- package/dist/scripts/memory-decay-tests.js +18 -52
- package/dist/scripts/memory-lifecycle-tests.js +657 -0
- package/dist/scripts/memory-lifecycle.js +196 -0
- package/dist/scripts/memory-metrics.js +24 -12
- package/dist/scripts/memory-value-report.js +474 -222
- package/dist/scripts/memory-value-tests.js +328 -0
- package/dist/scripts/metrics-report-tests.js +62 -199
- package/dist/scripts/model-override-e2e.js +66 -105
- package/dist/scripts/nightly-evidence-tests.js +64 -89
- package/dist/scripts/nightly-evidence.js +108 -111
- package/dist/scripts/nudge-digest.js +39 -28
- package/dist/scripts/onboarding-tests.js +63 -88
- package/dist/scripts/opensky-check.js +18 -13
- package/dist/scripts/opensky-tests.js +28 -28
- package/dist/scripts/persona-surface-guard-tests.js +37 -12
- package/dist/scripts/pnpm-dsh-closure-proof.js +2 -5
- package/dist/scripts/policy-anchor-359-registered-e2e.js +214 -0
- package/dist/scripts/price-drift-tests.js +126 -227
- package/dist/scripts/price-drift-watch.js +209 -253
- package/dist/scripts/probe-poi-tests.js +52 -45
- package/dist/scripts/product-metrics.js +187 -273
- package/dist/scripts/publish-preverify.js +47 -41
- package/dist/scripts/realtime-pricing-tests.js +37 -76
- package/dist/scripts/replay-async.js +20 -18
- package/dist/scripts/replay-real.js +16 -18
- package/dist/scripts/replay.js +52 -82
- package/dist/scripts/session-attach-diagnose.js +16 -12
- package/dist/scripts/session-attach-poc.js +34 -37
- package/dist/scripts/session-benchmark.js +66 -147
- package/dist/scripts/session-dida-live-e2e.js +151 -0
- package/dist/scripts/session-extract-tests.js +14 -35
- package/dist/scripts/session-login.js +14 -13
- package/dist/scripts/session-tests.js +545 -583
- package/dist/scripts/sf-live-benchmark.js +92 -110
- package/dist/scripts/sf-live-cli-tests.js +2 -9
- package/dist/scripts/sf-soft-score-tests.js +19 -60
- package/dist/scripts/sf-summary-tests.js +149 -0
- package/dist/scripts/sf-summary.js +466 -84
- package/dist/scripts/skeleton-check.js +16 -14
- package/dist/scripts/skeleton-integration-test.js +11 -10
- package/dist/scripts/skills-contract-tests.js +51 -46
- package/dist/scripts/smoke-session-gate-tests.js +12 -20
- package/dist/scripts/smoke.js +541 -422
- package/dist/scripts/sponsor-reuse-tests.js +262 -0
- package/dist/scripts/state-cli-tests.js +226 -101
- package/dist/scripts/state-cli.js +549 -212
- package/dist/scripts/static-golden-tests.js +46 -180
- package/dist/scripts/time-eval-tests.js +210 -192
- package/dist/scripts/travel-timeline-tests.js +29 -93
- package/dist/scripts/turn-handoff-collect-tests.js +46 -93
- package/dist/scripts/turn-handoff-collect.js +71 -99
- package/dist/scripts/turn-policy-tests.js +28 -29
- package/dist/scripts/typed-contract-canary.js +77 -162
- package/dist/scripts/tz-resolver-sanity.js +50 -0
- package/dist/scripts/tz-resolver-tests.js +584 -0
- package/dist/scripts/unified-tests.js +23 -19
- package/dist/scripts/visa-policy-tests.js +31 -44
- package/dist/scripts/weather-tests.js +121 -543
- package/dist/scripts/wish-channel-gate-tests.js +29 -86
- package/dist/scripts/write-gate-tests.js +522 -0
- package/dist/scripts/z3-lifecycle-fault-tests.js +188 -0
- package/dist/scripts/z3-lifecycle-tests.js +170 -0
- package/dist/scripts/z3-race-repeat-tests.js +39 -0
- package/dist/scripts/z3-race-tests.js +18 -12
- package/dist/src/artifact-gate.js +652 -221
- package/dist/src/backend/kernel.js +91 -0
- package/dist/src/backend/modules/booking-copilot.js +55 -0
- package/dist/src/backend/modules/booking-executor.js +124 -0
- package/dist/src/backend/modules/session-search.js +194 -0
- package/dist/src/benchmark-agent-conformance.js +296 -262
- package/dist/src/benchmark-environment-bridge.js +298 -413
- package/dist/src/benchmark-headless-child-diagnostics.js +93 -100
- package/dist/src/benchmark-tool-isolation.js +94 -67
- package/dist/src/bookable-facts.js +299 -174
- package/dist/src/booking-saga.js +64 -120
- package/dist/src/booking-surface/availability-policy.js +443 -576
- package/dist/src/booking-surface/cancel-refund-commission.js +379 -0
- package/dist/src/booking-surface/contracts.js +24 -80
- package/dist/src/booking-surface/dsh-planner.js +510 -281
- package/dist/src/booking-surface/dsh-plugin.js +17 -1
- package/dist/src/booking-surface/error-codes.js +82 -10
- package/dist/src/booking-surface/index.js +0 -3
- package/dist/src/booking-surface/profile.js +10 -54
- package/dist/src/booking-surface/recovery-chain.js +367 -0
- package/dist/src/booking-surface/runtime.js +765 -891
- package/dist/src/booking-surface/server.js +200 -251
- package/dist/src/booking-surface/startup.js +58 -54
- package/dist/src/booking-surface/validation.js +150 -218
- package/dist/src/bridge.js +20 -13
- package/dist/src/companions.js +48 -74
- package/dist/src/contracts.js +21 -43
- package/dist/src/dsh-llm.js +82 -120
- package/dist/src/engine.js +253 -239
- package/dist/src/evaluation-cadence.js +72 -209
- package/dist/src/evaluation-contracts.js +220 -843
- package/dist/src/flight-pack-adapter.js +54 -0
- package/dist/src/flight-pack-contract.js +2 -0
- package/dist/src/gotry-backend.js +70 -0
- package/dist/src/hotel-date-gate.js +118 -0
- package/dist/src/i18n.js +18 -7
- package/dist/src/index.js +1425 -1629
- package/dist/src/journey.js +132 -81
- package/dist/src/ledger-repair-apply.js +538 -0
- package/dist/src/ledger-repair-plan.js +463 -0
- package/dist/src/loop.js +257 -166
- package/dist/src/memory-capture.js +180 -32
- package/dist/src/memory-decay.js +30 -14
- package/dist/src/memory-lifecycle.js +1310 -0
- package/dist/src/memory-utility.js +27 -40
- package/dist/src/mock-llm.js +36 -61
- package/dist/src/model.js +81 -36
- package/dist/src/realtime-pricing.js +63 -72
- package/dist/src/slot-spec.js +84 -106
- package/dist/src/state-ledger.js +310 -471
- package/dist/src/subagent-job-id-guard.js +58 -0
- package/dist/src/time-anchor.js +231 -33
- package/dist/src/tool-packet.js +20 -8
- package/dist/src/travel-slots.js +58 -35
- package/dist/src/travel-timeline.js +44 -63
- package/dist/src/turn-deadline.js +121 -127
- package/dist/src/turn-policy.js +61 -89
- package/dist/src/tz-resolver.js +143 -0
- package/dist/src/unified.js +504 -381
- package/dist/src/wish-pool.js +39 -14
- package/dist/src/write-gate.js +495 -0
- package/dist/src/z3-shared.js +273 -7
- package/extension/README.md +4 -3
- package/extension/background.js +125 -14
- package/extension/content-bridge.js +23 -1
- package/extension/content-main.js +15 -4
- package/extension/manifest.json +16 -8
- package/package.json +251 -220
- package/ts/capabilities/agent-reach.ts +4 -4
- package/ts/capabilities/anything.ts +1 -1
- package/ts/capabilities/artifacts.ts +46 -9
- package/ts/capabilities/flyai.ts +156 -37
- package/ts/capabilities/hbcli.ts +1 -1
- package/ts/capabilities/incident-log.ts +88 -59
- package/ts/capabilities/session/adapters/ctrip-flight.ts +99 -36
- package/ts/capabilities/session/adapters/dida-portal.ts +250 -0
- package/ts/capabilities/session/adapters/rail-12306.ts +123 -24
- package/ts/capabilities/session/extension-bridge.ts +226 -101
- package/ts/capabilities/session/extension-channel.ts +22 -9
- package/ts/capabilities/session/wizard.ts +2 -2
- package/ts/capabilities/session-consent.ts +98 -14
- package/ts/capabilities/session-login.ts +1 -0
- package/ts/capabilities/session-search.ts +315 -31
- package/ts/dsh-runtime/vendor/README.md +8 -4
- package/ts/dsh-runtime/vendor/dsh-map-tools/README.en.md +7 -1
- package/ts/dsh-runtime/vendor/dsh-map-tools/README.md +5 -3
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/settings-ns.js +1 -1
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/settings-ns.d.ts +1 -1
- package/ts/dsh-runtime/vendor/dsh-map-tools/package.json +6 -6
- package/ts/package.json +236 -16
- package/ts/scripts/map-tools-vendor-package-proof.ts +152 -31
- package/ts/scripts/state-cli.ts +418 -51
- package/ts/src/artifact-gate.ts +561 -15
- package/ts/src/benchmark-environment-bridge.ts +37 -20
- package/ts/src/bookable-facts.ts +183 -7
- package/ts/src/booking-surface/cancel-refund-commission.ts +488 -0
- package/ts/src/booking-surface/dsh-planner.ts +240 -32
- package/ts/src/booking-surface/dsh-plugin.js +17 -1
- package/ts/src/booking-surface/error-codes.ts +75 -0
- package/ts/src/booking-surface/recovery-chain.ts +409 -0
- package/ts/src/booking-surface/server.ts +84 -51
- package/ts/src/booking-surface/validation.ts +2 -0
- package/ts/src/contracts.ts +12 -2
- package/ts/src/dsh-llm.ts +4 -8
- package/ts/src/index.ts +609 -90
- package/ts/src/loop.ts +116 -7
- package/ts/src/memory-capture.ts +227 -3
- package/ts/src/mock-llm.ts +4 -8
- package/ts/src/model.ts +88 -2
- package/ts/src/state-ledger.ts +47 -29
- package/ts/src/subagent-job-id-guard.ts +72 -0
- package/ts/src/time-anchor.ts +217 -13
- package/ts/src/unified.ts +316 -54
|
@@ -0,0 +1,509 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bounded ground-transfer seam for explicit airport/station coordinates.
|
|
3
|
+
*
|
|
4
|
+
* This capability owns only the boundary between a public
|
|
5
|
+
* `map_driving_route` result and the deterministic candidate input. It never
|
|
6
|
+
* calls a map client directly, never writes shared state, and never supplies
|
|
7
|
+
* a price: the selected candidate transfer keeps its static `priceCny`.
|
|
8
|
+
*
|
|
9
|
+
* Each `ground_transfer` request resolves two deterministic directions —
|
|
10
|
+
* outbound (origin → destination) and return (destination → origin) — and
|
|
11
|
+
* binds the resulting route minutes to the candidate transfer as
|
|
12
|
+
* `minutesOut` / `minutesRet` overrides consumed by the solver / arithmetic
|
|
13
|
+
* layer. Cache, freshness, and fallback are isolated per direction so a
|
|
14
|
+
* miss / error / stale / mismatch in one direction never borrows the
|
|
15
|
+
* dynamic value of the other.
|
|
16
|
+
*/
|
|
17
|
+
import { ToolCallId } from '@deepseek-ai/dsh-llm';
|
|
18
|
+
export const GROUND_TRANSFER_MODE = 'driving';
|
|
19
|
+
export const GROUND_TRANSFER_POSITION = 'destination';
|
|
20
|
+
export const GROUND_TRANSFER_STATIC_MODE = 'taxi';
|
|
21
|
+
export const GROUND_TRANSFER_STATIC_MODE_MISMATCH = 'ground_transfer_static_mode_mismatch';
|
|
22
|
+
export const GROUND_TRANSFER_STATIC_PRICE_EVIDENCE = '[静态包:估算]';
|
|
23
|
+
export const GROUND_TRANSFER_DEFAULT_MAX_AGE_S = 15 * 60;
|
|
24
|
+
const GROUND_TRANSFER_CACHE_LIMIT = 32;
|
|
25
|
+
function asRecord(value) {
|
|
26
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value)
|
|
27
|
+
? value
|
|
28
|
+
: null;
|
|
29
|
+
}
|
|
30
|
+
function coordinate(value) {
|
|
31
|
+
const record = asRecord(value);
|
|
32
|
+
const longitude = record?.['longitude'];
|
|
33
|
+
const latitude = record?.['latitude'];
|
|
34
|
+
if (typeof longitude !== 'number' || !Number.isFinite(longitude) || longitude < -180 || longitude > 180
|
|
35
|
+
|| typeof latitude !== 'number' || !Number.isFinite(latitude) || latitude < -90 || latitude > 90)
|
|
36
|
+
return null;
|
|
37
|
+
return { longitude, latitude };
|
|
38
|
+
}
|
|
39
|
+
function coordinatePair(value) {
|
|
40
|
+
const record = asRecord(value);
|
|
41
|
+
if (!record)
|
|
42
|
+
return null;
|
|
43
|
+
const origin = coordinate(record['origin']);
|
|
44
|
+
const destination = coordinate(record['destination']);
|
|
45
|
+
if (origin === null || destination === null)
|
|
46
|
+
return null;
|
|
47
|
+
return { origin, destination };
|
|
48
|
+
}
|
|
49
|
+
function coordinateText(value) {
|
|
50
|
+
return `${String(value.longitude)},${String(value.latitude)}`;
|
|
51
|
+
}
|
|
52
|
+
function cacheKey(direction) {
|
|
53
|
+
return JSON.stringify({
|
|
54
|
+
mode: GROUND_TRANSFER_MODE,
|
|
55
|
+
direction: direction.direction,
|
|
56
|
+
origin: direction.origin,
|
|
57
|
+
destination: direction.destination,
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
function nowMs(clock) {
|
|
61
|
+
const value = clock().getTime();
|
|
62
|
+
if (!Number.isFinite(value))
|
|
63
|
+
throw new Error('ground-transfer clock returned an invalid date');
|
|
64
|
+
return value;
|
|
65
|
+
}
|
|
66
|
+
function asOf(clock) {
|
|
67
|
+
return new Date(nowMs(clock)).toISOString();
|
|
68
|
+
}
|
|
69
|
+
function safeErrorMessage(value) {
|
|
70
|
+
const message = value instanceof Error ? value.message : String(value);
|
|
71
|
+
return message.replace(/[\r\n]+/g, ' ').trim().slice(0, 400) || 'unknown provider error';
|
|
72
|
+
}
|
|
73
|
+
function staticCache(status = 'bypass') {
|
|
74
|
+
return { status, ageS: null, asOf: null };
|
|
75
|
+
}
|
|
76
|
+
function notAppliedBase(parts) {
|
|
77
|
+
return {
|
|
78
|
+
requestedMode: parts.requestedMode,
|
|
79
|
+
requestedPosition: parts.requestedPosition,
|
|
80
|
+
provenance: 'none',
|
|
81
|
+
asOf: null,
|
|
82
|
+
freshness: 'not_attempted',
|
|
83
|
+
cache: staticCache('bypass'),
|
|
84
|
+
evidenceClass: 'none',
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
function staticDirectionResolution(direction, staticMinutes, reason) {
|
|
88
|
+
return {
|
|
89
|
+
applied: false,
|
|
90
|
+
direction,
|
|
91
|
+
staticMinutes,
|
|
92
|
+
provenance: 'static-transfer-pack',
|
|
93
|
+
asOf: null,
|
|
94
|
+
freshness: 'fallback',
|
|
95
|
+
cache: staticCache('bypass'),
|
|
96
|
+
evidenceClass: 'static_transfer_estimate',
|
|
97
|
+
fallbackReason: reason,
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
function parseRequest(value) {
|
|
101
|
+
const record = asRecord(value);
|
|
102
|
+
if (!record) {
|
|
103
|
+
return {
|
|
104
|
+
candidateId: null,
|
|
105
|
+
transferIndex: null,
|
|
106
|
+
requestedMode: null,
|
|
107
|
+
requestedPosition: null,
|
|
108
|
+
outbound: null,
|
|
109
|
+
ret: null,
|
|
110
|
+
invalidReason: 'ground_transfer_request_invalid:expected an object',
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
const candidateId = typeof record['candidate_id'] === 'string' && record['candidate_id'].trim()
|
|
114
|
+
? record['candidate_id']
|
|
115
|
+
: null;
|
|
116
|
+
const transferIndex = Number.isInteger(record['transfer_index']) && Number(record['transfer_index']) >= 0
|
|
117
|
+
? Number(record['transfer_index'])
|
|
118
|
+
: null;
|
|
119
|
+
const requestedMode = typeof record['mode'] === 'string' && record['mode'].trim()
|
|
120
|
+
? record['mode']
|
|
121
|
+
: null;
|
|
122
|
+
const requestedPosition = typeof record['position'] === 'string' && record['position'].trim()
|
|
123
|
+
? record['position']
|
|
124
|
+
: null;
|
|
125
|
+
let outbound = null;
|
|
126
|
+
let ret = null;
|
|
127
|
+
if (record['outbound'] !== undefined || record['return'] !== undefined) {
|
|
128
|
+
outbound = coordinatePair(record['outbound']);
|
|
129
|
+
ret = coordinatePair(record['return']);
|
|
130
|
+
}
|
|
131
|
+
else {
|
|
132
|
+
// Legacy single-pair shape: outbound = origin/destination; return = the
|
|
133
|
+
// swapped pair. Keeps pre-#364 payloads behavior-compatible.
|
|
134
|
+
const legacyOrigin = coordinate(record['origin']);
|
|
135
|
+
const legacyDestination = coordinate(record['destination']);
|
|
136
|
+
if (legacyOrigin && legacyDestination) {
|
|
137
|
+
outbound = { origin: legacyOrigin, destination: legacyDestination };
|
|
138
|
+
ret = { origin: legacyDestination, destination: legacyOrigin };
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
let invalidReason;
|
|
142
|
+
if (candidateId === null)
|
|
143
|
+
invalidReason = 'ground_transfer_binding_invalid:candidate_id is required';
|
|
144
|
+
else if (transferIndex === null)
|
|
145
|
+
invalidReason = 'ground_transfer_binding_invalid:transfer_index must be a non-negative integer';
|
|
146
|
+
else if (requestedPosition !== GROUND_TRANSFER_POSITION)
|
|
147
|
+
invalidReason = `unsupported_ground_transfer_position:${requestedPosition ?? 'unknown'}`;
|
|
148
|
+
else if (requestedMode !== GROUND_TRANSFER_MODE)
|
|
149
|
+
invalidReason = `unsupported_ground_transfer_mode:${requestedMode ?? 'unknown'}`;
|
|
150
|
+
else if (outbound === null || ret === null)
|
|
151
|
+
invalidReason = 'invalid_ground_transfer_coordinates:longitude/latitude must be finite and in range';
|
|
152
|
+
return {
|
|
153
|
+
candidateId,
|
|
154
|
+
transferIndex,
|
|
155
|
+
requestedMode,
|
|
156
|
+
requestedPosition,
|
|
157
|
+
outbound,
|
|
158
|
+
ret,
|
|
159
|
+
invalidReason,
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
function completeRequest(parts) {
|
|
163
|
+
if (parts.invalidReason || parts.candidateId === null || parts.transferIndex === null
|
|
164
|
+
|| parts.requestedMode !== GROUND_TRANSFER_MODE || parts.requestedPosition !== GROUND_TRANSFER_POSITION
|
|
165
|
+
|| parts.outbound === null || parts.ret === null)
|
|
166
|
+
return null;
|
|
167
|
+
return {
|
|
168
|
+
candidateId: parts.candidateId,
|
|
169
|
+
transferIndex: parts.transferIndex,
|
|
170
|
+
mode: GROUND_TRANSFER_MODE,
|
|
171
|
+
position: GROUND_TRANSFER_POSITION,
|
|
172
|
+
outbound: { direction: 'outbound', origin: parts.outbound.origin, destination: parts.outbound.destination },
|
|
173
|
+
ret: { direction: 'return', origin: parts.ret.origin, destination: parts.ret.destination },
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
function parseRouteResult(value) {
|
|
177
|
+
const record = asRecord(value);
|
|
178
|
+
if (!record)
|
|
179
|
+
throw new Error('map_driving_route returned an invalid public result');
|
|
180
|
+
const provider = record?.['provider'];
|
|
181
|
+
const distanceM = record?.['distanceM'];
|
|
182
|
+
const durationS = record?.['durationS'];
|
|
183
|
+
if (typeof provider !== 'string' || provider.trim() === ''
|
|
184
|
+
|| typeof distanceM !== 'number' || !Number.isFinite(distanceM) || distanceM < 0
|
|
185
|
+
|| typeof durationS !== 'number' || !Number.isFinite(durationS) || durationS < 0
|
|
186
|
+
// A positive distance cannot be covered in zero time: such a response is
|
|
187
|
+
// a self-contradictory route fact, not a credible provider hit. It falls
|
|
188
|
+
// into the explicit provider-miss fallback instead of silently binding
|
|
189
|
+
// zero minutes into the solver.
|
|
190
|
+
|| (distanceM > 0 && durationS === 0))
|
|
191
|
+
throw new Error('map_driving_route returned an invalid public result');
|
|
192
|
+
return {
|
|
193
|
+
provider,
|
|
194
|
+
distanceM,
|
|
195
|
+
durationS,
|
|
196
|
+
...(typeof record['polyline'] === 'string' ? { polyline: record['polyline'] } : {}),
|
|
197
|
+
...(Array.isArray(record['steps']) ? { steps: record['steps'] } : {}),
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
function buildRouteFact(direction, route, clock, cache) {
|
|
201
|
+
return {
|
|
202
|
+
direction: direction.direction,
|
|
203
|
+
origin: direction.origin,
|
|
204
|
+
destination: direction.destination,
|
|
205
|
+
mode: GROUND_TRANSFER_MODE,
|
|
206
|
+
provider: route.provider,
|
|
207
|
+
distanceM: route.distanceM,
|
|
208
|
+
durationS: route.durationS,
|
|
209
|
+
durationMin: Math.round(route.durationS / 60),
|
|
210
|
+
asOf: asOf(clock),
|
|
211
|
+
provenance: 'map_driving_route',
|
|
212
|
+
freshness: cache.status === 'hit' ? 'cache_hit' : 'fresh',
|
|
213
|
+
cache,
|
|
214
|
+
evidenceClass: 'public_map_route_estimate',
|
|
215
|
+
trafficStatus: 'not-live-route-estimate',
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
function selectionFor(candidates, parts) {
|
|
219
|
+
if (parts.candidateId === null || parts.transferIndex === null)
|
|
220
|
+
return null;
|
|
221
|
+
const matching = candidates
|
|
222
|
+
.map((candidate, index) => ({ candidate, index }))
|
|
223
|
+
.filter(({ candidate }) => candidate.id === parts.candidateId);
|
|
224
|
+
if (matching.length !== 1)
|
|
225
|
+
return null;
|
|
226
|
+
const match = matching[0];
|
|
227
|
+
const transfer = match.candidate.destTransfers[parts.transferIndex];
|
|
228
|
+
if (!transfer || typeof transfer.mode !== 'string' || !Number.isFinite(transfer.minutes) || transfer.minutes < 0 || !Number.isFinite(transfer.priceCny) || transfer.priceCny < 0)
|
|
229
|
+
return null;
|
|
230
|
+
return {
|
|
231
|
+
candidateIndex: match.index,
|
|
232
|
+
selection: {
|
|
233
|
+
candidateId: parts.candidateId,
|
|
234
|
+
transferIndex: parts.transferIndex,
|
|
235
|
+
staticMode: transfer.mode,
|
|
236
|
+
staticMinutes: transfer.minutes,
|
|
237
|
+
staticPriceCny: transfer.priceCny,
|
|
238
|
+
},
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
async function resolveDirection(direction, staticMinutes, provider, clock, cache, maxEntries, context) {
|
|
242
|
+
const key = cacheKey(direction);
|
|
243
|
+
const currentMs = nowMs(clock);
|
|
244
|
+
const cached = cache.get(key);
|
|
245
|
+
if (cached) {
|
|
246
|
+
const ageS = Math.max(0, currentMs - cached.storedAtMs) / 1000;
|
|
247
|
+
if (ageS <= GROUND_TRANSFER_DEFAULT_MAX_AGE_S) {
|
|
248
|
+
const hitCache = { status: 'hit', ageS, asOf: cached.fact.asOf };
|
|
249
|
+
const hitFact = { ...cached.fact, freshness: 'cache_hit', cache: hitCache };
|
|
250
|
+
return {
|
|
251
|
+
applied: true,
|
|
252
|
+
direction: direction.direction,
|
|
253
|
+
minutes: hitFact.durationMin,
|
|
254
|
+
staticMinutes,
|
|
255
|
+
provenance: 'map_driving_route',
|
|
256
|
+
asOf: hitFact.asOf,
|
|
257
|
+
freshness: 'cache_hit',
|
|
258
|
+
cache: hitCache,
|
|
259
|
+
evidenceClass: 'public_map_route_estimate',
|
|
260
|
+
routeFact: hitFact,
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
const requestForProvider = {
|
|
265
|
+
origin: coordinateText(direction.origin),
|
|
266
|
+
destination: coordinateText(direction.destination),
|
|
267
|
+
mode: GROUND_TRANSFER_MODE,
|
|
268
|
+
};
|
|
269
|
+
try {
|
|
270
|
+
const raw = await provider(requestForProvider, {
|
|
271
|
+
signal: context.signal ?? new AbortController().signal,
|
|
272
|
+
execution: context.execution,
|
|
273
|
+
});
|
|
274
|
+
const route = parseRouteResult(raw);
|
|
275
|
+
const cacheInfo = {
|
|
276
|
+
status: cached ? 'requery' : 'miss',
|
|
277
|
+
ageS: 0,
|
|
278
|
+
asOf: null,
|
|
279
|
+
};
|
|
280
|
+
const fact = buildRouteFact(direction, route, clock, cacheInfo);
|
|
281
|
+
cacheInfo.asOf = fact.asOf;
|
|
282
|
+
const entry = { fact, storedAtMs: currentMs };
|
|
283
|
+
if (cache.size >= maxEntries && !cache.has(key)) {
|
|
284
|
+
const oldest = cache.keys().next().value;
|
|
285
|
+
if (typeof oldest === 'string')
|
|
286
|
+
cache.delete(oldest);
|
|
287
|
+
}
|
|
288
|
+
cache.set(key, entry);
|
|
289
|
+
return {
|
|
290
|
+
applied: true,
|
|
291
|
+
direction: direction.direction,
|
|
292
|
+
minutes: fact.durationMin,
|
|
293
|
+
staticMinutes,
|
|
294
|
+
provenance: 'map_driving_route',
|
|
295
|
+
asOf: fact.asOf,
|
|
296
|
+
freshness: 'fresh',
|
|
297
|
+
cache: cacheInfo,
|
|
298
|
+
evidenceClass: 'public_map_route_estimate',
|
|
299
|
+
routeFact: fact,
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
catch (error) {
|
|
303
|
+
const detail = safeErrorMessage(error);
|
|
304
|
+
return {
|
|
305
|
+
applied: false,
|
|
306
|
+
direction: direction.direction,
|
|
307
|
+
staticMinutes,
|
|
308
|
+
provenance: 'static-transfer-pack',
|
|
309
|
+
asOf: null,
|
|
310
|
+
freshness: 'fallback',
|
|
311
|
+
cache: staticCache(cached ? 'stale' : 'bypass'),
|
|
312
|
+
evidenceClass: 'static_transfer_estimate',
|
|
313
|
+
fallbackReason: cached
|
|
314
|
+
? `stale_route_requery_error:${direction.direction}:${detail}`
|
|
315
|
+
: `map_driving_route_provider_error:${direction.direction}:${detail}`,
|
|
316
|
+
};
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
export function createGroundTransferResolver(options) {
|
|
320
|
+
const clock = options.clock ?? (() => new Date());
|
|
321
|
+
const maxEntries = Number.isInteger(options.maxEntries) && Number(options.maxEntries) > 0
|
|
322
|
+
? Number(options.maxEntries)
|
|
323
|
+
: GROUND_TRANSFER_CACHE_LIMIT;
|
|
324
|
+
const cache = new Map();
|
|
325
|
+
return {
|
|
326
|
+
async resolve(request, selection, context = {}) {
|
|
327
|
+
const [outbound, ret] = await Promise.all([
|
|
328
|
+
resolveDirection(request.outbound, selection.staticMinutes, options.provider, clock, cache, maxEntries, context),
|
|
329
|
+
resolveDirection(request.ret, selection.staticMinutes, options.provider, clock, cache, maxEntries, context),
|
|
330
|
+
]);
|
|
331
|
+
const applied = outbound.applied || ret.applied;
|
|
332
|
+
// Aggregate fields are a summary only: per-direction objects above are
|
|
333
|
+
// the authoritative per-direction record. A full cache hit keeps the
|
|
334
|
+
// legacy aggregate semantics ('cache_hit'), a re-query keeps 'requery'.
|
|
335
|
+
const aggregateProvenance = applied ? 'map_driving_route' : 'static-transfer-pack';
|
|
336
|
+
const aggregateEvidenceClass = applied ? 'public_map_route_estimate' : 'static_transfer_estimate';
|
|
337
|
+
const aggregateFreshness = !applied
|
|
338
|
+
? 'fallback'
|
|
339
|
+
: outbound.freshness === 'fresh' || ret.freshness === 'fresh'
|
|
340
|
+
? 'fresh'
|
|
341
|
+
: 'cache_hit';
|
|
342
|
+
const statuses = [outbound.cache.status, ret.cache.status];
|
|
343
|
+
const aggregateCache = !applied
|
|
344
|
+
? staticCache('bypass')
|
|
345
|
+
: {
|
|
346
|
+
status: statuses.includes('hit') ? 'hit' : statuses.includes('requery') ? 'requery' : 'miss',
|
|
347
|
+
ageS: 0,
|
|
348
|
+
asOf: null,
|
|
349
|
+
};
|
|
350
|
+
const aggregateAsOf = applied ? (outbound.asOf ?? ret.asOf) : null;
|
|
351
|
+
const resolution = {
|
|
352
|
+
applied,
|
|
353
|
+
candidateId: selection.candidateId,
|
|
354
|
+
transferIndex: selection.transferIndex,
|
|
355
|
+
requestedMode: request.mode,
|
|
356
|
+
requestedPosition: request.position,
|
|
357
|
+
staticMode: selection.staticMode,
|
|
358
|
+
staticMinutes: selection.staticMinutes,
|
|
359
|
+
priceCny: selection.staticPriceCny,
|
|
360
|
+
priceEvidence: GROUND_TRANSFER_STATIC_PRICE_EVIDENCE,
|
|
361
|
+
provenance: aggregateProvenance,
|
|
362
|
+
asOf: aggregateAsOf,
|
|
363
|
+
freshness: aggregateFreshness,
|
|
364
|
+
cache: aggregateCache,
|
|
365
|
+
evidenceClass: aggregateEvidenceClass,
|
|
366
|
+
outbound,
|
|
367
|
+
return: ret,
|
|
368
|
+
};
|
|
369
|
+
if (outbound.applied)
|
|
370
|
+
resolution.outboundMinutes = outbound.minutes;
|
|
371
|
+
if (ret.applied)
|
|
372
|
+
resolution.returnMinutes = ret.minutes;
|
|
373
|
+
if (!applied) {
|
|
374
|
+
resolution.fallbackReason = `both_directions_failed:${outbound.fallbackReason ?? 'unknown'};${ret.fallbackReason ?? 'unknown'}`;
|
|
375
|
+
}
|
|
376
|
+
return resolution;
|
|
377
|
+
},
|
|
378
|
+
};
|
|
379
|
+
}
|
|
380
|
+
/**
|
|
381
|
+
* Delegate through the registered public map tool. The dsh-tools types expose
|
|
382
|
+
* nested execution explicitly, so the current root/agent/signal are carried
|
|
383
|
+
* into the sub-dispatch instead of calling a private map client.
|
|
384
|
+
*/
|
|
385
|
+
export function createPublicMapDrivingRouteProvider(ctx) {
|
|
386
|
+
return async (request, context) => {
|
|
387
|
+
const execution = context.execution;
|
|
388
|
+
if (!execution)
|
|
389
|
+
throw new Error('GROUND_TRANSFER_NESTED_EXECUTION_UNSUPPORTED: current tool execution context is unavailable');
|
|
390
|
+
const tools = ctx.tools;
|
|
391
|
+
if (typeof tools?.execute !== 'function' || typeof tools?.get !== 'function') {
|
|
392
|
+
throw new Error('GROUND_TRANSFER_NESTED_EXECUTION_UNSUPPORTED: host must expose dsh-tools ToolRuntime.execute/get');
|
|
393
|
+
}
|
|
394
|
+
if (!tools.get('map_driving_route', execution.agent)) {
|
|
395
|
+
throw new Error('GROUND_TRANSFER_PUBLIC_MAP_TOOL_UNAVAILABLE: map_driving_route is not registered in the current tool scope');
|
|
396
|
+
}
|
|
397
|
+
const nested = {
|
|
398
|
+
callId: ToolCallId(`${String(execution.callId)}:ground-transfer`),
|
|
399
|
+
rootCallId: execution.rootCallId,
|
|
400
|
+
name: 'map_driving_route',
|
|
401
|
+
arguments: { origin: request.origin, destination: request.destination },
|
|
402
|
+
...(execution.agent !== undefined ? { agent: execution.agent } : {}),
|
|
403
|
+
parent: execution.token,
|
|
404
|
+
signal: execution.signal,
|
|
405
|
+
};
|
|
406
|
+
const result = await tools.execute(nested);
|
|
407
|
+
if (result.isError)
|
|
408
|
+
throw new Error(`map_driving_route failed: ${result.error.message}`);
|
|
409
|
+
return parseRouteResult(result.value);
|
|
410
|
+
};
|
|
411
|
+
}
|
|
412
|
+
/** Resolve and patch exactly one named transfer position before solver entry. */
|
|
413
|
+
export async function resolveGroundTransferPayload(rawRequest, candidates, resolver, context) {
|
|
414
|
+
const parts = parseRequest(rawRequest);
|
|
415
|
+
const bound = selectionFor(candidates, parts);
|
|
416
|
+
if (!bound) {
|
|
417
|
+
const base = notAppliedBase(parts);
|
|
418
|
+
return {
|
|
419
|
+
candidates: [...candidates],
|
|
420
|
+
resolution: {
|
|
421
|
+
...base,
|
|
422
|
+
applied: false,
|
|
423
|
+
...(parts.candidateId !== null ? { candidateId: parts.candidateId } : {}),
|
|
424
|
+
...(parts.transferIndex !== null ? { transferIndex: parts.transferIndex } : {}),
|
|
425
|
+
outbound: staticDirectionResolution('outbound', Number.NaN, 'ground_transfer_binding_mismatch'),
|
|
426
|
+
return: staticDirectionResolution('return', Number.NaN, 'ground_transfer_binding_mismatch'),
|
|
427
|
+
fallbackReason: 'ground_transfer_binding_mismatch: candidate_id and transfer_index must identify exactly one valid static transfer',
|
|
428
|
+
},
|
|
429
|
+
};
|
|
430
|
+
}
|
|
431
|
+
const complete = completeRequest(parts);
|
|
432
|
+
if (!complete) {
|
|
433
|
+
return {
|
|
434
|
+
candidates: [...candidates],
|
|
435
|
+
resolution: {
|
|
436
|
+
applied: false,
|
|
437
|
+
candidateId: bound.selection.candidateId,
|
|
438
|
+
transferIndex: bound.selection.transferIndex,
|
|
439
|
+
requestedMode: parts.requestedMode,
|
|
440
|
+
requestedPosition: parts.requestedPosition,
|
|
441
|
+
staticMode: bound.selection.staticMode,
|
|
442
|
+
staticMinutes: bound.selection.staticMinutes,
|
|
443
|
+
priceCny: bound.selection.staticPriceCny,
|
|
444
|
+
priceEvidence: GROUND_TRANSFER_STATIC_PRICE_EVIDENCE,
|
|
445
|
+
provenance: 'static-transfer-pack',
|
|
446
|
+
asOf: null,
|
|
447
|
+
freshness: 'fallback',
|
|
448
|
+
cache: staticCache('bypass'),
|
|
449
|
+
evidenceClass: 'static_transfer_estimate',
|
|
450
|
+
outbound: staticDirectionResolution('outbound', bound.selection.staticMinutes, parts.invalidReason ?? 'ground_transfer_request_invalid'),
|
|
451
|
+
return: staticDirectionResolution('return', bound.selection.staticMinutes, parts.invalidReason ?? 'ground_transfer_request_invalid'),
|
|
452
|
+
fallbackReason: `${parts.invalidReason ?? 'ground_transfer_request_invalid'}; static transfer minutes and price preserved`,
|
|
453
|
+
},
|
|
454
|
+
};
|
|
455
|
+
}
|
|
456
|
+
if (bound.selection.staticMode !== GROUND_TRANSFER_STATIC_MODE) {
|
|
457
|
+
const reason = `${GROUND_TRANSFER_STATIC_MODE_MISMATCH}: mode=${bound.selection.staticMode}; only taxi accepts a driving route; static transfer preserved`;
|
|
458
|
+
return {
|
|
459
|
+
candidates: [...candidates],
|
|
460
|
+
resolution: {
|
|
461
|
+
applied: false,
|
|
462
|
+
candidateId: bound.selection.candidateId,
|
|
463
|
+
transferIndex: bound.selection.transferIndex,
|
|
464
|
+
requestedMode: parts.requestedMode,
|
|
465
|
+
requestedPosition: parts.requestedPosition,
|
|
466
|
+
staticMode: bound.selection.staticMode,
|
|
467
|
+
staticMinutes: bound.selection.staticMinutes,
|
|
468
|
+
priceCny: bound.selection.staticPriceCny,
|
|
469
|
+
priceEvidence: GROUND_TRANSFER_STATIC_PRICE_EVIDENCE,
|
|
470
|
+
provenance: 'static-transfer-pack',
|
|
471
|
+
asOf: null,
|
|
472
|
+
freshness: 'not_attempted',
|
|
473
|
+
cache: staticCache('bypass'),
|
|
474
|
+
evidenceClass: 'static_transfer_estimate',
|
|
475
|
+
outbound: staticDirectionResolution('outbound', bound.selection.staticMinutes, reason),
|
|
476
|
+
return: staticDirectionResolution('return', bound.selection.staticMinutes, reason),
|
|
477
|
+
fallbackReason: reason,
|
|
478
|
+
},
|
|
479
|
+
};
|
|
480
|
+
}
|
|
481
|
+
const resolution = await resolver.resolve(complete, bound.selection, context);
|
|
482
|
+
if (!resolution.applied) {
|
|
483
|
+
return { candidates: [...candidates], resolution };
|
|
484
|
+
}
|
|
485
|
+
const patched = candidates.map((candidate, candidateIndex) => {
|
|
486
|
+
if (candidateIndex !== bound.candidateIndex)
|
|
487
|
+
return candidate;
|
|
488
|
+
const destTransfers = candidate.destTransfers.map((transfer, transferIndex) => {
|
|
489
|
+
if (transferIndex !== bound.selection.transferIndex)
|
|
490
|
+
return transfer;
|
|
491
|
+
const next = { ...transfer };
|
|
492
|
+
if (resolution.outboundMinutes !== undefined)
|
|
493
|
+
next['minutesOut'] = resolution.outboundMinutes;
|
|
494
|
+
if (resolution.returnMinutes !== undefined)
|
|
495
|
+
next['minutesRet'] = resolution.returnMinutes;
|
|
496
|
+
return next;
|
|
497
|
+
});
|
|
498
|
+
return { ...candidate, destTransfers };
|
|
499
|
+
});
|
|
500
|
+
return { candidates: patched, resolution };
|
|
501
|
+
}
|
|
502
|
+
export function exposeGroundTransferEvidence(result, resolution) {
|
|
503
|
+
const verdicts = Array.isArray(result['verdicts'])
|
|
504
|
+
? result['verdicts'].map(verdict => resolution.candidateId !== undefined && verdict['candidate_id'] === resolution.candidateId
|
|
505
|
+
? { ...verdict, transfer_evidence: resolution }
|
|
506
|
+
: verdict)
|
|
507
|
+
: result['verdicts'];
|
|
508
|
+
return { ...result, verdicts, ground_transfer: resolution };
|
|
509
|
+
}
|