imprint-mcp 0.5.0 → 0.6.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/README.md +6 -4
- package/examples/discoverandgo/README.md +1 -1
- package/examples/discoverandgo/book_discoverandgo_museum_pass/workflow.json +7 -0
- package/examples/google-flights/README.md +41 -14
- package/examples/google-flights/_shared/google_batchexecute_parser.ts +165 -0
- package/examples/google-flights/_shared/google_flights_transport.ts +70 -0
- package/examples/google-flights/_shared/google_flights_types.ts +56 -0
- package/examples/google-flights/get_flight_booking_options/backends.json +43 -0
- package/examples/google-flights/get_flight_booking_options/index.ts +129 -0
- package/examples/google-flights/get_flight_booking_options/parser.ts +209 -0
- package/examples/google-flights/get_flight_booking_options/playbook.yaml +46 -0
- package/examples/google-flights/get_flight_booking_options/request-transform.ts +210 -0
- package/examples/google-flights/get_flight_booking_options/workflow.json +85 -0
- package/examples/google-flights/get_flight_calendar_prices/backends.json +27 -0
- package/examples/google-flights/get_flight_calendar_prices/index.ts +44 -29
- package/examples/google-flights/get_flight_calendar_prices/parser.ts +76 -69
- package/examples/google-flights/get_flight_calendar_prices/playbook.yaml +67 -40
- package/examples/google-flights/get_flight_calendar_prices/workflow.json +29 -17
- package/examples/google-flights/get_flight_location_details/backends.json +27 -0
- package/examples/google-flights/{lookup_airport → get_flight_location_details}/index.ts +30 -20
- package/examples/google-flights/get_flight_location_details/parser.ts +103 -0
- package/examples/google-flights/get_flight_location_details/playbook.yaml +59 -0
- package/examples/google-flights/{lookup_airport → get_flight_location_details}/workflow.json +19 -12
- package/examples/google-flights/lookup_flight_locations/backends.json +27 -0
- package/examples/google-flights/lookup_flight_locations/index.ts +111 -0
- package/examples/google-flights/lookup_flight_locations/package.json +9 -0
- package/examples/google-flights/lookup_flight_locations/parser.ts +135 -0
- package/examples/google-flights/lookup_flight_locations/playbook.yaml +47 -0
- package/examples/google-flights/lookup_flight_locations/request-transform.ts +53 -0
- package/examples/google-flights/lookup_flight_locations/workflow.json +64 -0
- package/examples/google-flights/search_flights/backends.json +42 -0
- package/examples/google-flights/search_flights/index.ts +105 -69
- package/examples/google-flights/search_flights/parser.ts +186 -183
- package/examples/google-flights/search_flights/playbook.yaml +295 -88
- package/examples/google-flights/search_flights/request-transform.ts +247 -92
- package/examples/google-flights/search_flights/workflow.json +66 -40
- package/examples/google-flights/validate_flight_itinerary/backends.json +27 -0
- package/examples/google-flights/validate_flight_itinerary/index.ts +103 -0
- package/examples/google-flights/validate_flight_itinerary/package.json +9 -0
- package/examples/google-flights/validate_flight_itinerary/parser.ts +101 -0
- package/examples/google-flights/validate_flight_itinerary/playbook.yaml +84 -0
- package/examples/google-flights/validate_flight_itinerary/request-transform.ts +92 -0
- package/examples/google-flights/validate_flight_itinerary/workflow.json +59 -0
- package/examples/google-hotels/README.md +40 -15
- package/examples/google-hotels/get_hotel_details/backends.json +16 -0
- package/examples/google-hotels/get_hotel_details/index.ts +122 -0
- package/examples/google-hotels/get_hotel_details/parser.ts +275 -0
- package/examples/google-hotels/get_hotel_details/playbook.yaml +40 -0
- package/examples/google-hotels/get_hotel_details/request-transform.ts +67 -0
- package/examples/google-hotels/get_hotel_details/workflow.json +67 -0
- package/examples/google-hotels/get_hotel_photos/backends.json +16 -0
- package/examples/google-hotels/get_hotel_photos/index.ts +90 -0
- package/examples/google-hotels/get_hotel_photos/parser.ts +75 -0
- package/examples/google-hotels/get_hotel_photos/playbook.yaml +23 -0
- package/examples/google-hotels/get_hotel_photos/request-transform.ts +27 -0
- package/examples/google-hotels/get_hotel_photos/workflow.json +44 -0
- package/examples/google-hotels/get_hotel_reviews/backends.json +16 -0
- package/examples/google-hotels/get_hotel_reviews/index.ts +14 -17
- package/examples/google-hotels/get_hotel_reviews/parser.ts +177 -105
- package/examples/google-hotels/get_hotel_reviews/playbook.yaml +9 -54
- package/examples/google-hotels/get_hotel_reviews/workflow.json +10 -13
- package/examples/google-hotels/get_travel_area_boundaries/backends.json +16 -0
- package/examples/google-hotels/get_travel_area_boundaries/index.ts +104 -0
- package/examples/google-hotels/get_travel_area_boundaries/package.json +9 -0
- package/examples/google-hotels/get_travel_area_boundaries/parser.ts +127 -0
- package/examples/google-hotels/get_travel_area_boundaries/playbook.yaml +38 -0
- package/examples/google-hotels/get_travel_area_boundaries/request-transform.ts +52 -0
- package/examples/google-hotels/get_travel_area_boundaries/workflow.json +54 -0
- package/examples/google-hotels/list_hotel_booking_options/backends.json +16 -0
- package/examples/google-hotels/list_hotel_booking_options/index.ts +123 -0
- package/examples/google-hotels/list_hotel_booking_options/package.json +9 -0
- package/examples/google-hotels/list_hotel_booking_options/parser.ts +308 -0
- package/examples/google-hotels/list_hotel_booking_options/playbook.yaml +95 -0
- package/examples/google-hotels/list_hotel_booking_options/request-transform.ts +77 -0
- package/examples/google-hotels/list_hotel_booking_options/workflow.json +67 -0
- package/examples/google-hotels/search_hotel_web_links/backends.json +16 -0
- package/examples/google-hotels/search_hotel_web_links/index.ts +90 -0
- package/examples/google-hotels/search_hotel_web_links/package.json +9 -0
- package/examples/google-hotels/search_hotel_web_links/parser.ts +96 -0
- package/examples/google-hotels/search_hotel_web_links/playbook.yaml +53 -0
- package/examples/google-hotels/search_hotel_web_links/request-transform.ts +17 -0
- package/examples/google-hotels/search_hotel_web_links/workflow.json +43 -0
- package/examples/google-hotels/search_hotels/backends.json +16 -0
- package/examples/google-hotels/search_hotels/index.ts +32 -102
- package/examples/google-hotels/search_hotels/parser.ts +237 -216
- package/examples/google-hotels/search_hotels/playbook.yaml +177 -42
- package/examples/google-hotels/search_hotels/request-transform.ts +101 -170
- package/examples/google-hotels/search_hotels/workflow.json +24 -73
- package/examples/google-hotels/suggest_hotel_destinations/backends.json +16 -0
- package/examples/google-hotels/{autocomplete_hotel_location → suggest_hotel_destinations}/index.ts +30 -19
- package/examples/google-hotels/suggest_hotel_destinations/package.json +9 -0
- package/examples/google-hotels/suggest_hotel_destinations/parser.ts +138 -0
- package/examples/google-hotels/suggest_hotel_destinations/playbook.yaml +47 -0
- package/examples/google-hotels/suggest_hotel_destinations/request-transform.ts +33 -0
- package/examples/google-hotels/suggest_hotel_destinations/workflow.json +44 -0
- package/examples/southwest/README.md +31 -70
- package/examples/southwest/get_account_details/backends.json +22 -0
- package/examples/southwest/get_account_details/credentials.manifest.json +17 -0
- package/examples/southwest/get_account_details/index.ts +138 -0
- package/examples/{google-hotels/_shared → southwest/get_account_details}/package.json +2 -2
- package/examples/southwest/get_account_details/parser.ts +136 -0
- package/examples/southwest/get_account_details/playbook.yaml +19 -0
- package/examples/southwest/get_account_details/workflow.json +93 -0
- package/examples/southwest/get_flight_status/backends.json +21 -0
- package/examples/southwest/get_flight_status/index.ts +130 -0
- package/examples/southwest/get_flight_status/package.json +9 -0
- package/examples/southwest/get_flight_status/parser.ts +138 -0
- package/examples/southwest/get_flight_status/playbook.yaml +58 -0
- package/examples/southwest/get_flight_status/workflow.json +77 -0
- package/examples/southwest/get_low_fare_calendar/backends.json +21 -0
- package/examples/southwest/get_low_fare_calendar/index.ts +134 -0
- package/examples/southwest/get_low_fare_calendar/package.json +9 -0
- package/examples/southwest/get_low_fare_calendar/parser.ts +107 -0
- package/examples/southwest/get_low_fare_calendar/playbook.yaml +41 -0
- package/examples/southwest/get_low_fare_calendar/workflow.json +81 -0
- package/examples/southwest/list_upcoming_trips/backends.json +22 -0
- package/examples/southwest/list_upcoming_trips/credentials.manifest.json +17 -0
- package/examples/southwest/list_upcoming_trips/index.ts +123 -0
- package/examples/southwest/list_upcoming_trips/package.json +9 -0
- package/examples/southwest/list_upcoming_trips/parser.ts +160 -0
- package/examples/southwest/list_upcoming_trips/playbook.yaml +24 -0
- package/examples/southwest/list_upcoming_trips/workflow.json +78 -0
- package/examples/southwest/search_flights/backends.json +21 -0
- package/examples/southwest/search_flights/index.ts +150 -0
- package/examples/southwest/search_flights/package.json +9 -0
- package/examples/southwest/search_flights/parser.ts +214 -0
- package/examples/southwest/search_flights/playbook.yaml +52 -0
- package/examples/southwest/search_flights/workflow.json +88 -0
- package/package.json +1 -1
- package/prompts/audit-agent.md +2 -1
- package/prompts/build-planning.md +22 -1
- package/prompts/compile-agent.md +66 -53
- package/prompts/prereq-builder.md +3 -1
- package/prompts/tool-planning.md +2 -0
- package/src/cli.ts +8 -3
- package/src/imprint/audit.ts +337 -44
- package/src/imprint/backend-cache.ts +114 -0
- package/src/imprint/backend-ladder.ts +74 -24
- package/src/imprint/bot-defense.ts +12 -0
- package/src/imprint/build-plan.ts +346 -53
- package/src/imprint/cdp-browser-fetch.ts +96 -43
- package/src/imprint/cdp-jar-cache.ts +6 -5
- package/src/imprint/chromium.ts +34 -0
- package/src/imprint/codex-cli-compile.ts +13 -2
- package/src/imprint/compile-agent.ts +3 -1
- package/src/imprint/compile-tools.ts +715 -138
- package/src/imprint/compile.ts +41 -9
- package/src/imprint/concurrency.ts +26 -3
- package/src/imprint/cron.ts +0 -3
- package/src/imprint/endpoint-key.ts +23 -0
- package/src/imprint/llm-url.ts +69 -0
- package/src/imprint/llm.ts +366 -47
- package/src/imprint/login.ts +83 -75
- package/src/imprint/mcp-compile-server.ts +4 -1
- package/src/imprint/mcp-server.ts +51 -16
- package/src/imprint/param-grounding.ts +61 -52
- package/src/imprint/probe-backends.ts +21 -115
- package/src/imprint/redact.ts +17 -15
- package/src/imprint/request-capture.ts +94 -0
- package/src/imprint/runtime.ts +1 -78
- package/src/imprint/session-diff.ts +14 -1
- package/src/imprint/stealth-fetch.ts +30 -20
- package/src/imprint/stealth-token-cache.ts +2 -2
- package/src/imprint/teach-plan.ts +2 -0
- package/src/imprint/teach-state.ts +14 -9
- package/src/imprint/teach.ts +41 -7
- package/src/imprint/telemetry.ts +3 -0
- package/src/imprint/tool-candidates.ts +8 -7
- package/src/imprint/tool-plan.ts +29 -3
- package/src/imprint/tracing.ts +18 -2
- package/examples/google-flights/_shared/batchexecute.ts +0 -63
- package/examples/google-flights/_shared/flights_request.ts +0 -97
- package/examples/google-flights/get_flight_booking_details/index.ts +0 -159
- package/examples/google-flights/get_flight_booking_details/parser.ts +0 -182
- package/examples/google-flights/get_flight_booking_details/playbook.yaml +0 -138
- package/examples/google-flights/get_flight_booking_details/request-transform.ts +0 -86
- package/examples/google-flights/get_flight_booking_details/workflow.json +0 -98
- package/examples/google-flights/get_flight_calendar_prices/request-transform.ts +0 -31
- package/examples/google-flights/lookup_airport/parser.ts +0 -66
- package/examples/google-flights/lookup_airport/playbook.yaml +0 -47
- package/examples/google-flights/lookup_airport/request-transform.ts +0 -20
- package/examples/google-hotels/_shared/batchexecute.ts +0 -73
- package/examples/google-hotels/_shared/freq.ts +0 -158
- package/examples/google-hotels/autocomplete_hotel_location/parser.ts +0 -71
- package/examples/google-hotels/autocomplete_hotel_location/playbook.yaml +0 -36
- package/examples/google-hotels/autocomplete_hotel_location/request-transform.ts +0 -37
- package/examples/google-hotels/autocomplete_hotel_location/workflow.json +0 -36
- package/examples/google-hotels/get_hotel_booking_options/index.ts +0 -143
- package/examples/google-hotels/get_hotel_booking_options/parser.ts +0 -271
- package/examples/google-hotels/get_hotel_booking_options/playbook.yaml +0 -154
- package/examples/google-hotels/get_hotel_booking_options/request-transform.ts +0 -154
- package/examples/google-hotels/get_hotel_booking_options/workflow.json +0 -84
- package/examples/google-hotels/get_hotel_reviews/request-transform.ts +0 -42
- package/examples/southwest/search_southwest_flights/backends.json +0 -23
- package/examples/southwest/search_southwest_flights/cron.json +0 -19
- package/examples/southwest/search_southwest_flights/index.ts +0 -127
- package/examples/southwest/search_southwest_flights/playbook.yaml +0 -46
- package/examples/southwest/search_southwest_flights/workflow.json +0 -71
- /package/examples/google-flights/{get_flight_booking_details → get_flight_booking_options}/package.json +0 -0
- /package/examples/google-flights/{lookup_airport → get_flight_location_details}/package.json +0 -0
- /package/examples/google-hotels/{autocomplete_hotel_location → get_hotel_details}/package.json +0 -0
- /package/examples/google-hotels/{get_hotel_booking_options → get_hotel_photos}/package.json +0 -0
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* GENERATED by `imprint emit` — DO NOT EDIT BY HAND.
|
|
3
3
|
*
|
|
4
|
-
* Tool:
|
|
4
|
+
* Tool: get_flight_location_details
|
|
5
5
|
* Site: google-flights
|
|
6
|
-
* Intent: Resolve a city or
|
|
6
|
+
* Intent: Resolve a selected airport, city, or place into detailed Google Flights location metadata.
|
|
7
7
|
*
|
|
8
|
-
* To regenerate: imprint emit ~/.imprint/google-flights/
|
|
8
|
+
* To regenerate: imprint emit ~/.imprint/google-flights/get_flight_location_details/workflow.json --force
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
import { fileURLToPath } from 'node:url';
|
|
@@ -17,23 +17,31 @@ import {
|
|
|
17
17
|
import type { ToolResult, Workflow } from 'imprint/types';
|
|
18
18
|
|
|
19
19
|
const WORKFLOW: Workflow = {
|
|
20
|
-
"toolName": "
|
|
20
|
+
"toolName": "get_flight_location_details",
|
|
21
21
|
"intent": {
|
|
22
|
-
"description": "Resolve a city or
|
|
23
|
-
"userSaid": "multi city from SJC to SAN to LAX to SFO"
|
|
22
|
+
"description": "Resolve a selected airport, city, or place into detailed Google Flights location metadata.",
|
|
23
|
+
"userSaid": "searched for a multi city flight; multi city from SJC to SAN to LAX to SFO"
|
|
24
24
|
},
|
|
25
25
|
"parameters": [
|
|
26
26
|
{
|
|
27
|
-
"name": "
|
|
27
|
+
"name": "location_id",
|
|
28
28
|
"type": "string",
|
|
29
|
-
"description": "
|
|
29
|
+
"description": "Airport code such as SJC or Google place id such as /m/0d9jr.",
|
|
30
|
+
"default": "SJC",
|
|
31
|
+
"verified": true
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
"name": "location_type",
|
|
35
|
+
"type": "string",
|
|
36
|
+
"description": "Google Flights location type code: use 0 for airport_code or 5 for google_place_id/city place ids.",
|
|
37
|
+
"default": "0",
|
|
30
38
|
"verified": true
|
|
31
39
|
}
|
|
32
40
|
],
|
|
33
41
|
"requests": [
|
|
34
42
|
{
|
|
35
43
|
"method": "POST",
|
|
36
|
-
"url": "https://www.google.com/_/FlightsFrontendUi/data/batchexecute?rpcids=tDoGIe&source-path=%2Ftravel%2Fflights&f.sid=${state.f_sid}&bl=${state.bl}&hl=en-US&soc-app=162&soc-platform=1&soc-device=1&_reqid
|
|
44
|
+
"url": "https://www.google.com/_/FlightsFrontendUi/data/batchexecute?rpcids=tDoGIe&source-path=%2Ftravel%2Fflights&f.sid=${state.f_sid}&bl=${state.bl}&hl=en-US&soc-app=162&soc-platform=1&soc-device=1&_reqid=${generated.nonce}&rt=c",
|
|
37
45
|
"headers": {
|
|
38
46
|
"Content-Type": "application/x-www-form-urlencoded;charset=UTF-8",
|
|
39
47
|
"X-Same-Domain": "1",
|
|
@@ -41,7 +49,7 @@ const WORKFLOW: Workflow = {
|
|
|
41
49
|
"Accept-Language": "en-US,en;q=0.9",
|
|
42
50
|
"x-goog-ext-259736195-jspb": "[\"en-US\",\"US\",\"USD\",2,null,[420],null,null,7,[]]"
|
|
43
51
|
},
|
|
44
|
-
"body": "f.req=%5B%5B%5B%22tDoGIe%22%2C%22%5Bnull%2C%5B%5B%5C%22${param.
|
|
52
|
+
"body": "f.req=%5B%5B%5B%22tDoGIe%22%2C%22%5Bnull%2C%5B%5B%5C%22${param.location_id}%5C%22%2C${param.location_type}%5D%5D%5D%22%2Cnull%2C%22generic%22%5D%5D%5D&",
|
|
45
53
|
"effect": "safe"
|
|
46
54
|
}
|
|
47
55
|
],
|
|
@@ -49,14 +57,13 @@ const WORKFLOW: Workflow = {
|
|
|
49
57
|
"bootstrap": {
|
|
50
58
|
"url": "https://www.google.com/travel/flights",
|
|
51
59
|
"waitUntil": "domcontentloaded",
|
|
52
|
-
"timeoutMs": 30000,
|
|
53
60
|
"captures": [
|
|
54
61
|
{
|
|
55
62
|
"name": "f_sid",
|
|
56
63
|
"required": true,
|
|
57
64
|
"capability": "browser_bootstrap",
|
|
58
65
|
"source": "html_regex",
|
|
59
|
-
"pattern": "
|
|
66
|
+
"pattern": "\\\"FdrFJe\\\":\\\"([^\\\"]+)\\\"",
|
|
60
67
|
"group": 1
|
|
61
68
|
},
|
|
62
69
|
{
|
|
@@ -64,28 +71,31 @@ const WORKFLOW: Workflow = {
|
|
|
64
71
|
"required": true,
|
|
65
72
|
"capability": "browser_bootstrap",
|
|
66
73
|
"source": "html_regex",
|
|
67
|
-
"pattern": "
|
|
74
|
+
"pattern": "\\\"cfb2h\\\":\\\"([^\\\"]+)\\\"",
|
|
68
75
|
"group": 1
|
|
69
76
|
}
|
|
70
77
|
]
|
|
71
78
|
},
|
|
72
79
|
"parserModule": "./parser.ts",
|
|
73
|
-
"requestTransformModule": "
|
|
80
|
+
"requestTransformModule": "../_shared/google_flights_transport.ts",
|
|
74
81
|
"liveVerified": true
|
|
75
82
|
};
|
|
76
83
|
|
|
77
|
-
export interface
|
|
78
|
-
/**
|
|
79
|
-
|
|
84
|
+
export interface GetFlightLocationDetailsInput {
|
|
85
|
+
/** Airport code such as SJC or Google place id such as /m/0d9jr. */
|
|
86
|
+
location_id?: string;
|
|
87
|
+
/** Google Flights location type code: use 0 for airport_code or 5 for google_place_id/city place ids. */
|
|
88
|
+
location_type?: string;
|
|
80
89
|
}
|
|
81
90
|
|
|
82
|
-
export async function
|
|
83
|
-
input:
|
|
91
|
+
export async function getFlightLocationDetails(
|
|
92
|
+
input: GetFlightLocationDetailsInput,
|
|
84
93
|
opts: { credentials?: CredentialStore; fetchImpl?: typeof fetch; initialState?: Record<string, unknown> } = {},
|
|
85
94
|
): Promise<ToolResult> {
|
|
86
95
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
87
96
|
const params: Record<string, string | number | boolean> = {
|
|
88
|
-
|
|
97
|
+
location_id: input.location_id ?? "SJC",
|
|
98
|
+
location_type: input.location_type ?? "0",
|
|
89
99
|
|
|
90
100
|
};
|
|
91
101
|
return executeWorkflow({
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { extractWrbRecords, parseNestedPayload } from '../_shared/google_batchexecute_parser.ts';
|
|
2
|
+
import type { FlightLocation } from '../_shared/google_flights_types.ts';
|
|
3
|
+
|
|
4
|
+
type Output = {
|
|
5
|
+
locations: FlightLocation[];
|
|
6
|
+
location?: FlightLocation;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
function asArray(value: unknown): unknown[] | undefined {
|
|
10
|
+
return Array.isArray(value) ? value : undefined;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function asString(value: unknown): string | undefined {
|
|
14
|
+
return typeof value === 'string' && value.length > 0 ? value : undefined;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function asNumber(value: unknown): number | undefined {
|
|
18
|
+
return typeof value === 'number' && Number.isFinite(value) ? value : undefined;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function apiTypeToName(type: unknown): string {
|
|
22
|
+
if (type === 0) return 'airport_code';
|
|
23
|
+
if (type === 4) return 'city';
|
|
24
|
+
if (type === 5) return 'google_place_id';
|
|
25
|
+
return typeof type === 'number' ? `google_flights_type_${type}` : 'unknown';
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function imageUrls(details: unknown[]): string[] {
|
|
29
|
+
const images = asArray(details[2]) ?? [];
|
|
30
|
+
const urls: string[] = [];
|
|
31
|
+
for (const image of images) {
|
|
32
|
+
const nested = asArray(image);
|
|
33
|
+
const url = nested ? asString(nested[0]) : asString(image);
|
|
34
|
+
if (url) urls.push(url);
|
|
35
|
+
}
|
|
36
|
+
return urls;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function parseLocation(record: unknown): FlightLocation | undefined {
|
|
40
|
+
const item = asArray(record);
|
|
41
|
+
if (!item) return undefined;
|
|
42
|
+
|
|
43
|
+
const idPair = asArray(item[0]) ?? [];
|
|
44
|
+
const id = asString(idPair[0]);
|
|
45
|
+
const apiType = idPair[1];
|
|
46
|
+
const displayName = asString(item[1]);
|
|
47
|
+
const details = asArray(item[2]) ?? [];
|
|
48
|
+
const coordinatesRaw = asArray(item[3]) ?? [];
|
|
49
|
+
const lat = asNumber(coordinatesRaw[0]);
|
|
50
|
+
const lng = asNumber(coordinatesRaw[1]);
|
|
51
|
+
|
|
52
|
+
if (!id && !displayName) return undefined;
|
|
53
|
+
|
|
54
|
+
const detailId = asString(details[0]);
|
|
55
|
+
const detailName = asString(details[1]);
|
|
56
|
+
const airportCode = apiType === 0 ? id : asString(details[5]);
|
|
57
|
+
const placeId = id?.startsWith('/m/') ? id : detailId?.startsWith('/m/') ? detailId : undefined;
|
|
58
|
+
|
|
59
|
+
const location: FlightLocation = {
|
|
60
|
+
id: id ?? detailId ?? displayName ?? '',
|
|
61
|
+
type: apiTypeToName(apiType),
|
|
62
|
+
displayName: displayName ?? detailName ?? id ?? '',
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
if (detailName && detailName !== location.displayName) location.city = detailName;
|
|
66
|
+
if (asString(item[6])) location.region = asString(item[6]);
|
|
67
|
+
if (asString(details[3])) location.description = asString(details[3]);
|
|
68
|
+
if (airportCode) location.airportCode = airportCode;
|
|
69
|
+
if (placeId) location.placeId = placeId;
|
|
70
|
+
if (lat !== undefined && lng !== undefined) location.coordinates = { lat, lng };
|
|
71
|
+
|
|
72
|
+
const urls = imageUrls(details);
|
|
73
|
+
if (urls.length > 0) location.imageUrls = urls;
|
|
74
|
+
|
|
75
|
+
return location;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function payloadFromRaw(rawResponse: unknown): unknown {
|
|
79
|
+
if (typeof rawResponse === 'string') {
|
|
80
|
+
const records = extractWrbRecords(rawResponse).filter((record) => record.rpcid === 'tDoGIe');
|
|
81
|
+
return parseNestedPayload(records[0]?.payload);
|
|
82
|
+
}
|
|
83
|
+
return parseNestedPayload(rawResponse);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export function extract(rawResponse: unknown): Output {
|
|
87
|
+
const payload = payloadFromRaw(rawResponse);
|
|
88
|
+
const root = asArray(payload) ?? [];
|
|
89
|
+
const locationsRaw = asArray(root[1]) ?? [];
|
|
90
|
+
const locations = locationsRaw
|
|
91
|
+
.map(parseLocation)
|
|
92
|
+
.filter((location): location is FlightLocation => Boolean(location && location.id && location.displayName));
|
|
93
|
+
|
|
94
|
+
if (locations.length > 1) {
|
|
95
|
+
const [primary, ...rest] = locations;
|
|
96
|
+
if (primary) primary.nestedAirports = rest;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return {
|
|
100
|
+
locations,
|
|
101
|
+
location: locations[0],
|
|
102
|
+
};
|
|
103
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
toolName: get_flight_location_details
|
|
2
|
+
summary: Resolve a selected Google Flights airport, city, or place into normalized location metadata.
|
|
3
|
+
parameters:
|
|
4
|
+
- name: location_id
|
|
5
|
+
type: string
|
|
6
|
+
description: Airport code such as SJC or place id such as /m/0d9jr.
|
|
7
|
+
- name: location_type
|
|
8
|
+
type: string
|
|
9
|
+
description: Location id type, such as airport_code or google_place_id.
|
|
10
|
+
steps:
|
|
11
|
+
- action: navigate
|
|
12
|
+
url: https://www.google.com/travel/flights
|
|
13
|
+
wait_for: networkidle
|
|
14
|
+
- action: click
|
|
15
|
+
locators:
|
|
16
|
+
- by: role
|
|
17
|
+
value: textbox
|
|
18
|
+
name: Where from?
|
|
19
|
+
- by: aria_label
|
|
20
|
+
value_pattern: Where from
|
|
21
|
+
- by: text
|
|
22
|
+
value_pattern: Where from
|
|
23
|
+
- by: css
|
|
24
|
+
value: input[aria-label^="Where from"]
|
|
25
|
+
wait_for:
|
|
26
|
+
sleep_ms: 300
|
|
27
|
+
- action: type
|
|
28
|
+
locators:
|
|
29
|
+
- by: role
|
|
30
|
+
value: textbox
|
|
31
|
+
name: Where else?
|
|
32
|
+
- by: aria_label
|
|
33
|
+
value_pattern: Where else
|
|
34
|
+
- by: aria_label
|
|
35
|
+
value_pattern: Where from
|
|
36
|
+
- by: css
|
|
37
|
+
value: input[type="text"]
|
|
38
|
+
value: ${location_id}
|
|
39
|
+
wait_for:
|
|
40
|
+
xhr: rpcids=H028ib
|
|
41
|
+
- action: click
|
|
42
|
+
locators:
|
|
43
|
+
- by: role
|
|
44
|
+
value: option
|
|
45
|
+
name: ${location_id}
|
|
46
|
+
- by: aria_label
|
|
47
|
+
value_pattern: ${location_id}
|
|
48
|
+
- by: text
|
|
49
|
+
value_pattern: ${location_id}
|
|
50
|
+
- by: css
|
|
51
|
+
value: ul.DFGgtd li
|
|
52
|
+
wait_for:
|
|
53
|
+
xhr: rpcids=tDoGIe
|
|
54
|
+
result:
|
|
55
|
+
source: xhr
|
|
56
|
+
url_pattern: rpcids=tDoGIe
|
|
57
|
+
extract: "0.2"
|
|
58
|
+
return_as: location_details
|
|
59
|
+
notes: The selected-location response is a Google batchexecute payload; downstream parsing should strip the JSON prefix, find the wrb.fr record for tDoGIe, then parse its inner JSON string for coordinates, image URLs, display names, descriptions, canonical IDs, and country metadata.
|
package/examples/google-flights/{lookup_airport → get_flight_location_details}/workflow.json
RENAMED
|
@@ -1,27 +1,34 @@
|
|
|
1
1
|
{
|
|
2
|
-
"toolName": "
|
|
2
|
+
"toolName": "get_flight_location_details",
|
|
3
3
|
"intent": {
|
|
4
|
-
"description": "Resolve a city or
|
|
5
|
-
"userSaid": "multi city from SJC to SAN to LAX to SFO"
|
|
4
|
+
"description": "Resolve a selected airport, city, or place into detailed Google Flights location metadata.",
|
|
5
|
+
"userSaid": "searched for a multi city flight; multi city from SJC to SAN to LAX to SFO"
|
|
6
6
|
},
|
|
7
7
|
"site": "google-flights",
|
|
8
8
|
"parameters": [
|
|
9
9
|
{
|
|
10
|
-
"name": "
|
|
10
|
+
"name": "location_id",
|
|
11
11
|
"type": "string",
|
|
12
|
-
"description": "
|
|
12
|
+
"description": "Airport code such as SJC or Google place id such as /m/0d9jr.",
|
|
13
|
+
"default": "SJC",
|
|
14
|
+
"verified": true
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
"name": "location_type",
|
|
18
|
+
"type": "string",
|
|
19
|
+
"description": "Google Flights location type code: use 0 for airport_code or 5 for google_place_id/city place ids.",
|
|
20
|
+
"default": "0",
|
|
13
21
|
"verified": true
|
|
14
22
|
}
|
|
15
23
|
],
|
|
16
24
|
"bootstrap": {
|
|
17
25
|
"url": "https://www.google.com/travel/flights",
|
|
18
26
|
"waitUntil": "domcontentloaded",
|
|
19
|
-
"timeoutMs": 30000,
|
|
20
27
|
"captures": [
|
|
21
28
|
{
|
|
22
29
|
"source": "html_regex",
|
|
23
30
|
"name": "f_sid",
|
|
24
|
-
"pattern": "
|
|
31
|
+
"pattern": "\\\"FdrFJe\\\":\\\"([^\\\"]+)\\\"",
|
|
25
32
|
"group": 1,
|
|
26
33
|
"required": true,
|
|
27
34
|
"capability": "browser_bootstrap"
|
|
@@ -29,17 +36,19 @@
|
|
|
29
36
|
{
|
|
30
37
|
"source": "html_regex",
|
|
31
38
|
"name": "bl",
|
|
32
|
-
"pattern": "
|
|
39
|
+
"pattern": "\\\"cfb2h\\\":\\\"([^\\\"]+)\\\"",
|
|
33
40
|
"group": 1,
|
|
34
41
|
"required": true,
|
|
35
42
|
"capability": "browser_bootstrap"
|
|
36
43
|
}
|
|
37
44
|
]
|
|
38
45
|
},
|
|
46
|
+
"requestTransformModule": "../_shared/google_flights_transport.ts",
|
|
47
|
+
"parserModule": "./parser.ts",
|
|
39
48
|
"requests": [
|
|
40
49
|
{
|
|
41
50
|
"method": "POST",
|
|
42
|
-
"url": "https://www.google.com/_/FlightsFrontendUi/data/batchexecute?rpcids=tDoGIe&source-path=%2Ftravel%2Fflights&f.sid=${state.f_sid}&bl=${state.bl}&hl=en-US&soc-app=162&soc-platform=1&soc-device=1&_reqid
|
|
51
|
+
"url": "https://www.google.com/_/FlightsFrontendUi/data/batchexecute?rpcids=tDoGIe&source-path=%2Ftravel%2Fflights&f.sid=${state.f_sid}&bl=${state.bl}&hl=en-US&soc-app=162&soc-platform=1&soc-device=1&_reqid=${generated.nonce}&rt=c",
|
|
43
52
|
"headers": {
|
|
44
53
|
"Content-Type": "application/x-www-form-urlencoded;charset=UTF-8",
|
|
45
54
|
"X-Same-Domain": "1",
|
|
@@ -47,11 +56,9 @@
|
|
|
47
56
|
"Accept-Language": "en-US,en;q=0.9",
|
|
48
57
|
"x-goog-ext-259736195-jspb": "[\"en-US\",\"US\",\"USD\",2,null,[420],null,null,7,[]]"
|
|
49
58
|
},
|
|
50
|
-
"body": "f.req=%5B%5B%5B%22tDoGIe%22%2C%22%5Bnull%2C%5B%5B%5C%22${param.
|
|
59
|
+
"body": "f.req=%5B%5B%5B%22tDoGIe%22%2C%22%5Bnull%2C%5B%5B%5C%22${param.location_id}%5C%22%2C${param.location_type}%5D%5D%5D%22%2Cnull%2C%22generic%22%5D%5D%5D&",
|
|
51
60
|
"effect": "safe"
|
|
52
61
|
}
|
|
53
62
|
],
|
|
54
|
-
"parserModule": "./parser.ts",
|
|
55
|
-
"requestTransformModule": "./request-transform.ts",
|
|
56
63
|
"liveVerified": true
|
|
57
64
|
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"probedAt": "2026-07-01T01:16:34.053Z",
|
|
3
|
+
"imprintVersion": "0.5.0",
|
|
4
|
+
"schemaVersion": 2,
|
|
5
|
+
"workflowHash": "0540c5b796bac7278bf655a5f0dc27c2508e08483eed2cd9453be4356b8b69cf",
|
|
6
|
+
"capabilityHash": "5fc16811cd65aff7afee349ddca05c806a65c9c2c96948192ea1bafba9284c38",
|
|
7
|
+
"preferredOrder": [
|
|
8
|
+
"cdp-replay"
|
|
9
|
+
],
|
|
10
|
+
"results": {
|
|
11
|
+
"fetch": {
|
|
12
|
+
"outcome": "failed",
|
|
13
|
+
"durationMs": 18,
|
|
14
|
+
"error": "STATE_MISSING",
|
|
15
|
+
"detail": "STATE_MISSING: Workflow placeholder ${state.bl} but state \"bl\" has not been captured yet"
|
|
16
|
+
},
|
|
17
|
+
"fetch-bootstrap": {
|
|
18
|
+
"outcome": "forbidden",
|
|
19
|
+
"durationMs": 30187,
|
|
20
|
+
"detail": "FORBIDDEN: fetch-bootstrap: cdp-minted jar did not validate; cdp-replay (in-page) required."
|
|
21
|
+
},
|
|
22
|
+
"cdp-replay": {
|
|
23
|
+
"outcome": "ok",
|
|
24
|
+
"durationMs": 470
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GENERATED by `imprint emit` — DO NOT EDIT BY HAND.
|
|
3
|
+
*
|
|
4
|
+
* Tool: lookup_flight_locations
|
|
5
|
+
* Site: google-flights
|
|
6
|
+
* Intent: Search Google Flights location autocomplete for airports, cities, and travel places by text query.
|
|
7
|
+
*
|
|
8
|
+
* To regenerate: imprint emit ~/.imprint/google-flights/lookup_flight_locations/workflow.json --force
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { fileURLToPath } from 'node:url';
|
|
12
|
+
import { dirname, join } from 'node:path';
|
|
13
|
+
import {
|
|
14
|
+
executeWorkflow,
|
|
15
|
+
type CredentialStore,
|
|
16
|
+
} from 'imprint/runtime';
|
|
17
|
+
import type { ToolResult, Workflow } from 'imprint/types';
|
|
18
|
+
|
|
19
|
+
const WORKFLOW: Workflow = {
|
|
20
|
+
"toolName": "lookup_flight_locations",
|
|
21
|
+
"intent": {
|
|
22
|
+
"description": "Search Google Flights location autocomplete for airports, cities, and travel places by text query.",
|
|
23
|
+
"userSaid": "searched for a round trip flight; multi city from SJC to SAN to LAX to SFO; searched for a multi city flight"
|
|
24
|
+
},
|
|
25
|
+
"parameters": [
|
|
26
|
+
{
|
|
27
|
+
"name": "query",
|
|
28
|
+
"type": "string",
|
|
29
|
+
"description": "Partial or full airport, city, or place query typed by the user.",
|
|
30
|
+
"default": "san di",
|
|
31
|
+
"verified": true
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
"name": "location_types",
|
|
35
|
+
"type": "string",
|
|
36
|
+
"description": "Optional type filter. Use default for recorded Google Flights location types, or all to include the expanded region/county type.",
|
|
37
|
+
"default": "default",
|
|
38
|
+
"verified": true
|
|
39
|
+
}
|
|
40
|
+
],
|
|
41
|
+
"requests": [
|
|
42
|
+
{
|
|
43
|
+
"method": "POST",
|
|
44
|
+
"url": "https://www.google.com/_/FlightsFrontendUi/data/batchexecute?rpcids=H028ib&source-path=%2Ftravel%2Fflights&f.sid=${state.f_sid}&bl=${state.bl}&hl=en-US&soc-app=162&soc-platform=1&soc-device=1&rt=c",
|
|
45
|
+
"headers": {
|
|
46
|
+
"X-Same-Domain": "1",
|
|
47
|
+
"Content-Type": "application/x-www-form-urlencoded;charset=UTF-8",
|
|
48
|
+
"Referer": "https://www.google.com/travel/flights",
|
|
49
|
+
"x-goog-ext-259736195-jspb": "[\"en-US\",\"US\",\"USD\",2,null,[420],null,null,7,[]]"
|
|
50
|
+
},
|
|
51
|
+
"body": "f.req=${param.query}&location_types=${param.location_types}",
|
|
52
|
+
"effect": "safe"
|
|
53
|
+
}
|
|
54
|
+
],
|
|
55
|
+
"site": "google-flights",
|
|
56
|
+
"bootstrap": {
|
|
57
|
+
"url": "https://www.google.com/travel/flights",
|
|
58
|
+
"waitUntil": "load",
|
|
59
|
+
"waitMs": 1000,
|
|
60
|
+
"captures": [
|
|
61
|
+
{
|
|
62
|
+
"name": "f_sid",
|
|
63
|
+
"required": true,
|
|
64
|
+
"capability": "browser_bootstrap",
|
|
65
|
+
"source": "html_regex",
|
|
66
|
+
"pattern": "(?:\\\\?\"|")FdrFJe(?:\\\\?\"|")\\s*:\\s*(?:\\\\?\"|")(-?[0-9]+)",
|
|
67
|
+
"group": 1
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
"name": "bl",
|
|
71
|
+
"required": true,
|
|
72
|
+
"capability": "browser_bootstrap",
|
|
73
|
+
"source": "html_regex",
|
|
74
|
+
"pattern": "(?:\\\\?\"|")cfb2h(?:\\\\?\"|")\\s*:\\s*(?:\\\\?\"|")(boq_travel-frontend-flights-ui_[^\\\\\"&]+)",
|
|
75
|
+
"group": 1
|
|
76
|
+
}
|
|
77
|
+
]
|
|
78
|
+
},
|
|
79
|
+
"parserModule": "./parser.ts",
|
|
80
|
+
"requestTransformModule": "./request-transform.ts",
|
|
81
|
+
"liveVerified": true
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
export interface LookupFlightLocationsInput {
|
|
85
|
+
/** Partial or full airport, city, or place query typed by the user. */
|
|
86
|
+
query?: string;
|
|
87
|
+
/** Optional type filter. Use default for recorded Google Flights location types, or all to include the expanded region/county type. */
|
|
88
|
+
location_types?: string;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export async function lookupFlightLocations(
|
|
92
|
+
input: LookupFlightLocationsInput,
|
|
93
|
+
opts: { credentials?: CredentialStore; fetchImpl?: typeof fetch; initialState?: Record<string, unknown> } = {},
|
|
94
|
+
): Promise<ToolResult> {
|
|
95
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
96
|
+
const params: Record<string, string | number | boolean> = {
|
|
97
|
+
query: input.query ?? "san di",
|
|
98
|
+
location_types: input.location_types ?? "default",
|
|
99
|
+
|
|
100
|
+
};
|
|
101
|
+
return executeWorkflow({
|
|
102
|
+
workflow: WORKFLOW,
|
|
103
|
+
params,
|
|
104
|
+
credentials: opts.credentials,
|
|
105
|
+
fetchImpl: opts.fetchImpl,
|
|
106
|
+
initialState: opts.initialState,
|
|
107
|
+
workflowPath: join(__dirname, 'workflow.json'),
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export { WORKFLOW };
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { extractWrbRecords, parseNestedPayload } from '../_shared/google_batchexecute_parser.ts';
|
|
2
|
+
import type { FlightLocation } from '../_shared/google_flights_types.ts';
|
|
3
|
+
|
|
4
|
+
type LookupResult = {
|
|
5
|
+
query?: string;
|
|
6
|
+
items: FlightLocation[];
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
function asArray(value: unknown): unknown[] | undefined {
|
|
10
|
+
return Array.isArray(value) ? value : undefined;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function asString(value: unknown): string | undefined {
|
|
14
|
+
return typeof value === 'string' && value.length > 0 ? value : undefined;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function asNumber(value: unknown): number | undefined {
|
|
18
|
+
return typeof value === 'number' && Number.isFinite(value) ? value : undefined;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function typeName(typeCode: unknown): string {
|
|
22
|
+
switch (typeCode) {
|
|
23
|
+
case 1:
|
|
24
|
+
return 'airport';
|
|
25
|
+
case 3:
|
|
26
|
+
return 'city';
|
|
27
|
+
case 4:
|
|
28
|
+
return 'region';
|
|
29
|
+
case 5:
|
|
30
|
+
return 'station';
|
|
31
|
+
default:
|
|
32
|
+
return `type_${String(typeCode ?? 'unknown')}`;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function parseTuple(tuple: unknown, distanceText?: unknown): (FlightLocation & { score?: number; distance?: string }) | null {
|
|
37
|
+
const values = asArray(tuple);
|
|
38
|
+
if (!values) return null;
|
|
39
|
+
|
|
40
|
+
const type = typeName(values[0]);
|
|
41
|
+
const displayName = asString(values[1]);
|
|
42
|
+
const secondaryName = asString(values[2]);
|
|
43
|
+
const description = asString(values[3]);
|
|
44
|
+
const placeId = asString(values[4]);
|
|
45
|
+
const airportCode = asString(values[5]) ?? asString(values[8]);
|
|
46
|
+
const id = airportCode ?? placeId ?? displayName;
|
|
47
|
+
|
|
48
|
+
if (!id || !displayName) return null;
|
|
49
|
+
|
|
50
|
+
const location: FlightLocation & { score?: number; distance?: string } = {
|
|
51
|
+
id,
|
|
52
|
+
type,
|
|
53
|
+
displayName,
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
if (type === 'region') {
|
|
57
|
+
if (secondaryName) location.region = secondaryName;
|
|
58
|
+
} else if (secondaryName) {
|
|
59
|
+
location.city = secondaryName;
|
|
60
|
+
}
|
|
61
|
+
if (description) location.description = description;
|
|
62
|
+
if (airportCode) location.airportCode = airportCode;
|
|
63
|
+
if (placeId) location.placeId = placeId;
|
|
64
|
+
const score = asNumber(values[12]);
|
|
65
|
+
if (score !== undefined) location.score = score;
|
|
66
|
+
const distance = asString(distanceText);
|
|
67
|
+
if (distance) location.distance = distance;
|
|
68
|
+
|
|
69
|
+
return location;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function parseNestedAirports(value: unknown): FlightLocation[] | undefined {
|
|
73
|
+
const options = asArray(value);
|
|
74
|
+
if (!options) return undefined;
|
|
75
|
+
|
|
76
|
+
const nested = options
|
|
77
|
+
.map((option) => {
|
|
78
|
+
const parts = asArray(option);
|
|
79
|
+
if (!parts) return null;
|
|
80
|
+
return parseTuple(parts[0], parts[1]);
|
|
81
|
+
})
|
|
82
|
+
.filter((item): item is FlightLocation => Boolean(item));
|
|
83
|
+
|
|
84
|
+
return nested.length > 0 ? nested : undefined;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function unwrapRows(payload: unknown): unknown[] {
|
|
88
|
+
let parsed = payload;
|
|
89
|
+
for (let i = 0; i < 4; i += 1) {
|
|
90
|
+
parsed = parseNestedPayload(parsed);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const top = asArray(parsed);
|
|
94
|
+
return asArray(top?.[0]) ?? [];
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export function extract(
|
|
98
|
+
rawResponse: unknown,
|
|
99
|
+
context?: { params: Record<string, string | number | boolean>; responses: unknown[] },
|
|
100
|
+
): LookupResult {
|
|
101
|
+
const query = typeof context?.params?.query === 'string' ? context.params.query : undefined;
|
|
102
|
+
|
|
103
|
+
if (typeof rawResponse !== 'string') {
|
|
104
|
+
const rows = unwrapRows(rawResponse);
|
|
105
|
+
return {
|
|
106
|
+
query,
|
|
107
|
+
items: rows.map(parseRow).filter((item): item is FlightLocation => Boolean(item)),
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const records = extractWrbRecords(rawResponse);
|
|
112
|
+
const record = records.find((entry) => entry.rpcid === 'H028ib');
|
|
113
|
+
if (!record) {
|
|
114
|
+
throw new Error('Google Flights location lookup response did not include H028ib wrb.fr data');
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const rows = unwrapRows(record.rawPayload ?? record.payload);
|
|
118
|
+
return {
|
|
119
|
+
query,
|
|
120
|
+
items: rows.map(parseRow).filter((item): item is FlightLocation => Boolean(item)),
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function parseRow(row: unknown): FlightLocation | null {
|
|
125
|
+
const parts = asArray(row);
|
|
126
|
+
if (!parts) return null;
|
|
127
|
+
|
|
128
|
+
const location = parseTuple(parts[0]);
|
|
129
|
+
if (!location) return null;
|
|
130
|
+
|
|
131
|
+
const nestedAirports = parseNestedAirports(parts[1]);
|
|
132
|
+
if (nestedAirports) location.nestedAirports = nestedAirports;
|
|
133
|
+
|
|
134
|
+
return location;
|
|
135
|
+
}
|