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

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 (137) hide show
  1. package/README.md +42 -16
  2. package/README.zh-CN.md +37 -15
  3. package/bin/gotry-bootstrap.js +236 -3
  4. package/bin/gotry-inner.js +66 -20
  5. package/bin/gotry-runtime-resolution.d.ts +1 -1
  6. package/bin/gotry-runtime-resolution.js +7 -8
  7. package/cordis.gotry-patch.yml +15 -5
  8. package/data/stations-12306-verify.json +524 -0
  9. package/dist/capabilities/agent-reach.js +4 -4
  10. package/dist/capabilities/channel-health.js +103 -0
  11. package/dist/capabilities/channel-registry.js +213 -0
  12. package/dist/capabilities/doctor.js +285 -0
  13. package/dist/capabilities/effect.js +178 -5
  14. package/dist/capabilities/flyai.js +13 -0
  15. package/dist/capabilities/session/adapters/ctrip-hotel.js +207 -0
  16. package/dist/capabilities/session/adapters/rail-12306.js +285 -0
  17. package/dist/capabilities/session/extension-bridge.js +14 -1
  18. package/dist/capabilities/session/extension-channel.js +4 -2
  19. package/dist/capabilities/session/extension-distribution.js +1 -1
  20. package/dist/capabilities/session-search.js +311 -0
  21. package/dist/capabilities/visa-policy.js +126 -0
  22. package/dist/scripts/agent-planning-turn-deadline-e2e.js +304 -0
  23. package/dist/scripts/agent-planning-turn-deadline-tests.js +281 -0
  24. package/dist/scripts/benchmark-environment-bridge-e2e.js +5 -10
  25. package/dist/scripts/benchmark-environment-bridge-tests.js +148 -142
  26. package/dist/scripts/booking-copilot-availability-ledger-binding-tests.js +26 -26
  27. package/dist/scripts/{booking-copilot-availability-policy-v2-tests.js → booking-copilot-availability-policy-tests.js} +115 -115
  28. package/dist/scripts/booking-copilot-crossrepo-fixture-server.js +41 -1
  29. package/dist/scripts/booking-copilot-dsh-core-proof-tests.js +75 -120
  30. package/dist/scripts/booking-copilot-dsh-planner-proof-tests.js +370 -61
  31. package/dist/scripts/booking-copilot-dsh-plugin-proof-tests.js +2 -2
  32. package/dist/scripts/booking-copilot-event-sequence-concurrency-proof-tests.js +7 -3
  33. package/dist/scripts/booking-copilot-gap-code-contract-proof-tests.js +15 -9
  34. package/dist/scripts/booking-copilot-operation-ledger-concurrency-proof-tests.js +19 -11
  35. package/dist/scripts/booking-copilot-receipt-ledger-concurrency-proof-tests.js +16 -52
  36. package/dist/scripts/booking-copilot-runtime-proof-tests.js +3953 -155
  37. package/dist/scripts/booking-copilot-server-proof-tests.js +58 -23
  38. package/dist/scripts/booking-copilot-startup-proof-tests.js +10 -119
  39. package/dist/scripts/booking-surface-contract-proof-tests.js +1154 -326
  40. package/dist/scripts/bootstrap-tests.js +87 -2
  41. package/dist/scripts/build-metrics-report.js +339 -0
  42. package/dist/scripts/channel-probe-tests.js +148 -0
  43. package/dist/scripts/channel-probe.js +252 -0
  44. package/dist/scripts/channel-registry-tests.js +262 -0
  45. package/dist/scripts/doctor-tests.js +121 -0
  46. package/dist/scripts/effect-tests.js +274 -1
  47. package/dist/scripts/extension-tests.js +129 -11
  48. package/dist/scripts/fact-gate-tests.js +91 -2
  49. package/dist/scripts/flyai-tests.js +17 -1
  50. package/dist/scripts/hbcli-e2e-tests.js +1 -3
  51. package/dist/scripts/metrics-report-tests.js +288 -0
  52. package/dist/scripts/persona-surface-guard-tests.js +19 -0
  53. package/dist/scripts/session-tests.js +401 -1
  54. package/dist/scripts/smoke.js +100 -83
  55. package/dist/scripts/turn-handoff-collect-tests.js +216 -0
  56. package/dist/scripts/turn-handoff-collect.js +189 -0
  57. package/dist/scripts/turn-policy-tests.js +56 -0
  58. package/dist/scripts/typed-contract-canary.js +261 -0
  59. package/dist/scripts/visa-policy-tests.js +75 -0
  60. package/dist/scripts/wish-channel-gate-tests.js +111 -0
  61. package/dist/src/artifact-gate.js +91 -3
  62. package/dist/src/benchmark-agent-conformance.js +7 -4
  63. package/dist/src/benchmark-environment-bridge.js +68 -7
  64. package/dist/src/bookable-facts.js +62 -3
  65. package/dist/src/booking-surface/{availability-policy-v2.js → availability-policy.js} +89 -89
  66. package/dist/src/booking-surface/canonical-schema.js +8 -22
  67. package/dist/src/booking-surface/contracts.js +70 -19
  68. package/dist/src/booking-surface/dsh-planner.js +310 -177
  69. package/dist/src/booking-surface/dsh-plugin.js +3 -3
  70. package/dist/src/booking-surface/index.js +1 -5
  71. package/dist/src/booking-surface/profile.js +1 -1
  72. package/dist/src/booking-surface/runtime.js +1682 -247
  73. package/dist/src/booking-surface/server.js +371 -185
  74. package/dist/src/booking-surface/startup.js +17 -27
  75. package/dist/src/booking-surface/validation.js +270 -760
  76. package/dist/src/index.js +735 -149
  77. package/dist/src/turn-deadline.js +292 -0
  78. package/dist/src/turn-policy.js +146 -0
  79. package/dist/src/wish-pool.js +5 -0
  80. package/extension/background.js +23 -4
  81. package/extension/content-main.js +47 -9
  82. package/extension/manifest.json +27 -9
  83. package/package.json +6 -26
  84. package/schemas/booking.surface.schema.json +2593 -0
  85. package/ts/capabilities/agent-reach.ts +4 -4
  86. package/ts/capabilities/flyai.ts +21 -3
  87. package/ts/capabilities/session/action-cache.ts +1 -1
  88. package/ts/capabilities/session/adapters/ctrip-hotel.ts +238 -0
  89. package/ts/capabilities/session/adapters/rail-12306.ts +293 -0
  90. package/ts/capabilities/session/extension-bridge.ts +32 -2
  91. package/ts/capabilities/session/extension-channel.ts +9 -8
  92. package/ts/capabilities/session/extension-distribution.ts +3 -1
  93. package/ts/capabilities/session/health-watch.ts +1 -1
  94. package/ts/capabilities/session/read-guard.ts +1 -1
  95. package/ts/capabilities/session/wizard.ts +1 -1
  96. package/ts/capabilities/session-search.ts +323 -1
  97. package/ts/package.json +1 -0
  98. package/ts/scripts/turn-handoff-collect.ts +178 -0
  99. package/ts/src/artifact-gate.ts +82 -2
  100. package/ts/src/benchmark-agent-conformance.ts +14 -6
  101. package/ts/src/benchmark-environment-bridge.ts +29 -10
  102. package/ts/src/bookable-facts.ts +109 -5
  103. package/ts/src/booking-saga.ts +1 -1
  104. package/ts/src/booking-surface/{availability-policy-v2.ts → availability-policy.ts} +124 -125
  105. package/ts/src/booking-surface/canonical-schema.js +8 -22
  106. package/ts/src/booking-surface/contracts.ts +278 -239
  107. package/ts/src/booking-surface/dsh-planner.ts +333 -194
  108. package/ts/src/booking-surface/dsh-plugin.js +3 -3
  109. package/ts/src/booking-surface/index.ts +1 -5
  110. package/ts/src/booking-surface/profile.ts +11 -11
  111. package/ts/src/booking-surface/runtime.ts +1351 -351
  112. package/ts/src/booking-surface/server.ts +282 -175
  113. package/ts/src/booking-surface/startup.ts +36 -48
  114. package/ts/src/booking-surface/validation.ts +194 -442
  115. package/ts/src/contracts.ts +1 -1
  116. package/ts/src/index.ts +489 -171
  117. package/ts/src/loop.ts +1 -1
  118. package/ts/src/state-ledger.ts +1 -1
  119. package/ts/src/turn-deadline.ts +361 -0
  120. package/ts/src/turn-policy.ts +154 -0
  121. package/ts/src/wish-pool.ts +17 -4
  122. package/dist/scripts/agent-planning-budget-e2e.js +0 -227
  123. package/dist/scripts/agent-planning-budget-tests.js +0 -173
  124. package/dist/scripts/booking-copilot-v2-runtime-proof-tests.js +0 -3935
  125. package/dist/scripts/booking-surface-v2-contract-proof-tests.js +0 -1174
  126. package/dist/src/booking-surface/contracts-v2.js +0 -89
  127. package/dist/src/booking-surface/runtime-v2.js +0 -1771
  128. package/dist/src/booking-surface/server-v2.js +0 -334
  129. package/dist/src/booking-surface/validation-v2.js +0 -319
  130. package/dist/src/tool-budget.js +0 -136
  131. package/schemas/booking.surface.v1.schema.json +0 -927
  132. package/schemas/booking.surface.v2.schema.json +0 -61
  133. package/ts/src/booking-surface/contracts-v2.ts +0 -118
  134. package/ts/src/booking-surface/runtime-v2.ts +0 -1466
  135. package/ts/src/booking-surface/server-v2.ts +0 -247
  136. package/ts/src/booking-surface/validation-v2.ts +0 -205
  137. package/ts/src/tool-budget.ts +0 -165
@@ -1,9 +1,76 @@
1
1
  import { CircuitBreaker, withRetry } from './resilience.js';
2
2
  import { flyaiSearch } from './flyai.js';
3
3
  import { checkAvail, hotelRates, searchHotels } from './hbcli.js';
4
- import { sessionFlightSearch } from './session-search.js';
4
+ import { sessionFlightSearch, sessionHotelSearch, sessionTrainSearch } from './session-search.js';
5
5
  import { geocodePlace, getClimate, getForecast } from './weather.js';
6
6
  import { verifyFlight } from './opensky.js';
7
+ import { anythingSearch } from './anything.js';
8
+ import { continentListUrl, countryPageUrl, extractVisaSection, fetchVisaPolicyPage, listCountryPaths, policyFactsFromMfa } from './visa-policy.js';
9
+ import { readUrl, reach, reachStatus } from './agent-reach.js';
10
+ import { githubSearch, videoSubtitle } from './agent-reach-deep.js';
11
+ import { sessionLogin } from './session-login.js';
12
+ async function fetchVisaPolicyEntries(p) {
13
+ const started = Date.now();
14
+ const fetchedAt = new Date().toISOString();
15
+ const countries = [];
16
+ const facts = [];
17
+ const targets = [];
18
+ if (p.country) {
19
+ targets.push({
20
+ continent: p.continent ?? 'yz_645708',
21
+ country: p.country
22
+ });
23
+ } else if (p.continent) {
24
+ const listUrl = continentListUrl(p.continent);
25
+ if (!listUrl) return {
26
+ ok: false,
27
+ via: 'visa-policy-error',
28
+ evidence: `[visa-policy@${fetchedAt}] 非法洲路径`,
29
+ latencyMs: 0,
30
+ facts: [],
31
+ countries: [],
32
+ error: 'invalid_continent'
33
+ };
34
+ const page = await fetchVisaPolicyPage(listUrl, p.timeoutMs);
35
+ if (!page.ok || !page.html) return {
36
+ ok: false,
37
+ via: 'visa-policy-error',
38
+ evidence: `[visa-policy@${fetchedAt}] 洲列表抓取失败: ${page.error ?? '?'}`,
39
+ latencyMs: 0,
40
+ facts: [],
41
+ countries: [],
42
+ error: page.error
43
+ };
44
+ for (const c of listCountryPaths(page.html, p.continent).slice(0, Math.max(1, Math.min(10, p.limit ?? 10))))targets.push({
45
+ continent: p.continent,
46
+ country: c
47
+ });
48
+ }
49
+ for (const t of targets){
50
+ const url = countryPageUrl(t.continent, t.country);
51
+ if (!url) continue;
52
+ const page = await fetchVisaPolicyPage(url, p.timeoutMs);
53
+ countries.push(t.country);
54
+ if (!page.ok || !page.html) continue;
55
+ const section = extractVisaSection(page.html);
56
+ if (!section) continue;
57
+ facts.push(...policyFactsFromMfa({
58
+ countryLabel: t.country,
59
+ countryPath: t.country,
60
+ section,
61
+ fetchedAt
62
+ }));
63
+ }
64
+ const evidence = facts.length > 0 ? `[visa-policy:cs-mfa@${fetchedAt}] ${facts.length} 条政策事实(抓取 ${countries.length} 国)` : `[visa-policy@${fetchedAt}] 0 条(页面缺「签证入境」章节或抓取失败;不落负事实)`;
65
+ return {
66
+ ok: true,
67
+ via: 'cs-mfa',
68
+ evidence,
69
+ latencyMs: Date.now() - started,
70
+ facts,
71
+ countries
72
+ };
73
+ }
7
74
  const DEFAULT_HANDLERS = {
8
75
  FLYAI_SEARCH: (p)=>flyaiSearch(p),
9
76
  HBCLI_HOTEL_SEARCH: (p)=>searchHotels({
@@ -35,6 +102,8 @@ const DEFAULT_HANDLERS = {
35
102
  timeoutMs: p.timeoutMs
36
103
  }),
37
104
  SESSION_FLIGHT_SEARCH: (p)=>sessionFlightSearch(p),
105
+ SESSION_HOTEL_SEARCH: (p)=>sessionHotelSearch(p),
106
+ SESSION_TRAIN_SEARCH: (p)=>sessionTrainSearch(p),
38
107
  WEATHER_GEOCODE: (p)=>geocodePlace(p.name, {
39
108
  count: p.count,
40
109
  timeoutMs: p.timeoutMs
@@ -52,7 +121,24 @@ const DEFAULT_HANDLERS = {
52
121
  }, p.month, {
53
122
  timeoutMs: p.timeoutMs
54
123
  }),
55
- OPENSKY_FLIGHT_VERIFY: (p)=>verifyFlight(p)
124
+ OPENSKY_FLIGHT_VERIFY: (p)=>verifyFlight(p),
125
+ ANYTHING_SEARCH: (p)=>anythingSearch(p),
126
+ WEB_READ: (p)=>readUrl({
127
+ url: p.url,
128
+ timeoutMs: p.timeoutMs
129
+ }),
130
+ GITHUB_SEARCH: (p)=>githubSearch(p),
131
+ VIDEO_SUBTITLE: (p)=>videoSubtitle(p),
132
+ AGENT_REACH: (p)=>p.action === 'status' || !p.action && !p.channel ? reachStatus(p.timeoutMs) : reach({
133
+ channel: p.channel ?? '',
134
+ method: p.method ?? '',
135
+ args: p.args,
136
+ timeoutMs: p.timeoutMs
137
+ }),
138
+ SESSION_LOGIN: (p)=>sessionLogin({
139
+ waitMs: typeof p.waitSeconds === 'number' ? p.waitSeconds * 1000 : undefined
140
+ }),
141
+ VISA_POLICY_FETCH: (p)=>fetchVisaPolicyEntries(p)
56
142
  };
57
143
  const defaultIsFailure = (r)=>r?.ok === false;
58
144
  const flyaiTransient = (msg)=>msg.length > 0 && !/Sentinel/i.test(msg) && /timeout|timed out|ECONNRESET|ECONNREFUSED|ETIMEDOUT|socket|HTTP 5/i.test(msg);
@@ -65,6 +151,15 @@ const API_BREAKER = {
65
151
  failureThreshold: 3,
66
152
  openMs: 30_000
67
153
  };
154
+ const hbcliTimeout = (r)=>{
155
+ const msg = String((r ?? {}).error ?? '');
156
+ return msg.length > 0 && /timeout/i.test(msg);
157
+ };
158
+ const HBCLI_RETRY = {
159
+ maxAttempts: 2,
160
+ baseDelayMs: 300,
161
+ maxDelayMs: 1_000
162
+ };
68
163
  const SPECS = {
69
164
  FLYAI_SEARCH: {
70
165
  channel: 'cli',
@@ -85,7 +180,8 @@ const SPECS = {
85
180
  },
86
181
  HBCLI_HOTEL_SEARCH: {
87
182
  channel: 'cli',
88
- retry: null,
183
+ retry: HBCLI_RETRY,
184
+ isRetryable: (r)=>hbcliTimeout(r),
89
185
  breaker: {
90
186
  failureThreshold: 3,
91
187
  openMs: 60_000
@@ -94,7 +190,8 @@ const SPECS = {
94
190
  },
95
191
  HBCLI_HOTEL_RATES: {
96
192
  channel: 'cli',
97
- retry: null,
193
+ retry: HBCLI_RETRY,
194
+ isRetryable: (r)=>hbcliTimeout(r),
98
195
  breaker: {
99
196
  failureThreshold: 3,
100
197
  openMs: 60_000
@@ -103,7 +200,8 @@ const SPECS = {
103
200
  },
104
201
  HBCLI_CHECK_AVAIL: {
105
202
  channel: 'cli',
106
- retry: null,
203
+ retry: HBCLI_RETRY,
204
+ isRetryable: (r)=>hbcliTimeout(r),
107
205
  breaker: {
108
206
  failureThreshold: 3,
109
207
  openMs: 60_000
@@ -116,6 +214,18 @@ const SPECS = {
116
214
  breaker: null,
117
215
  isFailure: defaultIsFailure
118
216
  },
217
+ SESSION_HOTEL_SEARCH: {
218
+ channel: 'browser',
219
+ retry: null,
220
+ breaker: null,
221
+ isFailure: defaultIsFailure
222
+ },
223
+ SESSION_TRAIN_SEARCH: {
224
+ channel: 'browser',
225
+ retry: null,
226
+ breaker: null,
227
+ isFailure: defaultIsFailure
228
+ },
119
229
  WEATHER_GEOCODE: {
120
230
  channel: 'api',
121
231
  retry: API_RETRY,
@@ -140,6 +250,69 @@ const SPECS = {
140
250
  breaker: API_BREAKER,
141
251
  isRetryable: (_r, e)=>e != null || (_r ?? {}).verdict === 'unavailable',
142
252
  isFailure: (r)=>r.via === 'opensky-error'
253
+ },
254
+ ANYTHING_SEARCH: {
255
+ channel: 'cli',
256
+ retry: HBCLI_RETRY,
257
+ isRetryable: (r)=>hbcliTimeout(r),
258
+ breaker: {
259
+ failureThreshold: 3,
260
+ openMs: 60_000
261
+ },
262
+ isFailure: (r)=>r.via === 'hbcli-anything-error'
263
+ },
264
+ WEB_READ: {
265
+ channel: 'api',
266
+ retry: API_RETRY,
267
+ breaker: API_BREAKER,
268
+ isFailure: (r)=>r.via === 'r.jina.ai-error'
269
+ },
270
+ GITHUB_SEARCH: {
271
+ channel: 'cli',
272
+ retry: HBCLI_RETRY,
273
+ isRetryable: (r)=>r.verdict === 'timeout',
274
+ breaker: {
275
+ failureThreshold: 3,
276
+ openMs: 60_000
277
+ },
278
+ isFailure: (r)=>{
279
+ const v = r.verdict;
280
+ return v === 'error' || v === 'timeout';
281
+ }
282
+ },
283
+ VIDEO_SUBTITLE: {
284
+ channel: 'cli',
285
+ retry: HBCLI_RETRY,
286
+ isRetryable: (r)=>r.verdict === 'timeout',
287
+ breaker: {
288
+ failureThreshold: 3,
289
+ openMs: 60_000
290
+ },
291
+ isFailure: (r)=>{
292
+ const v = r.verdict;
293
+ return v === 'error' || v === 'timeout';
294
+ }
295
+ },
296
+ AGENT_REACH: {
297
+ channel: 'cli',
298
+ retry: null,
299
+ breaker: null,
300
+ isFailure: (r)=>r.verdict === 'error'
301
+ },
302
+ SESSION_LOGIN: {
303
+ channel: 'browser',
304
+ retry: null,
305
+ breaker: null,
306
+ isFailure: defaultIsFailure
307
+ },
308
+ VISA_POLICY_FETCH: {
309
+ channel: 'api',
310
+ retry: null,
311
+ breaker: {
312
+ failureThreshold: 2,
313
+ openMs: 300_000
314
+ },
315
+ isFailure: defaultIsFailure
143
316
  }
144
317
  };
145
318
  export function declinedObservation(effect, trace) {
@@ -150,6 +150,19 @@ export async function flyaiSearch(q) {
150
150
  });
151
151
  const latencyMs = Date.now() - started;
152
152
  if (r.error || r.code !== 0) {
153
+ const upstream = `${r.stderr}\n${r.stdout}`;
154
+ if (/Trial limit reached|HTTP\s*429|\b429\b/.test(upstream)) {
155
+ return {
156
+ ...base,
157
+ latencyMs,
158
+ ok: false,
159
+ via: 'flyai-error',
160
+ verdict: 'needs-setup',
161
+ evidence: `[实时API:flyai@error@${ts}] trial quota exhausted`,
162
+ error: `FlyAI 匿名试用额度已用尽(上游 429):${upstream.replace(/\s+/g, ' ').slice(0, 160)}`,
163
+ setup: 'FlyAI 试用额度达限——到 flyai.open.fliggy.com 控制台申请正式 API Key,配入环境变量 FLYAI_API_KEY 即恢复;本会话请勿重试 gotry_flyai_search,机/火/酒改走 gotry_session_search(账号会话)或 web 检索推进。'
164
+ };
165
+ }
153
166
  return {
154
167
  ...base,
155
168
  latencyMs,
@@ -0,0 +1,207 @@
1
+ const HOTEL_CITY_CODES = {
2
+ 上海: '2',
3
+ 北京: '1',
4
+ 广州: '32',
5
+ 深圳: '30',
6
+ 成都: '28',
7
+ 杭州: '17',
8
+ 南京: '12',
9
+ 苏州: '14',
10
+ 无锡: '13',
11
+ 扬州: '15',
12
+ 镇江: '16',
13
+ 青岛: '7',
14
+ 厦门: '25',
15
+ 珠海: '31',
16
+ 三亚: '43',
17
+ 海口: '42',
18
+ 桂林: '33',
19
+ 昆明: '34',
20
+ 大理: '36',
21
+ 丽江: '37',
22
+ 西双版纳: '35',
23
+ 贵阳: '38',
24
+ 拉萨: '41',
25
+ 乌鲁木齐: '39',
26
+ 太原: '105',
27
+ 长春: '158',
28
+ 南通: '82',
29
+ 敦煌: '11',
30
+ 舟山: '19',
31
+ 张家界: '27',
32
+ 黄山市: '23',
33
+ 九江: '24',
34
+ 武夷山: '26',
35
+ 迪拜: '220'
36
+ };
37
+ export function buildHotelEntryUrl(q) {
38
+ const unresolved = [];
39
+ const cityId = String(q.cityId ?? '').trim() || HOTEL_CITY_CODES[q.to.trim()];
40
+ if (!cityId) unresolved.push(q.to);
41
+ if (unresolved.length > 0) {
42
+ return {
43
+ ok: false,
44
+ unresolved
45
+ };
46
+ }
47
+ const params = new URLSearchParams({
48
+ city: cityId
49
+ });
50
+ if (q.checkIn) params.set('checkin', q.checkIn);
51
+ if (q.checkOut) params.set('checkout', q.checkOut);
52
+ if (q.adults && q.adults > 0) params.set('adult', String(q.adults));
53
+ return {
54
+ ok: true,
55
+ url: `https://hotels.ctrip.com/hotels/list?${params.toString()}`
56
+ };
57
+ }
58
+ export const HOTEL_NETWORK_HINTS = [
59
+ /hotels\.ctrip\.com\/(hotels\/api|domestic\/pc\/api)/i,
60
+ /GetHotelListBySOA|GetHotelListByCity|HotelSearch|hotelsearch/i,
61
+ /restapi\/soa2\/\d+\/fetchHotelList/i
62
+ ];
63
+ export function looksLikeHotelListBody(body) {
64
+ if (!body || body.length > 2_000_000) return false;
65
+ return /"hotelList"|"hotelMatchInfos"|"hotelName"/.test(body);
66
+ }
67
+ export const HOTEL_SITE_HOST = 'hotels.ctrip.com';
68
+ function hotelSignature(o) {
69
+ const name = o.hotelName ?? o.name ?? o.hotelNameEn;
70
+ if (typeof name !== 'string' || !name.trim()) return null;
71
+ let price;
72
+ let priceRaw;
73
+ if (typeof o.price === 'number' && o.price > 0) price = o.price;
74
+ else if (typeof o.price === 'string' && o.price.trim()) priceRaw = o.price.trim();
75
+ const probe = (v, seen = new Set())=>{
76
+ if (v == null || typeof v !== 'object') return;
77
+ if (seen.has(v)) return;
78
+ seen.add(v);
79
+ for (const [k, child] of Object.entries(v)){
80
+ if (/^(avgPrice|totalPrice|price|amount|total|startPrice)$/i.test(k) && typeof child === 'number' && child > 0 && price === undefined) price = child;
81
+ else if (/^(price|priceDisplay|amountText)$/i.test(k) && typeof child === 'string' && child.trim() && priceRaw === undefined) priceRaw = child.trim();
82
+ else probe(child, seen);
83
+ }
84
+ };
85
+ probe(o.priceInfo);
86
+ const flatPrice = o.totalPrice ?? o.avgPrice ?? o.minPrice;
87
+ if (typeof flatPrice === 'number' && flatPrice > 0 && price === undefined) price = flatPrice;
88
+ const flatRaw = o.priceText ?? o.priceDisplay;
89
+ if (typeof flatRaw === 'string' && flatRaw.trim() && priceRaw === undefined) priceRaw = flatRaw.trim();
90
+ if (price === undefined && priceRaw === undefined) return null;
91
+ const starRaw = o.star ?? o.level ?? o.hotelStar;
92
+ const star = typeof starRaw === 'number' && starRaw > 0 ? starRaw : Number.isFinite(Number(starRaw)) && Number(starRaw) > 0 ? Number(starRaw) : undefined;
93
+ const scoreRaw = o.score ?? o.commentScore ?? o.reviewScore;
94
+ const score = scoreRaw == null ? undefined : String(scoreRaw);
95
+ const pos = o.position;
96
+ const address = (typeof pos?.address === 'string' ? pos.address : undefined) ?? (typeof o.address === 'string' ? o.address : undefined);
97
+ const hotelId = (typeof o.hotelId === 'number' || typeof o.hotelId === 'string' ? String(o.hotelId) : undefined) ?? (typeof o.hotelIdStr === 'string' ? o.hotelIdStr : undefined);
98
+ return {
99
+ name: name.trim(),
100
+ price,
101
+ priceRaw,
102
+ star,
103
+ score,
104
+ address,
105
+ hotelId
106
+ };
107
+ }
108
+ function ctripStructuredCandidate(item) {
109
+ const info = item.hotelInfo;
110
+ const summary = info?.summary ?? item;
111
+ const nameInfo = summary.nameInfo;
112
+ const names = nameInfo?.names;
113
+ const nameRaw = nameInfo?.name ?? (Array.isArray(names) && names.length > 0 ? names[0] : undefined) ?? summary.hotelName ?? summary.name;
114
+ if (typeof nameRaw !== 'string' || !nameRaw.trim()) return null;
115
+ let price;
116
+ let priceRaw;
117
+ let encrypted = false;
118
+ const rooms = info?.roomInfo ?? item.roomInfo;
119
+ if (Array.isArray(rooms)) {
120
+ for (const room of rooms){
121
+ if (room == null || typeof room !== 'object') continue;
122
+ const pi = room.priceInfo;
123
+ if (!pi) continue;
124
+ if (typeof pi.price === 'number' && pi.price > 0) price = price === undefined ? pi.price : Math.min(price, pi.price);
125
+ if (typeof pi.price === 'string' && pi.price.trim() && !priceRaw) priceRaw = pi.price.trim();
126
+ if (typeof pi.displayPrice === 'string' && pi.displayPrice.trim() && !priceRaw) priceRaw = pi.displayPrice.trim();
127
+ if (pi.priceToken != null && pi.price === undefined) encrypted = true;
128
+ }
129
+ }
130
+ if (price === undefined && !priceRaw) {
131
+ const priceProbe = (v, depth, seen)=>{
132
+ if (price !== undefined || depth > 6 || v == null || typeof v !== 'object' || seen.has(v)) return;
133
+ seen.add(v);
134
+ for (const [k, child] of Object.entries(v)){
135
+ if (/^(price|avgPrice|totalPrice|minPrice|amount)$/i.test(k) && typeof child === 'number' && child > 0) {
136
+ price = child;
137
+ return;
138
+ }
139
+ if (/^(price|displayPrice|amountText)$/i.test(k) && typeof child === 'string' && child.trim()) {
140
+ priceRaw = priceRaw ?? child.trim();
141
+ continue;
142
+ }
143
+ priceProbe(child, depth + 1, seen);
144
+ }
145
+ };
146
+ priceProbe(item, 0, new Set());
147
+ }
148
+ if (price === undefined && !priceRaw && !encrypted) return null;
149
+ const starOf = summary.hotelStar;
150
+ const starRaw = starOf?.star ?? summary.star;
151
+ const star = typeof starRaw === 'number' && starRaw > 0 ? starRaw : undefined;
152
+ const scoreRaw = (info?.commentInfo ?? summary.commentInfo)?.commentScore;
153
+ const pos = summary.positionInfo;
154
+ const address = (typeof pos?.address === 'string' ? pos.address : undefined) ?? (typeof summary.address === 'string' ? summary.address : undefined);
155
+ const hotelId = summary.hotelId != null ? String(summary.hotelId) : undefined;
156
+ return {
157
+ name: nameRaw.trim(),
158
+ price,
159
+ priceRaw,
160
+ star,
161
+ score: scoreRaw == null ? undefined : String(scoreRaw),
162
+ address,
163
+ hotelId
164
+ };
165
+ }
166
+ export function parseCtripHotelList(body, opts = {}) {
167
+ let raw;
168
+ try {
169
+ raw = JSON.parse(body);
170
+ } catch {
171
+ return [];
172
+ }
173
+ const out = [];
174
+ const maxItems = opts.maxItems ?? 20;
175
+ const walk = (node, depth, seen)=>{
176
+ if (out.length >= maxItems || depth > 12 || node == null || typeof node !== 'object' || seen.has(node)) return;
177
+ if (Array.isArray(node)) {
178
+ for (const item of node){
179
+ if (out.length >= maxItems) return;
180
+ if (item == null || typeof item !== 'object' || Array.isArray(item)) continue;
181
+ const cand = hotelSignature(item) ?? ctripStructuredCandidate(item);
182
+ if (!cand) continue;
183
+ out.push({
184
+ name: cand.name,
185
+ price: cand.price ?? 0,
186
+ priceRaw: cand.priceRaw,
187
+ star: cand.star,
188
+ score: cand.score,
189
+ address: cand.address,
190
+ hotelId: cand.hotelId,
191
+ jumpUrl: cand.hotelId ? `https://hotels.ctrip.com/hotel/${cand.hotelId}` : undefined
192
+ });
193
+ }
194
+ return;
195
+ }
196
+ seen.add(node);
197
+ for (const child of Object.values(node)){
198
+ walk(child, depth + 1, seen);
199
+ if (out.length >= maxItems) return;
200
+ }
201
+ };
202
+ walk(raw, 0, new Set());
203
+ return out;
204
+ }
205
+
206
+
207
+ //# sourceURL=ts/capabilities/session/adapters/ctrip-hotel.ts