@danceiny/gotry 0.0.1-rc.9 → 0.2.0-rc.28
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 +172 -219
- package/README.zh-CN.md +222 -0
- package/bin/gotry-backend.js +53 -0
- package/bin/gotry-booking-copilot.js +53 -0
- package/bin/gotry-bootstrap.js +1788 -0
- package/bin/gotry-inner.js +733 -83
- package/bin/gotry-process-liveness.d.ts +27 -0
- package/bin/gotry-process-liveness.js +97 -0
- package/bin/gotry-runtime-resolution.d.ts +27 -0
- package/bin/gotry-runtime-resolution.js +49 -0
- package/bin/gotry.js +1 -1
- package/client/client.js +203 -0
- package/cordis.gotry-patch.yml +80 -56
- package/data/airline-airports.json +39 -0
- package/data/stations-12306-verify.json +524 -0
- package/dist/capabilities/agent-reach-deep.js +45 -122
- package/dist/capabilities/agent-reach.js +101 -146
- package/dist/capabilities/anything.js +161 -94
- package/dist/capabilities/artifacts.js +370 -0
- package/dist/capabilities/channel-health.js +131 -0
- package/dist/capabilities/channel-registry.js +256 -0
- package/dist/capabilities/doctor.js +553 -0
- package/dist/capabilities/effect.js +394 -0
- package/dist/capabilities/external-event.js +149 -0
- package/dist/capabilities/fact-log.js +55 -0
- package/dist/capabilities/flyai-config.js +337 -0
- package/dist/capabilities/flyai.js +947 -110
- package/dist/capabilities/fx-contract.js +293 -0
- package/dist/capabilities/geo-atlas.js +246 -0
- package/dist/capabilities/ground-transfer.js +676 -0
- package/dist/capabilities/hbcli.js +260 -123
- package/dist/capabilities/hotelbyte-transaction.js +194 -0
- package/dist/capabilities/incident-log.js +131 -61
- package/dist/capabilities/itinerary-artifact.js +190 -0
- package/dist/capabilities/itinerary-deck-artifact.js +194 -0
- package/dist/capabilities/itinerary-deck-export.js +339 -0
- package/dist/capabilities/lavish-local.js +924 -0
- package/dist/capabilities/model-override.js +38 -0
- package/dist/capabilities/opensky.js +54 -43
- package/dist/capabilities/provider-detail-sanitize.js +64 -0
- package/dist/capabilities/resilience.js +191 -0
- package/dist/capabilities/route-provider-conformance.js +341 -0
- package/dist/capabilities/session/action-cache.js +50 -44
- package/dist/capabilities/session/adapters/ctrip-flight.js +120 -62
- package/dist/capabilities/session/adapters/ctrip-hotel.js +222 -0
- package/dist/capabilities/session/adapters/dida-portal.js +274 -0
- package/dist/capabilities/session/adapters/meituan-local.js +35 -54
- package/dist/capabilities/session/adapters/rail-12306.js +342 -0
- package/dist/capabilities/session/benchmark.js +189 -0
- package/dist/capabilities/session/bridge-ledger.js +224 -0
- package/dist/capabilities/session/extension-bridge.js +636 -0
- package/dist/capabilities/session/extension-channel.js +109 -0
- package/dist/capabilities/session/extension-distribution.js +230 -0
- package/dist/capabilities/session/extract.js +31 -21
- package/dist/capabilities/session/golden-score.js +114 -0
- package/dist/capabilities/session/health-watch.js +215 -0
- package/dist/capabilities/session/read-guard.js +75 -24
- package/dist/capabilities/session/static-flight-golden.js +150 -0
- package/dist/capabilities/session/transport.js +97 -83
- package/dist/capabilities/session/wizard.js +117 -0
- package/dist/capabilities/session-consent.js +178 -0
- package/dist/capabilities/session-login.js +170 -0
- package/dist/capabilities/session-search.js +635 -44
- package/dist/capabilities/spawn-bounded.js +203 -0
- package/dist/capabilities/sponsor-plugin.js +341 -0
- package/dist/capabilities/visa-policy.js +140 -0
- package/dist/capabilities/weather.js +243 -138
- package/dist/data/session-golden-20.json +25 -0
- package/dist/data/sf-golden-manifest.json +103 -0
- package/dist/data/sf-static-routes.json +91 -0
- package/dist/scripts/action-cache-tests.js +24 -59
- package/dist/scripts/agent-planning-turn-deadline-e2e.js +242 -0
- package/dist/scripts/agent-planning-turn-deadline-tests.js +185 -0
- package/dist/scripts/agent-reach-deep-tests.js +30 -40
- package/dist/scripts/agent-reach-error-tests.js +354 -0
- package/dist/scripts/agent-reach-tests.js +26 -18
- package/dist/scripts/agent-reach-wrapper-tests.js +50 -50
- package/dist/scripts/anything-path-measure-tests.js +133 -0
- package/dist/scripts/anything-path-measure.js +150 -0
- package/dist/scripts/anything-tests.js +250 -60
- package/dist/scripts/artifact-client-contract-tests.js +220 -0
- package/dist/scripts/artifacts-capability-tests.js +727 -0
- package/dist/scripts/async-collect.js +50 -11
- package/dist/scripts/benchmark-environment-bridge-e2e.js +741 -0
- package/dist/scripts/benchmark-environment-bridge-tests.js +1452 -0
- package/dist/scripts/booking-copilot-availability-ledger-binding-tests.js +126 -0
- package/dist/scripts/booking-copilot-availability-policy-tests.js +265 -0
- package/dist/scripts/booking-copilot-bin-proof-tests.js +66 -0
- package/dist/scripts/booking-copilot-cleanup-proof-helper.js +76 -0
- package/dist/scripts/booking-copilot-crossrepo-fixture-server.js +144 -0
- package/dist/scripts/booking-copilot-dsh-core-proof-tests.js +424 -0
- package/dist/scripts/booking-copilot-dsh-planner-proof-tests.js +2839 -0
- package/dist/scripts/booking-copilot-dsh-plugin-proof-tests.js +309 -0
- package/dist/scripts/booking-copilot-dsh-readiness-proof.js +238 -0
- package/dist/scripts/booking-copilot-dsh-warmer-proof.js +126 -0
- package/dist/scripts/booking-copilot-event-sequence-concurrency-proof-tests.js +156 -0
- package/dist/scripts/booking-copilot-gap-code-contract-proof-tests.js +70 -0
- package/dist/scripts/booking-copilot-intent-proof-tests.js +422 -0
- package/dist/scripts/booking-copilot-operation-ledger-concurrency-proof-tests.js +184 -0
- package/dist/scripts/booking-copilot-planner-live-probe.js +95 -0
- package/dist/scripts/booking-copilot-proof-runtime.js +42 -0
- package/dist/scripts/booking-copilot-receipt-ledger-concurrency-proof-tests.js +210 -0
- package/dist/scripts/booking-copilot-runtime-proof-tests.js +2349 -0
- package/dist/scripts/booking-copilot-server-proof-tests.js +784 -0
- package/dist/scripts/booking-copilot-startup-proof-tests.js +94 -0
- 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 +125 -0
- package/dist/scripts/booking-surface-contract-proof-tests.js +212 -0
- package/dist/scripts/bootstrap-tests.js +1549 -0
- package/dist/scripts/bridge-events-tests.js +239 -0
- package/dist/scripts/bridge-ledger-tests.js +428 -0
- package/dist/scripts/build-changelog.js +238 -0
- package/dist/scripts/build-metrics-report.js +331 -0
- package/dist/scripts/changelog-tests.js +118 -0
- package/dist/scripts/channel-probe-tests.js +103 -0
- package/dist/scripts/channel-probe.js +169 -0
- package/dist/scripts/channel-registry-tests.js +208 -0
- package/dist/scripts/city-scenario-tier-tests.js +234 -0
- package/dist/scripts/companion-tests.js +25 -70
- package/dist/scripts/dida-cookie-probe.js +87 -0
- package/dist/scripts/dida-runner-stop-tests.js +311 -0
- package/dist/scripts/dida-sw-probe.js +114 -0
- package/dist/scripts/diff-test.js +17 -12
- package/dist/scripts/doctor-tests.js +600 -0
- package/dist/scripts/drill-multiuser-ledger-tests.js +1076 -0
- package/dist/scripts/drill-sdk-descendant-cleanup-tests.js +606 -0
- package/dist/scripts/drill-w2a-sensor-tests.js +788 -0
- package/dist/scripts/dsh-artifact-e2e.js +279 -0
- package/dist/scripts/dsh-artifact-web-e2e.js +675 -0
- package/dist/scripts/dsh-http-proxy-sse-proof.js +90 -0
- package/dist/scripts/dsh-llm-tests.js +437 -0
- package/dist/scripts/dsh-runtime-closure-tests.js +182 -0
- package/dist/scripts/dsh-runtime-closure.js +162 -0
- package/dist/scripts/dsh-session-v3-migration-proof.js +109 -0
- package/dist/scripts/dsh-subprocess-local-proof.js +150 -0
- package/dist/scripts/dsh-target-closure-proof.js +178 -0
- package/dist/scripts/effect-tests.js +539 -0
- package/dist/scripts/engine-run.js +1 -3
- package/dist/scripts/engine-tests.js +16 -10
- package/dist/scripts/evaluation-cadence-tests.js +174 -0
- package/dist/scripts/evaluation-contract-tests.js +268 -0
- package/dist/scripts/extension-distribution-cli.js +39 -0
- package/dist/scripts/extension-distribution-tests.js +443 -0
- package/dist/scripts/extension-tests.js +988 -0
- package/dist/scripts/external-event-tests.js +350 -0
- package/dist/scripts/fact-gate-tests.js +869 -0
- 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-setup-tests.js +228 -0
- package/dist/scripts/flyai-setup-tool-tests.js +54 -0
- package/dist/scripts/flyai-tests.js +692 -0
- package/dist/scripts/flyai-tool-registration-contract-tests.js +91 -0
- package/dist/scripts/fx-contract-tests.js +201 -0
- package/dist/scripts/g5-guard-tests.js +150 -0
- package/dist/scripts/g5-guard.js +193 -0
- package/dist/scripts/gen-lunar-anchors.js +106 -0
- package/dist/scripts/geo-atlas-tests.js +283 -0
- package/dist/scripts/gotry-backend-kernel-tests.js +206 -0
- package/dist/scripts/gotry-try-demo-tests.js +79 -0
- package/dist/scripts/gotry-try-demo.js +49 -0
- package/dist/scripts/ground-transfer-tests.js +956 -0
- package/dist/scripts/hbcli-e2e-tests.js +159 -0
- 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 +121 -69
- package/dist/scripts/health-watch-cli.js +73 -0
- 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 +41 -0
- 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 +569 -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/issue436-persisted-routing-e2e.js +247 -0
- package/dist/scripts/itinerary-artifact-tests.js +368 -0
- package/dist/scripts/itinerary-deck-artifact-tests.js +416 -0
- package/dist/scripts/itinerary-deck-export-tests.js +552 -0
- package/dist/scripts/itinerary-deck-tests.js +435 -0
- package/dist/scripts/itinerary-html-tests.js +426 -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/lavish-local-tests.js +1226 -0
- package/dist/scripts/lavish-product-tools-tests.js +537 -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 +382 -246
- package/dist/scripts/ledger-workflow-crash.js +10 -20
- package/dist/scripts/m3-cohort-tests.js +714 -0
- package/dist/scripts/m3-cohort.js +310 -0
- package/dist/scripts/managed-dsh-cleanup-diagnostic-proof.js +184 -0
- package/dist/scripts/managed-dsh-close-failure-proof.js +172 -0
- package/dist/scripts/managed-dsh-run-port-proof.js +153 -0
- package/dist/scripts/managed-dsh-terminal-proof.js +252 -0
- package/dist/scripts/map-tools-vendor-package-proof.js +365 -0
- 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 +631 -0
- package/dist/scripts/memory-value-tests.js +328 -0
- package/dist/scripts/metrics-report-tests.js +151 -0
- package/dist/scripts/model-override-e2e.js +137 -0
- package/dist/scripts/nightly-evidence-tests.js +203 -0
- package/dist/scripts/nightly-evidence.js +303 -0
- package/dist/scripts/nudge-digest.js +39 -28
- package/dist/scripts/observability-tests.js +381 -0
- package/dist/scripts/onboarding-tests.js +161 -0
- package/dist/scripts/opensky-check.js +18 -13
- package/dist/scripts/opensky-tests.js +28 -28
- package/dist/scripts/outcome-projection-tests.js +427 -0
- package/dist/scripts/persona-sim-tests.js +603 -0
- package/dist/scripts/persona-sim.js +1081 -0
- package/dist/scripts/persona-surface-guard-tests.js +49 -0
- package/dist/scripts/pnpm-dsh-closure-proof.js +17 -0
- package/dist/scripts/policy-anchor-359-registered-e2e.js +214 -0
- package/dist/scripts/price-drift-tests.js +340 -0
- package/dist/scripts/price-drift-watch.js +447 -0
- package/dist/scripts/probe-poi-tests.js +53 -45
- package/dist/scripts/product-metrics.js +483 -0
- package/dist/scripts/publish-preverify.js +110 -0
- package/dist/scripts/realtime-pricing-tests.js +76 -0
- package/dist/scripts/recall-tests.js +513 -0
- package/dist/scripts/replay-async.js +22 -18
- package/dist/scripts/replay-real.js +18 -18
- package/dist/scripts/replay.js +54 -82
- package/dist/scripts/route-provider-conformance-tests.js +391 -0
- package/dist/scripts/session-attach-diagnose.js +20 -20
- package/dist/scripts/session-attach-poc.js +34 -37
- package/dist/scripts/session-benchmark.js +180 -0
- package/dist/scripts/session-dida-live-e2e.js +199 -0
- package/dist/scripts/session-extract-tests.js +14 -35
- package/dist/scripts/session-link-tests.js +478 -0
- package/dist/scripts/session-login.js +27 -46
- package/dist/scripts/session-tests.js +796 -96
- package/dist/scripts/session-zone-crash.js +49 -0
- package/dist/scripts/session-zone-ledger-tests.js +432 -0
- package/dist/scripts/session-zone-observation-tests.js +492 -0
- package/dist/scripts/session-zone-observe.js +135 -0
- package/dist/scripts/session-zone-wiring-tests.js +566 -0
- package/dist/scripts/session-zones-tests.js +374 -0
- package/dist/scripts/sf-live-benchmark.js +367 -0
- package/dist/scripts/sf-live-challenge-stop-tests.js +453 -0
- package/dist/scripts/sf-live-cli-tests.js +92 -0
- package/dist/scripts/sf-live-options.js +46 -0
- package/dist/scripts/sf-manifest-tests.js +30 -0
- package/dist/scripts/sf-manifest.js +33 -0
- package/dist/scripts/sf-soft-score-tests.js +67 -0
- package/dist/scripts/sf-summary-tests.js +324 -0
- package/dist/scripts/sf-summary.js +525 -0
- package/dist/scripts/share-tests.js +432 -0
- package/dist/scripts/skeleton-check.js +16 -14
- package/dist/scripts/skeleton-integration-test.js +28 -11
- package/dist/scripts/skills-contract-tests.js +51 -46
- package/dist/scripts/smoke-session-gate-tests.js +21 -0
- package/dist/scripts/smoke.js +690 -246
- package/dist/scripts/spawn-bounded-abort-tests.js +244 -0
- package/dist/scripts/sponsor-reuse-tests.js +262 -0
- package/dist/scripts/state-cli-tests.js +226 -101
- package/dist/scripts/state-cli.js +565 -198
- package/dist/scripts/state-repair-cli.js +66 -0
- package/dist/scripts/state-repair-tests.js +140 -0
- package/dist/scripts/static-golden-tests.js +165 -0
- package/dist/scripts/time-eval-tests.js +212 -192
- package/dist/scripts/travel-timeline-tests.js +29 -93
- package/dist/scripts/turn-handoff-collect-tests.js +169 -0
- package/dist/scripts/turn-handoff-collect.js +161 -0
- package/dist/scripts/turn-policy-tests.js +55 -0
- package/dist/scripts/typed-contract-canary.js +176 -0
- package/dist/scripts/tz-resolver-sanity.js +50 -0
- package/dist/scripts/tz-resolver-tests.js +584 -0
- package/dist/scripts/unified-tests.js +60 -20
- package/dist/scripts/visa-policy-tests.js +62 -0
- package/dist/scripts/weather-tests.js +276 -41
- package/dist/scripts/wish-channel-gate-tests.js +54 -0
- 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 +46 -0
- package/dist/src/artifact-gate.js +829 -0
- package/dist/src/backend/kernel.js +91 -0
- package/dist/src/backend/modules/booking-copilot.js +92 -0
- package/dist/src/backend/modules/booking-executor.js +124 -0
- package/dist/src/backend/modules/session-search.js +551 -0
- package/dist/src/benchmark-agent-conformance.js +578 -0
- package/dist/src/benchmark-environment-bridge.js +515 -0
- package/dist/src/benchmark-headless-child-diagnostics.js +166 -0
- package/dist/src/benchmark-tool-isolation.js +151 -0
- package/dist/src/bookable-facts.js +533 -0
- package/dist/src/booking-saga.js +97 -0
- package/dist/src/booking-surface/availability-policy.js +697 -0
- package/dist/src/booking-surface/booking-intent.js +43 -0
- package/dist/src/booking-surface/cancel-refund-commission.js +379 -0
- package/dist/src/booking-surface/canonical-schema.js +116 -0
- package/dist/src/booking-surface/contracts.js +75 -0
- package/dist/src/booking-surface/dsh-planner.js +1854 -0
- package/dist/src/booking-surface/dsh-plugin.js +378 -0
- package/dist/src/booking-surface/dsh-worker.js +98 -0
- package/dist/src/booking-surface/error-codes.js +231 -0
- package/dist/src/booking-surface/index.js +9 -0
- package/dist/src/booking-surface/managed-dsh-cleanup-diagnostic.js +97 -0
- package/dist/src/booking-surface/managed-dsh-run-port.js +244 -0
- package/dist/src/booking-surface/profile.js +24 -0
- package/dist/src/booking-surface/recovery-chain.js +367 -0
- package/dist/src/booking-surface/runtime.js +1968 -0
- package/dist/src/booking-surface/server.js +674 -0
- package/dist/src/booking-surface/startup.js +168 -0
- package/dist/src/booking-surface/validation.js +261 -0
- package/dist/src/bridge.js +20 -13
- package/dist/src/city-scenario-tier.js +378 -0
- package/dist/src/companions.js +48 -74
- package/dist/src/contracts.js +21 -43
- package/dist/src/dsh-llm.js +171 -115
- package/dist/src/engine.js +276 -193
- package/dist/src/evaluation-cadence.js +97 -0
- package/dist/src/evaluation-contracts.js +283 -0
- package/dist/src/flight-pack-adapter.js +54 -0
- package/dist/src/flight-pack-contract.js +2 -0
- package/dist/src/flyai-setup-tool.js +70 -0
- package/dist/src/gotry-backend.js +125 -0
- package/dist/src/hotel-date-gate.js +118 -0
- package/dist/src/i18n.js +88 -0
- package/dist/src/index.js +2385 -881
- package/dist/src/itinerary-deck.js +139 -0
- package/dist/src/itinerary-doc-shared.js +661 -0
- package/dist/src/itinerary-html.js +131 -0
- package/dist/src/journey.js +155 -108
- package/dist/src/lavish-tools.js +400 -0
- package/dist/src/ledger-repair-apply.js +538 -0
- package/dist/src/ledger-repair-plan.js +463 -0
- package/dist/src/loop.js +331 -161
- package/dist/src/m3-cohort.js +1577 -0
- 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/outcome-projection.js +746 -0
- package/dist/src/realtime-pricing.js +103 -0
- package/dist/src/recall/card.js +74 -0
- package/dist/src/recall/evaluator.js +140 -0
- package/dist/src/recall/integration.js +17 -0
- package/dist/src/recall/tick.js +166 -0
- package/dist/src/session-link/plan-it-action.js +48 -0
- package/dist/src/session-link/session-link.js +248 -0
- package/dist/src/session-zone-ledger.js +312 -0
- package/dist/src/session-zone-observation.js +354 -0
- package/dist/src/session-zone-wiring.js +658 -0
- package/dist/src/session-zones.js +510 -0
- package/dist/src/share/adapters.js +51 -0
- package/dist/src/share/share-consent.js +76 -0
- package/dist/src/share/share-deck.js +58 -0
- package/dist/src/share/share-token.js +146 -0
- package/dist/src/slot-spec.js +84 -106
- package/dist/src/state-ledger.js +696 -524
- package/dist/src/state-repair.js +299 -0
- 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 +287 -0
- package/dist/src/turn-policy.js +118 -0
- package/dist/src/tz-resolver.js +143 -0
- package/dist/src/unified.js +600 -360
- package/dist/src/wish-pool.js +42 -12
- package/dist/src/write-gate.js +495 -0
- package/dist/src/z3-shared.js +284 -0
- package/extension/README.md +21 -0
- package/extension/README.zh-CN.md +21 -0
- package/extension/background.js +463 -0
- package/extension/content-bridge.js +270 -0
- package/extension/content-main.js +111 -0
- package/extension/manifest.json +60 -0
- package/package.json +407 -14
- package/schemas/booking.intent.schema.json +193 -0
- package/schemas/booking.surface.schema.json +2622 -0
- package/ts/capabilities/agent-reach-deep.ts +114 -0
- package/ts/capabilities/agent-reach.ts +198 -0
- package/ts/capabilities/anything.ts +282 -0
- package/ts/capabilities/artifacts.ts +428 -0
- package/ts/capabilities/fact-log.ts +54 -0
- package/ts/capabilities/flyai-config.ts +388 -0
- package/ts/capabilities/flyai.ts +1274 -0
- package/ts/capabilities/hbcli.ts +253 -71
- package/ts/capabilities/incident-log.ts +88 -59
- package/ts/capabilities/itinerary-artifact.ts +214 -0
- package/ts/capabilities/itinerary-deck-artifact.ts +218 -0
- package/ts/capabilities/itinerary-deck-export.ts +378 -0
- package/ts/capabilities/opensky.ts +121 -0
- package/ts/capabilities/session/action-cache.ts +148 -0
- package/ts/capabilities/session/adapters/ctrip-flight.ts +181 -0
- package/ts/capabilities/session/adapters/ctrip-hotel.ts +238 -0
- package/ts/capabilities/session/adapters/dida-portal.ts +338 -0
- package/ts/capabilities/session/adapters/meituan-local.ts +70 -0
- package/ts/capabilities/session/adapters/rail-12306.ts +392 -0
- package/ts/capabilities/session/benchmark.ts +276 -0
- package/ts/capabilities/session/bridge-ledger.ts +333 -0
- package/ts/capabilities/session/extension-bridge.ts +794 -0
- package/ts/capabilities/session/extension-channel.ts +144 -0
- package/ts/capabilities/session/extension-distribution.ts +266 -0
- package/ts/capabilities/session/extract.ts +54 -0
- package/ts/capabilities/session/golden-score.ts +139 -0
- package/ts/capabilities/session/health-watch.ts +264 -0
- package/ts/capabilities/session/read-guard.ts +141 -0
- package/ts/capabilities/session/static-flight-golden.ts +209 -0
- package/ts/capabilities/session/transport.ts +146 -0
- package/ts/capabilities/session/wizard.ts +140 -0
- package/ts/capabilities/session-consent.ts +211 -0
- package/ts/capabilities/session-login.ts +208 -0
- package/ts/capabilities/session-search.ts +893 -0
- package/ts/capabilities/weather.ts +330 -0
- package/ts/dsh-runtime/vendor/README.md +68 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/LICENSE +21 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/README.en.md +405 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/README.md +404 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/client/client.js +1584 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/cordis.patch.yml +5 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/icon.svg +5 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/clients/amap.js +595 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/clients/nominatim.js +63 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/clients/osrm.js +58 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/clients/photon.js +55 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/config-file.js +87 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/config-route.js +131 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/config.js +16 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/geo.js +232 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/index.js +120 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/settings-ns.js +40 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/staticmap-route.js +115 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/tools/geocode.js +127 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/tools/poi.js +84 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/tools/routes.js +267 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/clients/amap.d.ts +90 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/clients/nominatim.d.ts +17 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/clients/osrm.d.ts +15 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/clients/photon.d.ts +22 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/config-file.d.ts +33 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/config-route.d.ts +23 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/config.d.ts +23 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/geo.d.ts +61 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/index.d.ts +18 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/settings-ns.d.ts +14 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/staticmap-route.d.ts +31 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/tools/geocode.d.ts +11 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/tools/poi.d.ts +9 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/tools/routes.d.ts +75 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/types.d.ts +68 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types.js +16 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/package.json +97 -0
- package/ts/package.json +293 -3
- package/ts/scripts/async-collect.ts +91 -0
- package/ts/scripts/map-tools-vendor-package-proof.ts +404 -0
- package/ts/scripts/skeleton-check.ts +1 -1
- package/ts/scripts/skeleton-integration-test.ts +23 -1
- package/ts/scripts/state-cli.ts +586 -0
- package/ts/scripts/turn-handoff-collect.ts +178 -0
- package/ts/src/artifact-gate.ts +991 -0
- package/ts/src/benchmark-agent-conformance.ts +619 -0
- package/ts/src/benchmark-environment-bridge.ts +538 -0
- package/ts/src/benchmark-headless-child-diagnostics.ts +184 -0
- package/ts/src/benchmark-tool-isolation.ts +166 -0
- package/ts/src/bookable-facts.ts +837 -0
- package/ts/src/booking-saga.ts +133 -0
- package/ts/src/booking-surface/availability-policy.ts +522 -0
- package/ts/src/booking-surface/booking-intent.ts +86 -0
- package/ts/src/booking-surface/cancel-refund-commission.ts +488 -0
- package/ts/src/booking-surface/canonical-schema.js +116 -0
- package/ts/src/booking-surface/contracts.ts +475 -0
- package/ts/src/booking-surface/dsh-planner.ts +1949 -0
- package/ts/src/booking-surface/dsh-plugin.js +378 -0
- package/ts/src/booking-surface/dsh-worker.ts +110 -0
- package/ts/src/booking-surface/error-codes.ts +241 -0
- package/ts/src/booking-surface/index.ts +9 -0
- package/ts/src/booking-surface/managed-dsh-cleanup-diagnostic.ts +151 -0
- package/ts/src/booking-surface/managed-dsh-run-port.ts +273 -0
- package/ts/src/booking-surface/profile.ts +42 -0
- package/ts/src/booking-surface/recovery-chain.ts +409 -0
- package/ts/src/booking-surface/runtime.ts +1793 -0
- package/ts/src/booking-surface/server.ts +733 -0
- package/ts/src/booking-surface/startup.ts +201 -0
- package/ts/src/booking-surface/validation.ts +217 -0
- package/ts/src/companions.ts +113 -0
- package/ts/src/contracts.ts +17 -3
- package/ts/src/dsh-llm.ts +152 -17
- package/ts/src/flyai-setup-tool.ts +77 -0
- package/ts/src/index.ts +2123 -200
- package/ts/src/itinerary-deck.ts +189 -0
- package/ts/src/itinerary-doc-shared.ts +741 -0
- package/ts/src/itinerary-html.ts +171 -0
- package/ts/src/loop.ts +234 -26
- package/ts/src/memory-capture.ts +283 -0
- package/ts/src/memory-decay.ts +55 -0
- package/ts/src/memory-utility.ts +70 -0
- package/ts/src/mock-llm.ts +4 -8
- package/ts/src/model.ts +88 -2
- package/ts/src/recall/card.ts +106 -0
- package/ts/src/recall/evaluator.ts +170 -0
- package/ts/src/recall/integration.ts +35 -0
- package/ts/src/recall/tick.ts +213 -0
- package/ts/src/session-link/plan-it-action.ts +78 -0
- package/ts/src/session-link/session-link.ts +275 -0
- package/ts/src/share/adapters.ts +93 -0
- package/ts/src/share/share-consent.ts +105 -0
- package/ts/src/share/share-deck.ts +85 -0
- package/ts/src/share/share-token.ts +166 -0
- package/ts/src/slot-spec.ts +172 -0
- package/ts/src/state-ledger.ts +1166 -0
- package/ts/src/subagent-job-id-guard.ts +72 -0
- package/ts/src/time-anchor.ts +338 -0
- package/ts/src/tool-packet.ts +32 -0
- package/ts/src/travel-slots.ts +219 -0
- package/ts/src/travel-timeline.ts +83 -0
- package/ts/src/turn-deadline.ts +366 -0
- package/ts/src/turn-policy.ts +154 -0
- package/ts/src/unified.ts +444 -105
- package/ts/src/wish-pool.ts +79 -0
- package/dist/scripts/session-attach-wait.js +0 -41
|
@@ -1,131 +1,968 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
1
|
+
/**
|
|
2
|
+
* FlyAI 官方通道能力层(飞猪开放平台,data-sources.md §8 / RFC P0;issue #521)。
|
|
3
|
+
*
|
|
4
|
+
* 完整对接官方 @fly-ai/flyai-cli 1.0.16 的 8 个公开只读命令:
|
|
5
|
+
* flight/train: search-flight / search-train
|
|
6
|
+
* hotel: search-hotel(别名 search-hotels)
|
|
7
|
+
* poi: search-poi
|
|
8
|
+
* keyword: keyword-search(别名 fliggy-fast-search)
|
|
9
|
+
* ai: ai-search
|
|
10
|
+
* marriott: search-marriott-hotel / search-marriott-package
|
|
11
|
+
*
|
|
12
|
+
* 链路(CLI spawn,与 hbcli/weather 同构):
|
|
13
|
+
* gotry capabilities/flyai.ts → npx -y @fly-ai/flyai-cli <command> [opts]
|
|
14
|
+
* → 飞猪 MCP(实时直连官方商品库)
|
|
15
|
+
*
|
|
16
|
+
* 契约(L4 不变量):
|
|
17
|
+
* - 只读:8 命令全只读,交易经 jumpUrl/detailUrl 由人完成(WriteGate 同构);
|
|
18
|
+
* - 永不抛错:网络/超时/解析失败一律降级为结构化 verdict;
|
|
19
|
+
* - 证据链:成功 [实时API:flyai@ts];失败 [实时API:flyai@error@ts];
|
|
20
|
+
* - key 解析/endpoint 覆盖口径与官方 CLI 同源(flyai-config.ts);
|
|
21
|
+
* - 子进程资源有界:进程组边界统一复用 spawn-bounded.ts(与 hbcli/anything
|
|
22
|
+
* 同策略),主进程退出后 stderr drain 最多等 drainMs(默认 3s),随后杀残留
|
|
23
|
+
* 后代并以 drain-timeout fail-closed 返回——不被持管道的后代挂死,也不把
|
|
24
|
+
* 不完整收集写成成功库存事实。
|
|
25
|
+
*
|
|
26
|
+
* 故障闭集(verdict):
|
|
27
|
+
* hit / miss / auth-error(401)/ forbidden(403)/
|
|
28
|
+
* needs-setup(匿名试用 429 Trial limit)/ rate-limited(普通 429)/
|
|
29
|
+
* timeout / error(网络/Sentinel/malformed)。
|
|
30
|
+
*/
|
|
31
|
+
import { closeSync, openSync, readFileSync, unlinkSync } from 'node:fs';
|
|
32
|
+
import { tmpdir } from 'node:os';
|
|
33
|
+
import { join } from 'node:path';
|
|
34
|
+
import { resolveFlyaiEndpoint, resolveFlyaiKey } from './flyai-config.js';
|
|
35
|
+
import { spawnBounded } from './spawn-bounded.js';
|
|
36
|
+
function nonEmptyText(value) {
|
|
37
|
+
if (typeof value !== 'string')
|
|
38
|
+
return undefined;
|
|
39
|
+
const text = value.trim();
|
|
40
|
+
return text || undefined;
|
|
41
|
+
}
|
|
42
|
+
function optionalText(value, allowNull = false) {
|
|
43
|
+
if (value === undefined)
|
|
44
|
+
return undefined;
|
|
45
|
+
if (allowNull && value === null)
|
|
46
|
+
return null;
|
|
47
|
+
return nonEmptyText(value);
|
|
48
|
+
}
|
|
49
|
+
function positiveFiniteNumber(value) {
|
|
50
|
+
const number = typeof value === 'number'
|
|
51
|
+
? value
|
|
52
|
+
: typeof value === 'string' && value.trim() ? Number(value.trim()) : Number.NaN;
|
|
53
|
+
return Number.isFinite(number) && number > 0 ? number : undefined;
|
|
54
|
+
}
|
|
55
|
+
function positiveFiniteDuration(value) {
|
|
56
|
+
if (typeof value === 'number')
|
|
57
|
+
return positiveFiniteNumber(value);
|
|
58
|
+
const text = nonEmptyText(value);
|
|
59
|
+
if (!text)
|
|
60
|
+
return undefined;
|
|
61
|
+
const minutes = /^(\d+(?:\.\d+)?)\s*分钟$/.exec(text)?.[1];
|
|
62
|
+
return positiveFiniteNumber(minutes ?? text);
|
|
63
|
+
}
|
|
64
|
+
function parseTransportPrice(value) {
|
|
65
|
+
const raw = nonEmptyText(value);
|
|
66
|
+
if (!raw)
|
|
67
|
+
return undefined;
|
|
68
|
+
const bare = raw.replace(/^¥\s*/, '');
|
|
69
|
+
if (/^\d+(?:\.\d+)?$/.test(bare)) {
|
|
70
|
+
const price = Number(bare);
|
|
71
|
+
return Number.isFinite(price) && price > 0 ? { price } : undefined;
|
|
72
|
+
}
|
|
73
|
+
// 未鉴权态打码 1xxx/¥7xx:保留原值,绝不猜成数字。
|
|
74
|
+
if (/^\d+x+$/.test(bare))
|
|
75
|
+
return { price: 0, priceRaw: raw };
|
|
76
|
+
return undefined;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* 从 CLI stdout 提取首个含 data.itemList 的完整 JSON 对象。
|
|
80
|
+
* 扫描器识别字符串与转义;npx 前后缀日志不影响切分。
|
|
81
|
+
*/
|
|
82
|
+
export function parseFlyaiItemList(stdout) {
|
|
83
|
+
let start = -1;
|
|
84
|
+
let depth = 0;
|
|
85
|
+
let inString = false;
|
|
86
|
+
let escaped = false;
|
|
87
|
+
let sawIncompleteObject = false;
|
|
88
|
+
for (let index = 0; index < stdout.length; index += 1) {
|
|
89
|
+
const char = stdout[index];
|
|
90
|
+
if (start < 0) {
|
|
91
|
+
if (char === '{') {
|
|
92
|
+
start = index;
|
|
93
|
+
depth = 1;
|
|
94
|
+
inString = false;
|
|
95
|
+
escaped = false;
|
|
96
|
+
sawIncompleteObject = true;
|
|
97
|
+
}
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
if (inString) {
|
|
101
|
+
if (escaped)
|
|
102
|
+
escaped = false;
|
|
103
|
+
else if (char === '\\')
|
|
104
|
+
escaped = true;
|
|
105
|
+
else if (char === '"')
|
|
106
|
+
inString = false;
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
if (char === '"')
|
|
110
|
+
inString = true;
|
|
111
|
+
else if (char === '{')
|
|
112
|
+
depth += 1;
|
|
113
|
+
else if (char === '}') {
|
|
114
|
+
depth -= 1;
|
|
115
|
+
if (depth !== 0)
|
|
116
|
+
continue;
|
|
117
|
+
const candidate = stdout.slice(start, index + 1);
|
|
118
|
+
start = -1;
|
|
119
|
+
sawIncompleteObject = false;
|
|
120
|
+
try {
|
|
121
|
+
const parsed = JSON.parse(candidate);
|
|
122
|
+
if (Array.isArray(parsed.data?.itemList))
|
|
123
|
+
return parsed.data.itemList;
|
|
124
|
+
}
|
|
125
|
+
catch {
|
|
126
|
+
// 前缀日志里的成对花括号;继续找下一完整对象。
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
throw new Error(sawIncompleteObject
|
|
131
|
+
? 'incomplete FlyAI JSON object'
|
|
132
|
+
: 'no complete FlyAI itemList JSON object');
|
|
133
|
+
}
|
|
134
|
+
/** 解析 stdout 中首个完整 JSON 对象(不限 itemList;ai-search 用) */
|
|
135
|
+
export function parseFlyaiJsonEnvelope(stdout) {
|
|
136
|
+
let start = -1;
|
|
137
|
+
let depth = 0;
|
|
138
|
+
let inString = false;
|
|
139
|
+
let escaped = false;
|
|
140
|
+
for (let index = 0; index < stdout.length; index += 1) {
|
|
141
|
+
const char = stdout[index];
|
|
142
|
+
if (start < 0) {
|
|
143
|
+
if (char === '{') {
|
|
144
|
+
start = index;
|
|
145
|
+
depth = 1;
|
|
146
|
+
inString = false;
|
|
147
|
+
escaped = false;
|
|
148
|
+
}
|
|
149
|
+
continue;
|
|
150
|
+
}
|
|
151
|
+
if (inString) {
|
|
152
|
+
if (escaped)
|
|
153
|
+
escaped = false;
|
|
154
|
+
else if (char === '\\')
|
|
155
|
+
escaped = true;
|
|
156
|
+
else if (char === '"')
|
|
157
|
+
inString = false;
|
|
158
|
+
continue;
|
|
159
|
+
}
|
|
160
|
+
if (char === '"')
|
|
161
|
+
inString = true;
|
|
162
|
+
else if (char === '{')
|
|
163
|
+
depth += 1;
|
|
164
|
+
else if (char === '}') {
|
|
165
|
+
depth -= 1;
|
|
166
|
+
if (depth === 0) {
|
|
167
|
+
const candidate = stdout.slice(start, index + 1);
|
|
168
|
+
try {
|
|
169
|
+
return JSON.parse(candidate);
|
|
170
|
+
}
|
|
171
|
+
catch {
|
|
172
|
+
start = -1;
|
|
173
|
+
depth = 0;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
throw new Error('no complete FlyAI JSON envelope');
|
|
179
|
+
}
|
|
180
|
+
const REDACTED_FLYAI_KEY = '[已隐藏 FlyAI key]';
|
|
181
|
+
const REDACTED_FLYAI_ENDPOINT = '?(已隐藏 endpoint 敏感参数)';
|
|
182
|
+
function shellUrlToken(value) {
|
|
183
|
+
const match = /[),.;!?,。!?]+$/.exec(value);
|
|
184
|
+
const suffix = match?.[0] ?? '';
|
|
185
|
+
return { token: suffix ? value.slice(0, -suffix.length) : value, suffix };
|
|
186
|
+
}
|
|
187
|
+
/** 只针对实际 DEBUG endpoint 的 host 脱去 userinfo/query/fragment。 */
|
|
188
|
+
function redactFlyaiText(value, sensitive) {
|
|
189
|
+
let output = value;
|
|
190
|
+
if (sensitive.key)
|
|
191
|
+
output = output.split(sensitive.key).join(REDACTED_FLYAI_KEY);
|
|
192
|
+
if (!sensitive.debugEndpoint)
|
|
193
|
+
return output;
|
|
194
|
+
let debugHost;
|
|
195
|
+
try {
|
|
196
|
+
debugHost = new URL(sensitive.debugEndpoint).host.toLowerCase();
|
|
197
|
+
}
|
|
198
|
+
catch {
|
|
199
|
+
return output;
|
|
200
|
+
}
|
|
201
|
+
return output.replace(/\bhttps?:\/\/[^\s"'<>]+/gi, (raw) => {
|
|
202
|
+
const { token, suffix } = shellUrlToken(raw);
|
|
203
|
+
try {
|
|
204
|
+
const url = new URL(token);
|
|
205
|
+
if (url.host.toLowerCase() !== debugHost)
|
|
206
|
+
return raw;
|
|
207
|
+
const path = url.pathname || '/';
|
|
208
|
+
const hasSensitivePart = Boolean(url.username || url.password || url.search || url.hash);
|
|
209
|
+
return `${url.protocol}//${url.host}${path}${hasSensitivePart ? REDACTED_FLYAI_ENDPOINT : ''}${suffix}`;
|
|
210
|
+
}
|
|
211
|
+
catch {
|
|
212
|
+
return raw;
|
|
213
|
+
}
|
|
6
214
|
});
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
215
|
+
}
|
|
216
|
+
function redactFlyaiValue(value, sensitive) {
|
|
217
|
+
if (typeof value === 'string')
|
|
218
|
+
return redactFlyaiText(value, sensitive);
|
|
219
|
+
if (Array.isArray(value))
|
|
220
|
+
return value.map(item => redactFlyaiValue(item, sensitive));
|
|
221
|
+
if (value && typeof value === 'object') {
|
|
222
|
+
return Object.fromEntries(Object.entries(value).map(([key, item]) => [key, redactFlyaiValue(item, sensitive)]));
|
|
223
|
+
}
|
|
224
|
+
return value;
|
|
225
|
+
}
|
|
226
|
+
function safeFlyaiResult(result, sensitive) {
|
|
227
|
+
return redactFlyaiValue(result, sensitive);
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* 子进程 stdout 走临时文件而非管道(issue #84):管道在 CLI 异步写后
|
|
231
|
+
* 立即 exit 时丢尾部(实测截断 ~7.6KB,exit=0 静默);文件无此问题。
|
|
232
|
+
*
|
|
233
|
+
* 资源边界(#516):进程组/清理策略统一复用 capabilities/spawn-bounded.ts
|
|
234
|
+
* (与 hbcli/anything 同一进程树边界),本函数只叠加 flyai 特有的
|
|
235
|
+
* 文件 stdout 收集与本地终止分类——主进程 exit 后 stderr drain 最多等
|
|
236
|
+
* drainMs(默认 3s),超时杀组、drainTimedOut 结算,不写成成功事实。
|
|
237
|
+
*/
|
|
238
|
+
function sh(cmd, args, opts) {
|
|
239
|
+
// 预先取消:不 spawn 任何进程,不产生 stdout 临时文件。
|
|
240
|
+
if (opts.signal?.aborted) {
|
|
241
|
+
return Promise.resolve({ code: -1, stdout: '', stderr: '', cancelled: true, localTermination: 'signal' });
|
|
242
|
+
}
|
|
243
|
+
const outFile = join(tmpdir(), `gotry-flyai-${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}.out`);
|
|
244
|
+
const outFd = openSync(outFile, 'w');
|
|
245
|
+
const readOut = () => {
|
|
11
246
|
try {
|
|
12
|
-
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
247
|
+
return readFileSync(outFile, 'utf8');
|
|
248
|
+
}
|
|
249
|
+
catch {
|
|
250
|
+
return '';
|
|
251
|
+
}
|
|
252
|
+
};
|
|
253
|
+
const cleanupFile = () => {
|
|
254
|
+
try {
|
|
255
|
+
closeSync(outFd);
|
|
256
|
+
}
|
|
257
|
+
catch { /* ignore */ }
|
|
258
|
+
try {
|
|
259
|
+
unlinkSync(outFile);
|
|
260
|
+
}
|
|
261
|
+
catch { /* ignore */ }
|
|
262
|
+
};
|
|
263
|
+
return spawnBounded(cmd, args, {
|
|
264
|
+
env: opts.env ?? process.env,
|
|
265
|
+
cwd: process.cwd(),
|
|
266
|
+
timeoutMs: opts.timeoutMs,
|
|
267
|
+
signal: opts.signal,
|
|
268
|
+
stdoutFd: outFd,
|
|
269
|
+
drainMs: opts.drainMs ?? 3_000,
|
|
270
|
+
}).then((r) => {
|
|
271
|
+
// 必须先读出 stdout 再清理:closeSync 之后文件即删除,
|
|
272
|
+
// 先 cleanup 会把成功响应(含空 itemList miss)读成空串。
|
|
273
|
+
const stdout = readOut();
|
|
274
|
+
cleanupFile();
|
|
275
|
+
const localTermination = r.aborted
|
|
276
|
+
? 'signal'
|
|
277
|
+
: r.timedOut
|
|
278
|
+
? 'deadline'
|
|
279
|
+
: r.drainTimedOut
|
|
280
|
+
? 'drain-timeout'
|
|
281
|
+
: r.error
|
|
282
|
+
? 'spawn'
|
|
283
|
+
: r.signal
|
|
284
|
+
? 'empty-exit'
|
|
285
|
+
: undefined;
|
|
286
|
+
return {
|
|
287
|
+
code: r.code ?? -1,
|
|
288
|
+
stdout,
|
|
289
|
+
stderr: r.stderr,
|
|
290
|
+
...(r.signal !== null && r.signal !== undefined ? { signal: r.signal } : {}),
|
|
291
|
+
...(r.error ? { error: r.error } : {}),
|
|
292
|
+
...(r.timedOut ? { timedOut: true } : {}),
|
|
293
|
+
...(r.drainTimedOut ? { drainTimedOut: true } : {}),
|
|
294
|
+
...(r.aborted ? { cancelled: true } : {}),
|
|
295
|
+
...(localTermination ? { localTermination } : {}),
|
|
296
|
+
};
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
function localTerminationResult(kind, termination, r, latencyMs, ts, envNote) {
|
|
300
|
+
const detail = termination === 'spawn'
|
|
301
|
+
? `启动失败${r.error ? `:${r.error}` : ''}`
|
|
302
|
+
: termination === 'signal'
|
|
303
|
+
? `进程收到本机取消信号${r.signal ? `(${r.signal})` : ''}`
|
|
304
|
+
: termination === 'deadline'
|
|
305
|
+
? '达到本地 deadline'
|
|
306
|
+
: termination === 'drain-timeout'
|
|
307
|
+
? `主进程已退出(code ${r.code})但持有 stderr 的后代在 drain 时限内未释放,整组已终止——结果收集不完整`
|
|
308
|
+
: '进程退出但未返回可判定结果';
|
|
309
|
+
return {
|
|
310
|
+
kind, ...envNote, latencyMs, ok: false, via: 'flyai-error', verdict: 'error',
|
|
311
|
+
retryable: false, localTermination: termination,
|
|
312
|
+
evidence: `[实时API:flyai@error@${ts}] local termination: ${termination}`,
|
|
313
|
+
error: `FlyAI 本地终止(${detail})`,
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
const YMD = /^\d{4}-\d{2}-\d{2}$/;
|
|
317
|
+
/** search-poi 闭集类别(官方 references,2026-09 核实) */
|
|
318
|
+
export const FLYAI_POI_CATEGORIES = [
|
|
319
|
+
'自然风光', '山湖田园', '森林丛林', '峡谷瀑布', '沙滩海岛', '沙漠草原',
|
|
320
|
+
'人文古迹', '古镇古村', '历史古迹', '园林花园', '宗教场所', '公园乐园',
|
|
321
|
+
'主题乐园', '水上乐园', '影视基地', '动物园', '植物园', '海洋馆', '体育场馆',
|
|
322
|
+
'演出赛事', '剧院剧场', '博物馆', '纪念馆', '展览馆', '地标建筑', '市集',
|
|
323
|
+
'文创街区', '城市观光', '户外活动', '滑雪', '漂流', '冲浪', '潜水', '露营', '温泉',
|
|
324
|
+
];
|
|
325
|
+
const HOTEL_SORTS = ['distance_asc', 'rate_desc', 'price_asc', 'price_desc', 'no_rank'];
|
|
326
|
+
const MARRIOTT_PACKAGE_SORTS = ['price_asc', 'price_desc'];
|
|
327
|
+
function badArg(kind, error, ts) {
|
|
328
|
+
return {
|
|
329
|
+
kind, ok: false, via: 'flyai-error', verdict: 'error', latencyMs: 0,
|
|
330
|
+
evidence: `[实时API:flyai@error@${ts}] bad args`, error,
|
|
331
|
+
};
|
|
332
|
+
}
|
|
333
|
+
function hourOk(value) {
|
|
334
|
+
return Number.isInteger(value) && value >= 0 && value <= 23;
|
|
335
|
+
}
|
|
336
|
+
/** 各 kind 参数校验 → CLI args;不合法返回 error 串 */
|
|
337
|
+
function buildCliArgs(q) {
|
|
338
|
+
const csv = (value) => value ? [value] : [];
|
|
339
|
+
switch (q.kind) {
|
|
340
|
+
case 'flight':
|
|
341
|
+
case 'train': {
|
|
342
|
+
if (!(q.origin ?? '').trim())
|
|
343
|
+
return { error: 'origin(出发城市/机场)required' };
|
|
344
|
+
for (const d of [q.depDate, q.depDateStart, q.depDateEnd, q.backDate]) {
|
|
345
|
+
if (d && !YMD.test(d))
|
|
346
|
+
return { error: `日期需 YYYY-MM-DD,实际 ${d}` };
|
|
347
|
+
}
|
|
348
|
+
if (q.journeyType !== undefined && q.journeyType !== 1 && q.journeyType !== 2) {
|
|
349
|
+
return { error: 'journeyType 只接受 1(直达)/2(中转)' };
|
|
350
|
+
}
|
|
351
|
+
for (const h of [q.depHourStart, q.depHourEnd, q.arrHourStart, q.arrHourEnd]) {
|
|
352
|
+
if (h !== undefined && !hourOk(h))
|
|
353
|
+
return { error: '小时范围需 0–23 整数' };
|
|
354
|
+
}
|
|
355
|
+
if (q.maxPrice !== undefined && !(q.maxPrice > 0))
|
|
356
|
+
return { error: 'maxPrice 需为正数' };
|
|
357
|
+
if (q.totalDurationHour !== undefined && !(q.totalDurationHour > 0))
|
|
358
|
+
return { error: 'totalDurationHour 需为正数' };
|
|
359
|
+
const sub = q.kind === 'flight' ? 'search-flight' : 'search-train';
|
|
360
|
+
return {
|
|
361
|
+
args: [
|
|
362
|
+
sub, '--origin', q.origin.trim(),
|
|
363
|
+
...(q.destination?.trim() ? ['--destination', q.destination.trim()] : []),
|
|
364
|
+
...(q.depDate ? ['--dep-date', q.depDate] : []),
|
|
365
|
+
...(q.depDateStart ? ['--dep-date-start', q.depDateStart] : []),
|
|
366
|
+
...(q.depDateEnd ? ['--dep-date-end', q.depDateEnd] : []),
|
|
367
|
+
...(q.backDate ? ['--back-date', q.backDate] : []),
|
|
368
|
+
...(q.journeyType !== undefined ? ['--journey-type', String(q.journeyType)] : []),
|
|
369
|
+
...csv(q.seatClassName?.trim()).flatMap(v => ['--seat-class-name', v]),
|
|
370
|
+
...csv(q.transportNo?.trim()).flatMap(v => ['--transport-no', v]),
|
|
371
|
+
...csv(q.transferCity?.trim()).flatMap(v => ['--transfer-city', v]),
|
|
372
|
+
...(q.depHourStart !== undefined ? ['--dep-hour-start', String(q.depHourStart)] : []),
|
|
373
|
+
...(q.depHourEnd !== undefined ? ['--dep-hour-end', String(q.depHourEnd)] : []),
|
|
374
|
+
...(q.arrHourStart !== undefined ? ['--arr-hour-start', String(q.arrHourStart)] : []),
|
|
375
|
+
...(q.arrHourEnd !== undefined ? ['--arr-hour-end', String(q.arrHourEnd)] : []),
|
|
376
|
+
...(q.totalDurationHour !== undefined ? ['--total-duration-hour', String(q.totalDurationHour)] : []),
|
|
377
|
+
...(q.maxPrice !== undefined ? ['--max-price', String(q.maxPrice)] : []),
|
|
378
|
+
...(q.sortType?.trim() ? ['--sort-type', q.sortType.trim()] : []),
|
|
379
|
+
],
|
|
380
|
+
};
|
|
381
|
+
}
|
|
382
|
+
case 'hotel':
|
|
383
|
+
case 'marriott-hotel': {
|
|
384
|
+
const dest = (q.destName ?? '').trim();
|
|
385
|
+
if (!dest)
|
|
386
|
+
return { error: 'destName(目的地 国家/省/市/区)required' };
|
|
387
|
+
const badPair = (q.checkInDate ? 1 : 0) !== (q.checkOutDate ? 1 : 0)
|
|
388
|
+
|| (q.checkInDate && (!YMD.test(q.checkInDate) || !YMD.test(q.checkOutDate ?? '')));
|
|
389
|
+
if (badPair)
|
|
390
|
+
return { error: 'checkInDate/checkOutDate 须成对且为 YYYY-MM-DD' };
|
|
391
|
+
if (q.sort && !HOTEL_SORTS.includes(q.sort))
|
|
392
|
+
return { error: `sort 取值:${HOTEL_SORTS.join('/')}` };
|
|
393
|
+
if (q.maxPrice !== undefined && !(q.maxPrice > 0))
|
|
394
|
+
return { error: 'maxPrice 需为正数' };
|
|
395
|
+
if (q.hotelStars && !/^[1-5](,[1-5])*$/.test(q.hotelStars))
|
|
396
|
+
return { error: 'hotelStars 为 1–5 逗号分隔' };
|
|
397
|
+
const sub = q.kind === 'hotel' ? 'search-hotel' : 'search-marriott-hotel';
|
|
398
|
+
return {
|
|
399
|
+
args: [
|
|
400
|
+
sub, '--dest-name', dest,
|
|
401
|
+
...(q.keyWords?.trim() ? ['--key-words', q.keyWords.trim()] : []),
|
|
402
|
+
...(q.poiName?.trim() ? ['--poi-name', q.poiName.trim()] : []),
|
|
403
|
+
...(q.hotelTypes?.trim() ? ['--hotel-types', q.hotelTypes.trim()] : []),
|
|
404
|
+
...(q.sort ? ['--sort', q.sort] : []),
|
|
405
|
+
...(q.checkInDate && q.checkOutDate ? ['--check-in-date', q.checkInDate, '--check-out-date', q.checkOutDate] : []),
|
|
406
|
+
...(q.hotelStars ? ['--hotel-stars', q.hotelStars] : []),
|
|
407
|
+
...(q.hotelBedTypes?.trim() ? ['--hotel-bed-types', q.hotelBedTypes.trim()] : []),
|
|
408
|
+
...(q.maxPrice !== undefined ? ['--max-price', String(q.maxPrice)] : []),
|
|
409
|
+
...(q.kind === 'marriott-hotel'
|
|
410
|
+
? [
|
|
411
|
+
...(q.hotelBrands?.trim() ? ['--hotel-brands', q.hotelBrands.trim()] : []),
|
|
412
|
+
...(q.hotelName?.trim() ? ['--hotel-name', q.hotelName.trim()] : []),
|
|
413
|
+
]
|
|
414
|
+
: []),
|
|
415
|
+
],
|
|
416
|
+
};
|
|
417
|
+
}
|
|
418
|
+
case 'poi': {
|
|
419
|
+
if (!(q.cityName ?? '').trim())
|
|
420
|
+
return { error: 'cityName(景点所在城市)required' };
|
|
421
|
+
if (q.poiLevel !== undefined && !(Number.isInteger(q.poiLevel) && q.poiLevel >= 1 && q.poiLevel <= 5)) {
|
|
422
|
+
return { error: 'poiLevel 为 1–5 整数' };
|
|
423
|
+
}
|
|
424
|
+
if (q.category && !FLYAI_POI_CATEGORIES.includes(q.category)) {
|
|
425
|
+
return { error: `category 不在官方闭集(共 ${FLYAI_POI_CATEGORIES.length} 类)` };
|
|
426
|
+
}
|
|
427
|
+
return {
|
|
428
|
+
args: [
|
|
429
|
+
'search-poi', '--city-name', q.cityName.trim(),
|
|
430
|
+
...(q.poiLevel !== undefined ? ['--poi-level', String(q.poiLevel)] : []),
|
|
431
|
+
...(q.keyword?.trim() ? ['--keyword', q.keyword.trim()] : []),
|
|
432
|
+
...(q.category ? ['--category', q.category] : []),
|
|
433
|
+
],
|
|
434
|
+
};
|
|
435
|
+
}
|
|
436
|
+
case 'keyword':
|
|
437
|
+
if (!(q.query ?? '').trim())
|
|
438
|
+
return { error: 'query(关键词)required' };
|
|
439
|
+
return { args: ['keyword-search', '--query', q.query.trim()] };
|
|
440
|
+
case 'ai':
|
|
441
|
+
if (!(q.query ?? '').trim())
|
|
442
|
+
return { error: 'query(完整自然语言)required' };
|
|
443
|
+
return { args: ['ai-search', '--query', q.query.trim()] };
|
|
444
|
+
case 'marriott-package': {
|
|
445
|
+
if (![q.keyword, q.hotelName, q.provinceOrCity].some(value => value?.trim()))
|
|
446
|
+
return { error: 'keyword/hotelName/provinceOrCity 至少填写一项' };
|
|
447
|
+
if (q.sortType && !MARRIOTT_PACKAGE_SORTS.includes(q.sortType)) {
|
|
448
|
+
return { error: `sortType 仅接受 ${MARRIOTT_PACKAGE_SORTS.join('/')}` };
|
|
449
|
+
}
|
|
450
|
+
return {
|
|
451
|
+
args: [
|
|
452
|
+
'search-marriott-package',
|
|
453
|
+
...(q.keyword?.trim() ? ['--keyword', q.keyword.trim()] : []),
|
|
454
|
+
...(q.hotelName?.trim() ? ['--hotel-name', q.hotelName.trim()] : []),
|
|
455
|
+
...(q.provinceOrCity?.trim() ? ['--province-or-city', q.provinceOrCity.trim()] : []),
|
|
456
|
+
...(q.sortType ? ['--sort-type', q.sortType] : []),
|
|
457
|
+
],
|
|
458
|
+
};
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
const KIND_LABEL = {
|
|
463
|
+
flight: 'flight', train: 'train', hotel: 'hotel', poi: 'poi',
|
|
464
|
+
keyword: 'keyword', ai: 'ai', 'marriott-hotel': 'marriott hotel', 'marriott-package': 'marriott package',
|
|
465
|
+
};
|
|
466
|
+
/**
|
|
467
|
+
* FlyAI 官方只读检索(8 命令)— 任何失败走降级;不抛错。
|
|
468
|
+
*/
|
|
469
|
+
export async function flyaiSearch(q) {
|
|
470
|
+
const started = Date.now();
|
|
471
|
+
const ts = new Date().toISOString();
|
|
472
|
+
const envContext = await runtimeEnvNote();
|
|
473
|
+
const envNote = envContext.note;
|
|
474
|
+
const sensitive = envContext.sensitive;
|
|
475
|
+
const safe = (result) => safeFlyaiResult(result, sensitive);
|
|
476
|
+
const built = buildCliArgs(q);
|
|
477
|
+
if (built.error)
|
|
478
|
+
return { ...badArg(q.kind, built.error, ts), ...envNote };
|
|
479
|
+
const prefix = q.cliPrefixArgs ?? ['-y', '@fly-ai/flyai-cli@1.0.16'];
|
|
480
|
+
const r = await sh(q.cliBin ?? 'npx', [...prefix, ...built.args], {
|
|
481
|
+
timeoutMs: q.timeoutMs ?? 30_000,
|
|
482
|
+
drainMs: q.drainMs,
|
|
483
|
+
signal: q.signal,
|
|
484
|
+
});
|
|
485
|
+
const latencyMs = Date.now() - started;
|
|
486
|
+
const combined = `${r.stderr}\n${r.stdout}`;
|
|
487
|
+
if (r.cancelled) {
|
|
488
|
+
return safe({
|
|
489
|
+
kind: q.kind, ...envNote, latencyMs, ok: false, via: 'flyai-error', verdict: 'cancelled',
|
|
490
|
+
retryable: false, localTermination: 'signal',
|
|
491
|
+
evidence: `[实时API:flyai@error@${ts}] cancelled by caller`,
|
|
492
|
+
error: q.signal?.aborted ? '调用前已取消(未发起检索)' : '检索在途被取消(进程组已终止)',
|
|
21
493
|
});
|
|
22
|
-
|
|
23
|
-
|
|
494
|
+
}
|
|
495
|
+
if (r.timedOut) {
|
|
496
|
+
return safe({
|
|
497
|
+
kind: q.kind, ...envNote, latencyMs, ok: false, via: 'flyai-error', verdict: 'timeout',
|
|
498
|
+
retryable: false, localTermination: 'deadline',
|
|
499
|
+
evidence: `[实时API:flyai@error@${ts}] local deadline after ${q.timeoutMs ?? 30_000}ms`,
|
|
500
|
+
error: `FlyAI 本地终止(达到 deadline ${q.timeoutMs ?? 30_000}ms${r.drainTimedOut ? ';stderr 后代 drain 超时' : ''})`,
|
|
501
|
+
setup: '本机 deadline 已终止本次检索;请调整 timeoutMs 或稍后由上层重新发起。',
|
|
24
502
|
});
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
503
|
+
}
|
|
504
|
+
// A signal/empty exit/spawn failure carries no trustworthy upstream
|
|
505
|
+
// response. Handle it before HTTP text classification so stale partial
|
|
506
|
+
// stdout (for example a 429 printed before SIGTERM) cannot trigger retry.
|
|
507
|
+
if (r.localTermination) {
|
|
508
|
+
return safe(localTerminationResult(q.kind, r.localTermination, r, latencyMs, ts, envNote));
|
|
509
|
+
}
|
|
510
|
+
if (r.error || r.code !== 0 || /HTTP\s*(4\d\d|5\d\d)/.test(combined)) {
|
|
511
|
+
return safe(classifyUpstreamFailure(q.kind, r, latencyMs, ts, envNote));
|
|
512
|
+
}
|
|
513
|
+
// ai-search:data 是自由形状,不走 itemList。
|
|
514
|
+
if (q.kind === 'ai') {
|
|
515
|
+
let envelope;
|
|
516
|
+
try {
|
|
517
|
+
envelope = parseFlyaiJsonEnvelope(r.stdout);
|
|
518
|
+
}
|
|
519
|
+
catch (e) {
|
|
520
|
+
const raw = r.stdout.replace(/\s+/g, ' ').slice(0, 160);
|
|
521
|
+
return safe({
|
|
522
|
+
kind: q.kind, ...envNote, latencyMs, ok: false, via: 'flyai-error', verdict: 'error',
|
|
523
|
+
evidence: `[实时API:flyai@error@${ts}] parse failed: ${raw}`,
|
|
524
|
+
error: `failed to parse flyai ai output (${e.message}): ${raw}`,
|
|
525
|
+
});
|
|
526
|
+
}
|
|
527
|
+
const systemMessage = nonEmptyText(envelope.systemMessage);
|
|
528
|
+
if (envelope.status !== undefined && envelope.status !== 0) {
|
|
529
|
+
return safe({
|
|
530
|
+
kind: q.kind, ...envNote, latencyMs, ok: false, via: 'flyai-error', verdict: 'error',
|
|
531
|
+
evidence: `[实时API:flyai@error@${ts}] ai status=${String(envelope.status)}`,
|
|
532
|
+
error: nonEmptyText(envelope.message) ?? 'ai-search upstream failure',
|
|
533
|
+
...(systemMessage ? { systemMessage } : {}),
|
|
32
534
|
});
|
|
535
|
+
}
|
|
536
|
+
const aiData = envelope.data;
|
|
537
|
+
const empty = aiData === undefined || aiData === null || aiData === ''
|
|
538
|
+
|| (Array.isArray(aiData) && aiData.length === 0);
|
|
539
|
+
return safe(JSON.parse(JSON.stringify({
|
|
540
|
+
kind: q.kind, ...envNote, latencyMs, ok: true, via: 'flyai',
|
|
541
|
+
verdict: empty ? 'miss' : 'hit',
|
|
542
|
+
...(empty ? {} : { aiData }),
|
|
543
|
+
...(systemMessage ? { systemMessage } : {}),
|
|
544
|
+
evidence: `[实时API:flyai@${ts}] ai-search ${empty ? 'empty' : 'data returned'}`,
|
|
545
|
+
})));
|
|
546
|
+
}
|
|
547
|
+
let items;
|
|
548
|
+
try {
|
|
549
|
+
items = parseFlyaiItemList(r.stdout);
|
|
550
|
+
}
|
|
551
|
+
catch (e) {
|
|
552
|
+
const raw = r.stdout.replace(/\s+/g, ' ').slice(0, 160);
|
|
553
|
+
// Sentinel 限流:exit 0,stdout 为 {"message":"SentinelBlockException…"}
|
|
554
|
+
if (/Sentinel/i.test(raw)) {
|
|
555
|
+
return safe({
|
|
556
|
+
kind: q.kind, ...envNote, latencyMs, ok: false, via: 'flyai-error', verdict: 'error',
|
|
557
|
+
retryable: false,
|
|
558
|
+
evidence: `[实时API:flyai@error@${ts}] sentinel: ${raw}`,
|
|
559
|
+
error: `FlyAI Sentinel 限流:${raw}`,
|
|
560
|
+
setup: '平台 Sentinel 限流(恢复窗口未公开)——稍后重试,或改走 gotry_session_search(账号会话)。',
|
|
561
|
+
});
|
|
562
|
+
}
|
|
563
|
+
const reason = e.message;
|
|
564
|
+
return safe({
|
|
565
|
+
kind: q.kind, ...envNote, latencyMs, ok: false, via: 'flyai-error', verdict: 'error',
|
|
566
|
+
retryable: false,
|
|
567
|
+
evidence: `[实时API:flyai@error@${ts}] parse failed(${reason}): ${raw}`,
|
|
568
|
+
error: `failed to parse flyai output (${reason}): ${raw}`,
|
|
33
569
|
});
|
|
34
|
-
}
|
|
570
|
+
}
|
|
571
|
+
return safe(parseItemListResult(q.kind, items, latencyMs, ts, envNote, r.stdout));
|
|
35
572
|
}
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
latencyMs: 0
|
|
42
|
-
};
|
|
43
|
-
const origin = (q.origin ?? '').trim();
|
|
44
|
-
const destination = (q.destination ?? '').trim();
|
|
45
|
-
if (!origin || !destination || !/^\d{4}-\d{2}-\d{2}$/.test(q.depDate ?? '')) {
|
|
573
|
+
/** 当前进程 key 来源/endpoint 覆盖(仅展示,与 spawn 实际生效同源) */
|
|
574
|
+
async function runtimeEnvNote() {
|
|
575
|
+
try {
|
|
576
|
+
const key = resolveFlyaiKey();
|
|
577
|
+
const endpoint = resolveFlyaiEndpoint();
|
|
46
578
|
return {
|
|
47
|
-
...
|
|
48
|
-
|
|
49
|
-
via: 'flyai-error',
|
|
50
|
-
verdict: 'error',
|
|
51
|
-
evidence: `[实时API:flyai@error@${ts}] bad args`,
|
|
52
|
-
error: 'origin/destination/depDate(YYYY-MM-DD) required'
|
|
579
|
+
note: { keySource: key.source, ...(endpoint.debug ? { endpointDebug: true } : {}) },
|
|
580
|
+
sensitive: { ...(key.key ? { key: key.key } : {}), ...(endpoint.debug ? { debugEndpoint: endpoint.url } : {}) },
|
|
53
581
|
};
|
|
54
582
|
}
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
'
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
583
|
+
catch {
|
|
584
|
+
return { note: { keySource: 'none' }, sensitive: {} };
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
/** 非 0 退出 / HTTP 4xx-5xx 文本 → 故障闭集分类 */
|
|
588
|
+
function classifyUpstreamFailure(kind, r, latencyMs, ts, envNote) {
|
|
589
|
+
const upstream = `${r.stderr}\n${r.stdout}`.replace(/\s+/g, ' ').trim();
|
|
590
|
+
const tag = (verdict, evidence, extra) => ({ kind, ...envNote, latencyMs, ok: false, via: 'flyai-error', verdict, evidence, ...extra });
|
|
591
|
+
if (/Invalid API key|HTTP\s*401|\b401\b/.test(upstream)) {
|
|
592
|
+
return tag('auth-error', `[实时API:flyai@error@${ts}] 401 invalid api key`, {
|
|
593
|
+
retryable: false,
|
|
594
|
+
error: `FlyAI 鉴权失败(401):${upstream.slice(0, 160)}`,
|
|
595
|
+
setup: '当前 key 无效或已吊销——在本机终端运行 `gotry setup flyai` 重新设置并验证(模型无法替你输入 key);`gotry setup flyai --clear` 可清除错误 key,清除后仍可匿名试用。',
|
|
596
|
+
});
|
|
597
|
+
}
|
|
598
|
+
if (/HTTP\s*403|\b403\b/.test(upstream)) {
|
|
599
|
+
return tag('forbidden', `[实时API:flyai@error@${ts}] 403 forbidden`, {
|
|
600
|
+
retryable: false,
|
|
601
|
+
error: `FlyAI 拒绝访问(403):${upstream.slice(0, 160)}`,
|
|
602
|
+
setup: '上游 403:核对该 key 的权限范围与控制台状态;如 endpoint 被 DEBUG_FLYAI_MCP_URL 覆盖,请确认指向。',
|
|
603
|
+
});
|
|
604
|
+
}
|
|
605
|
+
if (/Trial limit reached/.test(upstream)) {
|
|
606
|
+
return tag('needs-setup', `[实时API:flyai@error@${ts}] trial quota exhausted`, {
|
|
607
|
+
retryable: false,
|
|
608
|
+
error: `FlyAI 匿名试用额度已用尽:${upstream.slice(0, 160)}`,
|
|
609
|
+
setup: '打开 https://flyai.open.fliggy.com/console,登录后复制 API Key,本机运行 `gotry setup flyai` 配置并验证;本会话请勿盲重试 gotry_flyai_search,机/火/酒改走 gotry_session_search。',
|
|
610
|
+
});
|
|
611
|
+
}
|
|
612
|
+
if (/HTTP\s*429|\b429\b/.test(upstream)) {
|
|
613
|
+
return tag('rate-limited', `[实时API:flyai@error@${ts}] 429 rate limited`, {
|
|
614
|
+
retryable: true,
|
|
615
|
+
error: `FlyAI 普通限流(非试用达限):${upstream.slice(0, 160)}`,
|
|
616
|
+
setup: '上游普通限流:稍候再查,勿高频连发;仍 429 请稍后或换 gotry_session_search。',
|
|
617
|
+
});
|
|
618
|
+
}
|
|
619
|
+
if (/timeout|timed out|ETIMEDOUT|ECONNRESET|ECONNREFUSED|ECONNABORTED|EHOSTUNREACH|ENETUNREACH|EAI_AGAIN|socket|network|fetch failed|HTTP\s*5\d\d|\b5\d\d\b/i.test(upstream)) {
|
|
620
|
+
return tag('error', `[实时API:flyai@error@${ts}] transient upstream`, {
|
|
621
|
+
retryable: true,
|
|
622
|
+
error: `FlyAI 瞬时网络/上游错误:${upstream.slice(0, 160)}`,
|
|
623
|
+
setup: '网络抖动或上游 5xx:稍后重试;持续失败请检查本机网络或改走 gotry_session_search。',
|
|
624
|
+
});
|
|
625
|
+
}
|
|
626
|
+
return tag('error', `[实时API:flyai@error@${ts}] ${r.error ?? `exit ${r.code}`}`, {
|
|
627
|
+
retryable: false,
|
|
628
|
+
error: r.error ?? upstream.slice(0, 200),
|
|
68
629
|
});
|
|
69
|
-
|
|
70
|
-
|
|
630
|
+
}
|
|
631
|
+
/** itemList(非 ai 类)→ 对应 typed 结果;空=miss;含 malformed=整体 error */
|
|
632
|
+
function parseItemListResult(kind, items, latencyMs, ts, envNote, rawStdout) {
|
|
633
|
+
let parsed;
|
|
634
|
+
switch (kind) {
|
|
635
|
+
case 'flight':
|
|
636
|
+
case 'train':
|
|
637
|
+
parsed = parseTransportItems(items);
|
|
638
|
+
break;
|
|
639
|
+
case 'hotel':
|
|
640
|
+
parsed = parseHotelItems(items);
|
|
641
|
+
break;
|
|
642
|
+
case 'marriott-hotel':
|
|
643
|
+
parsed = parseHotelItems(items);
|
|
644
|
+
break;
|
|
645
|
+
case 'poi':
|
|
646
|
+
parsed = parsePoiItems(items);
|
|
647
|
+
break;
|
|
648
|
+
case 'keyword':
|
|
649
|
+
parsed = parseKeywordItems(items);
|
|
650
|
+
break;
|
|
651
|
+
case 'marriott-package':
|
|
652
|
+
parsed = parsePackageItems(items);
|
|
653
|
+
break;
|
|
654
|
+
case 'ai':
|
|
655
|
+
parsed = { options: [], malformedCount: 0 };
|
|
656
|
+
break;
|
|
657
|
+
}
|
|
658
|
+
const systemMessage = (() => {
|
|
659
|
+
try {
|
|
660
|
+
return nonEmptyText(JSON.parse(rawStdout).systemMessage);
|
|
661
|
+
}
|
|
662
|
+
catch {
|
|
663
|
+
return undefined;
|
|
664
|
+
}
|
|
665
|
+
})();
|
|
666
|
+
if (parsed.malformedCount > 0) {
|
|
667
|
+
const label = KIND_LABEL[kind];
|
|
71
668
|
return {
|
|
72
|
-
...
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
evidence: `[实时API:flyai@error@${ts}] ${r.error ?? `exit ${r.code}`}`,
|
|
78
|
-
error: r.error ?? r.stderr.replace(/\s+/g, ' ').slice(0, 200)
|
|
669
|
+
kind, ...envNote, latencyMs, ok: false, via: 'flyai-error', verdict: 'error',
|
|
670
|
+
retryable: false,
|
|
671
|
+
evidence: `[实时API:flyai@error@${ts}] ${label} itemList malformed (${parsed.malformedCount}/${items.length} items failed typed validation)`,
|
|
672
|
+
error: `FlyAI ${label} itemList malformed: ${parsed.malformedCount}/${items.length} item(s) failed typed validation`,
|
|
673
|
+
...(systemMessage ? { systemMessage } : {}),
|
|
79
674
|
};
|
|
80
675
|
}
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
676
|
+
const count = parsed.options.length;
|
|
677
|
+
const verdict = count > 0 ? 'hit' : 'miss';
|
|
678
|
+
const evidence = `[实时API:flyai@${ts}] ${count}/${items.length} ${KIND_LABEL[kind]} options`;
|
|
679
|
+
const common = { kind, ...envNote, latencyMs, ok: true, via: 'flyai', verdict, evidence, ...(systemMessage ? { systemMessage } : {}) };
|
|
680
|
+
switch (kind) {
|
|
681
|
+
case 'flight':
|
|
682
|
+
case 'train':
|
|
683
|
+
return { ...common, options: parsed.options };
|
|
684
|
+
case 'hotel':
|
|
685
|
+
case 'marriott-hotel':
|
|
686
|
+
return { ...common, hotels: parsed.options };
|
|
687
|
+
case 'poi':
|
|
688
|
+
return { ...common, pois: parsed.options };
|
|
689
|
+
case 'keyword':
|
|
690
|
+
return { ...common, keywords: parsed.options };
|
|
691
|
+
case 'marriott-package':
|
|
692
|
+
return { ...common, packages: parsed.options };
|
|
693
|
+
case 'ai':
|
|
694
|
+
return { ...common };
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
// ── typed parsers ───────────────────────────────────────────────────────────────
|
|
698
|
+
/** 机/火条目 → FlyaiOption;非空列表含任一不完整条目即整体 malformed */
|
|
699
|
+
function parseTransportItems(items) {
|
|
700
|
+
const options = [];
|
|
701
|
+
let malformedCount = 0;
|
|
702
|
+
for (const raw of items) {
|
|
703
|
+
if (!raw || typeof raw !== 'object' || Array.isArray(raw)) {
|
|
704
|
+
malformedCount += 1;
|
|
705
|
+
continue;
|
|
706
|
+
}
|
|
707
|
+
const it = raw;
|
|
708
|
+
const journey = Array.isArray(it.journeys) ? it.journeys[0] : undefined;
|
|
709
|
+
const segments = journey && typeof journey === 'object' && !Array.isArray(journey) && Array.isArray(journey.segments)
|
|
710
|
+
? journey.segments
|
|
711
|
+
: undefined;
|
|
712
|
+
if (!segments || segments.length === 0) {
|
|
713
|
+
malformedCount += 1;
|
|
714
|
+
continue;
|
|
715
|
+
}
|
|
716
|
+
const parsedSegments = [];
|
|
717
|
+
for (const rawSegment of segments) {
|
|
718
|
+
if (!rawSegment || typeof rawSegment !== 'object' || Array.isArray(rawSegment))
|
|
719
|
+
continue;
|
|
720
|
+
const segment = rawSegment;
|
|
721
|
+
const no = nonEmptyText(segment.marketingTransportNo);
|
|
722
|
+
const name = optionalText(segment.marketingTransportName);
|
|
723
|
+
const depDateTime = nonEmptyText(segment.depDateTime);
|
|
724
|
+
const arrDateTime = nonEmptyText(segment.arrDateTime);
|
|
725
|
+
const depStation = nonEmptyText(segment.depStationName);
|
|
726
|
+
const arrStation = nonEmptyText(segment.arrStationName);
|
|
727
|
+
const durationMin = positiveFiniteDuration(segment.duration);
|
|
728
|
+
const seatClass = optionalText(segment.seatClassName);
|
|
729
|
+
if (!no || !depDateTime || !arrDateTime || !depStation || !arrStation || durationMin === undefined
|
|
730
|
+
|| (segment.marketingTransportName !== undefined && name === undefined)
|
|
731
|
+
|| (segment.seatClassName !== undefined && seatClass === undefined))
|
|
732
|
+
continue;
|
|
733
|
+
parsedSegments.push({ no, name: name ?? '', depDateTime, arrDateTime, depStation, arrStation, durationMin, seatClass: seatClass ?? undefined });
|
|
734
|
+
}
|
|
735
|
+
if (parsedSegments.length !== segments.length) {
|
|
736
|
+
malformedCount += 1;
|
|
737
|
+
continue;
|
|
738
|
+
}
|
|
739
|
+
const first = parsedSegments[0];
|
|
740
|
+
const last = parsedSegments[parsedSegments.length - 1];
|
|
741
|
+
const journeyType = optionalText(journey?.journeyType);
|
|
742
|
+
const nonstop = parsedSegments.length === 1 && !/^(?:2|中转|transfer)/i.test(journeyType ?? '');
|
|
743
|
+
const totalDuration = positiveFiniteDuration(journey?.totalDuration)
|
|
744
|
+
?? parsedSegments.reduce((sum, segment) => sum + segment.durationMin, 0);
|
|
745
|
+
const rawPriceValue = it.adultPrice !== undefined
|
|
746
|
+
? it.adultPrice
|
|
747
|
+
: it.ticketPrice !== undefined ? it.ticketPrice : it.price;
|
|
748
|
+
const parsedPrice = parseTransportPrice(rawPriceValue);
|
|
749
|
+
const jumpUrl = optionalText(it.jumpUrl);
|
|
750
|
+
if (!parsedPrice
|
|
751
|
+
|| (it.jumpUrl !== undefined && jumpUrl === undefined)) {
|
|
752
|
+
malformedCount += 1;
|
|
753
|
+
continue;
|
|
754
|
+
}
|
|
755
|
+
options.push({
|
|
756
|
+
no: first.no, name: first.name, depDateTime: first.depDateTime, arrDateTime: last.arrDateTime,
|
|
757
|
+
depStation: first.depStation, arrStation: last.arrStation, durationMin: totalDuration,
|
|
758
|
+
price: parsedPrice.price, priceRaw: parsedPrice.priceRaw,
|
|
759
|
+
seatClass: first.seatClass, jumpUrl: jumpUrl ?? undefined, nonstop, journeyType: journeyType ?? undefined,
|
|
760
|
+
segments: parsedSegments,
|
|
761
|
+
});
|
|
762
|
+
}
|
|
763
|
+
return { options, malformedCount };
|
|
764
|
+
}
|
|
765
|
+
function stringField(parent, key) {
|
|
766
|
+
const value = parent[key];
|
|
767
|
+
if (value === undefined)
|
|
768
|
+
return undefined;
|
|
769
|
+
if (value === null)
|
|
770
|
+
return null;
|
|
771
|
+
return nonEmptyText(value) ?? 'bad';
|
|
772
|
+
}
|
|
773
|
+
/** 酒店/万豪酒店条目 → FlyaiHotelOption(官方两形态字段集同构) */
|
|
774
|
+
function parseHotelItems(items) {
|
|
775
|
+
const options = [];
|
|
776
|
+
let malformedCount = 0;
|
|
777
|
+
for (const raw of items) {
|
|
778
|
+
if (!raw || typeof raw !== 'object' || Array.isArray(raw)) {
|
|
779
|
+
malformedCount += 1;
|
|
780
|
+
continue;
|
|
781
|
+
}
|
|
782
|
+
const it = raw;
|
|
783
|
+
const name = nonEmptyText(it.name);
|
|
784
|
+
const star = stringField(it, 'star');
|
|
785
|
+
const rawPrice = stringField(it, 'price');
|
|
786
|
+
const rate = it.rate === undefined ? undefined : it.rate === null ? null : nonEmptyText(it.rate) ?? 'bad';
|
|
787
|
+
const address = stringField(it, 'address');
|
|
788
|
+
const poiSource = it.interestsPoi !== undefined ? it.interestsPoi : it.nearbyPoi;
|
|
789
|
+
const poi = poiSource === undefined ? undefined : poiSource === null ? null : nonEmptyText(poiSource) ?? 'bad';
|
|
790
|
+
const hotelIdSource = it.shId !== undefined ? it.shId : it.shid;
|
|
791
|
+
const hotelId = hotelIdSource === undefined ? undefined : nonEmptyText(hotelIdSource) ?? (typeof hotelIdSource === 'number' ? String(hotelIdSource) : 'bad');
|
|
792
|
+
const jumpUrl = stringField(it, 'detailUrl');
|
|
793
|
+
const mainPic = stringField(it, 'mainPic');
|
|
794
|
+
const score = it.score === undefined ? undefined : it.score === null ? null : nonEmptyText(it.score) ?? 'bad';
|
|
795
|
+
const scoreDesc = stringField(it, 'scoreDesc');
|
|
796
|
+
const review = stringField(it, 'review');
|
|
797
|
+
const brandName = stringField(it, 'brandName');
|
|
798
|
+
const latitude = stringField(it, 'latitude');
|
|
799
|
+
const longitude = stringField(it, 'longitude');
|
|
800
|
+
const fields = [star, rawPrice, rate, address, poi, hotelId, jumpUrl, mainPic, score, scoreDesc, review, brandName, latitude, longitude];
|
|
801
|
+
if (!name || fields.includes('bad')) {
|
|
802
|
+
malformedCount += 1;
|
|
803
|
+
continue;
|
|
804
|
+
}
|
|
805
|
+
const priceText = (rawPrice ?? '');
|
|
806
|
+
const bare = priceText.replace(/^¥/, '');
|
|
807
|
+
const numericPrice = Number(bare);
|
|
808
|
+
options.push({
|
|
809
|
+
name,
|
|
810
|
+
star: star ?? undefined,
|
|
811
|
+
price: /^\d+(\.\d+)?$/.test(bare) && numericPrice > 0 ? numericPrice : 0,
|
|
812
|
+
priceRaw: priceText || undefined,
|
|
813
|
+
rate: rate ?? undefined,
|
|
814
|
+
address: address ?? undefined,
|
|
815
|
+
poi: poi ?? undefined,
|
|
816
|
+
hotelId: hotelId ?? undefined,
|
|
817
|
+
jumpUrl: jumpUrl ?? undefined,
|
|
818
|
+
mainPic: mainPic ?? undefined,
|
|
819
|
+
score: score ?? undefined,
|
|
820
|
+
scoreDesc: scoreDesc ?? undefined,
|
|
821
|
+
review: review ?? undefined,
|
|
822
|
+
brandName: brandName ?? undefined,
|
|
823
|
+
latitude: latitude ?? undefined,
|
|
824
|
+
longitude: longitude ?? undefined,
|
|
825
|
+
});
|
|
826
|
+
}
|
|
827
|
+
return { options, malformedCount };
|
|
828
|
+
}
|
|
829
|
+
/** 景点条目 → FlyaiPoiOption */
|
|
830
|
+
function parsePoiItems(items) {
|
|
831
|
+
const options = [];
|
|
832
|
+
let malformedCount = 0;
|
|
833
|
+
for (const raw of items) {
|
|
834
|
+
if (!raw || typeof raw !== 'object' || Array.isArray(raw)) {
|
|
835
|
+
malformedCount += 1;
|
|
836
|
+
continue;
|
|
837
|
+
}
|
|
838
|
+
const it = raw;
|
|
839
|
+
const name = nonEmptyText(it.name);
|
|
840
|
+
const poiId = it.id === undefined ? undefined : nonEmptyText(it.id) ?? (typeof it.id === 'number' ? String(it.id) : 'bad');
|
|
841
|
+
const mainPic = stringField(it, 'mainPic');
|
|
842
|
+
const jumpUrl = stringField(it, 'jumpUrl');
|
|
843
|
+
const address = stringField(it, 'address');
|
|
844
|
+
const freePoiStatus = stringField(it, 'freePoiStatus');
|
|
845
|
+
let ticketInfo;
|
|
846
|
+
if (it.ticketInfo !== undefined && it.ticketInfo !== null) {
|
|
847
|
+
if (typeof it.ticketInfo !== 'object' || Array.isArray(it.ticketInfo)) {
|
|
848
|
+
ticketInfo = 'bad';
|
|
849
|
+
}
|
|
850
|
+
else {
|
|
851
|
+
const t = it.ticketInfo;
|
|
852
|
+
const price = t.price === undefined ? undefined : t.price === null ? null : nonEmptyText(t.price) ?? 'bad';
|
|
853
|
+
const priceDate = t.priceDate === undefined || t.priceDate === null ? undefined : nonEmptyText(t.priceDate) ?? 'bad';
|
|
854
|
+
const ticketName = t.ticketName === undefined ? undefined : nonEmptyText(t.ticketName) ?? 'bad';
|
|
855
|
+
if ([price, priceDate, ticketName].includes('bad'))
|
|
856
|
+
ticketInfo = 'bad';
|
|
857
|
+
else
|
|
858
|
+
ticketInfo = { price: price ?? undefined, priceDate, ticketName };
|
|
859
|
+
}
|
|
860
|
+
}
|
|
861
|
+
if (!name || poiId === 'bad' || [mainPic, jumpUrl, address, freePoiStatus].includes('bad') || ticketInfo === 'bad') {
|
|
862
|
+
malformedCount += 1;
|
|
863
|
+
continue;
|
|
864
|
+
}
|
|
865
|
+
options.push({
|
|
866
|
+
name,
|
|
867
|
+
poiId: poiId ?? undefined,
|
|
868
|
+
mainPic: mainPic ?? undefined,
|
|
869
|
+
jumpUrl: jumpUrl ?? undefined,
|
|
870
|
+
address: address ?? undefined,
|
|
871
|
+
freePoiStatus: freePoiStatus ?? undefined,
|
|
872
|
+
...(ticketInfo ? { ticketInfo } : {}),
|
|
873
|
+
});
|
|
874
|
+
}
|
|
875
|
+
return { options, malformedCount };
|
|
876
|
+
}
|
|
877
|
+
/** 关键词条目 → FlyaiKeywordOption(官方形状 itemList[].info) */
|
|
878
|
+
function parseKeywordItems(items) {
|
|
879
|
+
const options = [];
|
|
880
|
+
let malformedCount = 0;
|
|
881
|
+
for (const raw of items) {
|
|
882
|
+
if (!raw || typeof raw !== 'object' || Array.isArray(raw)) {
|
|
883
|
+
malformedCount += 1;
|
|
884
|
+
continue;
|
|
885
|
+
}
|
|
886
|
+
const info = raw.info;
|
|
887
|
+
if (!info || typeof info !== 'object' || Array.isArray(info)) {
|
|
888
|
+
malformedCount += 1;
|
|
889
|
+
continue;
|
|
890
|
+
}
|
|
891
|
+
const it = info;
|
|
892
|
+
const title = nonEmptyText(it.title);
|
|
893
|
+
const str = (key) => {
|
|
894
|
+
const v = it[key];
|
|
895
|
+
if (v === undefined || v === null)
|
|
896
|
+
return undefined;
|
|
897
|
+
return nonEmptyText(v) ?? 'bad';
|
|
96
898
|
};
|
|
899
|
+
const jumpUrl = str('jumpUrl');
|
|
900
|
+
const picUrl = str('picUrl');
|
|
901
|
+
const price = str('price');
|
|
902
|
+
const scoreDesc = str('scoreDesc');
|
|
903
|
+
const star = str('star');
|
|
904
|
+
let tags;
|
|
905
|
+
if (it.tags !== undefined && it.tags !== null) {
|
|
906
|
+
if (!Array.isArray(it.tags) || it.tags.some(t => nonEmptyText(t) === undefined))
|
|
907
|
+
tags = 'bad';
|
|
908
|
+
else
|
|
909
|
+
tags = it.tags.map(t => String(t).trim());
|
|
910
|
+
}
|
|
911
|
+
if (!title || [jumpUrl, picUrl, price, scoreDesc, star].includes('bad') || tags === 'bad') {
|
|
912
|
+
malformedCount += 1;
|
|
913
|
+
continue;
|
|
914
|
+
}
|
|
915
|
+
options.push({
|
|
916
|
+
title,
|
|
917
|
+
jumpUrl: jumpUrl ?? undefined,
|
|
918
|
+
picUrl: picUrl ?? undefined,
|
|
919
|
+
price: price ?? undefined,
|
|
920
|
+
scoreDesc: scoreDesc ?? undefined,
|
|
921
|
+
star: star ?? undefined,
|
|
922
|
+
...(tags ? { tags } : {}),
|
|
923
|
+
});
|
|
97
924
|
}
|
|
925
|
+
return { options, malformedCount };
|
|
926
|
+
}
|
|
927
|
+
/** 万豪套餐条目 → FlyaiMarriottPackageOption */
|
|
928
|
+
function parsePackageItems(items) {
|
|
98
929
|
const options = [];
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
if (!
|
|
102
|
-
|
|
103
|
-
|
|
930
|
+
let malformedCount = 0;
|
|
931
|
+
for (const raw of items) {
|
|
932
|
+
if (!raw || typeof raw !== 'object' || Array.isArray(raw)) {
|
|
933
|
+
malformedCount += 1;
|
|
934
|
+
continue;
|
|
935
|
+
}
|
|
936
|
+
const it = raw;
|
|
937
|
+
const title = stringField(it, 'title');
|
|
938
|
+
const name = title !== undefined ? title : stringField(it, 'name');
|
|
939
|
+
const itemId = stringField(it, 'itemId');
|
|
940
|
+
const mainPicSource = it.picUrl !== undefined ? 'picUrl' : 'mainPic';
|
|
941
|
+
const sellingPointSource = it.sellPoint !== undefined ? 'sellPoint' : 'sellingPoint';
|
|
942
|
+
const fields_ = [
|
|
943
|
+
stringField(it, 'brandName'), stringField(it, 'hotelName'), stringField(it, 'cityName'),
|
|
944
|
+
stringField(it, 'price'), stringField(it, 'detailUrl'), stringField(it, mainPicSource),
|
|
945
|
+
stringField(it, sellingPointSource), itemId,
|
|
946
|
+
];
|
|
947
|
+
if (!name || fields_.includes('bad')) {
|
|
948
|
+
malformedCount += 1;
|
|
949
|
+
continue;
|
|
950
|
+
}
|
|
951
|
+
const [brandName, hotelName, cityName, price, detailUrl, mainPic, sellingPoint] = fields_;
|
|
104
952
|
options.push({
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
953
|
+
name: name,
|
|
954
|
+
...(title && title !== 'bad' ? { title } : {}),
|
|
955
|
+
...(itemId && itemId !== 'bad' ? { itemId } : {}),
|
|
956
|
+
brandName: brandName ?? undefined,
|
|
957
|
+
hotelName: hotelName ?? undefined,
|
|
958
|
+
cityName: cityName ?? undefined,
|
|
959
|
+
price: price ?? undefined,
|
|
960
|
+
detailUrl: detailUrl ?? undefined,
|
|
961
|
+
mainPic: mainPic ?? undefined,
|
|
962
|
+
picUrl: mainPic ?? undefined,
|
|
963
|
+
sellingPoint: sellingPoint ?? undefined,
|
|
964
|
+
sellPoint: sellingPoint ?? undefined,
|
|
116
965
|
});
|
|
117
966
|
}
|
|
118
|
-
|
|
119
|
-
return {
|
|
120
|
-
kind: q.kind,
|
|
121
|
-
latencyMs,
|
|
122
|
-
ok: true,
|
|
123
|
-
via: 'flyai',
|
|
124
|
-
verdict,
|
|
125
|
-
evidence: `[实时API:flyai@${ts}] ${options.length}/${items.length} ${q.kind} options`,
|
|
126
|
-
options
|
|
127
|
-
};
|
|
967
|
+
return { options, malformedCount };
|
|
128
968
|
}
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
//# sourceURL=/Users/bytedance/work/gotry/ts/capabilities/flyai.ts
|