@curless/hotel-storefront 0.31.2 → 0.32.0
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/dist/aggregate/hotels.d.ts.map +1 -1
- package/dist/aggregate/hotels.js +4 -1
- package/dist/aggregate/hotels.js.map +1 -1
- package/dist/backend.d.ts +15 -9
- package/dist/backend.d.ts.map +1 -1
- package/dist/backend.js.map +1 -1
- package/dist/card.generated.d.ts +1 -1
- package/dist/card.generated.js +2 -2
- package/dist/card.generated.js.map +1 -1
- package/dist/demo-hotels.d.ts +0 -8
- package/dist/demo-hotels.d.ts.map +1 -1
- package/dist/demo-hotels.js +80 -43
- package/dist/demo-hotels.js.map +1 -1
- package/dist/huazhu/backend.d.ts +3 -1
- package/dist/huazhu/backend.d.ts.map +1 -1
- package/dist/huazhu/backend.js +136 -95
- package/dist/huazhu/backend.js.map +1 -1
- package/dist/huazhu/bookings.d.ts +9 -6
- package/dist/huazhu/bookings.d.ts.map +1 -1
- package/dist/huazhu/bookings.js +7 -17
- package/dist/huazhu/bookings.js.map +1 -1
- package/dist/lib.d.ts +1 -1
- package/dist/lib.d.ts.map +1 -1
- package/dist/server.d.ts +26 -4
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +49 -7
- package/dist/server.js.map +1 -1
- package/dist/surface.d.ts +0 -10
- package/dist/surface.d.ts.map +1 -1
- package/dist/surface.js +13 -7
- package/dist/surface.js.map +1 -1
- package/dist/tools.d.ts.map +1 -1
- package/dist/tools.js +172 -11
- package/dist/tools.js.map +1 -1
- package/dist/types.d.ts +33 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/tools.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../src/tools.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,mBAAmB,EASzB,MAAM,cAAc,CAAC;AACtB,OAAO,KAAK,EAAa,YAAY,EAAgB,MAAM,cAAc,CAAC;AAC1E,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAK7C,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAkBhD,OAAO,EAIL,KAAK,SAAS,EAIf,MAAM,WAAW,CAAC;AACnB,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../src/tools.ts"],"names":[],"mappings":"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"}
|
package/dist/tools.js
CHANGED
|
@@ -1033,6 +1033,14 @@ export const buildOutputSchemas = (n) => ({
|
|
|
1033
1033
|
requested: ARR,
|
|
1034
1034
|
/** An accessible room was asked for, which this booking does not reserve. */
|
|
1035
1035
|
accessibility: BOOL,
|
|
1036
|
+
/** One per stay, when the trip held several. */
|
|
1037
|
+
bookings: ARR,
|
|
1038
|
+
/**
|
|
1039
|
+
* The stays that could not be booked when the others were - each with
|
|
1040
|
+
* why, still in the trip, and what could be booked instead (robin,
|
|
1041
|
+
* 2026-10-14).
|
|
1042
|
+
*/
|
|
1043
|
+
failed: ARR,
|
|
1036
1044
|
},
|
|
1037
1045
|
required: ['view'],
|
|
1038
1046
|
},
|
|
@@ -1198,6 +1206,66 @@ const memberBadge = (deps, key) => {
|
|
|
1198
1206
|
: { signedIn: false };
|
|
1199
1207
|
};
|
|
1200
1208
|
/** A market's group by its id or its name, as a guest or a model would say it. */
|
|
1209
|
+
/** How many stays that failed are each given alternatives - each is a search. */
|
|
1210
|
+
const MAX_FAILED_WITH_ALTERNATIVES = 3;
|
|
1211
|
+
/** How many alternatives a stay that failed is given. */
|
|
1212
|
+
const MAX_ALTERNATIVES = 3;
|
|
1213
|
+
/** A trip line of the stay that failed - its room, or an add-on on its nights. */
|
|
1214
|
+
const inFailedStay = (l, f) => l.hotelId === f.hotelId &&
|
|
1215
|
+
(l.kind === 'room'
|
|
1216
|
+
? l.stay.checkIn === f.stay.checkIn &&
|
|
1217
|
+
l.stay.checkOut === f.stay.checkOut &&
|
|
1218
|
+
(l.hourly?.start ?? '') === (f.hourly?.start ?? '')
|
|
1219
|
+
: !l.day || (f.stay.checkIn <= l.day && l.day <= f.stay.checkOut));
|
|
1220
|
+
/** How many rooms the stay that failed asked for. */
|
|
1221
|
+
const roomsIn = (lines, f) => Math.max(1, lines
|
|
1222
|
+
.filter((l) => l.kind === 'room' && inFailedStay(l, f))
|
|
1223
|
+
.reduce((n, l) => n + (l.kind === 'room' ? (l.rooms ?? 1) : 0), 0));
|
|
1224
|
+
/**
|
|
1225
|
+
* What could be booked instead of a stay that failed: hotels in its city with
|
|
1226
|
+
* a room for the same nights, the same party and as many rooms - the group's
|
|
1227
|
+
* own brand first, then the nearest in price to what the guest was shown
|
|
1228
|
+
* (robin, 2026-10-14: 给客人备选方案). Found when it failed, so a guest is
|
|
1229
|
+
* never left with "no" and nothing else; none found, none offered.
|
|
1230
|
+
*/
|
|
1231
|
+
const alternativesFor = async (deps, f, lines, shown, who) => {
|
|
1232
|
+
try {
|
|
1233
|
+
const failedAt = (await deps.backend.getHotel(f.hotelId))?.hotel;
|
|
1234
|
+
if (!failedAt)
|
|
1235
|
+
return { hotels: [] };
|
|
1236
|
+
const rooms = roomsIn(lines, f);
|
|
1237
|
+
const found = await deps.backend.searchHotels({
|
|
1238
|
+
city: failedAt.city,
|
|
1239
|
+
stay: f.stay,
|
|
1240
|
+
rooms,
|
|
1241
|
+
limit: 20,
|
|
1242
|
+
...(f.hourly ? { filters: { hourly: true } } : {}),
|
|
1243
|
+
}, who);
|
|
1244
|
+
// What the guest was shown for it: the stay's room lines, before taxes -
|
|
1245
|
+
// only to put the nearest first.
|
|
1246
|
+
const was = Object.entries(shown?.lines ?? {})
|
|
1247
|
+
.filter(([k]) => k.startsWith(`room|${f.hotelId}|`) &&
|
|
1248
|
+
k.includes(`|${f.stay.checkIn}|${f.stay.checkOut}|`))
|
|
1249
|
+
.reduce((n, [, v]) => n + v.total.minor, 0);
|
|
1250
|
+
const hotels = found.hotels
|
|
1251
|
+
.filter((h) => h.id !== f.hotelId && h.forStay && !h.noRoomsForStay)
|
|
1252
|
+
.map((h, i) => ({
|
|
1253
|
+
h,
|
|
1254
|
+
i,
|
|
1255
|
+
own: h.brand === failedAt.brand ? 0 : 1,
|
|
1256
|
+
off: was && h.forStay ? Math.abs(h.forStay.total.minor - was) : 0,
|
|
1257
|
+
}))
|
|
1258
|
+
.sort((a, b) => a.own - b.own || a.off - b.off || a.i - b.i)
|
|
1259
|
+
.slice(0, MAX_ALTERNATIVES)
|
|
1260
|
+
.map(({ h }) => h);
|
|
1261
|
+
return { city: failedAt.city, hotels };
|
|
1262
|
+
}
|
|
1263
|
+
catch {
|
|
1264
|
+
// An alternative is a kindness, not the booking: a search that fails
|
|
1265
|
+
// leaves the stay said, with its other ways on.
|
|
1266
|
+
return { hotels: [] };
|
|
1267
|
+
}
|
|
1268
|
+
};
|
|
1201
1269
|
const groupNamed = (deps, said) => {
|
|
1202
1270
|
const want = said.trim().toLowerCase();
|
|
1203
1271
|
return deps.backend
|
|
@@ -1279,6 +1347,21 @@ const paidLine = (deps, b) => {
|
|
|
1279
1347
|
return `All ${amount} has gone back to the card it was paid with${p.refundReason ? ` - ${p.refundReason}` : ''}.${test}`;
|
|
1280
1348
|
}
|
|
1281
1349
|
};
|
|
1350
|
+
/**
|
|
1351
|
+
* A stay sent to the hotel's own system whose answer is not in yet: being
|
|
1352
|
+
* confirmed - never counted as booked, and given no code until it is (总线程,
|
|
1353
|
+
* 2026-10-14). A hold or a demonstration's request IS the booking.
|
|
1354
|
+
*/
|
|
1355
|
+
const confirming = (deps, b) => holderOf(deps, b.supplier).caps.booking === 'pay-at-hotel' &&
|
|
1356
|
+
(b.status === 'requested' || b.status === 'unconfirmed');
|
|
1357
|
+
/** A booking for the card - with no code while it is being confirmed. */
|
|
1358
|
+
const madeForCard = (deps, b) => {
|
|
1359
|
+
const card = bookingForCard(b);
|
|
1360
|
+
if (!confirming(deps, b))
|
|
1361
|
+
return card;
|
|
1362
|
+
const { confirmationCode: _unconfirmed, ...rest } = card;
|
|
1363
|
+
return { ...rest, pending: true };
|
|
1364
|
+
};
|
|
1282
1365
|
const bookedLine = (deps, b) => {
|
|
1283
1366
|
const paid = paidLine(deps, b);
|
|
1284
1367
|
if (paid)
|
|
@@ -3162,6 +3245,23 @@ const answerTool = async (deps, name, rawArgs, ctx) => {
|
|
|
3162
3245
|
: 'Which nights? A room needs a check-in and a check-out date before it can go into the trip.',
|
|
3163
3246
|
};
|
|
3164
3247
|
}
|
|
3248
|
+
// ⚠️ TAKING A ROOM OUT NEEDS NO ROOM FOR SALE. The lookup below is of
|
|
3249
|
+
// what is for sale, so a room that had gone since it went into the trip -
|
|
3250
|
+
// the stay a booking could not make - could not be taken out of it
|
|
3251
|
+
// (2026-10-14). The line in the trip is the one asked about.
|
|
3252
|
+
if (args.rooms !== undefined && clampCount(args.rooms, MAX_ROOMS_PER_LINE) === 0) {
|
|
3253
|
+
const held = deps.trips
|
|
3254
|
+
.read(key)
|
|
3255
|
+
.find((l) => l.kind === 'room' &&
|
|
3256
|
+
l.hotelId === hotelId &&
|
|
3257
|
+
l.roomId === roomRef &&
|
|
3258
|
+
l.stay.checkIn === stay.checkIn &&
|
|
3259
|
+
l.stay.checkOut === stay.checkOut);
|
|
3260
|
+
if (held) {
|
|
3261
|
+
deps.trips.set(key, unset(held));
|
|
3262
|
+
return currentTrip(deps, key, identity(deps, ctx, key));
|
|
3263
|
+
}
|
|
3264
|
+
}
|
|
3165
3265
|
// The backend owns the calendar - it knows each hotel's own timezone, and a
|
|
3166
3266
|
// second opinion computed from this process's clock would disagree with it
|
|
3167
3267
|
// for eight hours a day.
|
|
@@ -3270,7 +3370,7 @@ const answerTool = async (deps, name, rawArgs, ctx) => {
|
|
|
3270
3370
|
const { requests, dropped } = readRequests(args);
|
|
3271
3371
|
try {
|
|
3272
3372
|
const shown = deps.trips.shown(key);
|
|
3273
|
-
const made = await deps.backend.requestBooking({ lines, ...(shown ? { shown } : {}) }, {
|
|
3373
|
+
const { booked: made, failed } = await deps.backend.requestBooking({ lines, ...(shown ? { shown } : {}) }, {
|
|
3274
3374
|
...guest,
|
|
3275
3375
|
...(str(args.phone) ? { phone: str(args.phone) } : {}),
|
|
3276
3376
|
...(requests?.note ? { notes: requests.note } : {}),
|
|
@@ -3284,16 +3384,26 @@ const answerTool = async (deps, name, rawArgs, ctx) => {
|
|
|
3284
3384
|
// only place an Identity is built; the guest's email still overrides,
|
|
3285
3385
|
// because a booking is found by the address it was made with.
|
|
3286
3386
|
{ ...identity(deps, ctx, key), email: guest.email }, requests);
|
|
3287
|
-
|
|
3387
|
+
// ⚠️ WHAT WAS BOOKED LEAVES THE TRIP; WHAT WAS NOT STAYS IN IT, add-ons
|
|
3388
|
+
// and all - to ask again, or to put another hotel in its place (robin,
|
|
3389
|
+
// 2026-10-14).
|
|
3390
|
+
const stillOut = (l) => failed.some((f) => inFailedStay(l, f));
|
|
3391
|
+
if (lines.length === inTrip.length && !lines.some(stillOut))
|
|
3288
3392
|
deps.trips.clear(key);
|
|
3289
3393
|
else
|
|
3290
3394
|
for (const l of lines)
|
|
3291
|
-
|
|
3395
|
+
if (!stillOut(l))
|
|
3396
|
+
deps.trips.set(key, unset(l));
|
|
3292
3397
|
// What is left is priced again, and that price is what the next
|
|
3293
|
-
//
|
|
3294
|
-
const rest = lines.length === inTrip.length
|
|
3398
|
+
// booking is held to (ShownQuote).
|
|
3399
|
+
const rest = lines.length === inTrip.length && !failed.length
|
|
3295
3400
|
? undefined
|
|
3296
3401
|
: await currentTrip(deps, key, identity(deps, ctx, key));
|
|
3402
|
+
const others = rest && lines.length !== inTrip.length;
|
|
3403
|
+
// What could be booked instead of each, found now (robin, 2026-10-14).
|
|
3404
|
+
const instead = await Promise.all(failed.map((f, i) => i < MAX_FAILED_WITH_ALTERNATIVES
|
|
3405
|
+
? alternativesFor(deps, f, lines, shown, identity(deps, ctx, key))
|
|
3406
|
+
: Promise.resolve({ hotels: [] })));
|
|
3297
3407
|
// ⚠️ REMEMBERED ONLY WHEN ASKED - see `rememberPreferences`. Said either
|
|
3298
3408
|
// way, so neither the guest nor the model assumes something was kept.
|
|
3299
3409
|
const signedIn = deps.members.read(key);
|
|
@@ -3343,9 +3453,53 @@ const answerTool = async (deps, name, rawArgs, ctx) => {
|
|
|
3343
3453
|
};
|
|
3344
3454
|
}));
|
|
3345
3455
|
const [first] = booked;
|
|
3346
|
-
if (!first)
|
|
3347
|
-
throw new Error('Nothing was booked.');
|
|
3348
3456
|
const several = booked.length > 1;
|
|
3457
|
+
const waiting = booked.filter(({ booking }) => confirming(deps, booking)).length;
|
|
3458
|
+
const notBooked = failed.map((f, i) => ({
|
|
3459
|
+
hotelId: f.hotelId,
|
|
3460
|
+
hotelName: f.hotelName,
|
|
3461
|
+
stay: f.stay,
|
|
3462
|
+
...(f.hourly ? { hourly: f.hourly } : {}),
|
|
3463
|
+
...(f.roomName ? { roomName: f.roomName } : {}),
|
|
3464
|
+
rooms: roomsIn(lines, f),
|
|
3465
|
+
// What to take out of the trip to put another hotel in its place.
|
|
3466
|
+
roomRefs: lines.flatMap((l) => (l.kind === 'room' && inFailedStay(l, f) ? [l.roomId] : [])),
|
|
3467
|
+
extraRefs: lines.flatMap((l) => l.kind === 'extra' && inFailedStay(l, f) ? [l.extraId] : []),
|
|
3468
|
+
why: f.why,
|
|
3469
|
+
...(f.code ? { code: f.code } : {}),
|
|
3470
|
+
...(f.args ? { args: f.args } : {}),
|
|
3471
|
+
...(instead[i]?.city ? { city: instead[i]?.city } : {}),
|
|
3472
|
+
alternatives: (instead[i]?.hotels ?? []).map((h) => ({
|
|
3473
|
+
hotelId: h.id,
|
|
3474
|
+
name: h.name,
|
|
3475
|
+
brand: h.brand,
|
|
3476
|
+
...(h.forStay
|
|
3477
|
+
? {
|
|
3478
|
+
total: formatMoney(h.forStay.total),
|
|
3479
|
+
room: h.forStay.room,
|
|
3480
|
+
freeCancellation: h.forStay.freeCancellation,
|
|
3481
|
+
}
|
|
3482
|
+
: {}),
|
|
3483
|
+
})),
|
|
3484
|
+
}));
|
|
3485
|
+
const failedText = notBooked.map((f) => `NOT BOOKED - ${f.hotelName} · ${stayWords(f.stay, f.hourly)}: ${f.why} It is still in the trip.${f.alternatives.length
|
|
3486
|
+
? ` Bookable instead for the same nights: ${f.alternatives.map((a) => `${a.name}${a.room ? ` (${a.room}` : ''}${a.total ? `${a.room ? ', ' : ' ('}${a.total} for the stay)` : a.room ? ')' : ''} [hotel \`${a.hotelId}\`]`).join('; ')}.`
|
|
3487
|
+
: ''} The guest can ask again, take another hotel, or leave it.`);
|
|
3488
|
+
if (!first) {
|
|
3489
|
+
// Nothing booked at all: every stay said, each with its way on.
|
|
3490
|
+
return {
|
|
3491
|
+
structured: {
|
|
3492
|
+
view: 'booking',
|
|
3493
|
+
...common(deps, key),
|
|
3494
|
+
paid: false,
|
|
3495
|
+
failed: notBooked,
|
|
3496
|
+
},
|
|
3497
|
+
text: [
|
|
3498
|
+
`Nothing was booked: ${notBooked.length === 1 ? 'the stay' : `none of the ${notBooked.length} stays`} could be.`,
|
|
3499
|
+
...failedText,
|
|
3500
|
+
].join('\n'),
|
|
3501
|
+
};
|
|
3502
|
+
}
|
|
3349
3503
|
return {
|
|
3350
3504
|
structured: {
|
|
3351
3505
|
// ⚠️ 'booking' EVEN WHEN IT IS AWAITING PAYMENT: the card draws a view
|
|
@@ -3355,13 +3509,13 @@ const answerTool = async (deps, name, rawArgs, ctx) => {
|
|
|
3355
3509
|
// view with it.
|
|
3356
3510
|
view: 'booking',
|
|
3357
3511
|
...common(deps, key),
|
|
3358
|
-
booking:
|
|
3512
|
+
booking: madeForCard(deps, first.booking),
|
|
3359
3513
|
// ⚠️ ONE PER STAY - see `requestBooking`. The card draws each with its
|
|
3360
3514
|
// own code; `booking` stays the first, for a host that reads one.
|
|
3361
3515
|
...(several
|
|
3362
3516
|
? {
|
|
3363
3517
|
bookings: booked.map(({ booking, asked }) => ({
|
|
3364
|
-
...
|
|
3518
|
+
...madeForCard(deps, booking),
|
|
3365
3519
|
total: formatMoney(booking.quote.total),
|
|
3366
3520
|
...(asked.length ? { requested: asked } : {}),
|
|
3367
3521
|
// Each its own warning colour - it rode on the first alone.
|
|
@@ -3379,18 +3533,25 @@ const answerTool = async (deps, name, rawArgs, ctx) => {
|
|
|
3379
3533
|
...(first.booking.requests?.kinds.includes('accessible-room')
|
|
3380
3534
|
? { accessibility: true }
|
|
3381
3535
|
: {}),
|
|
3536
|
+
...(notBooked.length ? { failed: notBooked } : {}),
|
|
3382
3537
|
},
|
|
3383
3538
|
text: [
|
|
3539
|
+
// ⚠️ BOOKED IS CONFIRMED: a stay still being confirmed is said as
|
|
3540
|
+
// that, and not counted (总线程, 2026-10-14).
|
|
3541
|
+
notBooked.length || (booked.length > 1 && waiting)
|
|
3542
|
+
? `${booked.length - waiting} of ${booked.length + notBooked.length} stays booked${waiting ? `, ${waiting} being confirmed by the hotel - no code until it is` : ''}${notBooked.length ? `; ${notBooked.length === 1 ? 'one' : notBooked.length} could not be, said below` : ''} - what was booked stays booked.`
|
|
3543
|
+
: '',
|
|
3384
3544
|
several
|
|
3385
3545
|
? `${booked.length} bookings, one for each stay - each hotel keeps its own nights under its own code, and each is ${holderOf(deps, first.booking.supplier).caps.changes ? 'changed or cancelled' : 'cancelled'} on its own.`
|
|
3386
3546
|
: '',
|
|
3387
3547
|
...booked.flatMap(({ booking, asked }) => [
|
|
3388
|
-
`**${booking.confirmationCode}** · ${booking.hotelName} · ${stayWords(booking.stay, booking.hourly)}`,
|
|
3548
|
+
`**${confirming(deps, booking) ? 'Being confirmed' : booking.confirmationCode}** · ${booking.hotelName} · ${stayWords(booking.stay, booking.hourly)}`,
|
|
3389
3549
|
bookedLine(deps, booking),
|
|
3390
3550
|
...asked,
|
|
3391
3551
|
]),
|
|
3552
|
+
...failedText,
|
|
3392
3553
|
remembered?.line ?? '',
|
|
3393
|
-
|
|
3554
|
+
others ? `The trip's other stays are still in it, not booked:\n${rest?.text ?? ''}` : '',
|
|
3394
3555
|
deps.vocab.demo ? DEMO_NOTE : '',
|
|
3395
3556
|
]
|
|
3396
3557
|
.filter(Boolean)
|