@fortrip/agent-contract 0.0.0-stage → 1.0.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 FortripEngineering
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,39 @@
1
- # Temporary Holding Version
1
+ # @fortrip/agent-contract
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ The contract of Fortrip's agent API, `/api/agent/v1`: the limits, endpoint
4
+ paths, error reasons and field rules that the Fortrip CLI (`fortrip`), the
5
+ hosted MCP server and the store listings all share, so they cannot drift
6
+ apart.
7
+
8
+ It has one peer dependency, `zod` (`^4.4.0`), and runs anywhere
9
+ ES2023 does. The package version follows the API: `1.x` serves
10
+ `/api/agent/v1`.
11
+
12
+ ```ts
13
+ import { API, LIMITS, containsCardNumber } from "@fortrip/agent-contract";
14
+
15
+ API.search; // "/api/agent/v1/hotels/search"
16
+ LIMITS.rooms; // 8
17
+ ```
18
+
19
+ It exports:
20
+
21
+ - `API`, `LIMITS`, `BOARD_CODES`, `CURRENCIES`, `SEARCH_SORTS` and
22
+ `ERROR_REASONS`: paths, limits and error reasons;
23
+ - `searchHotelsInput`, `getHotelInput`, `getRatesInput` and
24
+ `createBookingInput`, the zod schemas of the requests, and `bookingResult`,
25
+ `meResult` and `authConfig`, those of responses;
26
+ - `COMMANDS` and `GLOBAL_FLAGS`, the CLI's commands and flags, and `TOOLS`,
27
+ the MCP tools;
28
+ - `renderGuide(surface, topic)`, the agent guide as Markdown for `"mcp"`,
29
+ `"claude-skill"` or `"cli"`;
30
+ - field rules such as `containsCardNumber`, the guest-name rule and the date
31
+ helpers, and `CONTRACT_VERSION`.
32
+
33
+ Browser code that only needs the guest-name rule (`GUEST_NAME_PATTERN`,
34
+ `GUEST_NAME_TITLE`, `GUEST_NAME_MESSAGE`, `guestNameRe`, `normalizeGuestName`)
35
+ can import it from `@fortrip/agent-contract/guest-name`. That module imports
36
+ no package, so it does not pull in `zod` or the schemas the root entry builds
37
+ at load. The root entry exports the same names.
38
+
39
+ MIT licensed. Source: <https://github.com/FortripEngineering/fortrip/tree/main/packages/contract>.
@@ -0,0 +1,18 @@
1
+ // src/guest-name.ts
2
+ var CURLY_APOSTROPHES = /[‘’]/g;
3
+ var CORE = "[A-Za-z][A-Za-z '\u2018\u2019.\\-]*";
4
+ var GUEST_NAME_PATTERN = `\\s*|\\s*${CORE}\\s*`;
5
+ var guestNameRe = new RegExp(`^${CORE}$`);
6
+ var GUEST_NAME_TITLE = "Latin letters as in the passport \u2014 start with a letter; spaces, hyphens, apostrophes and periods are fine after that. Transliterate accents (M\xFCller \u2192 Mueller).";
7
+ var GUEST_NAME_MESSAGE = "Guest names must use Latin letters as on the passport (e.g. ZHANG, Wei; M\xFCller \u2192 Mueller).";
8
+ function normalizeGuestName(value) {
9
+ return value.replace(CURLY_APOSTROPHES, "'").replace(/\s+/g, " ").trim();
10
+ }
11
+
12
+ export {
13
+ GUEST_NAME_PATTERN,
14
+ guestNameRe,
15
+ GUEST_NAME_TITLE,
16
+ GUEST_NAME_MESSAGE,
17
+ normalizeGuestName
18
+ };
@@ -0,0 +1,71 @@
1
+ /**
2
+ * What a guest name may contain.
3
+ *
4
+ * The old rule was `[A-Za-z\s-]+`, carrying the comment "the supplier only
5
+ * accepts Latin guest names". The supplier's booking endpoint actually types
6
+ * `firstName` / `lastName` as bare strings with NO character constraint —
7
+ * Latin is a convention its examples follow, not a documented rule. Evidence
8
+ * the old rule was never verified: `contactName`, which travels the same
9
+ * request, has never been pattern-checked at all.
10
+ *
11
+ * So the apostrophe and the period are back: `O'Brien`, `D'Angelo`,
12
+ * `St. John`, `Jr.` are ordinary passport spellings that we were rejecting
13
+ * before the supplier ever saw them. Curly apostrophes come in from phone
14
+ * keyboards and normalize to straight ones rather than being refused.
15
+ *
16
+ * Still refused: CJK, other non-Latin scripts, and accented Latin (José,
17
+ * Müller). Be clear about whose rule this is — **ours**. The supplier accepts
18
+ * CJK names outright: `伟`/`张` booked successfully against its sandbox on
19
+ * 2026-08-07. The old comment here claimed "the supplier only accepts Latin
20
+ * guest names" and that was simply false, in whole, not just in the
21
+ * apostrophe it also got wrong.
22
+ *
23
+ * We keep the restriction for a reason the supplier API can't speak to: the
24
+ * property reads these names at check-in, most of them can't render CJK, and
25
+ * passports carry an ASCII transliteration in the machine-readable zone (JOSE,
26
+ * MUELLER) — that is the spelling a front desk matches. So the model
27
+ * transliterates and the form asks the user to. If that trade is ever
28
+ * revisited, revisit it as a product decision about hotels, not as "restoring
29
+ * supplier parity" — parity already permits CJK.
30
+ */
31
+ /**
32
+ * HTML `pattern` body — implicitly anchored by the browser, and applied to the
33
+ * RAW field value, which is why it is looser than `guestNameRe` rather than
34
+ * identical to it.
35
+ *
36
+ * Two things force the slack. Browsers skip `pattern` only when a value is the
37
+ * empty string, so `" "` is validated — and a `CORE`-only pattern would
38
+ * refuse it. That breaks the reveal affordance outright: a user who opens a
39
+ * row, types a name, then deletes it leaving one stray space has no Remove
40
+ * button and no way forward, stranded on the payment screen by the very
41
+ * mechanism that was supposed to be the undo. Allowing an all-blank value
42
+ * hands that row to `collectGuestsFromForm`, which drops it — the undo works.
43
+ * Surrounding whitespace is allowed for the same reason a pasted `" Wei"`
44
+ * should not be rejected in the browser when `normalizeGuestName` would trim it
45
+ * server-side anyway (the old `[A-Za-z\s\-]+` accepted it; refusing it here
46
+ * would be a regression).
47
+ *
48
+ * The cost is deliberate: a required lead row holding only spaces passes the
49
+ * browser and is caught server-side ("Room 1 needs at least one guest name.").
50
+ * That trade favours the common case — stray whitespace while erasing — over
51
+ * the rare one, someone typing nothing but spaces into a required field.
52
+ */
53
+ declare const GUEST_NAME_PATTERN = "\\s*|\\s*[A-Za-z][A-Za-z '\u2018\u2019.\\-]*\\s*";
54
+ /** Server-side check, applied AFTER `normalizeGuestName`, so it sees no
55
+ * surrounding whitespace and can be strict. Deliberately not derived from
56
+ * `GUEST_NAME_PATTERN` — they validate different values at different stages.
57
+ * `guest-name.test.ts` pins the relationship that actually matters: anything
58
+ * the browser accepts is, once normalized, either empty or accepted here. */
59
+ declare const guestNameRe: RegExp;
60
+ /** Shown as the `title` on every guest-name input, so a rejected value
61
+ * explains itself. Names the first-character rule because the pattern enforces
62
+ * it and a Dutch `'t Hooft` would otherwise be refused with no explanation. */
63
+ declare const GUEST_NAME_TITLE = "Latin letters as in the passport \u2014 start with a letter; spaces, hyphens, apostrophes and periods are fine after that. Transliterate accents (M\u00FCller \u2192 Mueller).";
64
+ /** The server's answer to a name `guestNameRe` refuses: what to type
65
+ * instead, for a caller that can't see the form's `title`. */
66
+ declare const GUEST_NAME_MESSAGE = "Guest names must use Latin letters as on the passport (e.g. ZHANG, Wei; M\u00FCller \u2192 Mueller).";
67
+ /** Collapse whitespace and straighten curly apostrophes. Applied server-side so
68
+ * both booking surfaces store one spelling of the same name. */
69
+ declare function normalizeGuestName(value: string): string;
70
+
71
+ export { GUEST_NAME_MESSAGE, GUEST_NAME_PATTERN, GUEST_NAME_TITLE, guestNameRe, normalizeGuestName };
@@ -0,0 +1,14 @@
1
+ import {
2
+ GUEST_NAME_MESSAGE,
3
+ GUEST_NAME_PATTERN,
4
+ GUEST_NAME_TITLE,
5
+ guestNameRe,
6
+ normalizeGuestName
7
+ } from "./chunk-HR6L3RDB.js";
8
+ export {
9
+ GUEST_NAME_MESSAGE,
10
+ GUEST_NAME_PATTERN,
11
+ GUEST_NAME_TITLE,
12
+ guestNameRe,
13
+ normalizeGuestName
14
+ };
@@ -0,0 +1,422 @@
1
+ import { z } from 'zod';
2
+ export { GUEST_NAME_MESSAGE, GUEST_NAME_PATTERN, GUEST_NAME_TITLE, guestNameRe, normalizeGuestName } from './guest-name.js';
3
+
4
+ /** Shown on a booking field that seems to hold a payment card number. */
5
+ declare const CARD_NUMBER_MESSAGE = "This looks like a payment card number. Payment happens only on the payment page; if it isn't a card number, rewrite it.";
6
+ /**
7
+ * Whether `text` holds a payment card number in one of the two ways people
8
+ * type one: 13–19 digits in a single run, or groups of four digits (the last
9
+ * 1–4) split by one space or one hyphen. It must start like a card (Mir
10
+ * 2200–2204, Mastercard 2221–2720, 3, 4, 5, 6, RuPay 81/82) and pass the
11
+ * Luhn check. Full-width digits count (NFKC).
12
+ *
13
+ * Deliberately narrow, because every looser rule flagged ordinary text: no
14
+ * joining across `/`, `.`, `_`, line breaks or double spaces (two phone
15
+ * numbers, a list of room numbers, one number per line), and no Amex-style
16
+ * 4-6-5 grouping (missed; a 15-digit Amex in one run is still caught). A
17
+ * card may start at any digit group, so a room number or date in front of it
18
+ * can't hide it. Right after `+` come a phone number's country code and
19
+ * number, so digits there count only from 16 on, too many for a phone.
20
+ *
21
+ * `minDigits` is the shortest card looked for. A phone field passes 16, so a
22
+ * phone isn't mistaken for a card there, at the cost of missing cards of
23
+ * 13–15 digits (Amex).
24
+ */
25
+ declare function containsCardNumber(text: string, minDigits?: number): boolean;
26
+
27
+ type ToolName = "get_planning_guide" | "search_hotels" | "get_hotel" | "get_rates" | "create_booking";
28
+ interface ToolSpec {
29
+ name: ToolName;
30
+ title: string;
31
+ description: string;
32
+ annotations: {
33
+ readOnlyHint: boolean;
34
+ idempotentHint: boolean;
35
+ openWorldHint: boolean;
36
+ destructiveHint: boolean;
37
+ };
38
+ /** What the tool takes; `null` for a tool without arguments. */
39
+ input: z.ZodType | null;
40
+ /** What it answers with, when that is a schema; `null` otherwise. */
41
+ output: z.ZodType | null;
42
+ }
43
+ /** `getRatesInput` as the MCP tool takes it: the same, with its own default
44
+ * for `maxRoomsPerHotel`. */
45
+ declare const MCP_RATES_INPUT: z.ZodObject<{
46
+ hotelIds: z.ZodArray<z.ZodNumber>;
47
+ checkIn: z.ZodString;
48
+ checkOut: z.ZodString;
49
+ rooms: z.ZodNumber;
50
+ adults: z.ZodNumber;
51
+ childrenAges: z.ZodOptional<z.ZodArray<z.ZodNumber>>;
52
+ nationality: z.ZodOptional<z.ZodString>;
53
+ currency: z.ZodDefault<z.ZodEnum<{
54
+ CNY: "CNY";
55
+ USD: "USD";
56
+ }>>;
57
+ bookableOnly: z.ZodDefault<z.ZodBoolean>;
58
+ boardCodes: z.ZodOptional<z.ZodArray<z.ZodEnum<{
59
+ RO: "RO";
60
+ BB: "BB";
61
+ HB: "HB";
62
+ FB: "FB";
63
+ BL: "BL";
64
+ BD: "BD";
65
+ HBBL: "HBBL";
66
+ HBLD: "HBLD";
67
+ AI: "AI";
68
+ }>>>;
69
+ freeCancellation: z.ZodOptional<z.ZodBoolean>;
70
+ maxRoomsPerHotel: z.ZodDefault<z.ZodNumber>;
71
+ }, z.core.$strict>;
72
+ /** Rides on every create_booking answer: some clients never show the model
73
+ * the server instructions. */
74
+ declare const BOOKING_NEXT_STEP = "Show the user amount, currency, the stay (hotel, room, dates, rooms, guests per room) and every item in terms (meals, cancellation, confirmation, pay-at-hotel fees), then end your reply by asking them to confirm. Only after they say yes, give them paymentUrl exactly as returned.";
75
+ /** create_booking's answer over MCP: the booking result plus `nextStep`. */
76
+ declare const MCP_BOOKING_RESULT: z.ZodObject<{
77
+ agentOrderId: z.ZodString;
78
+ paymentUrl: z.ZodString;
79
+ amount: z.ZodNullable<z.ZodNumber>;
80
+ currency: z.ZodString;
81
+ orderUrl: z.ZodString;
82
+ statusUrl: z.ZodOptional<z.ZodString>;
83
+ stay: z.ZodObject<{
84
+ hotelId: z.ZodNumber;
85
+ hotelName: z.ZodNullable<z.ZodString>;
86
+ roomName: z.ZodNullable<z.ZodString>;
87
+ checkIn: z.ZodString;
88
+ checkOut: z.ZodString;
89
+ nights: z.ZodNumber;
90
+ rooms: z.ZodNumber;
91
+ adultsPerRoom: z.ZodNumber;
92
+ childrenAgesPerRoom: z.ZodArray<z.ZodNumber>;
93
+ }, z.core.$strip>;
94
+ terms: z.ZodNullable<z.ZodObject<{
95
+ boardCode: z.ZodString;
96
+ boardCount: z.ZodNumber;
97
+ refundable: z.ZodBoolean;
98
+ freeCancelUntil: z.ZodNullable<z.ZodString>;
99
+ cancellation: z.ZodArray<z.ZodObject<{
100
+ amount: z.ZodNumber;
101
+ from: z.ZodString;
102
+ }, z.core.$strip>>;
103
+ isInstantConfirm: z.ZodBoolean;
104
+ confirmByMins: z.ZodNullable<z.ZodNumber>;
105
+ payAtHotelFees: z.ZodArray<z.ZodObject<{
106
+ type: z.ZodString;
107
+ isMandatory: z.ZodBoolean;
108
+ amount: z.ZodNumber;
109
+ currency: z.ZodString;
110
+ }, z.core.$strip>>;
111
+ }, z.core.$strip>>;
112
+ nextStep: z.ZodString;
113
+ }, z.core.$strip>;
114
+ declare const TOOLS: readonly ToolSpec[];
115
+
116
+ /** A flag that sets one field of a tool's input. */
117
+ interface FlagSpec {
118
+ /** The long name without dashes: `check-in` for `--check-in`. */
119
+ flag: string;
120
+ /** The one-letter name without its dash: `q` for `-q`. */
121
+ short?: string;
122
+ /** The input field it sets; dots step into an object (`near.lat`). */
123
+ field: string;
124
+ /**
125
+ * How the CLI reads the value. `id` is digits only, a positive whole
126
+ * number. A `boolean` flag sets its field to true, except where the field
127
+ * defaults to true (`bookableOnly`): `--include-unbookable` sets it to
128
+ * false. A `string` flag whose field is an object (`near`, read as
129
+ * `lat,lng,radiusKm`) or a list of objects (`guests`, read as
130
+ * `First Last[:roomNo]`) is one value the CLI parses into that shape.
131
+ */
132
+ type: "string" | "number" | "boolean" | "id";
133
+ /** Given once per item; its field is a list. */
134
+ multiple?: boolean;
135
+ /**
136
+ * What the value looks like in help, as it follows the flag name: `<n>`,
137
+ * `YYYY-MM-DD`, `CNY|USD` (an enum's values), `"First Last[:roomNo]"`.
138
+ * Every flag that takes a value has one.
139
+ */
140
+ placeholder?: string;
141
+ description: string;
142
+ }
143
+ /** An option of the CLI itself that no tool takes: not a field of any input. */
144
+ interface OptionSpec {
145
+ /** The long name without dashes. */
146
+ flag: string;
147
+ /** The one-letter name without its dash. */
148
+ short?: string;
149
+ type: "string" | "boolean";
150
+ /** As on `FlagSpec`; every option that takes a value has one. */
151
+ placeholder?: string;
152
+ description: string;
153
+ }
154
+ interface CommandSpec {
155
+ name: "search" | "hotel" | "rates" | "book" | "login" | "logout" | "whoami" | "guide" | "mcp";
156
+ summary: string;
157
+ /** The MCP tool the command mirrors; `null` for one that is the CLI's own. */
158
+ tool: ToolName | null;
159
+ /** The tool's input fields the command sets. */
160
+ flags: readonly FlagSpec[];
161
+ /** Named after the input field they set, except `guide`'s `topic`. */
162
+ positionals?: readonly string[];
163
+ /** Options that are not input fields, such as `--body`. */
164
+ options?: readonly OptionSpec[];
165
+ }
166
+ declare const COMMANDS: readonly CommandSpec[];
167
+ /** Options of the CLI itself: `--help` after any command, `--version` on its own. */
168
+ declare const GLOBAL_FLAGS: readonly OptionSpec[];
169
+
170
+ /** True only for a real Gregorian calendar date in canonical yyyy-MM-dd form. */
171
+ declare function isIsoDate(value: string): boolean;
172
+ /** Signed number of UTC calendar days in the half-open range [start, end). */
173
+ declare function countIsoDateDays(start: string, end: string): number;
174
+ declare function addIsoDateDays(date: string, days: number): string;
175
+
176
+ /** An object that names the keys it doesn't know instead of dropping them. */
177
+ declare function strictInput<T extends z.core.$ZodLooseShape>(shape: T): z.ZodObject<{ -readonly [P in keyof T]: T[P]; }, z.core.$strict>;
178
+ declare function wholeNumber(name: string, min: number, max: number): z.ZodNumber;
179
+ declare function positiveId(name: string): z.ZodNumber;
180
+ declare function isoDate(name: string): z.ZodString;
181
+ declare function nonEmpty(name: string): z.ZodString;
182
+ declare function text(name: string, min: number, max: number): z.ZodString;
183
+ declare function oneOf<const T extends readonly [string, ...string[]]>(name: string, values: T): z.ZodEnum<{ [k_1 in T[number]]: k_1; } extends infer T_1 ? { [k in keyof T_1]: T_1[k]; } : never>;
184
+ declare function flag(name: string): z.ZodBoolean;
185
+ /** Each child's age in one room, up to `max` children. */
186
+ declare function childrenAges(max: number): z.ZodArray<z.ZodNumber>;
187
+
188
+ type GuideSurface = "mcp" | "claude-skill" | "cli";
189
+ type GuideTopic = "main" | "planning";
190
+ /** The guide on `topic` as `surface` states it, in Markdown. */
191
+ declare function renderGuide(surface: GuideSurface, topic: GuideTopic): string;
192
+
193
+ /** `POST /api/agent/v1/hotels/search` and the MCP `search_hotels` arguments. */
194
+ declare const searchHotelsInput: z.ZodObject<{
195
+ query: z.ZodOptional<z.ZodString>;
196
+ cityCode: z.ZodOptional<z.ZodNumber>;
197
+ near: z.ZodOptional<z.ZodObject<{
198
+ lat: z.ZodNumber;
199
+ lng: z.ZodNumber;
200
+ radiusKm: z.ZodNumber;
201
+ }, z.core.$strict>>;
202
+ starMin: z.ZodOptional<z.ZodNumber>;
203
+ starMax: z.ZodOptional<z.ZodNumber>;
204
+ facilities: z.ZodOptional<z.ZodPipe<z.ZodArray<z.ZodString>, z.ZodTransform<string[], string[]>>>;
205
+ checkIn: z.ZodString;
206
+ checkOut: z.ZodString;
207
+ currency: z.ZodDefault<z.ZodEnum<{
208
+ CNY: "CNY";
209
+ USD: "USD";
210
+ }>>;
211
+ priceMin: z.ZodOptional<z.ZodNumber>;
212
+ priceMax: z.ZodOptional<z.ZodNumber>;
213
+ sortBy: z.ZodDefault<z.ZodEnum<{
214
+ recommended: "recommended";
215
+ "price-asc": "price-asc";
216
+ "price-desc": "price-desc";
217
+ "star-desc": "star-desc";
218
+ distance: "distance";
219
+ }>>;
220
+ page: z.ZodDefault<z.ZodNumber>;
221
+ pageSize: z.ZodDefault<z.ZodNumber>;
222
+ }, z.core.$strict>;
223
+ /**
224
+ * `GET /api/agent/v1/hotels/{id}` and the MCP `get_hotel` arguments. Not
225
+ * strict: it has always dropped a key it doesn't know. `hotelId` is coerced,
226
+ * so the numeric string a URL path gives parses as the number.
227
+ */
228
+ declare const getHotelInput: z.ZodObject<{
229
+ hotelId: z.ZodCoercedNumber<unknown>;
230
+ includeRooms: z.ZodOptional<z.ZodBoolean>;
231
+ }, z.core.$strip>;
232
+ /** `POST /api/agent/v1/hotels/rates` and the MCP `get_rates` arguments. */
233
+ declare const getRatesInput: z.ZodObject<{
234
+ hotelIds: z.ZodArray<z.ZodNumber>;
235
+ checkIn: z.ZodString;
236
+ checkOut: z.ZodString;
237
+ rooms: z.ZodNumber;
238
+ adults: z.ZodNumber;
239
+ childrenAges: z.ZodOptional<z.ZodArray<z.ZodNumber>>;
240
+ nationality: z.ZodOptional<z.ZodString>;
241
+ currency: z.ZodDefault<z.ZodEnum<{
242
+ CNY: "CNY";
243
+ USD: "USD";
244
+ }>>;
245
+ bookableOnly: z.ZodDefault<z.ZodBoolean>;
246
+ boardCodes: z.ZodOptional<z.ZodArray<z.ZodEnum<{
247
+ RO: "RO";
248
+ BB: "BB";
249
+ HB: "HB";
250
+ FB: "FB";
251
+ BL: "BL";
252
+ BD: "BD";
253
+ HBBL: "HBBL";
254
+ HBLD: "HBLD";
255
+ AI: "AI";
256
+ }>>>;
257
+ freeCancellation: z.ZodOptional<z.ZodBoolean>;
258
+ maxRoomsPerHotel: z.ZodDefault<z.ZodNumber>;
259
+ }, z.core.$strict>;
260
+ /**
261
+ * `POST /api/agent/v1/hotels/bookings` and the MCP `create_booking`
262
+ * arguments. The booking action validates guest names (Latin letters) and
263
+ * the room assignment again. Occupancy is per room, every room the same.
264
+ * Unknown keys are refused, not dropped: a misspelled `children` would
265
+ * otherwise book the default `childrenAges: []`, a different party.
266
+ */
267
+ declare const createBookingInput: z.ZodObject<{
268
+ hotelId: z.ZodNumber;
269
+ roomId: z.ZodString;
270
+ ratePlanCode: z.ZodString;
271
+ checkIn: z.ZodString;
272
+ checkOut: z.ZodString;
273
+ rooms: z.ZodNumber;
274
+ adults: z.ZodNumber;
275
+ childrenAges: z.ZodDefault<z.ZodArray<z.ZodNumber>>;
276
+ guests: z.ZodArray<z.ZodObject<{
277
+ firstName: z.ZodString;
278
+ lastName: z.ZodString;
279
+ roomNo: z.ZodCoercedNumber<unknown>;
280
+ }, z.core.$strict>>;
281
+ contactName: z.ZodString;
282
+ contactPhone: z.ZodString;
283
+ contactEmail: z.ZodOptional<z.ZodString>;
284
+ specialRequests: z.ZodOptional<z.ZodString>;
285
+ }, z.core.$strict>;
286
+ /** What a caller sends (defaults optional), and what a handler gets once parsed. */
287
+ type SearchHotelsInput = z.input<typeof searchHotelsInput>;
288
+ type SearchHotelsParsed = z.output<typeof searchHotelsInput>;
289
+ type GetHotelInput = z.input<typeof getHotelInput>;
290
+ type GetHotelParsed = z.output<typeof getHotelInput>;
291
+ type GetRatesInput = z.input<typeof getRatesInput>;
292
+ type GetRatesParsed = z.output<typeof getRatesInput>;
293
+ type CreateBookingInput = z.input<typeof createBookingInput>;
294
+ type CreateBookingParsed = z.output<typeof createBookingInput>;
295
+
296
+ /** The ceilings and floors every agent surface enforces and documents. */
297
+ declare const LIMITS: {
298
+ readonly rooms: 8;
299
+ readonly adultsPerRoom: 8;
300
+ readonly childrenPerRoom: 8;
301
+ readonly childAgeMax: 17;
302
+ readonly nights: 15;
303
+ readonly ratesHotels: 15;
304
+ readonly facilities: 10;
305
+ readonly queryLength: 128;
306
+ readonly nearRadiusKm: 200;
307
+ readonly page: 100;
308
+ readonly pageSize: 50;
309
+ readonly guestName: 60;
310
+ readonly contactName: 120;
311
+ readonly contactPhoneMin: 3;
312
+ readonly contactPhoneMax: 40;
313
+ readonly specialRequests: 500;
314
+ readonly maxRoomsPerHotel: 200;
315
+ };
316
+ /** Board (meal plan) codes a rate can carry and a search can ask for. */
317
+ declare const BOARD_CODES: readonly ["RO", "BB", "HB", "FB", "BL", "BD", "HBBL", "HBLD", "AI"];
318
+ /** Currencies prices can be shown and paid in. */
319
+ declare const CURRENCIES: readonly ["CNY", "USD"];
320
+ /** Orders a hotel search can be sorted in. */
321
+ declare const SEARCH_SORTS: readonly ["recommended", "price-asc", "price-desc", "star-desc", "distance"];
322
+ /** Where a person goes when an agent gets stuck. */
323
+ declare const SUPPORT_EMAIL = "info@fortrip.ai";
324
+
325
+ /**
326
+ * What `create_booking` / `POST /api/agent/v1/hotels/bookings` answers with.
327
+ * Its JSON Schema allows no other keys, so a caller that parses its answer
328
+ * through this schema drops a field the server adds until it is added here,
329
+ * rather than failing a strict client's validation. The MCP tool adds a
330
+ * `nextStep` to it; the HTTP answer has none.
331
+ */
332
+ declare const bookingResult: z.ZodObject<{
333
+ agentOrderId: z.ZodString;
334
+ paymentUrl: z.ZodString;
335
+ amount: z.ZodNullable<z.ZodNumber>;
336
+ currency: z.ZodString;
337
+ orderUrl: z.ZodString;
338
+ statusUrl: z.ZodOptional<z.ZodString>;
339
+ stay: z.ZodObject<{
340
+ hotelId: z.ZodNumber;
341
+ hotelName: z.ZodNullable<z.ZodString>;
342
+ roomName: z.ZodNullable<z.ZodString>;
343
+ checkIn: z.ZodString;
344
+ checkOut: z.ZodString;
345
+ nights: z.ZodNumber;
346
+ rooms: z.ZodNumber;
347
+ adultsPerRoom: z.ZodNumber;
348
+ childrenAgesPerRoom: z.ZodArray<z.ZodNumber>;
349
+ }, z.core.$strip>;
350
+ terms: z.ZodNullable<z.ZodObject<{
351
+ boardCode: z.ZodString;
352
+ boardCount: z.ZodNumber;
353
+ refundable: z.ZodBoolean;
354
+ freeCancelUntil: z.ZodNullable<z.ZodString>;
355
+ cancellation: z.ZodArray<z.ZodObject<{
356
+ amount: z.ZodNumber;
357
+ from: z.ZodString;
358
+ }, z.core.$strip>>;
359
+ isInstantConfirm: z.ZodBoolean;
360
+ confirmByMins: z.ZodNullable<z.ZodNumber>;
361
+ payAtHotelFees: z.ZodArray<z.ZodObject<{
362
+ type: z.ZodString;
363
+ isMandatory: z.ZodBoolean;
364
+ amount: z.ZodNumber;
365
+ currency: z.ZodString;
366
+ }, z.core.$strip>>;
367
+ }, z.core.$strip>>;
368
+ }, z.core.$strip>;
369
+ type BookingResult = z.infer<typeof bookingResult>;
370
+ /** `GET /api/agent/v1/me`: the signed-in user. */
371
+ declare const meResult: z.ZodObject<{
372
+ userId: z.ZodString;
373
+ email: z.ZodNullable<z.ZodString>;
374
+ name: z.ZodNullable<z.ZodString>;
375
+ }, z.core.$strip>;
376
+ type MeResult = z.infer<typeof meResult>;
377
+ /**
378
+ * `GET /api/agent/v1/auth/config` (public): where and how the CLI signs in
379
+ * with the device flow. `scopes` is one space-separated string.
380
+ */
381
+ declare const authConfig: z.ZodObject<{
382
+ issuer: z.ZodString;
383
+ clientId: z.ZodString;
384
+ scopes: z.ZodString;
385
+ deviceAuthorizationEndpoint: z.ZodString;
386
+ tokenEndpoint: z.ZodString;
387
+ revocationEndpoint: z.ZodString;
388
+ }, z.core.$strip>;
389
+ type AuthConfig = z.infer<typeof authConfig>;
390
+
391
+ /** Where the agent API lives. Paths only: the origin is the caller's to choose. */
392
+ declare const API: {
393
+ readonly me: "/api/agent/v1/me";
394
+ readonly authConfig: "/api/agent/v1/auth/config";
395
+ readonly search: "/api/agent/v1/hotels/search";
396
+ readonly rates: "/api/agent/v1/hotels/rates";
397
+ readonly bookings: "/api/agent/v1/hotels/bookings";
398
+ readonly mcp: "/api/mcp";
399
+ readonly hotel: (id: number, includeRooms?: boolean) => string;
400
+ };
401
+
402
+ declare const reasonStatuses: {
403
+ readonly already_booked: readonly [409];
404
+ readonly price_or_terms_changed: readonly [409];
405
+ readonly rate_unavailable: readonly [422];
406
+ readonly refused: readonly [422];
407
+ readonly supplier_unavailable: readonly [503, 504];
408
+ };
409
+ type ErrorReason = keyof typeof reasonStatuses;
410
+ /**
411
+ * The `reason` an agent error can carry, each with the HTTP statuses it is
412
+ * sent with. Typed as plain number lists so `ERROR_REASONS[reason].includes(status)`
413
+ * type-checks for a status read off a response.
414
+ */
415
+ declare const ERROR_REASONS: {
416
+ readonly [R in ErrorReason]: readonly number[];
417
+ };
418
+
419
+ /** The contract's version, from package.json, the one place a release bumps it. */
420
+ declare const CONTRACT_VERSION: string;
421
+
422
+ export { API, type AuthConfig, BOARD_CODES, BOOKING_NEXT_STEP, type BookingResult, CARD_NUMBER_MESSAGE, COMMANDS, CONTRACT_VERSION, CURRENCIES, type CommandSpec, type CreateBookingInput, type CreateBookingParsed, ERROR_REASONS, type ErrorReason, type FlagSpec, GLOBAL_FLAGS, type GetHotelInput, type GetHotelParsed, type GetRatesInput, type GetRatesParsed, type GuideSurface, type GuideTopic, LIMITS, MCP_BOOKING_RESULT, MCP_RATES_INPUT, type MeResult, type OptionSpec, SEARCH_SORTS, SUPPORT_EMAIL, type SearchHotelsInput, type SearchHotelsParsed, TOOLS, type ToolName, type ToolSpec, addIsoDateDays, authConfig, bookingResult, childrenAges, containsCardNumber, countIsoDateDays, createBookingInput, flag, getHotelInput, getRatesInput, isIsoDate, isoDate, meResult, nonEmpty, oneOf, positiveId, renderGuide, searchHotelsInput, strictInput, text, wholeNumber };