@danceiny/gotry 0.0.1-rc.16 → 0.0.1-rc.18

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.
Files changed (229) hide show
  1. package/README.md +161 -116
  2. package/README.zh-CN.md +173 -144
  3. package/bin/gotry-booking-copilot.js +53 -0
  4. package/bin/gotry-bootstrap.js +113 -66
  5. package/bin/gotry-inner.js +436 -70
  6. package/bin/gotry-runtime-resolution.d.ts +27 -0
  7. package/bin/gotry-runtime-resolution.js +50 -0
  8. package/bin/gotry.js +1 -1
  9. package/cordis.gotry-patch.yml +5 -1
  10. package/dist/capabilities/agent-reach-deep.js +1 -1
  11. package/dist/capabilities/agent-reach.js +1 -1
  12. package/dist/capabilities/anything.js +1 -1
  13. package/dist/capabilities/artifacts.js +1 -1
  14. package/dist/capabilities/effect.js +1 -1
  15. package/dist/capabilities/fact-log.js +1 -1
  16. package/dist/capabilities/flyai.js +20 -6
  17. package/dist/capabilities/hbcli.js +1 -1
  18. package/dist/capabilities/incident-log.js +1 -1
  19. package/dist/capabilities/model-override.js +18 -0
  20. package/dist/capabilities/opensky.js +1 -1
  21. package/dist/capabilities/resilience.js +1 -1
  22. package/dist/capabilities/session/action-cache.js +1 -1
  23. package/dist/capabilities/session/adapters/ctrip-flight.js +1 -1
  24. package/dist/capabilities/session/adapters/meituan-local.js +1 -1
  25. package/dist/capabilities/session/benchmark.js +1 -1
  26. package/dist/capabilities/session/extension-bridge.js +19 -6
  27. package/dist/capabilities/session/extension-channel.js +3 -3
  28. package/dist/capabilities/session/extension-distribution.js +234 -0
  29. package/dist/capabilities/session/extract.js +1 -1
  30. package/dist/capabilities/session/golden-score.js +92 -0
  31. package/dist/capabilities/session/health-watch.js +1 -1
  32. package/dist/capabilities/session/read-guard.js +1 -1
  33. package/dist/capabilities/session/static-flight-golden.js +137 -0
  34. package/dist/capabilities/session/transport.js +1 -1
  35. package/dist/capabilities/session/wizard.js +21 -251
  36. package/dist/capabilities/session-consent.js +1 -1
  37. package/dist/capabilities/session-login.js +11 -5
  38. package/dist/capabilities/session-search.js +46 -4
  39. package/dist/capabilities/weather.js +168 -46
  40. package/dist/data/session-golden-20.json +25 -0
  41. package/dist/data/sf-golden-manifest.json +102 -0
  42. package/dist/data/sf-static-routes.json +91 -0
  43. package/dist/scripts/action-cache-tests.js +1 -1
  44. package/dist/scripts/agent-planning-budget-e2e.js +227 -0
  45. package/dist/scripts/agent-planning-budget-tests.js +173 -0
  46. package/dist/scripts/agent-reach-deep-tests.js +1 -1
  47. package/dist/scripts/agent-reach-tests.js +1 -1
  48. package/dist/scripts/agent-reach-wrapper-tests.js +1 -1
  49. package/dist/scripts/anything-tests.js +1 -1
  50. package/dist/scripts/async-collect.js +1 -1
  51. package/dist/scripts/benchmark-environment-bridge-e2e.js +981 -0
  52. package/dist/scripts/benchmark-environment-bridge-tests.js +2544 -0
  53. package/dist/scripts/booking-copilot-availability-ledger-binding-tests.js +335 -0
  54. package/dist/scripts/booking-copilot-availability-policy-v2-tests.js +1355 -0
  55. package/dist/scripts/booking-copilot-bin-proof-tests.js +89 -0
  56. package/dist/scripts/booking-copilot-crossrepo-fixture-server.js +113 -0
  57. package/dist/scripts/booking-copilot-dsh-core-proof-tests.js +356 -0
  58. package/dist/scripts/booking-copilot-dsh-planner-proof-tests.js +557 -0
  59. package/dist/scripts/booking-copilot-dsh-plugin-proof-tests.js +140 -0
  60. package/dist/scripts/booking-copilot-event-sequence-concurrency-proof-tests.js +202 -0
  61. package/dist/scripts/booking-copilot-gap-code-contract-proof-tests.js +102 -0
  62. package/dist/scripts/booking-copilot-operation-ledger-concurrency-proof-tests.js +374 -0
  63. package/dist/scripts/booking-copilot-receipt-ledger-concurrency-proof-tests.js +447 -0
  64. package/dist/scripts/booking-copilot-runtime-proof-tests.js +205 -0
  65. package/dist/scripts/booking-copilot-server-proof-tests.js +317 -0
  66. package/dist/scripts/booking-copilot-startup-proof-tests.js +283 -0
  67. package/dist/scripts/booking-copilot-v2-runtime-proof-tests.js +3935 -0
  68. package/dist/scripts/booking-saga-tests.js +1 -1
  69. package/dist/scripts/booking-surface-contract-proof-tests.js +393 -0
  70. package/dist/scripts/booking-surface-v2-contract-proof-tests.js +1174 -0
  71. package/dist/scripts/bootstrap-tests.js +45 -39
  72. package/dist/scripts/build-changelog.js +1 -1
  73. package/dist/scripts/changelog-tests.js +1 -1
  74. package/dist/scripts/companion-tests.js +1 -1
  75. package/dist/scripts/diff-test.js +1 -1
  76. package/dist/scripts/dsh-runtime-closure-tests.js +225 -0
  77. package/dist/scripts/dsh-runtime-closure.js +150 -0
  78. package/dist/scripts/effect-tests.js +1 -1
  79. package/dist/scripts/engine-run.js +1 -1
  80. package/dist/scripts/engine-tests.js +1 -1
  81. package/dist/scripts/evaluation-cadence-tests.js +347 -0
  82. package/dist/scripts/evaluation-contract-tests.js +574 -0
  83. package/dist/scripts/extension-distribution-cli.js +35 -0
  84. package/dist/scripts/extension-distribution-tests.js +341 -0
  85. package/dist/scripts/extension-tests.js +128 -23
  86. package/dist/scripts/fact-gate-tests.js +1 -1
  87. package/dist/scripts/flyai-tests.js +1 -1
  88. package/dist/scripts/hbcli-e2e-tests.js +1 -1
  89. package/dist/scripts/hbcli-tests.js +1 -1
  90. package/dist/scripts/health-watch-cli.js +1 -1
  91. package/dist/scripts/i18n-tests.js +1 -1
  92. package/dist/scripts/incident-tests.js +1 -1
  93. package/dist/scripts/journey-tests.js +1 -1
  94. package/dist/scripts/ledger-tests.js +1 -1
  95. package/dist/scripts/ledger-workflow-crash.js +1 -1
  96. package/dist/scripts/memory-capture-tests.js +1 -1
  97. package/dist/scripts/memory-decay-tests.js +1 -1
  98. package/dist/scripts/memory-metrics.js +1 -1
  99. package/dist/scripts/memory-value-report.js +1 -1
  100. package/dist/scripts/model-override-e2e.js +176 -0
  101. package/dist/scripts/nightly-evidence-tests.js +1 -1
  102. package/dist/scripts/nightly-evidence.js +1 -1
  103. package/dist/scripts/nudge-digest.js +1 -1
  104. package/dist/scripts/onboarding-tests.js +21 -53
  105. package/dist/scripts/opensky-check.js +1 -1
  106. package/dist/scripts/opensky-tests.js +1 -1
  107. package/dist/scripts/pnpm-dsh-closure-proof.js +20 -0
  108. package/dist/scripts/price-drift-tests.js +1 -1
  109. package/dist/scripts/price-drift-watch.js +1 -1
  110. package/dist/scripts/probe-poi-tests.js +1 -1
  111. package/dist/scripts/product-metrics.js +1 -1
  112. package/dist/scripts/publish-preverify.js +40 -4
  113. package/dist/scripts/realtime-pricing-tests.js +1 -1
  114. package/dist/scripts/replay-async.js +1 -1
  115. package/dist/scripts/replay-real.js +1 -1
  116. package/dist/scripts/replay.js +1 -1
  117. package/dist/scripts/session-attach-diagnose.js +1 -1
  118. package/dist/scripts/session-attach-poc.js +1 -1
  119. package/dist/scripts/session-benchmark.js +1 -1
  120. package/dist/scripts/session-extract-tests.js +1 -1
  121. package/dist/scripts/session-login.js +1 -1
  122. package/dist/scripts/session-tests.js +70 -20
  123. package/dist/scripts/sf-live-benchmark.js +338 -0
  124. package/dist/scripts/sf-live-cli-tests.js +21 -0
  125. package/dist/scripts/sf-soft-score-tests.js +108 -0
  126. package/dist/scripts/sf-summary.js +93 -0
  127. package/dist/scripts/skeleton-check.js +1 -1
  128. package/dist/scripts/skeleton-integration-test.js +1 -1
  129. package/dist/scripts/skills-contract-tests.js +1 -1
  130. package/dist/scripts/smoke-session-gate-tests.js +29 -0
  131. package/dist/scripts/smoke.js +79 -33
  132. package/dist/scripts/state-cli-tests.js +1 -1
  133. package/dist/scripts/state-cli.js +1 -1
  134. package/dist/scripts/static-golden-tests.js +299 -0
  135. package/dist/scripts/time-eval-tests.js +1 -1
  136. package/dist/scripts/travel-timeline-tests.js +1 -1
  137. package/dist/scripts/unified-tests.js +1 -1
  138. package/dist/scripts/weather-tests.js +694 -44
  139. package/dist/scripts/z3-race-tests.js +1 -1
  140. package/dist/src/artifact-gate.js +1 -1
  141. package/dist/src/benchmark-agent-conformance.js +370 -0
  142. package/dist/src/benchmark-environment-bridge.js +384 -0
  143. package/dist/src/benchmark-headless-child-diagnostics.js +173 -0
  144. package/dist/src/benchmark-tool-isolation.js +124 -0
  145. package/dist/src/bookable-facts.js +1 -1
  146. package/dist/src/booking-saga.js +1 -1
  147. package/dist/src/booking-surface/availability-policy-v2.js +830 -0
  148. package/dist/src/booking-surface/canonical-schema.js +113 -0
  149. package/dist/src/booking-surface/contracts-v2.js +89 -0
  150. package/dist/src/booking-surface/contracts.js +46 -0
  151. package/dist/src/booking-surface/dsh-planner.js +453 -0
  152. package/dist/src/booking-surface/dsh-plugin.js +93 -0
  153. package/dist/src/booking-surface/error-codes.js +94 -0
  154. package/dist/src/booking-surface/index.js +15 -0
  155. package/dist/src/booking-surface/profile.js +68 -0
  156. package/dist/src/booking-surface/runtime-v2.js +1771 -0
  157. package/dist/src/booking-surface/runtime.js +351 -0
  158. package/dist/src/booking-surface/server-v2.js +334 -0
  159. package/dist/src/booking-surface/server.js +302 -0
  160. package/dist/src/booking-surface/startup.js +159 -0
  161. package/dist/src/booking-surface/validation-v2.js +319 -0
  162. package/dist/src/booking-surface/validation.js +809 -0
  163. package/dist/src/bridge.js +1 -1
  164. package/dist/src/companions.js +1 -1
  165. package/dist/src/contracts.js +1 -1
  166. package/dist/src/dsh-llm.js +1 -1
  167. package/dist/src/engine.js +1 -1
  168. package/dist/src/evaluation-cadence.js +234 -0
  169. package/dist/src/evaluation-contracts.js +906 -0
  170. package/dist/src/i18n.js +1 -1
  171. package/dist/src/index.js +50 -19
  172. package/dist/src/journey.js +1 -1
  173. package/dist/src/loop.js +1 -1
  174. package/dist/src/memory-capture.js +1 -1
  175. package/dist/src/memory-decay.js +1 -1
  176. package/dist/src/memory-utility.js +1 -1
  177. package/dist/src/mock-llm.js +1 -1
  178. package/dist/src/model.js +1 -1
  179. package/dist/src/realtime-pricing.js +1 -1
  180. package/dist/src/slot-spec.js +1 -1
  181. package/dist/src/state-ledger.js +2 -1
  182. package/dist/src/time-anchor.js +1 -1
  183. package/dist/src/tool-budget.js +136 -0
  184. package/dist/src/tool-packet.js +1 -1
  185. package/dist/src/travel-slots.js +1 -1
  186. package/dist/src/travel-timeline.js +1 -1
  187. package/dist/src/unified.js +1 -1
  188. package/dist/src/wish-pool.js +1 -1
  189. package/dist/src/z3-shared.js +1 -1
  190. package/extension/README.md +31 -7
  191. package/package.json +286 -11
  192. package/schemas/booking.surface.v1.schema.json +927 -0
  193. package/schemas/booking.surface.v2.schema.json +61 -0
  194. package/ts/capabilities/flyai.ts +16 -3
  195. package/ts/capabilities/session/extension-bridge.ts +37 -14
  196. package/ts/capabilities/session/extension-channel.ts +6 -3
  197. package/ts/capabilities/session/extension-distribution.ts +264 -0
  198. package/ts/capabilities/session/golden-score.ts +139 -0
  199. package/ts/capabilities/session/health-watch.ts +1 -1
  200. package/ts/capabilities/session/static-flight-golden.ts +209 -0
  201. package/ts/capabilities/session/wizard.ts +34 -176
  202. package/ts/capabilities/session-login.ts +15 -5
  203. package/ts/capabilities/session-search.ts +40 -3
  204. package/ts/capabilities/weather.ts +141 -52
  205. package/ts/package.json +3 -3
  206. package/ts/src/benchmark-agent-conformance.ts +448 -0
  207. package/ts/src/benchmark-environment-bridge.ts +348 -0
  208. package/ts/src/benchmark-headless-child-diagnostics.ts +184 -0
  209. package/ts/src/benchmark-tool-isolation.ts +166 -0
  210. package/ts/src/booking-surface/availability-policy-v2.ts +523 -0
  211. package/ts/src/booking-surface/canonical-schema.js +113 -0
  212. package/ts/src/booking-surface/contracts-v2.ts +118 -0
  213. package/ts/src/booking-surface/contracts.ts +380 -0
  214. package/ts/src/booking-surface/dsh-planner.ts +452 -0
  215. package/ts/src/booking-surface/dsh-plugin.js +93 -0
  216. package/ts/src/booking-surface/error-codes.ts +101 -0
  217. package/ts/src/booking-surface/index.ts +12 -0
  218. package/ts/src/booking-surface/profile.ts +42 -0
  219. package/ts/src/booking-surface/runtime-v2.ts +1466 -0
  220. package/ts/src/booking-surface/runtime.ts +483 -0
  221. package/ts/src/booking-surface/server-v2.ts +247 -0
  222. package/ts/src/booking-surface/server.ts +324 -0
  223. package/ts/src/booking-surface/startup.ts +196 -0
  224. package/ts/src/booking-surface/validation-v2.ts +205 -0
  225. package/ts/src/booking-surface/validation.ts +453 -0
  226. package/ts/src/index.ts +64 -11
  227. package/ts/src/state-ledger.ts +1 -0
  228. package/ts/src/tool-budget.ts +165 -0
  229. package/dist/scripts/wizard-bootstrap.js +0 -32
@@ -1,8 +1,9 @@
1
1
  import { openSession } from './session/transport.js';
2
- import { extensionCookieNames, extensionSearchJob, classifyBridgeFailure, NEEDS_EXTENSION_HINT } from './session/extension-channel.js';
2
+ import { extensionCookieNames, extensionSearchJob, classifyBridgeFailure } from './session/extension-channel.js';
3
3
  import { appendFileSync, mkdirSync } from 'node:fs';
4
4
  import { dirname } from 'node:path';
5
5
  import { buildEntryUrl, NETWORK_HINTS, parseBatchSearch, LOGIN_COOKIE_NAMES, SITE_DOMAIN } from './session/adapters/ctrip-flight.js';
6
+ import { EXTENSION_STORE_URL } from './session/extension-bridge.js';
6
7
  const MIN_INTERVAL_MS = 30_000;
7
8
  const lastCallAt = new Map();
8
9
  export function __resetRateLimiterForTest() {
@@ -16,6 +17,23 @@ export function resolveTransportMode(profileDir) {
16
17
  if (profileDir !== undefined) return 'persistent';
17
18
  return (process.env.GOTRY_SESSION_TRANSPORT ?? '').trim().toLowerCase() === 'cdp' ? 'cdp' : 'extension';
18
19
  }
20
+ export function sessionLiveEnabled(env = process.env) {
21
+ return env.GOTRY_SESSION_LIVE === '1';
22
+ }
23
+ export function offlineSessionFlightResult() {
24
+ return {
25
+ ok: false,
26
+ via: 'session-ctrip-flight-error',
27
+ evidence: '[会话:ctrip-flight@offline] GOTRY_SESSION_LIVE!=1; live transport not invoked',
28
+ latencyMs: 0,
29
+ verdict: 'error',
30
+ error: 'GOTRY_SESSION_LIVE=1 required for live session transport'
31
+ };
32
+ }
33
+ export async function gatedSessionFlightSearch(q, search = sessionFlightSearch, env = process.env) {
34
+ if (!sessionLiveEnabled(env)) return offlineSessionFlightResult();
35
+ return search(q);
36
+ }
19
37
  export function appendExtensionAudit(auditPath, entry) {
20
38
  if (!auditPath) return;
21
39
  try {
@@ -60,7 +78,19 @@ export async function sessionFlightSearch(q) {
60
78
  });
61
79
  if (!login.ok) {
62
80
  const verdict = classifyBridgeFailure(login.kind);
63
- return err(verdict, verdict === 'needs-extension' ? `${login.summary};${NEEDS_EXTENSION_HINT}` : login.summary);
81
+ if (verdict === 'needs-extension') {
82
+ return {
83
+ ok: false,
84
+ via: 'session-ctrip-flight-error',
85
+ evidence: '[会话:ctrip-flight-needs-extension@ts]',
86
+ latencyMs: Date.now() - started,
87
+ verdict,
88
+ error: login.summary,
89
+ installUrl: EXTENSION_STORE_URL,
90
+ installAction: 'add-to-chrome'
91
+ };
92
+ }
93
+ return err(verdict, login.summary);
64
94
  }
65
95
  if (login.tickets.length === 0 && !q.allowAnonymous) {
66
96
  return err('needs-login', '未检出你本人登录态——调用 gotry_session_login 为用户打开携程登录入口(登录在携程官网完成;gotry 永不经手密码/验证码/cookie 值)');
@@ -79,7 +109,19 @@ export async function sessionFlightSearch(q) {
79
109
  });
80
110
  if (!r.ok) {
81
111
  const verdict = classifyBridgeFailure(r.kind);
82
- return err(verdict, verdict === 'needs-extension' ? `${r.summary};${NEEDS_EXTENSION_HINT}` : r.summary);
112
+ if (verdict === 'needs-extension') {
113
+ return {
114
+ ok: false,
115
+ via: 'session-ctrip-flight-error',
116
+ evidence: '[会话:ctrip-flight-needs-extension@ts]',
117
+ latencyMs: Date.now() - started,
118
+ verdict,
119
+ error: r.summary,
120
+ installUrl: EXTENSION_STORE_URL,
121
+ installAction: 'add-to-chrome'
122
+ };
123
+ }
124
+ return err(verdict, r.summary);
83
125
  }
84
126
  const title = r.title;
85
127
  const head = r.body.slice(0, 5000);
@@ -160,4 +202,4 @@ export async function sessionFlightSearch(q) {
160
202
  }
161
203
 
162
204
 
163
- //# sourceURL=/Users/bytedance/work/gotry-publish/ts/capabilities/session-search.ts
205
+ //# sourceURL=ts/capabilities/session-search.ts
@@ -32,11 +32,45 @@ const WMO_ZH = {
32
32
  export function wmoLabel(code) {
33
33
  return WMO_ZH[code] ?? `天气码${code}`;
34
34
  }
35
- async function fetchJson(url, timeoutMs, headers = {}) {
36
- const ctrl = new AbortController();
37
- const timer = setTimeout(()=>ctrl.abort(), timeoutMs);
35
+ const monotonicNow = ()=>performance.now();
36
+ const MIN_TIMER_MS = 1;
37
+ const TIMEOUT_ERROR = 'timeoutMs must be a finite positive integer';
38
+ const BUDGET_EXHAUSTED_ERROR = 'total timeout budget exhausted';
39
+ const REQUEST_ERROR = 'request failed';
40
+ const normalizeTimeout = (value)=>Number.isInteger(value) && value > 0 ? value : null;
41
+ const normalizeError = (value)=>{
38
42
  try {
39
- const res = await fetch(url, {
43
+ if (value instanceof Error && typeof value.message === 'string' && value.message.length > 0) {
44
+ return value.message.slice(0, 200);
45
+ }
46
+ if (typeof value === 'string' && value.length > 0) return value.slice(0, 200);
47
+ } catch {
48
+ return REQUEST_ERROR;
49
+ }
50
+ return REQUEST_ERROR;
51
+ };
52
+ async function fetchJson(url, timeoutMs, headers = {}, fetchImpl = globalThis.fetch) {
53
+ if (!Number.isFinite(timeoutMs) || timeoutMs < MIN_TIMER_MS) return {
54
+ ok: false,
55
+ error: BUDGET_EXHAUSTED_ERROR
56
+ };
57
+ const ctrl = new AbortController();
58
+ const timerDelay = Math.floor(timeoutMs);
59
+ if (timerDelay < MIN_TIMER_MS) return {
60
+ ok: false,
61
+ error: BUDGET_EXHAUSTED_ERROR
62
+ };
63
+ let timer;
64
+ let timedOut = false;
65
+ const timeout = new Promise((_, reject)=>{
66
+ timer = setTimeout(()=>{
67
+ timedOut = true;
68
+ ctrl.abort();
69
+ reject(new Error(`timeout after ${timeoutMs}ms`));
70
+ }, timerDelay);
71
+ });
72
+ const operation = (async ()=>{
73
+ const res = await fetchImpl(url, {
40
74
  signal: ctrl.signal,
41
75
  headers: {
42
76
  Accept: 'application/json',
@@ -51,17 +85,28 @@ async function fetchJson(url, timeoutMs, headers = {}) {
51
85
  ok: true,
52
86
  data: await res.json()
53
87
  };
88
+ })();
89
+ try {
90
+ return await Promise.race([
91
+ operation,
92
+ timeout
93
+ ]);
54
94
  } catch (e) {
55
95
  return {
56
96
  ok: false,
57
- error: e.message.slice(0, 200)
97
+ error: normalizeError(e),
98
+ ...timedOut ? {
99
+ timedOut: true
100
+ } : {}
58
101
  };
59
102
  } finally{
60
- clearTimeout(timer);
103
+ if (timer !== undefined) clearTimeout(timer);
104
+ void operation.catch(()=>undefined);
61
105
  }
62
106
  }
63
107
  const NOMINATIM_BASE = 'https://nominatim.openstreetmap.org/search';
64
108
  const MAJOR_FEATURE = /^(PPLC|PPLA|PPLB|PPLX)/;
109
+ const isRecord = (value)=>value !== null && typeof value === 'object' && !Array.isArray(value);
65
110
  function stripHits(hits) {
66
111
  return hits.map(({ name, latitude, longitude, country, admin1 })=>({
67
112
  ...country ? {
@@ -79,9 +124,27 @@ export async function geocodePlace(name, opts = {}) {
79
124
  const ts = new Date().toISOString();
80
125
  const count = opts.count ?? 5;
81
126
  const query = name.trim();
127
+ const timeoutMs = normalizeTimeout(opts.timeoutMs ?? 15_000);
128
+ if (timeoutMs === null) return {
129
+ ok: false,
130
+ evidence: `[实时API:open-meteo-geo@error@${ts}]`,
131
+ results: [],
132
+ error: TIMEOUT_ERROR
133
+ };
82
134
  const omUrl = `${GEOCODE_BASE}?name=${encodeURIComponent(query)}&count=${count}&language=zh&format=json`;
83
- const r = await fetchJson(omUrl, opts.timeoutMs ?? 15_000);
84
- const omResults = r.ok ? (r.data?.results ?? []).map((item)=>{
135
+ const deadline = monotonicNow() + timeoutMs;
136
+ const remaining = ()=>{
137
+ const value = deadline - monotonicNow();
138
+ return Number.isFinite(value) && value >= MIN_TIMER_MS ? value : null;
139
+ };
140
+ const primaryRemaining = remaining();
141
+ const r = primaryRemaining === null ? {
142
+ ok: false,
143
+ error: BUDGET_EXHAUSTED_ERROR
144
+ } : await fetchJson(omUrl, primaryRemaining, {}, opts.fetchImpl);
145
+ const primaryPayload = r.ok && isRecord(r.data) && Array.isArray(r.data['results']) ? r.data['results'] : null;
146
+ const omResults = primaryPayload ? primaryPayload.map((item)=>{
147
+ if (!isRecord(item)) return null;
85
148
  const it = item;
86
149
  return {
87
150
  name: String(it['name'] ?? ''),
@@ -92,7 +155,7 @@ export async function geocodePlace(name, opts = {}) {
92
155
  population: Number(it['population'] ?? 0) || 0,
93
156
  featureCode: String(it['feature_code'] ?? '')
94
157
  };
95
- }).filter((h)=>h.name && Number.isFinite(h.latitude) && Number.isFinite(h.longitude)) : [];
158
+ }).filter((h)=>!!h && !!h.name && Number.isFinite(h.latitude) && Number.isFinite(h.longitude)) : [];
96
159
  omResults.sort((a, b)=>b.population - a.population || Number(MAJOR_FEATURE.test(b.featureCode)) - Number(MAJOR_FEATURE.test(a.featureCode)));
97
160
  const top = omResults[0];
98
161
  if (top && (top.population > 0 || MAJOR_FEATURE.test(top.featureCode))) {
@@ -105,20 +168,39 @@ export async function geocodePlace(name, opts = {}) {
105
168
  }
106
169
  const nomTs = new Date().toISOString();
107
170
  const nomUrl = `${NOMINATIM_BASE}?q=${encodeURIComponent(query)}&format=jsonv2&limit=${count}&accept-language=zh&addressdetails=1`;
108
- const nom = await fetchJson(nomUrl, opts.timeoutMs ?? 15_000, {
171
+ const fallbackRemaining = primaryRemaining === null || r.timedOut ? null : remaining();
172
+ const nom = fallbackRemaining === null ? {
173
+ ok: false,
174
+ error: BUDGET_EXHAUSTED_ERROR
175
+ } : await fetchJson(nomUrl, fallbackRemaining, {
109
176
  'User-Agent': 'gotry-travel-agent/0.1 (+https://github.com/Danceiny/gotry)'
110
- });
111
- const nomResults = nom.ok ? (nom.data ?? []).map((item)=>{
112
- const it = item;
113
- const addr = it['address'] ?? {};
114
- return {
115
- name: String(it['name'] ?? ''),
116
- latitude: Number(it['lat']),
117
- longitude: Number(it['lon']),
118
- country: addr['country'],
119
- admin1: addr['province'] ?? addr['state'] ?? addr['county']
120
- };
121
- }).filter((h)=>h.name && Number.isFinite(h.latitude) && Number.isFinite(h.longitude)) : [];
177
+ }, opts.fetchImpl);
178
+ const nomResults = [];
179
+ if (nom.ok && Array.isArray(nom.data)) {
180
+ const parsedNom = nom.data.map((item)=>{
181
+ if (!isRecord(item)) return null;
182
+ const it = item;
183
+ const addr = isRecord(it['address']) ? it['address'] : {};
184
+ const country = typeof addr['country'] === 'string' ? addr['country'] : undefined;
185
+ const admin1 = [
186
+ addr['province'],
187
+ addr['state'],
188
+ addr['county']
189
+ ].find((v)=>typeof v === 'string');
190
+ return {
191
+ name: String(it['name'] ?? ''),
192
+ latitude: Number(it['lat']),
193
+ longitude: Number(it['lon']),
194
+ ...country ? {
195
+ country
196
+ } : {},
197
+ ...admin1 ? {
198
+ admin1
199
+ } : {}
200
+ };
201
+ }).filter((h)=>!!h && !!h.name && Number.isFinite(h.latitude) && Number.isFinite(h.longitude));
202
+ nomResults.push(...parsedNom);
203
+ }
122
204
  if (nomResults.length) {
123
205
  const omNote = r.ok ? `open-meteo ${omResults.length} 条弱命中(无人口/行政级,不足采信)` : `open-meteo 失败:${r.error ?? 'HTTP error'}`;
124
206
  return {
@@ -144,12 +226,21 @@ export async function geocodePlace(name, opts = {}) {
144
226
  };
145
227
  }
146
228
  export async function getForecast(point, opts = {}) {
147
- const started = Date.now();
229
+ const started = monotonicNow();
148
230
  const ts = new Date().toISOString();
149
- const days = Math.min(opts.days ?? 7, 16);
231
+ const requestedDays = opts.days ?? 7;
232
+ const days = Number.isInteger(requestedDays) && requestedDays > 0 ? Math.min(requestedDays, 16) : 0;
233
+ const timeoutMs = normalizeTimeout(opts.timeoutMs ?? 15_000);
234
+ if (days < 1 || timeoutMs === null) return {
235
+ ok: false,
236
+ via: 'open-meteo-error',
237
+ evidence: `[实时API:open-meteo@error@${ts}]`,
238
+ latencyMs: monotonicNow() - started,
239
+ error: days < 1 ? 'days must be valid positive values' : TIMEOUT_ERROR
240
+ };
150
241
  const url = `${FORECAST_BASE}?latitude=${point.latitude}&longitude=${point.longitude}` + `&daily=temperature_2m_max,temperature_2m_min,precipitation_probability_max,weathercode` + `&forecast_days=${days}&timezone=auto`;
151
- const r = await fetchJson(url, opts.timeoutMs ?? 15_000);
152
- const latencyMs = Date.now() - started;
242
+ const r = await fetchJson(url, timeoutMs, {}, opts.fetchImpl);
243
+ const latencyMs = monotonicNow() - started;
153
244
  if (!r.ok) {
154
245
  return {
155
246
  ok: false,
@@ -159,8 +250,9 @@ export async function getForecast(point, opts = {}) {
159
250
  error: r.error
160
251
  };
161
252
  }
162
- const daily = r.data.daily;
163
- if (!daily) {
253
+ const payload = isRecord(r.data) ? r.data : null;
254
+ const daily = isRecord(payload?.['daily']) ? payload['daily'] : null;
255
+ if (!daily || !Array.isArray(daily.time) || !Array.isArray(daily.temperature_2m_max) || !Array.isArray(daily.temperature_2m_min) || !Array.isArray(daily.weathercode) || daily.time.length !== days || daily.temperature_2m_max.length !== days || daily.temperature_2m_min.length !== days || daily.weathercode.length !== days || daily.time.some((v)=>typeof v !== 'string') || daily.temperature_2m_max.some((v)=>typeof v !== 'number' || !Number.isFinite(v)) || daily.temperature_2m_min.some((v)=>typeof v !== 'number' || !Number.isFinite(v)) || daily.weathercode.some((v)=>typeof v !== 'number' || !Number.isFinite(v)) || daily.precipitation_probability_max !== undefined && daily.precipitation_probability_max !== null && !Array.isArray(daily.precipitation_probability_max) || Array.isArray(daily.precipitation_probability_max) && (daily.precipitation_probability_max.length !== days || daily.precipitation_probability_max.some((v)=>v !== null && (typeof v !== 'number' || !Number.isFinite(v))))) {
164
256
  return {
165
257
  ok: false,
166
258
  via: 'open-meteo-error',
@@ -169,32 +261,57 @@ export async function getForecast(point, opts = {}) {
169
261
  error: 'empty daily payload'
170
262
  };
171
263
  }
172
- const rows = daily.time.map((date, i)=>({
264
+ const times = daily.time;
265
+ const maxes = daily.temperature_2m_max;
266
+ const mins = daily.temperature_2m_min;
267
+ const codes = daily.weathercode;
268
+ const precip = Array.isArray(daily.precipitation_probability_max) ? daily.precipitation_probability_max : undefined;
269
+ const rows = times.map((date, i)=>({
173
270
  date,
174
- tempMaxC: daily.temperature_2m_max[i] ?? NaN,
175
- tempMinC: daily.temperature_2m_min[i] ?? NaN,
176
- precipProbMaxPct: daily.precipitation_probability_max?.[i] ?? null,
177
- weatherCode: daily.weathercode[i] ?? -1
271
+ tempMaxC: maxes[i],
272
+ tempMinC: mins[i],
273
+ precipProbMaxPct: precip?.[i] ?? null,
274
+ weatherCode: codes[i]
178
275
  }));
179
276
  return {
180
277
  ok: true,
181
278
  via: 'open-meteo',
182
279
  evidence: `[实时API:open-meteo@${ts}]`,
183
280
  latencyMs,
184
- timezone: r.data.timezone,
281
+ timezone: typeof payload?.['timezone'] === 'string' ? payload['timezone'] : undefined,
185
282
  daily: rows
186
283
  };
187
284
  }
188
285
  export async function getClimate(point, month, opts = {}) {
189
- const started = Date.now();
286
+ const started = monotonicNow();
190
287
  const ts = new Date().toISOString();
191
- const year = opts.year ?? new Date().getFullYear() - 1;
288
+ const timeoutMs = normalizeTimeout(opts.timeoutMs ?? 15_000);
289
+ if (timeoutMs === null) return {
290
+ ok: false,
291
+ via: 'open-meteo-error',
292
+ evidence: `[实时API:open-meteo-climate@error@${ts}]`,
293
+ latencyMs: monotonicNow() - started,
294
+ error: TIMEOUT_ERROR
295
+ };
296
+ const year = opts.year ?? new Date().getUTCFullYear() - 1;
297
+ if (!Number.isInteger(month) || month < 1 || month > 12 || !Number.isInteger(year) || year < 1940 || year > new Date().getUTCFullYear()) {
298
+ return {
299
+ ok: false,
300
+ via: 'open-meteo-error',
301
+ evidence: `[实时API:open-meteo-climate@error@${ts}]`,
302
+ latencyMs: monotonicNow() - started,
303
+ error: 'month/year must be valid historical calendar values'
304
+ };
305
+ }
192
306
  const lastDay = new Date(Date.UTC(year, month, 0)).getUTCDate();
307
+ const expectedDates = Array.from({
308
+ length: lastDay
309
+ }, (_, i)=>`${year}-${String(month).padStart(2, '0')}-${String(i + 1).padStart(2, '0')}`);
193
310
  const start = `${year}-${String(month).padStart(2, '0')}-01`;
194
311
  const end = `${year}-${String(month).padStart(2, '0')}-${String(lastDay).padStart(2, '0')}`;
195
312
  const url = `${ARCHIVE_BASE}?latitude=${point.latitude}&longitude=${point.longitude}` + `&start_date=${start}&end_date=${end}` + `&daily=temperature_2m_max,temperature_2m_min,precipitation_sum,weathercode&timezone=auto`;
196
- const r = await fetchJson(url, opts.timeoutMs ?? 15_000);
197
- const latencyMs = Date.now() - started;
313
+ const r = await fetchJson(url, timeoutMs, {}, opts.fetchImpl);
314
+ const latencyMs = monotonicNow() - started;
198
315
  if (!r.ok) {
199
316
  return {
200
317
  ok: false,
@@ -204,8 +321,9 @@ export async function getClimate(point, month, opts = {}) {
204
321
  error: r.error
205
322
  };
206
323
  }
207
- const daily = r.data.daily;
208
- if (!daily) {
324
+ const payload = isRecord(r.data) ? r.data : null;
325
+ const daily = isRecord(payload?.['daily']) ? payload['daily'] : null;
326
+ if (!daily || !Array.isArray(daily.time) || !Array.isArray(daily.temperature_2m_max) || !Array.isArray(daily.temperature_2m_min) || !Array.isArray(daily.weathercode) || daily.time.length !== lastDay || daily.time.length !== daily.temperature_2m_max.length || daily.time.length !== daily.temperature_2m_min.length || daily.time.length !== daily.weathercode.length || daily.time.some((v)=>typeof v !== 'string') || daily.time.some((v, i)=>v !== expectedDates[i]) || daily.temperature_2m_max.some((v)=>typeof v !== 'number' || !Number.isFinite(v)) || daily.temperature_2m_min.some((v)=>typeof v !== 'number' || !Number.isFinite(v)) || daily.weathercode.some((v)=>typeof v !== 'number' || !Number.isFinite(v))) {
209
327
  return {
210
328
  ok: false,
211
329
  via: 'open-meteo-error',
@@ -214,22 +332,26 @@ export async function getClimate(point, month, opts = {}) {
214
332
  error: 'empty daily payload'
215
333
  };
216
334
  }
217
- const rows = daily.time.map((date, i)=>({
335
+ const times = daily.time;
336
+ const maxes = daily.temperature_2m_max;
337
+ const mins = daily.temperature_2m_min;
338
+ const codes = daily.weathercode;
339
+ const rows = times.map((date, i)=>({
218
340
  date,
219
- tempMaxC: daily.temperature_2m_max[i] ?? NaN,
220
- tempMinC: daily.temperature_2m_min[i] ?? NaN,
341
+ tempMaxC: maxes[i],
342
+ tempMinC: mins[i],
221
343
  precipProbMaxPct: null,
222
- weatherCode: daily.weathercode[i] ?? -1
344
+ weatherCode: codes[i]
223
345
  }));
224
346
  return {
225
347
  ok: true,
226
348
  via: 'open-meteo',
227
349
  evidence: `[实时API:open-meteo-climate@${ts}]`,
228
350
  latencyMs,
229
- timezone: r.data.timezone,
351
+ timezone: typeof payload?.['timezone'] === 'string' ? payload['timezone'] : undefined,
230
352
  daily: rows
231
353
  };
232
354
  }
233
355
 
234
356
 
235
- //# sourceURL=/Users/bytedance/work/gotry-publish/ts/capabilities/weather.ts
357
+ //# sourceURL=ts/capabilities/weather.ts
@@ -0,0 +1,25 @@
1
+ {
2
+ "comment": "会话数据面金标准查询集(RFC P2 监控面输入;只增不改语义,对齐 time-slot-eval 惯例)。city 字段约束:from/to 必须同时命中 ctrip 城市码表与美团拼音表(双适配器通用)。",
3
+ "queries": [
4
+ { "id": "sf-01", "kind": "flight", "from": "上海", "to": "丽江", "date": "2026-10-01" },
5
+ { "id": "sf-02", "kind": "flight", "from": "北京", "to": "大理", "date": "2026-10-02" },
6
+ { "id": "sf-03", "kind": "flight", "from": "上海", "to": "三亚", "date": "2026-11-11" },
7
+ { "id": "sf-04", "kind": "flight", "from": "广州", "to": "昆明", "date": "2026-12-20" },
8
+ { "id": "sf-05", "kind": "flight", "from": "深圳", "to": "成都", "date": "2026-10-05" },
9
+ { "id": "sf-06", "kind": "flight", "from": "杭州", "to": "厦门", "date": "2026-10-06" },
10
+ { "id": "sf-07", "kind": "flight", "from": "西安", "to": "桂林", "date": "2026-10-07" },
11
+ { "id": "sf-08", "kind": "flight", "from": "重庆", "to": "贵阳", "date": "2026-10-08" },
12
+ { "id": "mt-01", "kind": "meituan-hotel", "city": "上海", "keyword": "陆家嘴" },
13
+ { "id": "mt-02", "kind": "meituan-hotel", "city": "大理", "keyword": "洱海" },
14
+ { "id": "mt-03", "kind": "meituan-hotel", "city": "成都", "keyword": "太古里" },
15
+ { "id": "mt-04", "kind": "meituan-hotel", "city": "北京", "keyword": "故宫" },
16
+ { "id": "mt-05", "kind": "meituan-minsu", "city": "丽江", "keyword": "古城" },
17
+ { "id": "mt-06", "kind": "meituan-minsu", "city": "厦门", "keyword": "鼓浪屿" },
18
+ { "id": "mt-07", "kind": "meituan-minsu", "city": "杭州", "keyword": "西湖" },
19
+ { "id": "mt-08", "kind": "meituan-minsu", "city": "三亚", "keyword": "亚龙湾" },
20
+ { "id": "fa-01", "kind": "flyai-flight", "from": "上海", "to": "丽江", "date": "2026-10-01" },
21
+ { "id": "fa-02", "kind": "flyai-flight", "from": "北京", "to": "大理", "date": "2026-10-02" },
22
+ { "id": "fa-03", "kind": "flyai-train", "from": "上海", "to": "大理", "date": "2026-10-01" },
23
+ { "id": "fa-04", "kind": "flyai-train", "from": "北京", "to": "昆明", "date": "2026-10-03" }
24
+ ]
25
+ }
@@ -0,0 +1,102 @@
1
+ {
2
+ "comment": "sf-live-benchmark golden(issue #21 验收「字段准确率 ≥90%」对照源)。本表=公开班期 + 价格区间,不绑任何 vendor;字段评分子集硬命中(query_id/from/to/currency/source/verdict)+ 时间/价格/班次软命中(±60min/±15%/子串包含)。sf-01 fixture 来自 ts/scripts/session-benchmark.ts:26-42 已知期望;其余 7 条以公开班期知识写时间窗口与价格带(具体班次由 session 真实落,匹配子串算 correct)。",
3
+ "threshold": 0.9,
4
+ "matches": [
5
+ {
6
+ "query_id": "sf-01",
7
+ "from": "上海",
8
+ "to": "丽江",
9
+ "date": "2026-10-01",
10
+ "window_dep_local": { "earliest": "06:00", "latest": "22:00" },
11
+ "window_arr_local": { "earliest": "10:00", "latest": "23:59" },
12
+ "duration_min": { "min": 180, "max": 720 },
13
+ "price_cny": { "min": 800, "max": 4000 },
14
+ "transport_hint": "直飞或 1 段中转,承运含 MU/HO/9C/CA/CZ 之一",
15
+ "known_flights": ["HO5577", "MU6145", "9C8779"]
16
+ },
17
+ {
18
+ "query_id": "sf-02",
19
+ "from": "北京",
20
+ "to": "大理",
21
+ "date": "2026-10-02",
22
+ "window_dep_local": { "earliest": "06:00", "latest": "22:00" },
23
+ "window_arr_local": { "earliest": "10:00", "latest": "23:59" },
24
+ "duration_min": { "min": 240, "max": 900 },
25
+ "price_cny": { "min": 800, "max": 4500 },
26
+ "transport_hint": "直飞稀,多经昆明/成都中转 1-2 段,承运含 CA/MU/3U/CZ",
27
+ "known_flights": ["CA1441", "MU5712", "3U8831"]
28
+ },
29
+ {
30
+ "query_id": "sf-03",
31
+ "from": "上海",
32
+ "to": "三亚",
33
+ "date": "2026-11-11",
34
+ "window_dep_local": { "earliest": "06:00", "latest": "22:00" },
35
+ "window_arr_local": { "earliest": "10:00", "latest": "23:59" },
36
+ "duration_min": { "min": 180, "max": 600 },
37
+ "price_cny": { "min": 500, "max": 3000 },
38
+ "transport_hint": "旺季直飞多,承运含 9C/MU/HU/CZ/CA",
39
+ "known_flights": ["9C8779", "MU5377", "HU7177", "CZ6766"]
40
+ },
41
+ {
42
+ "query_id": "sf-04",
43
+ "from": "广州",
44
+ "to": "昆明",
45
+ "date": "2026-12-20",
46
+ "window_dep_local": { "earliest": "06:00", "latest": "22:00" },
47
+ "window_arr_local": { "earliest": "09:00", "latest": "23:59" },
48
+ "duration_min": { "min": 150, "max": 600 },
49
+ "price_cny": { "min": 400, "max": 2500 },
50
+ "transport_hint": "直飞密集,承运含 CZ/MU/3U/8L",
51
+ "known_flights": ["CZ3497", "MU5738", "3U8805", "8L9628"]
52
+ },
53
+ {
54
+ "query_id": "sf-05",
55
+ "from": "深圳",
56
+ "to": "成都",
57
+ "date": "2026-10-05",
58
+ "window_dep_local": { "earliest": "06:00", "latest": "22:00" },
59
+ "window_arr_local": { "earliest": "09:00", "latest": "23:59" },
60
+ "duration_min": { "min": 150, "max": 600 },
61
+ "price_cny": { "min": 400, "max": 2500 },
62
+ "transport_hint": "直飞密集,承运含 CZ/MU/CA/3U/ZH",
63
+ "known_flights": ["CZ3453", "MU5402", "CA4314", "3U8744"]
64
+ },
65
+ {
66
+ "query_id": "sf-06",
67
+ "from": "杭州",
68
+ "to": "厦门",
69
+ "date": "2026-10-06",
70
+ "window_dep_local": { "earliest": "06:00", "latest": "22:00" },
71
+ "window_arr_local": { "earliest": "09:00", "latest": "23:59" },
72
+ "duration_min": { "min": 90, "max": 360 },
73
+ "price_cny": { "min": 300, "max": 2000 },
74
+ "transport_hint": "短途直飞,承运含 GJ/MU/CZ/CA",
75
+ "known_flights": ["GJ7153", "MU5520", "CZ6955"]
76
+ },
77
+ {
78
+ "query_id": "sf-07",
79
+ "from": "西安",
80
+ "to": "桂林",
81
+ "date": "2026-10-07",
82
+ "window_dep_local": { "earliest": "06:00", "latest": "22:00" },
83
+ "window_arr_local": { "earliest": "09:00", "latest": "23:59" },
84
+ "duration_min": { "min": 150, "max": 600 },
85
+ "price_cny": { "min": 400, "max": 2500 },
86
+ "transport_hint": "直飞较稀,多中转 1 段,承运含 MU/CZ/9C/HU",
87
+ "known_flights": ["JD5143", "MU2176", "CZ6319"]
88
+ },
89
+ {
90
+ "query_id": "sf-08",
91
+ "from": "重庆",
92
+ "to": "贵阳",
93
+ "date": "2026-10-08",
94
+ "window_dep_local": { "earliest": "06:00", "latest": "22:00" },
95
+ "window_arr_local": { "earliest": "09:00", "latest": "23:59" },
96
+ "duration_min": { "min": 60, "max": 300 },
97
+ "price_cny": { "min": 250, "max": 1800 },
98
+ "transport_hint": "极短途直飞,承运含 G5/CZ/MU/GY",
99
+ "known_flights": ["G52667", "CZ5817", "MU2146", "GY7122"]
100
+ }
101
+ ]
102
+ }
@@ -0,0 +1,91 @@
1
+ {
2
+ "schema_version": "sf-static-routes.v1",
3
+ "source": {
4
+ "name": "OpenFlights routes.dat",
5
+ "url": "https://raw.githubusercontent.com/jpatokal/openflights/4b969f8e91eb800c45f0e0e2355a0fbb93de27e4/data/routes.dat",
6
+ "revision": "4b969f8e91eb800c45f0e0e2355a0fbb93de27e4",
7
+ "license": "ODbL-1.0",
8
+ "retrieved_at": "2026-08-30",
9
+ "limitations": [
10
+ "route/carrier coverage only",
11
+ "no schedule",
12
+ "no price",
13
+ "no availability"
14
+ ]
15
+ },
16
+ "routes": [
17
+ {
18
+ "query_id": "sf-01",
19
+ "from_city": "上海",
20
+ "to_city": "丽江",
21
+ "route_kind": "direct",
22
+ "legs": [
23
+ { "from_iata": "SHA", "to_iata": "LJG", "carrier_codes": ["MU"] }
24
+ ]
25
+ },
26
+ {
27
+ "query_id": "sf-02",
28
+ "from_city": "北京",
29
+ "to_city": "大理",
30
+ "route_kind": "transfer",
31
+ "legs": [
32
+ { "from_iata": "PEK", "to_iata": "KMG", "carrier_codes": ["8L", "CA", "CZ", "HU", "MU", "ZH"] },
33
+ { "from_iata": "KMG", "to_iata": "DLU", "carrier_codes": ["8L", "CZ", "MU"] }
34
+ ]
35
+ },
36
+ {
37
+ "query_id": "sf-03",
38
+ "from_city": "上海",
39
+ "to_city": "三亚",
40
+ "route_kind": "direct",
41
+ "legs": [
42
+ { "from_iata": "PVG", "to_iata": "SYX", "carrier_codes": ["9C", "CZ", "FM", "HO", "MU"] }
43
+ ]
44
+ },
45
+ {
46
+ "query_id": "sf-04",
47
+ "from_city": "广州",
48
+ "to_city": "昆明",
49
+ "route_kind": "direct",
50
+ "legs": [
51
+ { "from_iata": "CAN", "to_iata": "KMG", "carrier_codes": ["CA", "CZ", "KY", "MU", "ZH"] }
52
+ ]
53
+ },
54
+ {
55
+ "query_id": "sf-05",
56
+ "from_city": "深圳",
57
+ "to_city": "成都",
58
+ "route_kind": "direct",
59
+ "legs": [
60
+ { "from_iata": "SZX", "to_iata": "CTU", "carrier_codes": ["3U", "CA", "CZ", "EU", "HU", "TV", "ZH"] }
61
+ ]
62
+ },
63
+ {
64
+ "query_id": "sf-06",
65
+ "from_city": "杭州",
66
+ "to_city": "厦门",
67
+ "route_kind": "direct",
68
+ "legs": [
69
+ { "from_iata": "HGH", "to_iata": "XMN", "carrier_codes": ["CA", "CZ", "MF", "MU", "SC"] }
70
+ ]
71
+ },
72
+ {
73
+ "query_id": "sf-07",
74
+ "from_city": "西安",
75
+ "to_city": "桂林",
76
+ "route_kind": "direct",
77
+ "legs": [
78
+ { "from_iata": "XIY", "to_iata": "KWL", "carrier_codes": ["CA", "CZ", "GS", "JD", "MU", "NS", "ZH"] }
79
+ ]
80
+ },
81
+ {
82
+ "query_id": "sf-08",
83
+ "from_city": "重庆",
84
+ "to_city": "贵阳",
85
+ "route_kind": "direct",
86
+ "legs": [
87
+ { "from_iata": "CKG", "to_iata": "KWE", "carrier_codes": ["CA", "CZ", "G5", "GS", "SC"] }
88
+ ]
89
+ }
90
+ ]
91
+ }
@@ -104,4 +104,4 @@ console.log(`\nACTION-CACHE: ${pass} pass, ${fail} fail`);
104
104
  process.exit(fail > 0 ? 1 : 0);
105
105
 
106
106
 
107
- //# sourceURL=/Users/bytedance/work/gotry-publish/ts/scripts/action-cache-tests.ts
107
+ //# sourceURL=ts/scripts/action-cache-tests.ts