@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.
- package/README.md +42 -16
- package/README.zh-CN.md +37 -15
- package/bin/gotry-bootstrap.js +236 -3
- package/bin/gotry-inner.js +66 -20
- package/bin/gotry-runtime-resolution.d.ts +1 -1
- package/bin/gotry-runtime-resolution.js +7 -8
- package/cordis.gotry-patch.yml +15 -5
- package/data/stations-12306-verify.json +524 -0
- package/dist/capabilities/agent-reach.js +4 -4
- package/dist/capabilities/channel-health.js +103 -0
- package/dist/capabilities/channel-registry.js +213 -0
- package/dist/capabilities/doctor.js +285 -0
- package/dist/capabilities/effect.js +178 -5
- package/dist/capabilities/flyai.js +13 -0
- package/dist/capabilities/session/adapters/ctrip-hotel.js +207 -0
- package/dist/capabilities/session/adapters/rail-12306.js +285 -0
- package/dist/capabilities/session/extension-bridge.js +14 -1
- package/dist/capabilities/session/extension-channel.js +4 -2
- package/dist/capabilities/session/extension-distribution.js +1 -1
- package/dist/capabilities/session-search.js +311 -0
- package/dist/capabilities/visa-policy.js +126 -0
- package/dist/scripts/agent-planning-turn-deadline-e2e.js +304 -0
- package/dist/scripts/agent-planning-turn-deadline-tests.js +281 -0
- package/dist/scripts/benchmark-environment-bridge-e2e.js +5 -10
- package/dist/scripts/benchmark-environment-bridge-tests.js +148 -142
- package/dist/scripts/booking-copilot-availability-ledger-binding-tests.js +26 -26
- package/dist/scripts/{booking-copilot-availability-policy-v2-tests.js → booking-copilot-availability-policy-tests.js} +115 -115
- package/dist/scripts/booking-copilot-crossrepo-fixture-server.js +41 -1
- package/dist/scripts/booking-copilot-dsh-core-proof-tests.js +75 -120
- package/dist/scripts/booking-copilot-dsh-planner-proof-tests.js +370 -61
- package/dist/scripts/booking-copilot-dsh-plugin-proof-tests.js +2 -2
- package/dist/scripts/booking-copilot-event-sequence-concurrency-proof-tests.js +7 -3
- package/dist/scripts/booking-copilot-gap-code-contract-proof-tests.js +15 -9
- package/dist/scripts/booking-copilot-operation-ledger-concurrency-proof-tests.js +19 -11
- package/dist/scripts/booking-copilot-receipt-ledger-concurrency-proof-tests.js +16 -52
- package/dist/scripts/booking-copilot-runtime-proof-tests.js +3953 -155
- package/dist/scripts/booking-copilot-server-proof-tests.js +58 -23
- package/dist/scripts/booking-copilot-startup-proof-tests.js +10 -119
- package/dist/scripts/booking-surface-contract-proof-tests.js +1154 -326
- package/dist/scripts/bootstrap-tests.js +87 -2
- package/dist/scripts/build-metrics-report.js +339 -0
- package/dist/scripts/channel-probe-tests.js +148 -0
- package/dist/scripts/channel-probe.js +252 -0
- package/dist/scripts/channel-registry-tests.js +262 -0
- package/dist/scripts/doctor-tests.js +121 -0
- package/dist/scripts/effect-tests.js +274 -1
- package/dist/scripts/extension-tests.js +129 -11
- package/dist/scripts/fact-gate-tests.js +91 -2
- package/dist/scripts/flyai-tests.js +17 -1
- package/dist/scripts/hbcli-e2e-tests.js +1 -3
- package/dist/scripts/metrics-report-tests.js +288 -0
- package/dist/scripts/persona-surface-guard-tests.js +19 -0
- package/dist/scripts/session-tests.js +401 -1
- package/dist/scripts/smoke.js +100 -83
- package/dist/scripts/turn-handoff-collect-tests.js +216 -0
- package/dist/scripts/turn-handoff-collect.js +189 -0
- package/dist/scripts/turn-policy-tests.js +56 -0
- package/dist/scripts/typed-contract-canary.js +261 -0
- package/dist/scripts/visa-policy-tests.js +75 -0
- package/dist/scripts/wish-channel-gate-tests.js +111 -0
- package/dist/src/artifact-gate.js +91 -3
- package/dist/src/benchmark-agent-conformance.js +7 -4
- package/dist/src/benchmark-environment-bridge.js +68 -7
- package/dist/src/bookable-facts.js +62 -3
- package/dist/src/booking-surface/{availability-policy-v2.js → availability-policy.js} +89 -89
- package/dist/src/booking-surface/canonical-schema.js +8 -22
- package/dist/src/booking-surface/contracts.js +70 -19
- package/dist/src/booking-surface/dsh-planner.js +310 -177
- package/dist/src/booking-surface/dsh-plugin.js +3 -3
- package/dist/src/booking-surface/index.js +1 -5
- package/dist/src/booking-surface/profile.js +1 -1
- package/dist/src/booking-surface/runtime.js +1682 -247
- package/dist/src/booking-surface/server.js +371 -185
- package/dist/src/booking-surface/startup.js +17 -27
- package/dist/src/booking-surface/validation.js +270 -760
- package/dist/src/index.js +735 -149
- package/dist/src/turn-deadline.js +292 -0
- package/dist/src/turn-policy.js +146 -0
- package/dist/src/wish-pool.js +5 -0
- package/extension/background.js +23 -4
- package/extension/content-main.js +47 -9
- package/extension/manifest.json +27 -9
- package/package.json +6 -26
- package/schemas/booking.surface.schema.json +2593 -0
- package/ts/capabilities/agent-reach.ts +4 -4
- package/ts/capabilities/flyai.ts +21 -3
- package/ts/capabilities/session/action-cache.ts +1 -1
- package/ts/capabilities/session/adapters/ctrip-hotel.ts +238 -0
- package/ts/capabilities/session/adapters/rail-12306.ts +293 -0
- package/ts/capabilities/session/extension-bridge.ts +32 -2
- package/ts/capabilities/session/extension-channel.ts +9 -8
- package/ts/capabilities/session/extension-distribution.ts +3 -1
- package/ts/capabilities/session/health-watch.ts +1 -1
- package/ts/capabilities/session/read-guard.ts +1 -1
- package/ts/capabilities/session/wizard.ts +1 -1
- package/ts/capabilities/session-search.ts +323 -1
- package/ts/package.json +1 -0
- package/ts/scripts/turn-handoff-collect.ts +178 -0
- package/ts/src/artifact-gate.ts +82 -2
- package/ts/src/benchmark-agent-conformance.ts +14 -6
- package/ts/src/benchmark-environment-bridge.ts +29 -10
- package/ts/src/bookable-facts.ts +109 -5
- package/ts/src/booking-saga.ts +1 -1
- package/ts/src/booking-surface/{availability-policy-v2.ts → availability-policy.ts} +124 -125
- package/ts/src/booking-surface/canonical-schema.js +8 -22
- package/ts/src/booking-surface/contracts.ts +278 -239
- package/ts/src/booking-surface/dsh-planner.ts +333 -194
- package/ts/src/booking-surface/dsh-plugin.js +3 -3
- package/ts/src/booking-surface/index.ts +1 -5
- package/ts/src/booking-surface/profile.ts +11 -11
- package/ts/src/booking-surface/runtime.ts +1351 -351
- package/ts/src/booking-surface/server.ts +282 -175
- package/ts/src/booking-surface/startup.ts +36 -48
- package/ts/src/booking-surface/validation.ts +194 -442
- package/ts/src/contracts.ts +1 -1
- package/ts/src/index.ts +489 -171
- package/ts/src/loop.ts +1 -1
- package/ts/src/state-ledger.ts +1 -1
- package/ts/src/turn-deadline.ts +361 -0
- package/ts/src/turn-policy.ts +154 -0
- package/ts/src/wish-pool.ts +17 -4
- package/dist/scripts/agent-planning-budget-e2e.js +0 -227
- package/dist/scripts/agent-planning-budget-tests.js +0 -173
- package/dist/scripts/booking-copilot-v2-runtime-proof-tests.js +0 -3935
- package/dist/scripts/booking-surface-v2-contract-proof-tests.js +0 -1174
- package/dist/src/booking-surface/contracts-v2.js +0 -89
- package/dist/src/booking-surface/runtime-v2.js +0 -1771
- package/dist/src/booking-surface/server-v2.js +0 -334
- package/dist/src/booking-surface/validation-v2.js +0 -319
- package/dist/src/tool-budget.js +0 -136
- package/schemas/booking.surface.v1.schema.json +0 -927
- package/schemas/booking.surface.v2.schema.json +0 -61
- package/ts/src/booking-surface/contracts-v2.ts +0 -118
- package/ts/src/booking-surface/runtime-v2.ts +0 -1466
- package/ts/src/booking-surface/server-v2.ts +0 -247
- package/ts/src/booking-surface/validation-v2.ts +0 -205
- 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:
|
|
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:
|
|
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:
|
|
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
|